Remove check of isatty when guessing color.
[gentoolkit.git] / pym / gentoolkit / equery / __init__.py
1 # Copyright(c) 2009, Gentoo Foundation
2 #
3 # Licensed under the GNU General Public License, v2
4 #
5 # $Header: $
6
7 """Gentoo package query tool"""
8
9 from __future__ import print_function
10
11 __all__ = (
12         'format_options',
13         'format_package_names',
14         'mod_usage'
15 )
16 __docformat__ = 'epytext'
17 # version is dynamically set by distutils sdist
18 __version__ = "svn"
19
20 # =======
21 # Imports
22 # =======
23
24 import errno
25 import os
26 import sys
27 import time
28 from getopt import getopt, GetoptError
29
30 import portage
31
32 import gentoolkit
33 from gentoolkit import CONFIG
34 from gentoolkit import errors
35 from gentoolkit import pprinter as pp
36 from gentoolkit.textwrap_ import TextWrapper
37
38 __productname__ = "equery"
39 __authors__ = (
40         'Karl Trygve Kalleberg - Original author',
41         'Douglas Anderson - 0.3.0 author'
42 )
43
44 # =======
45 # Globals
46 # =======
47
48 NAME_MAP = {
49         'b': 'belongs',
50         'c': 'changes',
51         'k': 'check',
52         'd': 'depends',
53         'g': 'depgraph',
54         'f': 'files',
55         'h': 'hasuse',
56         'l': 'list_',
57         'y': 'keywords',
58         'a': 'has',
59         'm': 'meta',
60         's': 'size',
61         'u': 'uses',
62         'w': 'which'
63 }
64
65 # =========
66 # Functions
67 # =========
68
69 def print_help(with_description=True):
70         """Print description, usage and a detailed help message.
71
72         @param with_description (bool): Option to print module's __doc__ or not
73         """
74
75         if with_description:
76                 print(__doc__)
77         print(main_usage())
78         print()
79         print(pp.globaloption("global options"))
80         print(format_options((
81                 (" -h, --help", "display this help message"),
82                 (" -q, --quiet", "minimal output"),
83                 (" -C, --no-color", "turn off colors"),
84                 (" -N, --no-pipe", "turn off pipe detection"),
85                 (" -V, --version", "display version info")
86         )))
87         print()
88         print(pp.command("modules") + " (" + pp.command("short name") + ")")
89         print(format_options((
90                 (" (b)elongs", "list what package FILES belong to"),
91                 (" (c)hanges", "list changelog entries for ATOM"),
92                 (" chec(k)", "verify checksums and timestamps for PKG"),
93                 (" (d)epends", "list all packages directly depending on ATOM"),
94                 (" dep(g)raph", "display a tree of all dependencies for PKG"),
95                 (" (f)iles", "list all files installed by PKG"),
96                 (" h(a)s", "list all packages for matching ENVIRONMENT data stored in /var/db/pkg"),
97                 (" (h)asuse", "list all packages that have USE flag"),
98                 (" ke(y)words", "display keywords for specified PKG"),
99                 (" (l)ist", "list package matching PKG"),
100                 (" (m)eta", "display metadata about PKG"),
101                 (" (s)ize", "display total size of all files owned by PKG"),
102                 (" (u)ses", "display USE flags for PKG"),
103                 (" (w)hich", "print full path to ebuild for PKG")
104         )))
105
106
107 def expand_module_name(module_name):
108         """Returns one of the values of NAME_MAP or raises KeyError"""
109
110         if module_name == 'list':
111                 # list is a Python builtin type, so we must rename our module
112                 return 'list_'
113         elif module_name in NAME_MAP.values():
114                 return module_name
115         else:
116                 return NAME_MAP[module_name]
117
118
119 def format_options(options):
120         """Format module options.
121
122         @type options: list
123         @param options: [('option 1', 'description 1'), ('option 2', 'des... )]
124         @rtype: str
125         @return: formatted options string
126         """
127
128         result = []
129         twrap = TextWrapper(width=CONFIG['termWidth'])
130         opts = (x[0] for x in options)
131         descs = (x[1] for x in options)
132         for opt, desc in zip(opts, descs):
133                 twrap.initial_indent = pp.emph(opt.ljust(25))
134                 twrap.subsequent_indent = " " * 25
135                 result.append(twrap.fill(desc))
136
137         return '\n'.join(result)
138
139
140 def format_filetype(path, fdesc, show_type=False, show_md5=False,
141                 show_timestamp=False):
142         """Format a path for printing.
143
144         @type path: str
145         @param path: the path
146         @type fdesc: list
147         @param fdesc: [file_type, timestamp, MD5 sum/symlink target]
148                 file_type is one of dev, dir, obj, sym.
149                 If file_type is dir, there is no timestamp or MD5 sum.
150                 If file_type is sym, fdesc[2] is the target of the symlink.
151         @type show_type: bool
152         @param show_type: if True, prepend the file's type to the formatted string
153         @type show_md5: bool
154         @param show_md5: if True, append MD5 sum to the formatted string
155         @type show_timestamp: bool
156         @param show_timestamp: if True, append time-of-creation after pathname
157         @rtype: str
158         @return: formatted pathname with optional added information
159         """
160
161         ftype = fpath = stamp = md5sum = ""
162
163         if fdesc[0] == "obj":
164                 ftype = "file"
165                 fpath = path
166                 stamp = format_timestamp(fdesc[1])
167                 md5sum = fdesc[2]
168         elif fdesc[0] == "dir":
169                 ftype = "dir"
170                 fpath = pp.path(path)
171         elif fdesc[0] == "sym":
172                 ftype = "sym"
173                 stamp = format_timestamp(fdesc[1])
174                 tgt = fdesc[2].split()[0]
175                 if CONFIG["piping"]:
176                         fpath = path
177                 else:
178                         fpath = pp.path_symlink(path + " -> " + tgt)
179         elif fdesc[0] == "dev":
180                 ftype = "dev"
181                 fpath = path
182         else:
183                 sys.stderr.write(
184                         pp.error("%s has unknown type: %s" % (path, fdesc[0]))
185                 )
186
187         result = ""
188         if show_type:
189                 result += "%4s " % ftype
190         result += fpath
191         if show_timestamp:
192                 result += "  " + stamp
193         if show_md5:
194                 result += "  " + md5sum
195
196         return result
197
198
199 def format_timestamp(timestamp):
200         """Format a timestamp into, e.g., '2009-01-31 21:19:44' format"""
201
202         return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(timestamp)))
203
204
205 def initialize_configuration():
206         """Setup the standard equery config"""
207
208         # Get terminal size
209         term_width = pp.output.get_term_size()[1]
210         if term_width < 1:
211                 # get_term_size() failed. Set a sane default width:
212                 term_width = 80
213
214         # Terminal size, minus a 1-char margin for text wrapping
215         CONFIG['termWidth'] = term_width - 1
216
217         # Guess color output
218         if (CONFIG['color'] == -1 and os.getenv("NOCOLOR") in ("yes", "true") or
219                 CONFIG['color'] == 0):
220                 pp.output.nocolor()
221
222         if CONFIG['piping']:
223                 CONFIG['verbose'] = False
224                 # set extra wide, should disable wrapping unless 
225                 # there is some extra long text 
226                 CONFIG['termWidth'] = 600
227
228         CONFIG['debug'] = bool(os.getenv('DEBUG', False))
229
230
231 def main_usage():
232         """Return the main usage message for equery"""
233
234         return "%(usage)s %(product)s [%(g_opts)s] %(mod_name)s [%(mod_opts)s]" % {
235                 'usage': pp.emph("Usage:"),
236                 'product': pp.productname(__productname__),
237                 'g_opts': pp.globaloption("global-options"),
238                 'mod_name': pp.command("module-name"),
239                 'mod_opts': pp.localoption("module-options")
240         }
241
242
243 def mod_usage(mod_name="module", arg="pkgspec", optional=False):
244         """Provide a consistent usage message to the calling module.
245
246         @type arg: string
247         @param arg: what kind of argument the module takes (pkgspec, filename, etc)
248         @type optional: bool
249         @param optional: is the argument optional?
250         """
251
252         return "%(usage)s: %(mod_name)s [%(opts)s] %(arg)s" % {
253                 'usage': pp.emph("Usage"),
254                 'mod_name': pp.command(mod_name),
255                 'opts': pp.localoption("options"),
256                 'arg': ("[%s]" % pp.emph(arg)) if optional else pp.emph(arg)
257         }
258
259
260 def parse_global_options(global_opts, args):
261         """Parse global input args and return True if we should display help for
262         the called module, else False (or display help and exit from here).
263         """
264
265         need_help = False
266         do_help = False
267         opts = (opt[0] for opt in global_opts)
268         for opt in opts:
269                 if opt in ('-h', '--help'):
270                         if args:
271                                 need_help = True
272                         else:
273                                 do_help = True
274                 elif opt in ('-q','--quiet'):
275                         CONFIG['quiet'] = True
276                 elif opt in ('-C', '--no-color', '--nocolor'):
277                         CONFIG['color'] = 0
278                         pp.output.nocolor()
279                 elif opt in ('-N', '--no-pipe'):
280                         CONFIG['piping'] = False
281                 elif opt in ('-V', '--version'):
282                         print_version()
283                         sys.exit(0)
284                 elif opt in ('--debug'):
285                         CONFIG['debug'] = True
286         if do_help:
287                 print_help()
288                 sys.exit(0)
289         return need_help
290
291
292 def print_version():
293         """Print the version of this tool to the console."""
294
295         print("%(product)s (%(version)s) - %(docstring)s" % {
296                 "product": pp.productname(__productname__),
297                 "version": __version__,
298                 "docstring": __doc__
299         })
300
301
302 def split_arguments(args):
303         """Separate module name from module arguments"""
304
305         return args.pop(0), args
306
307
308 def main(argv):
309         """Parse input and run the program."""
310
311         short_opts = "hqCNV"
312         long_opts = (
313                 'help', 'quiet', 'nocolor', 'no-color', 'no-pipe', 'version', 'debug'
314         )
315
316         initialize_configuration()
317
318         try:
319                 global_opts, args = getopt(argv[1:], short_opts, long_opts)
320         except GetoptError as err:
321                 sys.stderr.write(pp.error("Global %s" % err))
322                 print_help(with_description=False)
323                 sys.exit(2)
324
325         # Parse global options
326         need_help = parse_global_options(global_opts, args)
327
328         # verbose is shorthand for the very common 'not quiet or piping'
329         if CONFIG['quiet'] or CONFIG['piping']:
330                 CONFIG['verbose'] = False
331         else:
332                 CONFIG['verbose'] = True
333
334         try:
335                 module_name, module_args = split_arguments(args)
336         except IndexError:
337                 print_help()
338                 sys.exit(2)
339
340         if need_help:
341                 module_args.append('--help')
342
343         try:
344                 expanded_module_name = expand_module_name(module_name)
345         except KeyError:
346                 sys.stderr.write(pp.error("Unknown module '%s'" % module_name))
347                 print_help(with_description=False)
348                 sys.exit(2)
349
350         try:
351                 loaded_module = __import__(
352                         expanded_module_name, globals(), locals(), [], -1
353                 )
354                 loaded_module.main(module_args)
355         except portage.exception.AmbiguousPackageName as err:
356                 raise errors.GentoolkitAmbiguousPackage(err.args[0])
357         except IOError as err:
358                 if err.errno != errno.EPIPE:
359                         raise
360
361 if __name__ == '__main__':
362         main(sys.argv)