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