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