diff options
Diffstat (limited to 'gentoolkit/pym/gentoolkit/equery')
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/__init__.py | 351 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/belongs.py | 156 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/changes.py | 205 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/check.py | 291 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/depends.py | 193 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/depgraph.py | 223 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/files.py | 320 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/hasuse.py | 156 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/list_.py | 224 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/meta.py | 494 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/size.py | 193 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/uses.py | 317 | ||||
-rw-r--r-- | gentoolkit/pym/gentoolkit/equery/which.py | 102 |
13 files changed, 3225 insertions, 0 deletions
diff --git a/gentoolkit/pym/gentoolkit/equery/__init__.py b/gentoolkit/pym/gentoolkit/equery/__init__.py new file mode 100644 index 0000000..5833f29 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/__init__.py @@ -0,0 +1,351 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Gentoo package query tool""" + +# Move to Imports section after Python 2.6 is stable +from __future__ import with_statement + +__all__ = ( + 'format_options', + 'format_package_names', + 'mod_usage' +) +__docformat__ = 'epytext' +# version is dynamically set by distutils sdist +__version__ = "svn" + +# ======= +# Imports +# ======= + +import errno +import os +import sys +import time +from getopt import getopt, GetoptError + +import portage + +import gentoolkit +from gentoolkit import CONFIG +from gentoolkit import errors +from gentoolkit import pprinter as pp +from gentoolkit.textwrap_ import TextWrapper + +__productname__ = "equery" +__authors__ = ( + 'Karl Trygve Kalleberg - Original author', + 'Douglas Anderson - 0.3.0 author' +) + +# ======= +# Globals +# ======= + +NAME_MAP = { + 'b': 'belongs', + 'c': 'changes', + 'k': 'check', + 'd': 'depends', + 'g': 'depgraph', + 'f': 'files', + 'h': 'hasuse', + 'l': 'list_', + 'm': 'meta', + 's': 'size', + 'u': 'uses', + 'w': 'which' +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @param with_description (bool): Option to print module's __doc__ or not + """ + + if with_description: + print __doc__ + print main_usage() + print + print pp.globaloption("global options") + print format_options(( + (" -h, --help", "display this help message"), + (" -q, --quiet", "minimal output"), + (" -C, --no-color", "turn off colors"), + (" -N, --no-pipe", "turn off pipe detection"), + (" -V, --version", "display version info") + )) + print + print pp.command("modules") + " (" + pp.command("short name") + ")" + print format_options(( + (" (b)elongs", "list what package FILES belong to"), + (" (c)hanges", "list changelog entries for ATOM"), + (" chec(k)", "verify checksums and timestamps for PKG"), + (" (d)epends", "list all packages directly depending on ATOM"), + (" dep(g)raph", "display a tree of all dependencies for PKG"), + (" (f)iles", "list all files installed by PKG"), + (" (h)asuse", "list all packages that have USE flag"), + (" (l)ist", "list package matching PKG"), + (" (m)eta", "display metadata about PKG"), + (" (s)ize", "display total size of all files owned by PKG"), + (" (u)ses", "display USE flags for PKG"), + (" (w)hich", "print full path to ebuild for PKG") + )) + + +def expand_module_name(module_name): + """Returns one of the values of NAME_MAP or raises KeyError""" + + if module_name == 'list': + # list is a Python builtin type, so we must rename our module + return 'list_' + elif module_name in NAME_MAP.values(): + return module_name + else: + return NAME_MAP[module_name] + + +def format_options(options): + """Format module options. + + @type options: list + @param options: [('option 1', 'description 1'), ('option 2', 'des... )] + @rtype: str + @return: formatted options string + """ + + result = [] + twrap = TextWrapper(width=CONFIG['termWidth']) + opts = (x[0] for x in options) + descs = (x[1] for x in options) + for opt, desc in zip(opts, descs): + twrap.initial_indent = pp.emph(opt.ljust(25)) + twrap.subsequent_indent = " " * 25 + result.append(twrap.fill(desc)) + + return '\n'.join(result) + + +def format_filetype(path, fdesc, show_type=False, show_md5=False, + show_timestamp=False): + """Format a path for printing. + + @type path: str + @param path: the path + @type fdesc: list + @param fdesc: [file_type, timestamp, MD5 sum/symlink target] + file_type is one of dev, dir, obj, sym. + If file_type is dir, there is no timestamp or MD5 sum. + If file_type is sym, fdesc[2] is the target of the symlink. + @type show_type: bool + @param show_type: if True, prepend the file's type to the formatted string + @type show_md5: bool + @param show_md5: if True, append MD5 sum to the formatted string + @type show_timestamp: bool + @param show_timestamp: if True, append time-of-creation after pathname + @rtype: str + @return: formatted pathname with optional added information + """ + + ftype = fpath = stamp = md5sum = "" + + if fdesc[0] == "obj": + ftype = "file" + fpath = path + stamp = format_timestamp(fdesc[1]) + md5sum = fdesc[2] + elif fdesc[0] == "dir": + ftype = "dir" + fpath = pp.path(path) + elif fdesc[0] == "sym": + ftype = "sym" + stamp = format_timestamp(fdesc[1]) + tgt = fdesc[2].split()[0] + if CONFIG["piping"]: + fpath = path + else: + fpath = pp.path_symlink(path + " -> " + tgt) + elif fdesc[0] == "dev": + ftype = "dev" + fpath = path + else: + sys.stderr.write( + pp.error("%s has unknown type: %s" % (path, fdesc[0])) + ) + + result = "" + if show_type: + result += "%4s " % ftype + result += fpath + if show_timestamp: + result += " " + stamp + if show_md5: + result += " " + md5sum + + return result + + +def format_timestamp(timestamp): + """Format a timestamp into, e.g., '2009-01-31 21:19:44' format""" + + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(timestamp))) + + +def initialize_configuration(): + """Setup the standard equery config""" + + # Get terminal size + term_width = pp.output.get_term_size()[1] + if term_width == -1: + # get_term_size() failed. Set a sane default width: + term_width = 80 + + # Terminal size, minus a 1-char margin for text wrapping + CONFIG['termWidth'] = term_width - 1 + + # Guess color output + if (CONFIG['color'] == -1 and (not sys.stdout.isatty() or + os.getenv("NOCOLOR") in ("yes", "true")) or CONFIG['color'] == 0): + pp.output.nocolor() + + CONFIG['verbose'] = not CONFIG['piping'] + + +def main_usage(): + """Return the main usage message for equery""" + + return "%(usage)s %(product)s [%(g_opts)s] %(mod_name)s [%(mod_opts)s]" % { + 'usage': pp.emph("Usage:"), + 'product': pp.productname(__productname__), + 'g_opts': pp.globaloption("global-options"), + 'mod_name': pp.command("module-name"), + 'mod_opts': pp.localoption("module-options") + } + + +def mod_usage(mod_name="module", arg="pkgspec", optional=False): + """Provide a consistent usage message to the calling module. + + @type arg: string + @param arg: what kind of argument the module takes (pkgspec, filename, etc) + @type optional: bool + @param optional: is the argument optional? + """ + + return "%(usage)s: %(mod_name)s [%(opts)s] %(arg)s" % { + 'usage': pp.emph("Usage"), + 'mod_name': pp.command(mod_name), + 'opts': pp.localoption("options"), + 'arg': ("[%s]" % pp.emph(arg)) if optional else pp.emph(arg) + } + + +def parse_global_options(global_opts, args): + """Parse global input args and return True if we should display help for + the called module, else False (or display help and exit from here). + """ + + need_help = False + opts = (opt[0] for opt in global_opts) + for opt in opts: + if opt in ('-h', '--help'): + if args: + need_help = True + else: + print_help() + sys.exit(0) + elif opt in ('-q','--quiet'): + CONFIG['quiet'] = True + elif opt in ('-C', '--no-color', '--nocolor'): + CONFIG['color'] = 0 + pp.output.nocolor() + elif opt in ('-N', '--no-pipe'): + CONFIG['piping'] = False + CONFIG['verbose'] = True + elif opt in ('-V', '--version'): + print_version() + sys.exit(0) + elif opt in ('--debug'): + CONFIG['debug'] = True + + return need_help + + +def print_version(): + """Print the version of this tool to the console.""" + + print "%(product)s (%(version)s) - %(docstring)s" % { + "product": pp.productname(__productname__), + "version": __version__, + "docstring": __doc__ + } + + +def split_arguments(args): + """Separate module name from module arguments""" + + return args.pop(0), args + + +def main(): + """Parse input and run the program.""" + + short_opts = "hqCNV" + long_opts = ( + 'help', 'quiet', 'nocolor', 'no-color', 'no-pipe', 'version', 'debug' + ) + + initialize_configuration() + + try: + global_opts, args = getopt(sys.argv[1:], short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Global %s" % err)) + print_help(with_description=False) + sys.exit(2) + + # Parse global options + need_help = parse_global_options(global_opts, args) + + # FIXME: There are a few places that make use of both quiet and verbose. + # Consider combining. + if CONFIG['quiet']: + CONFIG['verbose'] = False + + try: + module_name, module_args = split_arguments(args) + except IndexError: + print_help() + sys.exit(2) + + if need_help: + module_args.append('--help') + + try: + expanded_module_name = expand_module_name(module_name) + except KeyError: + sys.stderr.write(pp.error("Unknown module '%s'" % module_name)) + print_help(with_description=False) + sys.exit(2) + + try: + loaded_module = __import__( + expanded_module_name, globals(), locals(), [], -1 + ) + loaded_module.main(module_args) + except portage.exception.AmbiguousPackageName, err: + raise errors.GentoolkitAmbiguousPackage(err) + except IOError, err: + if err.errno != errno.EPIPE: + raise + +if __name__ == '__main__': + main() diff --git a/gentoolkit/pym/gentoolkit/equery/belongs.py b/gentoolkit/pym/gentoolkit/equery/belongs.py new file mode 100644 index 0000000..3845b9d --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/belongs.py @@ -0,0 +1,156 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""List all packages owning a particular file + +Note: Normally, only one package will own a file. If multiple packages own + the same file, it usually constitutes a problem, and should be reported. +""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit.equery import (format_filetype, format_options, mod_usage, + CONFIG) +from gentoolkit.helpers import FileOwner + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "fullRegex": False, + "earlyOut": False, + "nameOnly": False +} + +# ======= +# Classes +# ======= + +class BelongsPrinter(object): + """Outputs a formatted list of packages that claim to own a files.""" + + def __init__(self, verbose=True, name_only=False): + if verbose: + self.print_fn = self.print_verbose + else: + self.print_fn = self.print_quiet + + self.name_only = name_only + + def __call__(self, pkg, cfile): + self.print_fn(pkg, cfile) + + # W0613: *Unused argument %r* + # pylint: disable-msg=W0613 + def print_quiet(self, pkg, cfile): + "Format for minimal output." + if self.name_only: + name = pkg.cpv.cp + else: + name = str(pkg.cpv) + print name + + def print_verbose(self, pkg, cfile): + "Format for full output." + file_str = pp.path(format_filetype(cfile, pkg.parsed_contents()[cfile])) + if self.name_only: + name = pkg.cpv.cp + else: + name = str(pkg.cpv) + print pp.cpv(name), "(" + file_str + ")" + + +# ========= +# Functions +# ========= + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h','--help'): + print_help() + sys.exit(0) + elif opt in ('-e', '--early-out', '--earlyout'): + if opt == '--earlyout': + sys.stderr.write(pp.warn("Use of --earlyout is deprecated.")) + sys.stderr.write(pp.warn("Please use --early-out.")) + print + QUERY_OPTS['earlyOut'] = True + elif opt in ('-f', '--full-regex'): + QUERY_OPTS['fullRegex'] = True + elif opt in ('-n', '--name-only'): + QUERY_OPTS['nameOnly'] = True + + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="belongs", arg="filename") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -f, --full-regex", "supplied query is a regex" ), + (" -e, --early-out", "stop when first match is found"), + (" -n, --name-only", "don't print the version") + )) + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "h:fen" + long_opts = ('help', 'full-regex', 'early-out', 'earlyout', + 'name-only') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + if CONFIG['verbose']: + print " * Searching for %s ... " % (pp.regexpquery(",".join(queries))) + + printer_fn = BelongsPrinter( + verbose=CONFIG['verbose'], name_only=QUERY_OPTS['nameOnly'] + ) + + find_owner = FileOwner( + is_regex=QUERY_OPTS['fullRegex'], + early_out=QUERY_OPTS['earlyOut'], + printer_fn=printer_fn + ) + + find_owner(queries) + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/changes.py b/gentoolkit/pym/gentoolkit/equery/changes.py new file mode 100644 index 0000000..8adf246 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/changes.py @@ -0,0 +1,205 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 or higher +# +# $Header: $ + +"""Displays the ChangeLog entry for the latest installable version of an atom""" + +# Move to Imports sections when Python 2.6 is stable +from __future__ import with_statement + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.atom import Atom +from gentoolkit.equery import format_options, mod_usage +from gentoolkit.helpers import ChangeLog, find_best_match, find_packages + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + 'onlyLatest': False, + 'showFullLog': False, + 'limit': None, + 'from': None, + 'to': None +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="changes") + print + print pp.emph("examples") + print (" c portage # show latest visible " + "version's entry") + print " c portage --full --limit=3 # show 3 latest entries" + print " c '=sys-apps/portage-2.1.6*' # use atom syntax" + print " c portage --from=2.2_rc20 --to=2.2_rc30 # use version ranges" + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -l, --latest", "display only the latest ChangeLog entry"), + (" -f, --full", "display the full ChangeLog"), + (" --limit=NUM", + "limit the number of entries displayed (with --full)"), + (" --from=VER", "set which version to display from"), + (" --to=VER", "set which version to display to"), + )) + + +def get_match(query): + """Find a valid package from which to get the ChangeLog path. + + @raise GentoolkitNoMatches: if no matches found + """ + + match = matches = None + match = find_best_match(query) + + if not match: + matches = find_packages(query, include_masked=True) + else: + matches = [match] + + if not matches: + raise errors.GentoolkitNoMatches(query) + + return matches[0] + + +def is_ranged(atom): + """Return True if an atom string appears to be ranged, else False.""" + + return atom.startswith(('~', '<', '>')) or atom.endswith('*') + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + posargs = (x[1] for x in module_opts) + for opt, posarg in zip(opts, posargs): + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-f', '--full'): + QUERY_OPTS['showFullLog'] = True + elif opt in ('-l', '--latest'): + QUERY_OPTS['onlyLatest'] = True + elif opt in ('--limit',): + set_limit(posarg) + elif opt in ('--from',): + QUERY_OPTS['from'] = posarg + elif opt in ('--to',): + QUERY_OPTS['to'] = posarg + + +def print_entries(entries): + """Print entries and strip trailing whitespace from the last entry.""" + + len_entries = len(entries) + for i, entry in enumerate(entries): # , start=1): in py2.6 + i += 1 + if i < len_entries: + print entry + else: + print entry.strip() + + +def set_limit(posarg): + """Set a limit in QUERY_OPTS on how many ChangeLog entries to display. + + Die if posarg is not an integer. + """ + + if posarg.isdigit(): + QUERY_OPTS['limit'] = int(posarg) + else: + err = "Module option --limit requires integer (got '%s')" + sys.stderr.write(pp.error(err % posarg)) + print + print_help(with_description=False) + sys.exit(2) + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hlf" + long_opts = ('help', 'full', 'from=', 'latest', 'limit=', 'to=') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + first_run = True + for query in queries: + if not first_run: + print + + match = get_match(query) + changelog_path = os.path.join(match.package_path(), 'ChangeLog') + changelog = ChangeLog(changelog_path) + + # + # Output + # + + if (QUERY_OPTS['onlyLatest'] or ( + changelog.entries and not changelog.indexed_entries + )): + print changelog.latest.strip() + else: + end = QUERY_OPTS['limit'] or len(changelog.indexed_entries) + if QUERY_OPTS['to'] or QUERY_OPTS['from']: + print_entries( + changelog.entries_matching_range( + from_ver=QUERY_OPTS['from'], + to_ver=QUERY_OPTS['to'] + )[:end] + ) + elif QUERY_OPTS['showFullLog']: + print_entries(changelog.full[:end]) + else: + # Raises GentoolkitInvalidAtom here if invalid + atom = Atom(query) if is_ranged(query) else '=' + str(match.cpv) + print_entries(changelog.entries_matching_atom(atom)[:end]) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/check.py b/gentoolkit/pym/gentoolkit/equery/check.py new file mode 100644 index 0000000..5969804 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/check.py @@ -0,0 +1,291 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Checks timestamps and MD5 sums for files owned by a given installed package""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import sys +from functools import partial +from getopt import gnu_getopt, GetoptError + +import portage.checksum as checksum + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import do_lookup + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "includeInstalled": True, + "includeOverlayTree": False, + "includePortTree": False, + "checkMD5sum": True, + "checkTimestamp" : True, + "isRegex": False, + "onlyFailures": False, + "printMatchInfo": False, + "showSummary" : True, + "showPassedFiles" : False, + "showFailedFiles" : True +} + +# ======= +# Classes +# ======= + +class VerifyContents(object): + """Verify installed packages' CONTENTS files. + + The CONTENTS file contains timestamps and MD5 sums for each file owned + by a package. + """ + def __init__(self, printer_fn=None): + """Create a VerifyObjects instance. + + @type printer_fn: callable + @param printer_fn: if defined, will be applied to each result as found + """ + self.check_sums = True + self.check_timestamps = True + self.printer_fn = printer_fn + + self.is_regex = False + + def __call__( + self, + pkgs, + is_regex=False, + check_sums=True, + check_timestamps=True + ): + self.is_regex = is_regex + self.check_sums = check_sums + self.check_timestamps = check_timestamps + + result = {} + for pkg in pkgs: + # _run_checks returns tuple(n_passed, n_checked, err) + check_results = self._run_checks(pkg.parsed_contents()) + result[pkg.cpv] = check_results + if self.printer_fn is not None: + self.printer_fn(pkg.cpv, check_results) + + return result + + def _run_checks(self, files): + """Run some basic sanity checks on a package's contents. + + If the file type (ftype) is not a directory or symlink, optionally + verify MD5 sums or mtimes via L{self._verify_obj}. + + @see: gentoolkit.packages.get_contents() + @type files: dict + @param files: in form {'PATH': ['TYPE', 'TIMESTAMP', 'MD5SUM']} + @rtype: tuple + @return: + n_passed (int): number of files that passed all checks + n_checked (int): number of files checked + errs (list): check errors' descriptions + """ + n_checked = 0 + n_passed = 0 + errs = [] + for cfile in files: + n_checked += 1 + ftype = files[cfile][0] + if not os.path.exists(cfile): + errs.append("%s does not exist" % cfile) + continue + elif ftype == "dir": + if not os.path.isdir(cfile): + err = "%(cfile)s exists, but is not a directory" + errs.append(err % locals()) + continue + elif ftype == "obj": + obj_errs = self._verify_obj(files, cfile, errs) + if len(obj_errs) > len(errs): + errs = obj_errs[:] + continue + elif ftype == "sym": + target = files[cfile][2].strip() + if not os.path.islink(cfile): + err = "%(cfile)s exists, but is not a symlink" + errs.append(err % locals()) + continue + tgt = os.readlink(cfile) + if tgt != target: + err = "%(cfile)s does not point to %(target)s" + errs.append(err % locals()) + continue + else: + err = "%(cfile)s has unknown type %(ftype)s" + errs.append(err % locals()) + continue + n_passed += 1 + + return n_passed, n_checked, errs + + def _verify_obj(self, files, cfile, errs): + """Verify the MD5 sum and/or mtime and return any errors.""" + + obj_errs = errs[:] + if self.check_sums: + md5sum = files[cfile][2] + try: + cur_checksum = checksum.perform_md5(cfile, calc_prelink=1) + except IOError: + err = "Insufficient permissions to read %(cfile)s" + obj_errs.append(err % locals()) + return obj_errs + if cur_checksum != md5sum: + err = "%(cfile)s has incorrect MD5sum" + obj_errs.append(err % locals()) + return obj_errs + if self.check_timestamps: + mtime = int(files[cfile][1]) + st_mtime = int(os.lstat(cfile).st_mtime) + if st_mtime != mtime: + err = ( + "%(cfile)s has wrong mtime (is %(st_mtime)d, should be " + "%(mtime)d)" + ) + obj_errs.append(err % locals()) + return obj_errs + + return obj_errs + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + + # Deprecation warning added by djanderson, 12/2008 + depwarning = ( + "Default action for this module has changed in Gentoolkit 0.3.", + "Use globbing to simulate the old behavior (see man equery).", + "Use '*' to check all installed packages.", + "Use 'foo-bar/*' to filter by category." + ) + for line in depwarning: + sys.stderr.write(pp.warn(line)) + print + + print mod_usage(mod_name="check") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -f, --full-regex", "query is a regular expression"), + (" -o, --only-failures", "only display packages that do not pass"), + )) + + +def checks_printer(cpv, data, verbose=True, only_failures=False): + """Output formatted results of pkg file(s) checks""" + seen = [] + + n_passed, n_checked, errs = data + n_failed = n_checked - n_passed + if only_failures and not n_failed: + return + else: + if verbose: + if not cpv in seen: + print "* Checking %s ..." % (pp.emph(str(cpv))) + seen.append(cpv) + else: + print "%s:" % cpv, + + if verbose: + for err in errs: + sys.stderr.write(pp.error(err)) + + if verbose: + n_passed = pp.number(str(n_passed)) + n_checked = pp.number(str(n_checked)) + info = " %(n_passed)s out of %(n_checked)s files passed" + print info % locals() + else: + print "failed(%s)" % n_failed + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-f', '--full-regex'): + QUERY_OPTS['isRegex'] = True + elif opt in ('-o', '--only-failures'): + QUERY_OPTS['onlyFailures'] = True + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hof" + long_opts = ('help', 'only-failures', 'full-regex') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + first_run = True + for query in queries: + if not first_run: + print + + matches = do_lookup(query, QUERY_OPTS) + + if not matches: + raise errors.GentoolkitNoMatches(query, in_installed=True) + + matches.sort() + + printer = partial( + checks_printer, + verbose=CONFIG['verbose'], + only_failures=QUERY_OPTS['onlyFailures'] + ) + check = VerifyContents(printer_fn=printer) + check(matches) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/depends.py b/gentoolkit/pym/gentoolkit/equery/depends.py new file mode 100644 index 0000000..a1061fc --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/depends.py @@ -0,0 +1,193 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""List all packages that depend on a atom given query""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit.dependencies import Dependencies +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import (get_cpvs, get_installed_cpvs, + compare_package_strings) + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "includeMasked": False, + "onlyDirect": True, + "maxDepth": -1, +} + +# ======= +# Classes +# ======= + +class DependPrinter(object): + """Output L{gentoolkit.dependencies.Dependencies} objects.""" + def __init__(self, verbose=True): + if verbose: + self.print_fn = self.print_verbose + else: + self.print_fn = self.print_quiet + + def __call__(self, dep, dep_is_displayed=False): + self.format_depend(dep, dep_is_displayed) + + @staticmethod + def print_verbose(indent, cpv, use_conditional, depatom): + """Verbosely prints a set of dep strings.""" + + sep = ' ? ' if (depatom and use_conditional) else '' + print indent + pp.cpv(cpv), "(" + use_conditional + sep + depatom + ")" + + # W0613: *Unused argument %r* + # pylint: disable-msg=W0613 + @staticmethod + def print_quiet(indent, cpv, use_conditional, depatom): + """Quietly prints a subset set of dep strings.""" + + print indent + pp.cpv(cpv) + + def format_depend(self, dep, dep_is_displayed): + """Format a dependency for printing. + + @type dep: L{gentoolkit.dependencies.Dependencies} + @param dep: the dependency to display + """ + + depth = getattr(dep, 'depth', 0) + indent = " " * depth + mdep = dep.matching_dep + use_conditional = "" + if mdep.use_conditional: + use_conditional = " & ".join( + pp.useflag(u) for u in mdep.use_conditional.split() + ) + if mdep.operator == '=*': + formatted_dep = '=%s*' % str(mdep.cpv) + else: + formatted_dep = mdep.operator + str(mdep.cpv) + if mdep.slot: + formatted_dep += pp.emph(':') + pp.slot(mdep.slot) + if mdep.use: + useflags = pp.useflag(','.join(mdep.use.tokens)) + formatted_dep += (pp.emph('[') + useflags + pp.emph(']')) + + if dep_is_displayed: + indent = indent + " " * len(str(dep.cpv)) + self.print_fn(indent, '', use_conditional, formatted_dep) + else: + self.print_fn(indent, str(dep.cpv), use_conditional, formatted_dep) + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="depends") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -a, --all-packages", + "include dependencies that are not installed (slow)"), + (" -D, --indirect", + "search both direct and indirect dependencies"), + (" --depth=N", "limit indirect dependency tree to specified depth") + )) + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + posargs = (x[1] for x in module_opts) + for opt, posarg in zip(opts, posargs): + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-a', '--all-packages'): + QUERY_OPTS['includeMasked'] = True + elif opt in ('-D', '--indirect'): + QUERY_OPTS['onlyDirect'] = False + elif opt in ('--depth'): + if posarg.isdigit(): + depth = int(posarg) + else: + err = "Module option --depth requires integer (got '%s')" + sys.stdout.write(pp.error(err % posarg)) + print + print_help(with_description=False) + sys.exit(2) + QUERY_OPTS["maxDepth"] = depth + + +def main(input_args): + """Parse input and run the program""" + short_opts = "hadD" # -d, --direct was old option for default action + long_opts = ('help', 'all-packages', 'direct', 'indirect', 'depth=') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + # + # Output + # + + dep_print = DependPrinter(verbose=CONFIG['verbose']) + first_run = True + for query in queries: + if not first_run: + print + + pkg = Dependencies(query) + if QUERY_OPTS['includeMasked']: + pkggetter = get_cpvs + else: + pkggetter = get_installed_cpvs + + if CONFIG['verbose']: + print " * These packages depend on %s:" % pp.emph(str(pkg.cpv)) + pkg.graph_reverse_depends( + pkgset=sorted(pkggetter(), cmp=compare_package_strings), + max_depth=QUERY_OPTS["maxDepth"], + only_direct=QUERY_OPTS["onlyDirect"], + printer_fn=dep_print + ) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/depgraph.py b/gentoolkit/pym/gentoolkit/equery/depgraph.py new file mode 100644 index 0000000..9c7e346 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/depgraph.py @@ -0,0 +1,223 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Display a direct dependency graph for a given package""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from functools import partial +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import do_lookup + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "depth": 1, + "noAtom": False, + "noIndent": False, + "noUseflags": False, + "includeInstalled": True, + "includePortTree": True, + "includeOverlayTree": True, + "includeMasked": True, + "isRegex": False, + "matchExact": True, + "printMatchInfo": (not CONFIG['quiet']) +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print "Default depth is set to 1 (direct only). Use --depth=0 for no max." + print + print mod_usage(mod_name="depgraph") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -A, --no-atom", "do not show dependency atom"), + (" -U, --no-useflags", "do not show USE flags"), + (" -l, --linear", "do not format the graph by indenting dependencies"), + (" --depth=N", "limit dependency graph to specified depth") + )) + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + posargs = (x[1] for x in module_opts) + for opt, posarg in zip(opts, posargs): + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + if opt in ('-A', '--no-atom'): + QUERY_OPTS["noAtom"] = True + if opt in ('-U', '--no-useflags'): + QUERY_OPTS["noUseflags"] = True + if opt in ('-l', '--linear'): + QUERY_OPTS["noIndent"] = True + if opt in ('--depth'): + if posarg.isdigit(): + depth = int(posarg) + else: + err = "Module option --depth requires integer (got '%s')" + sys.stderr.write(pp.error(err % posarg)) + print + print_help(with_description=False) + sys.exit(2) + QUERY_OPTS["depth"] = depth + + +def depgraph_printer( + depth, + pkg, + dep, + no_use=False, + no_atom=False, + no_indent=False, + initial_pkg=False +): + """Display L{gentoolkit.dependencies.Dependencies.graph_depends} results. + + @type depth: int + @param depth: depth of indirection, used to calculate indent + @type pkg: L{gentoolkit.package.Package} + @param pkg: "best match" package matched by B{dep} + @type dep: L{gentoolkit.atom.Atom} + @param dep: dependency that matched B{pkg} + @type no_use: bool + @param no_use: don't output USE flags + @type no_atom: bool + @param no_atom: don't output dep atom + @type no_indent: bool + @param no_indent: don't output indent based on B{depth} + @type initial_pkg: bool + @param initial_pkg: somewhat of a hack used to print the root package of + the graph with absolutely no indent + """ + indent = '' if no_indent or initial_pkg else ' ' + (' ' * depth) + decorator = '[%3d] ' % depth if no_indent else '`-- ' + use = '' + try: + atom = '' if no_atom else ' (%s)' % dep.atom + if not no_use and dep is not None and dep.use: + use = ' [%s]' % ' '.join( + pp.useflag(x, enabled=True) for x in dep.use.tokens + ) + except AttributeError: + # 'NoneType' object has no attribute 'atom' + atom = '' + try: + print ''.join((indent, decorator, pp.cpv(str(pkg.cpv)), atom, use)) + except AttributeError: + # 'NoneType' object has no attribute 'cpv' + print ''.join((indent, decorator, "(no match for %r)" % dep.atom)) + + +def make_depgraph(pkg, printer_fn): + """Create and display depgraph for each package.""" + + if CONFIG['verbose']: + print " * direct dependency graph for %s:" % pp.cpv(str(pkg.cpv)) + else: + print "%s:" % str(pkg.cpv) + + # Print out the first package + printer_fn(0, pkg, None, initial_pkg=True) + + deps = pkg.deps.graph_depends( + max_depth=QUERY_OPTS['depth'], + printer_fn=printer_fn, + # Use this to set this pkg as the graph's root; better way? + result=[(0, pkg)] + ) + + if CONFIG['verbose']: + pkgname = pp.cpv(str(pkg.cpv)) + n_packages = pp.number(str(len(deps))) + max_seen = pp.number(str(max(x[0] for x in deps))) + info = "[ %s stats: packages (%s), max depth (%s) ]" + print info % (pkgname, n_packages, max_seen) + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hAUl" + long_opts = ('help', 'no-atom', 'no-useflags', 'depth=') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + # + # Output + # + + first_run = True + for query in queries: + if not first_run: + print + + matches = do_lookup(query, QUERY_OPTS) + + if not matches: + raise errors.GentoolkitNoMatches(query) + + if CONFIG['verbose']: + printer = partial( + depgraph_printer, + no_atom=QUERY_OPTS['noAtom'], + no_indent=QUERY_OPTS['noIndent'], + no_use=QUERY_OPTS['noUseflags'] + ) + else: + printer = partial( + depgraph_printer, + no_atom=True, + no_indent=True, + no_use=True + ) + + for pkg in matches: + make_depgraph(pkg, printer) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/files.py b/gentoolkit/pym/gentoolkit/equery/files.py new file mode 100644 index 0000000..f0c40ee --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/files.py @@ -0,0 +1,320 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""List files owned by a given package""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import sys +from getopt import gnu_getopt, GetoptError + +import portage + +import gentoolkit.pprinter as pp +from gentoolkit.equery import (format_filetype, format_options, mod_usage, + CONFIG) +from gentoolkit.helpers import do_lookup + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "categoryFilter": None, + "includeInstalled": True, + "includePortTree": False, + "includeOverlayTree": False, + "includeMasked": True, + "isRegex": False, + "matchExact": True, + "outputTree": False, + "printMatchInfo": (not CONFIG['quiet']), + "showType": False, + "showTimestamp": False, + "showMD5": False, + "typeFilter": None +} + +FILTER_RULES = ( + 'dir', 'obj', 'sym', 'dev', 'path', 'conf', 'cmd', 'doc', 'man', 'info' +) + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="files") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -m, --md5sum", "include MD5 sum in output"), + (" -s, --timestamp", "include timestamp in output"), + (" -t, --type", "include file type in output"), + (" --tree", "display results in a tree (turns off other options)"), + (" -f, --filter=RULES", "filter output by file type"), + (" RULES", + "a comma-separated list (no spaces); choose from:") + )) + print " " * 24, ', '.join(pp.emph(x) for x in FILTER_RULES) + + +# R0912: *Too many branches (%s/%s)* +# pylint: disable-msg=R0912 +def display_files(contents): + """Display the content of an installed package. + + @see: gentoolkit.package.Package.parsed_contents + @type contents: dict + @param contents: {'path': ['filetype', ...], ...} + """ + + filenames = contents.keys() + filenames.sort() + last = [] + + for name in filenames: + if QUERY_OPTS["outputTree"]: + dirdepth = name.count('/') + indent = " " + if dirdepth == 2: + indent = " " + elif dirdepth > 2: + indent = " " * (dirdepth - 1) + + basename = name.rsplit("/", dirdepth - 1) + if contents[name][0] == "dir": + if len(last) == 0: + last = basename + print pp.path(indent + basename[0]) + continue + for i, directory in enumerate(basename): + try: + if directory in last[i]: + continue + except IndexError: + pass + last = basename + if len(last) == 1: + print pp.path(indent + last[0]) + continue + print pp.path(indent + "> /" + last[-1]) + elif contents[name][0] == "sym": + print pp.path(indent + "+"), + print pp.path_symlink(basename[-1] + " -> " + contents[name][2]) + else: + print pp.path(indent + "+ ") + basename[-1] + else: + print format_filetype( + name, + contents[name], + show_type=QUERY_OPTS["showType"], + show_md5=QUERY_OPTS["showMD5"], + show_timestamp=QUERY_OPTS["showTimestamp"] + ) + + +def filter_by_doc(contents, content_filter): + """Return a copy of content filtered by documentation.""" + + filtered_content = {} + for doctype in ('doc' ,'man' ,'info'): + # List only files from /usr/share/{doc,man,info} + if doctype in content_filter: + docpath = os.path.join(os.sep, 'usr', 'share', doctype) + for path in contents: + if contents[path][0] == 'obj' and path.startswith(docpath): + filtered_content[path] = contents[path] + + return filtered_content + + +def filter_by_command(contents): + """Return a copy of content filtered by executable commands.""" + + filtered_content = {} + userpath = os.environ["PATH"].split(os.pathsep) + userpath = [os.path.normpath(x) for x in userpath] + for path in contents: + if (contents[path][0] in ['obj', 'sym'] and + os.path.dirname(path) in userpath): + filtered_content[path] = contents[path] + + return filtered_content + + +def filter_by_path(contents): + """Return a copy of content filtered by file paths.""" + + filtered_content = {} + paths = list(reversed(sorted(contents.keys()))) + while paths: + basepath = paths.pop() + if contents[basepath][0] == 'dir': + check_subdirs = False + for path in paths: + if (contents[path][0] != "dir" and + os.path.dirname(path) == basepath): + filtered_content[basepath] = contents[basepath] + check_subdirs = True + break + if check_subdirs: + while (paths and paths[-1].startswith(basepath)): + paths.pop() + + return filtered_content + + +def filter_by_conf(contents): + """Return a copy of content filtered by configuration files.""" + + filtered_content = {} + conf_path = portage.settings["CONFIG_PROTECT"].split() + conf_path = tuple(os.path.normpath(x) for x in conf_path) + conf_mask_path = portage.settings["CONFIG_PROTECT_MASK"].split() + conf_mask_path = tuple(os.path.normpath(x) for x in conf_mask_path) + for path in contents: + if contents[path][0] == 'obj' and path.startswith(conf_path): + if not path.startswith(conf_mask_path): + filtered_content[path] = contents[path] + + return filtered_content + + +def filter_contents(contents): + """Filter files by type if specified by the user. + + @see: gentoolkit.package.Package.parsed_contents + @type contents: dict + @param contents: {'path': ['filetype', ...], ...} + @rtype: dict + @return: contents with unrequested filetypes stripped + """ + + if QUERY_OPTS['typeFilter']: + content_filter = QUERY_OPTS['typeFilter'] + else: + return contents + + filtered_content = {} + if frozenset(('dir', 'obj', 'sym', 'dev')).intersection(content_filter): + # Filter elements by type (as recorded in CONTENTS) + for path in contents: + if contents[path][0] in content_filter: + filtered_content[path] = contents[path] + if "cmd" in content_filter: + filtered_content.update(filter_by_command(contents)) + if "path" in content_filter: + filtered_content.update(filter_by_path(contents)) + if "conf" in content_filter: + filtered_content.update(filter_by_conf(contents)) + if frozenset(('doc' ,'man' ,'info')).intersection(content_filter): + filtered_content.update(filter_by_doc(contents, content_filter)) + + return filtered_content + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + content_filter = [] + opts = (x[0] for x in module_opts) + posargs = (x[1] for x in module_opts) + for opt, posarg in zip(opts, posargs): + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-e', '--exact-name'): + QUERY_OPTS["matchExact"] = True + elif opt in ('-m', '--md5sum'): + QUERY_OPTS["showMD5"] = True + elif opt in ('-s', '--timestamp'): + QUERY_OPTS["showTimestamp"] = True + elif opt in ('-t', '--type'): + QUERY_OPTS["showType"] = True + elif opt in ('--tree'): + QUERY_OPTS["outputTree"] = True + elif opt in ('-f', '--filter'): + f_split = posarg.split(',') + content_filter.extend(x.lstrip('=') for x in f_split) + for rule in content_filter: + if not rule in FILTER_RULES: + sys.stderr.write( + pp.error("Invalid filter rule '%s'" % rule) + ) + print + print_help(with_description=False) + sys.exit(2) + QUERY_OPTS["typeFilter"] = content_filter + + +def main(input_args): + """Parse input and run the program""" + + # -e, --exact-name is legacy option. djanderson '09 + short_opts = "hemstf:" + long_opts = ('help', 'exact-name', 'md5sum', 'timestamp', 'type', 'tree', + 'filter=') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + # Turn off filtering for tree output + if QUERY_OPTS["outputTree"]: + QUERY_OPTS["typeFilter"] = None + + # + # Output files + # + + first_run = True + for query in queries: + if not first_run: + print + + matches = do_lookup(query, QUERY_OPTS) + + if not matches: + sys.stderr.write( + pp.error("No matching packages found for %s" % query) + ) + + for pkg in matches: + if CONFIG['verbose']: + print " * Contents of %s:" % pp.cpv(str(pkg.cpv)) + + contents = pkg.parsed_contents() + display_files(filter_contents(contents)) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/hasuse.py b/gentoolkit/pym/gentoolkit/equery/hasuse.py new file mode 100644 index 0000000..8d51013 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/hasuse.py @@ -0,0 +1,156 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 or higher +# +# $Header: $ + +"""List all installed packages that have a given USE flag""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import do_lookup +from gentoolkit.package import PackageFormatter + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "categoryFilter": None, + "includeInstalled": True, + "includePortTree": False, + "includeOverlayTree": False, + "includeMasked": True, + "isRegex": False, # Necessary for do_lookup, don't change + "printMatchInfo": False +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="hasuse", arg="USE-flag") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -I, --exclude-installed", + "exclude installed packages from search path"), + (" -o, --overlay-tree", "include overlays in search path"), + (" -p, --portage-tree", "include entire portage tree in search path") + )) + + +def display_useflags(query, pkg): + """Display USE flag information for a given package.""" + + try: + useflags = [x.lstrip("+-") for x in pkg.environment("IUSE").split()] + except errors.GentoolkitFatalError: + # aux_get KeyError or other unexpected result + return + + if query not in useflags: + return + + if CONFIG['verbose']: + fmt_pkg = PackageFormatter(pkg, do_format=True) + else: + fmt_pkg = PackageFormatter(pkg, do_format=False) + + if (QUERY_OPTS["includeInstalled"] and + not QUERY_OPTS["includePortTree"] and + not QUERY_OPTS["includeOverlayTree"]): + if not 'I' in fmt_pkg.location: + return + if (QUERY_OPTS["includePortTree"] and + not QUERY_OPTS["includeOverlayTree"]): + if not 'P' in fmt_pkg.location: + return + if (QUERY_OPTS["includeOverlayTree"] and + not QUERY_OPTS["includePortTree"]): + if not 'O' in fmt_pkg.location: + return + print fmt_pkg + + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + # Parse module options + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-I', '--exclue-installed'): + QUERY_OPTS['includeInstalled'] = False + elif opt in ('-p', '--portage-tree'): + QUERY_OPTS['includePortTree'] = True + elif opt in ('-o', '--overlay-tree'): + QUERY_OPTS['includeOverlayTree'] = True + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hiIpo" # -i was option for default action + # --installed is no longer needed, kept for compatibility (djanderson '09) + long_opts = ('help', 'installed', 'exclude-installed', 'portage-tree', + 'overlay-tree') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + matches = do_lookup("*", QUERY_OPTS) + matches.sort() + + # + # Output + # + + first_run = True + for query in queries: + if not first_run: + print + + if CONFIG['verbose']: + print " * Searching for USE flag %s ... " % pp.emph(query) + + for pkg in matches: + display_useflags(query, pkg) + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/list_.py b/gentoolkit/pym/gentoolkit/equery/list_.py new file mode 100644 index 0000000..3de8355 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/list_.py @@ -0,0 +1,224 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 or higher +# +# $Header: $ + +"""List installed packages matching the query pattern""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit +import gentoolkit.pprinter as pp +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import do_lookup, get_installed_cpvs +from gentoolkit.package import Package, PackageFormatter + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "duplicates": False, + "includeInstalled": True, + "includePortTree": False, + "includeOverlayTree": False, + "includeMasked": True, + "includeMaskReason": False, + "isRegex": False, + "printMatchInfo": (not CONFIG['quiet']) +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + + # Deprecation warning added by djanderson, 12/2008 + depwarning = ( + "Default action for this module has changed in Gentoolkit 0.3.", + "Use globbing to simulate the old behavior (see man equery).", + "Use '*' to check all installed packages.", + "Use 'foo-bar/*' to filter by category." + ) + for line in depwarning: + sys.stderr.write(pp.warn(line)) + print + + print mod_usage(mod_name="list") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -d, --duplicates", "list only installed duplicate packages"), + (" -f, --full-regex", "query is a regular expression"), + (" -m, --mask-reason", "include reason for package mask"), + (" -I, --exclude-installed", + "exclude installed packages from output"), + (" -o, --overlay-tree", "list packages in overlays"), + (" -p, --portage-tree", "list packages in the main portage tree") + )) + + +def get_duplicates(matches): + """Return only packages that have more than one version installed.""" + + dups = {} + result = [] + for pkg in matches: + if pkg.cpv.cp in dups: + dups[pkg.cpv.cp].append(pkg) + else: + dups[pkg.cpv.cp] = [pkg] + + for cpv in dups.values(): + if len(cpv) > 1: + result.extend(cpv) + + return result + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + posargs = (x[1] for x in module_opts) + for opt, posarg in zip(opts, posargs): + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-I', '--exclude-installed'): + QUERY_OPTS['includeInstalled'] = False + elif opt in ('-p', '--portage-tree'): + QUERY_OPTS['includePortTree'] = True + elif opt in ('-o', '--overlay-tree'): + QUERY_OPTS['includeOverlayTree'] = True + elif opt in ('-f', '--full-regex'): + QUERY_OPTS['isRegex'] = True + elif opt in ('-m', '--mask-reason'): + QUERY_OPTS['includeMaskReason'] = True + elif opt in ('-e', '--exact-name'): + sys.stderr.write(pp.warn("-e, --exact-name is now default.")) + sys.stderr.write( + pp.warn("Use globbing to simulate the old behavior.") + ) + print + elif opt in ('-d', '--duplicates'): + QUERY_OPTS['duplicates'] = True + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hdefiImop" # -i, -e were options for default actions + + # 04/09: djanderson + # --all is no longer needed. Kept for compatibility. + # --installed is no longer needed. Kept for compatibility. + # --exact-name is no longer needed. Kept for compatibility. + long_opts = ('help', 'all', 'installed', 'exclude-installed', + 'mask-reason', 'portage-tree', 'overlay-tree', 'full-regex', 'exact-name', + 'duplicates') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + # Only search installed packages when listing duplicate packages + if QUERY_OPTS["duplicates"]: + QUERY_OPTS["includeInstalled"] = True + QUERY_OPTS["includePortTree"] = False + QUERY_OPTS["includeOverlayTree"] = False + QUERY_OPTS["includeMaskReason"] = False + + if not queries: + print_help() + sys.exit(2) + + first_run = True + for query in queries: + if not first_run: + print + + matches = do_lookup(query, QUERY_OPTS) + + # Find duplicate packages + if QUERY_OPTS["duplicates"]: + matches = get_duplicates(matches) + + matches.sort() + + # + # Output + # + + for pkg in matches: + if CONFIG['verbose']: + pkgstr = PackageFormatter(pkg, do_format=True) + else: + pkgstr = PackageFormatter(pkg, do_format=False) + + if (QUERY_OPTS["includeInstalled"] and + not QUERY_OPTS["includePortTree"] and + not QUERY_OPTS["includeOverlayTree"]): + if not 'I' in pkgstr.location: + continue + if (QUERY_OPTS["includePortTree"] and + not QUERY_OPTS["includeOverlayTree"]): + if not 'P' in pkgstr.location: + continue + if (QUERY_OPTS["includeOverlayTree"] and + not QUERY_OPTS["includePortTree"]): + if not 'O' in pkgstr.location: + continue + print pkgstr + + if QUERY_OPTS["includeMaskReason"]: + ms_int, ms_orig = pkgstr.format_mask_status() + if not ms_int > 2: + # ms_int is a number representation of mask level. + # Only 2 and above are "hard masked" and have reasons. + continue + mask_reason = pkg.mask_reason() + if not mask_reason: + # Package not on system or not masked + continue + elif not any(mask_reason): + print " * No mask reason given" + else: + status = ', '.join(ms_orig) + explanation = mask_reason[0] + mask_location = mask_reason[1] + print " * Masked by %r" % status + print " * %s:" % mask_location + print '\n'.join( + [' * %s' % line.lstrip(' #') + for line in explanation.splitlines()] + ) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/meta.py b/gentoolkit/pym/gentoolkit/equery/meta.py new file mode 100644 index 0000000..19c23a6 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/meta.py @@ -0,0 +1,494 @@ +# Copyright 2009-2010 Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 or higher +# +# $Header: $ + +"""Display metadata about a given package""" + +# Move to Imports section after Python-2.6 is stable +from __future__ import with_statement + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import re +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import find_packages, print_sequence, print_file +from gentoolkit.textwrap_ import TextWrapper + +# ======= +# Globals +# ======= + +# E1101: Module 'portage.output' has no $color member +# portage.output creates color functions dynamically +# pylint: disable-msg=E1101 + +QUERY_OPTS = { + 'current': False, + 'description': False, + 'herd': False, + 'keywords': False, + 'maintainer': False, + 'useflags': False, + 'upstream': False, + 'xml': False +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True, with_usage=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + if with_usage: + print mod_usage(mod_name="meta") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -d, --description", "show an extended package description"), + (" -H, --herd", "show the herd(s) for the package"), + (" -k, --keywords", "show keywords for all matching package versions"), + (" -m, --maintainer", "show the maintainer(s) for the package"), + (" -u, --useflags", "show per-package USE flag descriptions"), + (" -U, --upstream", "show package's upstream information"), + (" -x, --xml", "show the plain metadata.xml file") + )) + + +def filter_keywords(matches): + """Filters non-unique keywords per slot. + + Does not filter arch mask keywords (-). Besides simple non-unique keywords, + also remove unstable keywords (~) if a higher version in the same slot is + stable. This view makes version bumps easier for package maintainers. + + @type matches: array + @param matches: set of L{gentoolkit.package.Package} instances whose + 'key' are all the same. + @rtype: dict + @return: a dict with L{gentoolkit.package.Package} instance keys and + 'array of keywords not found in a higher version of pkg within the + same slot' values. + """ + def del_archmask(keywords): + """Don't add arch_masked to filter set.""" + return [x for x in keywords if not x.startswith('-')] + + def add_unstable(keywords): + """Add unstable keyword for all stable keywords to filter set.""" + result = list(keywords) + result.extend( + ['~%s' % x for x in keywords if not x.startswith(('-', '~'))] + ) + return result + + result = {} + slot_map = {} + # Start from the newest + rev_matches = reversed(matches) + for pkg in rev_matches: + keywords_str, slot = pkg.environment(('KEYWORDS', 'SLOT'), + prefer_vdb=False) + keywords = keywords_str.split() + result[pkg] = [x for x in keywords if x not in slot_map.get(slot, [])] + try: + slot_map[slot].update(del_archmask(add_unstable(keywords))) + except KeyError: + slot_map[slot] = set(del_archmask(add_unstable(keywords))) + + return result + + +def format_herds(herds): + """Format herd information for display.""" + + result = [] + for herd in herds: + herdstr = '' + email = "(%s)" % herd[1] if herd[1] else '' + herdstr = herd[0] + if CONFIG['verbose']: + herdstr += " %s" % (email,) + result.append(herdstr) + + return result + + +def format_maintainers(maints): + """Format maintainer information for display.""" + + result = [] + for maint in maints: + maintstr = '' + maintstr = maint.email + if CONFIG['verbose']: + maintstr += " (%s)" % (maint.name,) if maint.name else '' + maintstr += " - %s" % (maint.restrict,) if maint.restrict else '' + maintstr += "\n%s" % ( + (maint.description,) if maint.description else '' + ) + result.append(maintstr) + + return result + + +def format_upstream(upstream): + """Format upstream information for display.""" + + def _format_upstream_docs(docs): + result = [] + for doc in docs: + doc_location = doc[0] + doc_lang = doc[1] + docstr = doc_location + if doc_lang is not None: + docstr += " (%s)" % (doc_lang,) + result.append(docstr) + return result + + def _format_upstream_ids(ids): + result = [] + for id_ in ids: + site = id_[0] + proj_id = id_[1] + idstr = "%s ID: %s" % (site, proj_id) + result.append(idstr) + return result + + result = [] + for up in upstream: + upmaints = format_maintainers(up.maintainers) + for upmaint in upmaints: + result.append(format_line(upmaint, "Maintainer: ", " " * 13)) + + for upchange in up.changelogs: + result.append(format_line(upchange, "ChangeLog: ", " " * 13)) + + updocs = _format_upstream_docs(up.docs) + for updoc in updocs: + result.append(format_line(updoc, "Docs: ", " " * 13)) + + for upbug in up.bugtrackers: + result.append(format_line(upbug, "Bugs-to: ", " " * 13)) + + upids = _format_upstream_ids(up.remoteids) + for upid in upids: + result.append(format_line(upid, "Remote-ID: ", " " * 13)) + + return result + + +def format_useflags(useflags): + """Format USE flag information for display.""" + + result = [] + for flag in useflags: + result.append(pp.useflag(flag.name)) + result.append(flag.description) + result.append("") + + return result + + +def format_keywords(keywords): + """Sort and colorize keywords for display.""" + + result = [] + + for kw in sorted(keywords): + if kw.startswith('-'): + # arch masked + kw = pp.keyword(kw, stable=False, hard_masked=True) + elif kw.startswith('~'): + # keyword masked + kw = pp.keyword(kw, stable=False, hard_masked=False) + else: + # stable + kw = pp.keyword(kw, stable=True, hard_masked=False) + result.append(kw) + + return ' '.join(result) + + +def format_keywords_line(pkg, fmtd_keywords, slot, verstr_len): + """Format the entire keywords line for display.""" + + ver = pkg.fullversion + result = "%s:%s: %s" % (ver, pp.slot(slot), fmtd_keywords) + if CONFIG['verbose'] and fmtd_keywords: + result = format_line(fmtd_keywords, "%s:%s: " % (ver, pp.slot(slot)), + " " * (verstr_len + 2)) + + return result + + +# R0912: *Too many branches (%s/%s)* +# pylint: disable-msg=R0912 +def call_format_functions(matches): + """Call information gathering functions and display the results.""" + + # Choose a good package to reference metadata from + ref_pkg = get_reference_pkg(matches) + + if CONFIG['verbose']: + repo = ref_pkg.repo_name() + print " * %s [%s]" % (pp.cpv(ref_pkg.cp), pp.section(repo)) + + got_opts = False + if any(QUERY_OPTS.values()): + # Specific information requested, less formatting + got_opts = True + + if QUERY_OPTS["herd"] or not got_opts: + herds = format_herds(ref_pkg.metadata.herds(include_email=True)) + if QUERY_OPTS["herd"]: + print_sequence(format_list(herds)) + else: + for herd in herds: + print format_line(herd, "Herd: ", " " * 13) + + if QUERY_OPTS["maintainer"] or not got_opts: + maints = format_maintainers(ref_pkg.metadata.maintainers()) + if QUERY_OPTS["maintainer"]: + print_sequence(format_list(maints)) + else: + if not maints: + print format_line([], "Maintainer: ", " " * 13) + else: + for maint in maints: + print format_line(maint, "Maintainer: ", " " * 13) + + if QUERY_OPTS["upstream"] or not got_opts: + upstream = format_upstream(ref_pkg.metadata.upstream()) + if QUERY_OPTS["upstream"]: + upstream = format_list(upstream) + else: + upstream = format_list(upstream, "Upstream: ", " " * 13) + print_sequence(upstream) + + if not got_opts: + pkg_loc = ref_pkg.package_path() + print format_line(pkg_loc, "Location: ", " " * 13) + + if QUERY_OPTS["keywords"] or not got_opts: + # Get {<Package 'dev-libs/glib-2.20.5'>: [u'ia64', u'm68k', ...], ...} + keyword_map = filter_keywords(matches) + + for match in matches: + slot = match.environment('SLOT') + verstr_len = len(match.fullversion) + len(slot) + fmtd_keywords = format_keywords(keyword_map[match]) + keywords_line = format_keywords_line( + match, fmtd_keywords, slot, verstr_len + ) + if QUERY_OPTS["keywords"]: + print keywords_line + else: + indent = " " * (16 + verstr_len) + print format_line(keywords_line, "Keywords: ", indent) + + if QUERY_OPTS["description"]: + desc = ref_pkg.metadata.descriptions() + print_sequence(format_list(desc)) + + if QUERY_OPTS["useflags"]: + useflags = format_useflags(ref_pkg.metadata.use()) + print_sequence(format_list(useflags)) + + if QUERY_OPTS["xml"]: + print_file(os.path.join(ref_pkg.package_path(), 'metadata.xml')) + + +def format_line(line, first="", subsequent="", force_quiet=False): + """Wrap a string at word boundaries and optionally indent the first line + and/or subsequent lines with custom strings. + + Preserve newlines if the longest line is not longer than + CONFIG['termWidth']. To force the preservation of newlines and indents, + split the string into a list and feed it to format_line via format_list. + + @see: format_list() + @type line: string + @param line: text to format + @type first: string + @param first: text to prepend to the first line + @type subsequent: string + @param subsequent: text to prepend to subsequent lines + @type force_quiet: boolean + @rtype: string + @return: A wrapped line + """ + + if line: + line = line.expandtabs().strip("\n").splitlines() + else: + if force_quiet: + return + else: + return first + "None specified" + + if len(first) > len(subsequent): + wider_indent = first + else: + wider_indent = subsequent + + widest_line_len = len(max(line, key=len)) + len(wider_indent) + + if widest_line_len > CONFIG['termWidth']: + twrap = TextWrapper(width=CONFIG['termWidth'], expand_tabs=False, + initial_indent=first, subsequent_indent=subsequent) + line = " ".join(line) + line = re.sub("\s+", " ", line) + line = line.lstrip() + result = twrap.fill(line) + else: + # line will fit inside CONFIG['termWidth'], so preserve whitespace and + # newlines + line[0] = first + line[0] # Avoid two newlines if len == 1 + + if len(line) > 1: + line[0] = line[0] + "\n" + for i in range(1, (len(line[1:-1]) + 1)): + line[i] = subsequent + line[i] + "\n" + line[-1] = subsequent + line[-1] # Avoid two newlines on last line + + if line[-1].isspace(): + del line[-1] # Avoid trailing blank lines + + result = "".join(line) + + return result.encode("utf-8") + + +def format_list(lst, first="", subsequent="", force_quiet=False): + """Feed elements of a list to format_line(). + + @see: format_line() + @type lst: list + @param lst: list to format + @type first: string + @param first: text to prepend to the first line + @type subsequent: string + @param subsequent: text to prepend to subsequent lines + @rtype: list + @return: list with element text wrapped at CONFIG['termWidth'] + """ + + result = [] + if lst: + # Format the first line + line = format_line(lst[0], first, subsequent, force_quiet) + result.append(line) + # Format subsequent lines + for elem in lst[1:]: + if elem: + result.append(format_line(elem, subsequent, subsequent, + force_quiet)) + else: + # We don't want to send a blank line to format_line() + result.append("") + else: + if CONFIG['verbose']: + if force_quiet: + result = None + else: + # Send empty list, we'll get back first + `None specified' + result.append(format_line(lst, first, subsequent)) + + return result + + +def get_reference_pkg(matches): + """Find a package in the Portage tree to reference.""" + + pkg = None + rev_matches = list(reversed(matches)) + while rev_matches: + pkg = rev_matches.pop() + if not pkg.is_overlay(): + break + + return pkg + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-d', '--description'): + QUERY_OPTS["description"] = True + elif opt in ('-H', '--herd'): + QUERY_OPTS["herd"] = True + elif opt in ('-m', '--maintainer'): + QUERY_OPTS["maintainer"] = True + elif opt in ('-k', '--keywords'): + QUERY_OPTS["keywords"] = True + elif opt in ('-u', '--useflags'): + QUERY_OPTS["useflags"] = True + elif opt in ('-U', '--upstream'): + QUERY_OPTS["upstream"] = True + elif opt in ('-x', '--xml'): + QUERY_OPTS["xml"] = True + + +def main(input_args): + """Parse input and run the program.""" + + short_opts = "hdHkmuUx" + long_opts = ('help', 'description', 'herd', 'keywords', 'maintainer', + 'useflags', 'upstream', 'xml') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + # Find queries' Portage directory and throw error if invalid + if not queries: + print_help() + sys.exit(2) + + first_run = True + for query in queries: + matches = find_packages(query, include_masked=True) + if not matches: + raise errors.GentoolkitNoMatches(query) + + if not first_run: + print + + matches.sort() + call_format_functions(matches) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/size.py b/gentoolkit/pym/gentoolkit/equery/size.py new file mode 100644 index 0000000..4d2a686 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/size.py @@ -0,0 +1,193 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Print total size of files contained in a given package""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import do_lookup + +# ======= +# Globals +# ======= + +QUERY_OPTS = { + "includeInstalled": True, + "includePortTree": False, + "includeOverlayTree": False, + "includeMasked": True, + "isRegex": False, + "matchExact": False, + "printMatchInfo": False, + "sizeInBytes": False +} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + + # Deprecation warning added by djanderson, 12/2008 + depwarning = ( + "Default action for this module has changed in Gentoolkit 0.3.", + "Use globbing to simulate the old behavior (see man equery).", + "Use '*' to check all installed packages.", + "Use 'foo-bar/*' to filter by category." + ) + for line in depwarning: + sys.stderr.write(pp.warn(line)) + print + + print mod_usage(mod_name="size") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -b, --bytes", "report size in bytes"), + (" -f, --full-regex", "query is a regular expression") + )) + + +def display_size(match_set): + """Display the total size of all accessible files owned by packages. + + @type match_set: list + @param match_set: package cat/pkg-ver strings + """ + + for pkg in match_set: + size, files, uncounted = pkg.size() + + if CONFIG['verbose']: + print " * %s" % pp.cpv(str(pkg.cpv)) + print "Total files : %s".rjust(25) % pp.number(str(files)) + + if uncounted: + print ("Inaccessible files : %s".rjust(25) % + pp.number(str(uncounted))) + + if QUERY_OPTS["sizeInBytes"]: + size_str = pp.number(str(size)) + else: + size_str = "%s %s" % format_bytes(size) + + print "Total size : %s".rjust(25) % size_str + else: + info = "%s: total(%d), inaccessible(%d), size(%s)" + print info % (str(pkg.cpv), files, uncounted, size) + + +def format_bytes(bytes_, precision=2): + """Format bytes into human-readable format (IEC naming standard). + + @see: http://mail.python.org/pipermail/python-list/2008-August/503423.html + @rtype: tuple + @return: (str(num), str(label)) + """ + + labels = ( + (1<<40L, 'TiB'), + (1<<30L, 'GiB'), + (1<<20L, 'MiB'), + (1<<10L, 'KiB'), + (1, 'bytes') + ) + + if bytes_ == 0: + return (pp.number('0'), 'bytes') + elif bytes_ == 1: + return (pp.number('1'), 'byte') + + for factor, label in labels: + if not bytes_ >= factor: + continue + + float_split = str(bytes_/float(factor)).split('.') + integer = float_split[0] + decimal = float_split[1] + if int(decimal[0:precision]): + float_string = '.'.join([integer, decimal[0:precision]]) + else: + float_string = integer + + return (pp.number(float_string), label) + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-b', '--bytes'): + QUERY_OPTS["sizeInBytes"] = True + elif opt in ('-e', '--exact-name'): + sys.stderr.write(pp.warn("-e, --exact-name is now default.")) + warning = pp.warn("Use globbing to simulate the old behavior.") + sys.stderr.write(warning) + print + elif opt in ('-f', '--full-regex'): + QUERY_OPTS['isRegex'] = True + + +def main(input_args): + """Parse input and run the program""" + + # -e, --exact-name is no longer needed. Kept for compatibility. + # 04/09 djanderson + short_opts = "hbfe" + long_opts = ('help', 'bytes', 'full-regex', 'exact-name') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + first_run = True + for query in queries: + if not first_run: + print + + matches = do_lookup(query, QUERY_OPTS) + + if not matches: + sys.stderr.write(pp.error("No package found matching %s" % query)) + + display_size(matches) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/uses.py b/gentoolkit/pym/gentoolkit/equery/uses.py new file mode 100644 index 0000000..8358d49 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/uses.py @@ -0,0 +1,317 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Display USE flags for a given package""" + +# Move to imports section when Python 2.6 is stable +from __future__ import with_statement + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import sys +from functools import partial +from getopt import gnu_getopt, GetoptError +from glob import glob + +from portage import settings + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage, CONFIG +from gentoolkit.helpers import find_best_match, find_packages, uniqify +from gentoolkit.textwrap_ import TextWrapper + +# ======= +# Globals +# ======= + +QUERY_OPTS = {"allVersions" : False} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name=__name__.split('.')[-1]) + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -a, --all", "include all package versions") + )) + + +def display_useflags(output): + """Print USE flag descriptions and statuses. + + @type output: list + @param output: [(inuse, inused, flag, desc, restrict), ...] + inuse (int) = 0 or 1; if 1, flag is set in make.conf + inused (int) = 0 or 1; if 1, package is installed with flag enabled + flag (str) = the name of the USE flag + desc (str) = the flag's description + restrict (str) = corresponds to the text of restrict in metadata + """ + + maxflag_len = len(max([t[2] for t in output], key=len)) + + twrap = TextWrapper() + twrap.width = CONFIG['termWidth'] + twrap.subsequent_indent = " " * (maxflag_len + 8) + + markers = ("-", "+") + color = ( + partial(pp.useflag, enabled=False), partial(pp.useflag, enabled=True) + ) + for in_makeconf, in_installed, flag, desc, restrict in output: + if CONFIG['verbose']: + flag_name = "" + if in_makeconf != in_installed: + flag_name += pp.emph(" %s %s" % + (markers[in_makeconf], markers[in_installed])) + else: + flag_name += (" %s %s" % + (markers[in_makeconf], markers[in_installed])) + + flag_name += " " + color[in_makeconf](flag.ljust(maxflag_len)) + flag_name += " : " + + # print description + if restrict: + restrict = "(%s %s)" % (pp.emph("Restricted to"), + pp.cpv(restrict)) + twrap.initial_indent = flag_name + print twrap.fill(restrict) + if desc: + twrap.initial_indent = twrap.subsequent_indent + print twrap.fill(desc) + else: + print " : <unknown>" + else: + if desc: + twrap.initial_indent = flag_name + desc = twrap.fill(desc) + print desc + else: + twrap.initial_indent = flag_name + print twrap.fill("<unknown>") + else: + print markers[in_makeconf] + flag + + +def get_global_useflags(): + """Get global and expanded USE flag variables from + PORTDIR/profiles/use.desc and PORTDIR/profiles/desc/*.desc respectively. + + @rtype: dict + @return: {'flag_name': 'flag description', ...} + """ + + global_usedesc = {} + # Get global USE flag descriptions + try: + path = os.path.join(settings["PORTDIR"], 'profiles', 'use.desc') + with open(path) as open_file: + for line in open_file: + if line.startswith('#'): + continue + # Ex. of fields: ['syslog', 'Enables support for syslog\n'] + fields = line.split(" - ", 1) + if len(fields) == 2: + global_usedesc[fields[0]] = fields[1].rstrip() + except IOError: + sys.stderr.write( + pp.warn( + "Could not load USE flag descriptions from %s" % pp.path(path) + ) + ) + + del path, open_file + # Add USE_EXPANDED variables to usedesc hash -- Bug #238005 + for path in glob(os.path.join(settings["PORTDIR"], + 'profiles', 'desc', '*.desc')): + try: + with open(path) as open_file: + for line in open_file: + if line.startswith('#'): + continue + fields = [field.strip() for field in line.split(" - ", 1)] + if len(fields) == 2: + expanded_useflag = "%s_%s" % \ + (path.split("/")[-1][0:-5], fields[0]) + global_usedesc[expanded_useflag] = fields[1] + except IOError: + sys.stderr.write( + pp.warn("Could not load USE flag descriptions from %s" % path) + ) + + return global_usedesc + + +def get_matches(query): + """Get packages matching query.""" + + if not QUERY_OPTS["allVersions"]: + matches = [find_best_match(query)] + if None in matches: + matches = find_packages(query, include_masked=False) + if matches: + matches.sort() + else: + matches = find_packages(query, include_masked=True) + + if not matches: + raise errors.GentoolkitNoMatches(query) + + return matches + + +def get_output_descriptions(pkg, global_usedesc): + """Prepare descriptions and usage information for each USE flag.""" + + local_usedesc = pkg.metadata.use() + iuse = pkg.environment("IUSE") + + if iuse: + usevar = uniqify([x.lstrip('+-') for x in iuse.split()]) + usevar.sort() + else: + usevar = [] + + if pkg.is_installed(): + used_flags = pkg.use().split() + else: + used_flags = settings["USE"].split() + + # store (inuse, inused, flag, desc, restrict) + output = [] + for flag in usevar: + inuse = False + inused = False + + local_use = None + for use in local_usedesc: + if use.name == flag: + local_use = use + break + + try: + desc = local_use.description + except AttributeError: + try: + desc = global_usedesc[flag] + except KeyError: + desc = "" + + try: + restrict = local_use.restrict + restrict = restrict if restrict is not None else "" + except AttributeError: + restrict = "" + + if flag in pkg.settings("USE").split(): + inuse = True + if flag in used_flags: + inused = True + + output.append((inuse, inused, flag, desc, restrict)) + + return output + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-a', '--all'): + QUERY_OPTS['allVersions'] = True + + +def print_legend(): + """Print a legend to explain the output format.""" + + print "[ Legend : %s - flag is set in make.conf ]" % pp.emph("U") + print "[ : %s - package is installed with flag ]" % pp.emph("I") + print "[ Colors : %s, %s ]" % ( + pp.useflag("set", enabled=True), pp.useflag("unset", enabled=False)) + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "ha" + long_opts = ('help', 'all') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + # + # Output + # + + first_run = True + legend_printed = False + for query in queries: + if not first_run: + print + + if CONFIG['verbose']: + print " * Searching for %s ..." % pp.pkgquery(query) + + matches = get_matches(query) + matches.sort() + + global_usedesc = get_global_useflags() + for pkg in matches: + + output = get_output_descriptions(pkg, global_usedesc) + if output: + if CONFIG['verbose']: + if not legend_printed: + print_legend() + legend_printed = True + print (" * Found these USE flags for %s:" % + pp.cpv(str(pkg.cpv))) + print pp.emph(" U I") + display_useflags(output) + else: + if CONFIG['verbose']: + sys.stderr.write( + pp.warn("No USE flags found for %s" % pp.cpv(pkg.cpv)) + ) + + first_run = False + +# vim: set ts=4 sw=4 tw=79: diff --git a/gentoolkit/pym/gentoolkit/equery/which.py b/gentoolkit/pym/gentoolkit/equery/which.py new file mode 100644 index 0000000..be4f5e8 --- /dev/null +++ b/gentoolkit/pym/gentoolkit/equery/which.py @@ -0,0 +1,102 @@ +# Copyright(c) 2009-2010, Gentoo Foundation +# +# Licensed under the GNU General Public License, v2 +# +# $Header: $ + +"""Display the path to the ebuild that would be used by Portage with the current +configuration +""" + +__docformat__ = 'epytext' + +# ======= +# Imports +# ======= + +import os +import sys +from getopt import gnu_getopt, GetoptError + +import gentoolkit.pprinter as pp +from gentoolkit import errors +from gentoolkit.equery import format_options, mod_usage +from gentoolkit.helpers import find_packages + +# ======= +# Globals +# ======= + +QUERY_OPTS = {"includeMasked": False} + +# ========= +# Functions +# ========= + +def print_help(with_description=True): + """Print description, usage and a detailed help message. + + @type with_description: bool + @param with_description: if true, print module's __doc__ string + """ + + if with_description: + print __doc__.strip() + print + print mod_usage(mod_name="which") + print + print pp.command("options") + print format_options(( + (" -h, --help", "display this help message"), + (" -m, --include-masked", "return highest version ebuild available") + )) + + +def parse_module_options(module_opts): + """Parse module options and update QUERY_OPTS""" + + opts = (x[0] for x in module_opts) + for opt in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + elif opt in ('-m', '--include-masked'): + QUERY_OPTS['includeMasked'] = True + + +def main(input_args): + """Parse input and run the program""" + + short_opts = "hm" + long_opts = ('help', 'include-masked') + + try: + module_opts, queries = gnu_getopt(input_args, short_opts, long_opts) + except GetoptError, err: + sys.stderr.write(pp.error("Module %s" % err)) + print + print_help(with_description=False) + sys.exit(2) + + parse_module_options(module_opts) + + if not queries: + print_help() + sys.exit(2) + + for query in queries: + + matches = find_packages(query, QUERY_OPTS['includeMasked']) + if matches: + pkg = sorted(matches).pop() + ebuild_path = pkg.ebuild_path() + if ebuild_path: + print os.path.normpath(ebuild_path) + else: + sys.stderr.write( + pp.warn("No ebuilds to satisfy %s" % pkg.cpv.name) + ) + else: + raise errors.GentoolkitNoMatches(query) + +# vim: set ts=4 sw=4 tw=79: |