From: fuzzyray Date: Wed, 3 Jun 2009 23:04:31 +0000 (-0000) Subject: Apply updates from genscripts repo X-Git-Tag: gentoolkit-0.3.0_rc7~3 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=955b96d2437f139d6a65ecea44c94a1f9a9bb2b5;p=gentoolkit.git Apply updates from genscripts repo svn path=/trunk/gentoolkit/; revision=659 --- diff --git a/pym/gentoolkit/__init__.py b/pym/gentoolkit/__init__.py index 62e359b..35d89ac 100644 --- a/pym/gentoolkit/__init__.py +++ b/pym/gentoolkit/__init__.py @@ -6,47 +6,37 @@ # # $Header$ -__author__ = "Karl Trygve Kalleberg" -__productname__ = "gentoolkit" -__description__ = "Gentoolkit Common Library" +# ======= +# Imports +# ======= -import os -import sys -try: - import portage -except ImportError: - sys.path.insert(0, "/usr/lib/portage/pym") - import portage -import re +import portage try: from threading import Lock except ImportError: # If we don't have thread support, we don't need to worry about # locking the global settings object. So we define a "null" Lock. - class Lock: + class Lock(object): def acquire(self): pass def release(self): pass -try: - import portage.exception as portage_exception -except ImportError: - import portage_exception +# ======= +# Globals +# ======= -try: - settingslock = Lock() - settings = portage.config(clone=portage.settings) - porttree = portage.db[portage.root]["porttree"] - vartree = portage.db[portage.root]["vartree"] - virtuals = portage.db[portage.root]["virtuals"] -except portage_exception.PermissionDenied, e: - sys.stderr.write("Permission denied: '%s'\n" % str(e)) - sys.exit(e.errno) +PORTDB = portage.db[portage.root]["porttree"].dbapi +VARDB = portage.db[portage.root]["vartree"].dbapi +VIRTUALS = portage.db[portage.root]["virtuals"] Config = { "verbosityLevel": 3 } -from helpers import * -from package import * +try: + settingslock = Lock() + settings = portage.config(clone=portage.settings) +except portage.exception.PermissionDenied, err: + sys.stderr.write("Permission denied: '%s'\n" % str(err)) + sys.exit(e.errno) diff --git a/pym/gentoolkit/equery/__init__.py b/pym/gentoolkit/equery/__init__.py index c270eb9..8764e1c 100644 --- a/pym/gentoolkit/equery/__init__.py +++ b/pym/gentoolkit/equery/__init__.py @@ -25,9 +25,11 @@ import sys import time from getopt import getopt, GetoptError +from portage import exception + import gentoolkit import gentoolkit.pprinter as pp -from gentoolkit import catpkgsplit, settings, Package, Config +from gentoolkit import settings, Config from gentoolkit.textwrap_ import TextWrapper __productname__ = "equery" @@ -123,34 +125,6 @@ def format_options(options): return '\n'.join(result) -def format_package_names(match_set, status): - """Add location and mask status to package names. - - @type match_set: list of gentoolkit.package.Package - @param match_set: packages to format - @rtype: list - @return: formatted packages - """ - - arch = gentoolkit.settings["ARCH"] - formatted_packages = [] - pfxmodes = ['---', 'I--', '-P-', '--O'] - maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-'] - - for pkg in match_set: - mask = get_mask_status(pkg, arch) - pkgcpv = pkg.get_cpv() - slot = pkg.get_env_var("SLOT") - - formatted_packages.append("[%s] [%s] %s (%s)" % - (pfxmodes[status], - pp.maskflag(maskmodes[mask]), - pp.cpv(pkgcpv), - str(slot))) - - return formatted_packages - - def format_filetype(path, fdesc, show_type=False, show_md5=False, show_timestamp=False): """Format a path for printing. @@ -214,36 +188,6 @@ def format_timestamp(timestamp): return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(timestamp))) -def get_mask_status(pkg, arch): - """Get the mask status of a given package. - - @type pkg: gentoolkit.package.Package - @param pkg: pkg to get mask status of - @type arch: str - @param arch: output of gentoolkit.settings["ARCH"] - @rtype: int - @return: an index for this list: [" ", " ~", " -", "M ", "M~", "M-"] - 0 = not masked - 1 = keyword masked - 2 = arch masked - 3 = hard masked - 4 = hard and keyword masked, - 5 = hard and arch masked - """ - - # Determining mask status - keywords = pkg.get_env_var("KEYWORDS").split() - mask_status = 0 - if pkg.is_masked(): - mask_status += 3 - if ("~%s" % arch) in keywords: - mask_status += 1 - elif ("-%s" % arch) in keywords or "-*" in keywords: - mask_status += 2 - - return mask_status - - def initialize_configuration(): """Setup the standard equery config""" @@ -259,6 +203,8 @@ def initialize_configuration(): # Color handling: -1: Use Portage settings, 0: Force off, 1: Force on Config['color'] = -1 + Config['quiet'] = False + # Guess color output if (Config['color'] == -1 and (not sys.stdout.isatty() or settings["NOCOLOR"] in ("yes", "true")) or @@ -316,7 +262,7 @@ def parse_global_options(global_opts, args): print_help() sys.exit(0) elif opt in ('-q','--quiet'): - Config["verbosityLevel"] = 0 + Config["quiet"] = True elif opt in ('-C', '--no-color', '--nocolor'): Config['color'] = 0 pp.output.nocolor() @@ -368,7 +314,6 @@ def main(): print_help(with_description=False) sys.exit(2) - # Parse global options need_help = parse_global_options(global_opts, args) @@ -381,6 +326,11 @@ def main(): if need_help: module_args.append('--help') + if Config['piping'] or Config['quiet']: + Config['verbose'] = False + else: + Config['verbose'] = True + try: expanded_module_name = expand_module_name(module_name) except KeyError: @@ -392,16 +342,10 @@ def main(): loaded_module = __import__(expanded_module_name, globals(), locals(), [], -1) loaded_module.main(module_args) - except ValueError, err: - if isinstance(err[0], list): - pp.print_error("Ambiguous package name. Use one of: ") - while err[0]: - print " " + err[0].pop() - else: - pp.print_error("Internal portage error, terminating") - if err: - pp.print_error(str(err[0])) - sys.exit(1) + except exception.AmbiguousPackageName, err: + pp.print_error("Ambiguous package name. Use one of: ") + while err[0]: + print " " + err[0].pop() except IOError, err: if err.errno != errno.EPIPE: raise diff --git a/pym/gentoolkit/equery/belongs.py b/pym/gentoolkit/equery/belongs.py index 6408ec7..215fbd1 100644 --- a/pym/gentoolkit/equery/belongs.py +++ b/pym/gentoolkit/equery/belongs.py @@ -132,7 +132,7 @@ def main(input_args): query_re = prepare_search_regex(queries) - if not Config["piping"]: + if Config['verbose']: pp.print_info(3, " * Searching for %s ... " % (pp.regexpquery(",".join(queries)))) @@ -148,13 +148,13 @@ def main(input_args): pkg_str = pkg.key else: pkg_str = pkg.cpv - if Config['piping']: - print pkg_str - else: + if Config['verbose']: file_str = pp.path(format_filetype(cfile, files[cfile])) - pp.print_info(0, "%s (%s)" % (pkg_str, file_str)) + print "%s (%s)" % (pkg_str, file_str) + else: + print pkg_str - found_match = True + found_match = True if found_match and QUERY_OPTS["earlyOut"]: break diff --git a/pym/gentoolkit/equery/changes.py b/pym/gentoolkit/equery/changes.py index b7644be..604bb45 100644 --- a/pym/gentoolkit/equery/changes.py +++ b/pym/gentoolkit/equery/changes.py @@ -28,7 +28,8 @@ import gentoolkit.pprinter as pp from gentoolkit import errors from gentoolkit.equery import format_options, mod_usage from gentoolkit.helpers2 import find_best_match, find_packages -from gentoolkit.package import Package, VersionMatch +from gentoolkit.package import Package +from gentoolkit.versionmatch import VersionMatch # ======= # Globals @@ -89,7 +90,7 @@ def get_logpath(pkg): logpath = os.path.join(pkg.get_package_path(), 'ChangeLog') if not os.path.isfile(logpath) or not os.access(logpath, os.R_OK): - pp.die(1, "%s does not exist or is unreadable" + raise errors.GentoolkitFatalError("%s does not exist or is unreadable" % pp.path(logpath)) return logpath @@ -133,6 +134,8 @@ def index_changelog(entries): # Extract the package name from the entry, ex: # *xterm-242 (07 Mar 2009) => xterm-242 pkg_name = entry.split(' ', 1)[0].lstrip('*') + if not pkg_name.strip(): + continue pkg_split = pkgsplit(pkg_name) result.append( (VersionMatch(op="=", ver=pkg_split[1], rev=pkg_split[2]), entry)) @@ -315,6 +318,9 @@ def main(input_args): pkg = get_match(query) logpath = get_logpath(pkg) log_entries = split_changelog(logpath) + if not any(log_entries): + raise errors.GentoolkitFatalError( + "%s exists but doesn't contain entries." % pp.path(logpath)) indexed_entries = index_changelog(log_entries) # @@ -328,6 +334,9 @@ def main(input_args): for entry in log_entries[:end]: print entry first_run = False + elif log_entries and not indexed_entries: + # We can't match anything, so just print latest: + print log_entries[0].strip() else: if ranged_query: pkg = ranged_query diff --git a/pym/gentoolkit/equery/check.py b/pym/gentoolkit/equery/check.py index ffddf72..2531970 100644 --- a/pym/gentoolkit/equery/check.py +++ b/pym/gentoolkit/equery/check.py @@ -213,20 +213,20 @@ def main(input_args): matches.sort() for pkg in matches: - if not Config["piping"] and Config["verbosityLevel"] >= 3: - print "[ Checking %s ]" % pp.cpv(pkg.cpv) + if Config['verbose']: + print " * Checking %s ..." % pp.emph(pkg.cpv) else: print "%s:" % pkg.cpv passed, checked, errs = run_checks(pkg.get_contents()) - if not Config["piping"] and Config["verbosityLevel"] >= 3: + if Config['verbose']: for err in errs: pp.print_error(err) passed = pp.number(str(passed)) checked = pp.number(str(checked)) - info = " * %(passed)s out of %(checked)s files passed" + info = " %(passed)s out of %(checked)s files passed" print info % locals() first_run = False diff --git a/pym/gentoolkit/equery/depends.py b/pym/gentoolkit/equery/depends.py index 394c35b..6377f2c 100644 --- a/pym/gentoolkit/equery/depends.py +++ b/pym/gentoolkit/equery/depends.py @@ -33,10 +33,8 @@ QUERY_OPTS = { "includePortTree": False, "includeOverlayTree": False, "isRegex": False, - "matchExact": True, "onlyDirect": True, - "onlyInstalled": True, - "printMatchInfo": True, + "printMatchInfo": (not Config['quiet']), "indentLevel": 0, "depth": -1 } @@ -77,12 +75,11 @@ def cache_package_list(pkg_cache=None): """Ensure that the package cache is set.""" if not pkg_cache: - if QUERY_OPTS["onlyInstalled"]: - # TODO: move away from using strings here - packages = get_installed_cpvs() + if QUERY_OPTS['includePortTree']: + packages = [Package(x) for x in get_cpvs()] else: - packages = get_cpvs() - packages.sort(compare_package_strings) + packages = [Package(x) for x in get_installed_cpvs()] + packages.sort() pkg_cache = packages else: packages = pkg_cache @@ -108,18 +105,18 @@ def display_dependencies(cpv_is_displayed, dependency, cpv): if not cpv_is_displayed: if dependency[1]: - if not Config["piping"] and Config["verbosityLevel"] >= 3: + if Config['verbose']: print indent + pp.cpv(cpv), print "(" + useflags + " ? " + atom + ")" else: print indent + cpv else: - if not Config["piping"] and Config["verbosityLevel"] >= 3: + if Config['verbose']: print indent + pp.cpv(cpv), print "(" + atom + ")" else: print indent + cpv - elif not Config["piping"] and Config["verbosityLevel"] >= 3: + elif Config['verbose']: indent = indent + " " * len(cpv) if dependency[1]: print indent + " (" + useflags + " ? " + atom + ")" @@ -134,7 +131,7 @@ def find_dependencies(matches, pkg_cache): @param queries: packages to find the dependencies for """ - for pkg in [Package(x) for x in cache_package_list(pkg_cache)]: + for pkg in cache_package_list(pkg_cache): if not pkg.cpv in PKGDEPS: try: deps = pkg.get_runtime_deps() + pkg.get_compiletime_deps() @@ -192,11 +189,11 @@ def parse_module_options(module_opts): print_help() sys.exit(0) elif opt in ('-a', '--all-packages'): - QUERY_OPTS["onlyInstalled"] = False + QUERY_OPTS['includePortTree'] = True elif opt in ('-d', '--direct'): continue elif opt in ('-D', '--indirect'): - QUERY_OPTS["onlyDirect"] = False + QUERY_OPTS['onlyDirect'] = False elif opt in ('--depth'): if posarg.isdigit(): depth = int(posarg) diff --git a/pym/gentoolkit/equery/depgraph.py b/pym/gentoolkit/equery/depgraph.py index f4723c2..b9cd0a1 100644 --- a/pym/gentoolkit/equery/depgraph.py +++ b/pym/gentoolkit/equery/depgraph.py @@ -19,7 +19,7 @@ import gentoolkit import gentoolkit.pprinter as pp from gentoolkit import errors from gentoolkit.equery import format_options, mod_usage, Config -from gentoolkit.helpers2 import do_lookup +from gentoolkit.helpers2 import do_lookup, find_best_match # ======= # Globals @@ -36,14 +36,9 @@ QUERY_OPTS = { "includeMasked": True, "isRegex": False, "matchExact": True, - "printMatchInfo": True + "printMatchInfo": (not Config['quiet']) } -if not Config["piping"] and Config["verbosityLevel"] >= 3: - VERBOSE = True -else: - VERBOSE = False - # ========= # Functions # ========= @@ -98,7 +93,7 @@ def display_graph(pkg, stats, level=0, seen_pkgs=None, suffix=""): for dep in deps: suffix = "" depcpv = dep[2] - deppkg = gentoolkit.find_best_match(dep[0] + depcpv) + deppkg = find_best_match(dep[0] + depcpv) if not deppkg: print (pfx + dep[0] + depcpv), print "(unable to resolve: package masked or removed)" @@ -178,14 +173,14 @@ def main(input_args): for pkg in matches: stats = {"maxdepth": 0, "packages": 0} - if VERBOSE: + if Config['verbose']: pp.print_info(3, " * dependency graph for %s:" % pp.cpv(pkg.cpv)) else: pp.print_info(0, "%s:" % pkg.cpv) stats = display_graph(pkg, stats)[1] - if VERBOSE: + if Config['verbose']: info = ''.join(["[ ", pp.cpv(pkg.cpv), " stats: packages (", pp.number(str(stats["packages"])), "), max depth (", pp.number(str(stats["maxdepth"])), ") ]"]) diff --git a/pym/gentoolkit/equery/files.py b/pym/gentoolkit/equery/files.py index f25ae5a..80caf1d 100644 --- a/pym/gentoolkit/equery/files.py +++ b/pym/gentoolkit/equery/files.py @@ -35,7 +35,7 @@ QUERY_OPTS = { "isRegex": False, "matchExact": True, "outputTree": False, - "printMatchInfo": True, + "printMatchInfo": (not Config['quiet']), "showType": False, "showTimestamp": False, "showMD5": False, @@ -45,11 +45,6 @@ QUERY_OPTS = { FILTER_RULES = ('dir', 'obj', 'sym', 'dev', 'path', 'conf', 'cmd', 'doc', 'man', 'info') -if not Config["piping"] and Config["verbosityLevel"] >= 3: - VERBOSE = True -else: - VERBOSE = False - # ========= # Functions # ========= @@ -302,8 +297,8 @@ def main(input_args): pp.print_error("No matching packages found for %s" % query) for pkg in matches: - if VERBOSE: - pp.print_info(1, " * Contents of %s:" % pp.cpv(pkg.cpv)) + if Config['verbose']: + print " * Contents of %s:" % pp.cpv(pkg.cpv) contents = pkg.get_contents() display_files(filter_contents(contents)) diff --git a/pym/gentoolkit/equery/hasuse.py b/pym/gentoolkit/equery/hasuse.py index 6580bbf..c110a22 100644 --- a/pym/gentoolkit/equery/hasuse.py +++ b/pym/gentoolkit/equery/hasuse.py @@ -17,10 +17,9 @@ from getopt import gnu_getopt, GetoptError import gentoolkit import gentoolkit.pprinter as pp -from gentoolkit.equery import format_options, format_package_names, \ - mod_usage, Config -from gentoolkit.helpers2 import do_lookup, get_installed_cpvs -from gentoolkit.package import Package +from gentoolkit.equery import format_options, mod_usage, Config +from gentoolkit.helpers2 import do_lookup, get_installed_cpvs, print_sequence +from gentoolkit.package import Package, PackageFormatter # ======= # Globals @@ -28,7 +27,7 @@ from gentoolkit.package import Package QUERY_OPTS = { "categoryFilter": None, - "includeInstalled": False, + "includeInstalled": True, "includePortTree": False, "includeOverlayTree": False, "includeMasked": True, @@ -55,8 +54,8 @@ def print_help(with_description=True): print pp.command("options") print format_options(( (" -h, --help", "display this help message"), - (" -i, --installed", - "include installed packages in search path (default)"), + (" -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") )) @@ -71,63 +70,19 @@ def parse_module_options(module_opts): if opt in ('-h', '--help'): print_help() sys.exit(0) - elif opt in ('-i', '--installed'): - QUERY_OPTS['includeInstalled'] = True + 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 print_sequence(seq): - """Print every item of a sequence.""" - - for item in seq: - print item - - -def sort_by_location(query, matches): - """Take a list of packages and sort them by location. - - @rtype: tuple - @return: - installed: list of all packages in matches that are in the vdb - overlay: list of all packages in matches that reside in an overlay - porttree: list of all packages that are not in the vdb or an overlay - """ - - all_installed_packages = set() - if QUERY_OPTS["includeInstalled"]: - all_installed_packages = set(Package(x) for x in get_installed_cpvs()) - - # Cache package sets - installed = [] - overlay = [] - porttree = [] - - for pkg in matches: - useflags = [f.lstrip("+-") for f in pkg.get_env_var("IUSE").split()] - if query not in useflags: - continue - - if QUERY_OPTS["includeInstalled"]: - if pkg in all_installed_packages: - installed.append(pkg) - continue - if pkg.is_overlay(): - if QUERY_OPTS["includeOverlayTree"]: - overlay.append(pkg) - continue - if QUERY_OPTS["includePortTree"]: - porttree.append(pkg) - - return installed, overlay, porttree - - def main(input_args): """Parse input and run the program""" - short_opts = "hiIpo" + 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') @@ -144,10 +99,6 @@ def main(input_args): if not queries: print_help() sys.exit(2) - elif not (QUERY_OPTS['includeInstalled'] or - QUERY_OPTS['includePortTree'] or QUERY_OPTS['includeOverlayTree']): - # Got queries but no search path; set a sane default - QUERY_OPTS['includeInstalled'] = True matches = do_lookup("*", QUERY_OPTS) matches.sort() @@ -161,29 +112,33 @@ def main(input_args): if not first_run: print - if not Config["piping"]: - print " * Searching for USE flag %s ... " % pp.useflag(query) - - installed, overlay, porttree = sort_by_location(query, matches) - - if QUERY_OPTS["includeInstalled"]: - print " * installed packages:" - if not Config["piping"]: - installed = format_package_names(installed, 1) - print_sequence(installed) - - if QUERY_OPTS["includePortTree"]: - portdir = pp.path(gentoolkit.settings["PORTDIR"]) - print " * Portage tree (%s):" % portdir - if not Config["piping"]: - porttree = format_package_names(porttree, 2) - print_sequence(porttree) - - if QUERY_OPTS["includeOverlayTree"]: - portdir_overlay = pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) - print " * overlay tree (%s):" % portdir_overlay - if not Config["piping"]: - overlay = format_package_names(overlay, 3) - print_sequence(overlay) + if Config['verbose']: + print " * Searching for USE flag %s ... " % pp.emph(query) + + for pkg in matches: + + useflags = [x.lstrip("+-") for x in pkg.get_env_var("IUSE").split()] + if query not in useflags: + continue + + if Config['verbose']: + pkgstr = PackageFormatter(pkg, format=True) + else: + pkgstr = PackageFormatter(pkg, 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 first_run = False diff --git a/pym/gentoolkit/equery/list_.py b/pym/gentoolkit/equery/list_.py index 20cd376..dd13029 100644 --- a/pym/gentoolkit/equery/list_.py +++ b/pym/gentoolkit/equery/list_.py @@ -17,10 +17,9 @@ from getopt import gnu_getopt, GetoptError import gentoolkit import gentoolkit.pprinter as pp -from gentoolkit.equery import format_options, format_package_names, \ - mod_usage, Config +from gentoolkit.equery import format_options, mod_usage, Config from gentoolkit.helpers2 import do_lookup, get_installed_cpvs -from gentoolkit.package import Package +from gentoolkit.package import Package, PackageFormatter # ======= # Globals @@ -29,12 +28,12 @@ from gentoolkit.package import Package QUERY_OPTS = { "categoryFilter": None, "duplicates": False, - "includeInstalled": False, + "includeInstalled": True, "includePortTree": False, "includeOverlayTree": False, "includeMasked": True, "isRegex": False, - "printMatchInfo": True + "printMatchInfo": (not Config['quiet']) } # ========= @@ -66,38 +65,13 @@ def print_help(with_description=True): (" -c, --category CAT", "only search in the category CAT"), (" -d, --duplicates", "list only installed duplicate packages"), (" -f, --full-regex", "query is a regular expression"), - (" -i, --installed", "list installed packages matching query"), + (" -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 adjust_query_environment(queries): - """Make sure the search environment is good to go.""" - - if not queries and not (QUERY_OPTS["duplicates"] or - QUERY_OPTS["includeInstalled"] or QUERY_OPTS["includePortTree"] or - QUERY_OPTS["includeOverlayTree"]): - print_help() - sys.exit(2) - elif queries and not (QUERY_OPTS["duplicates"] or - QUERY_OPTS["includeInstalled"] or QUERY_OPTS["includePortTree"] or - QUERY_OPTS["includeOverlayTree"]): - QUERY_OPTS["includeInstalled"] = True - elif not queries and (QUERY_OPTS["duplicates"] or - QUERY_OPTS["includeInstalled"] or QUERY_OPTS["includePortTree"] or - QUERY_OPTS["includeOverlayTree"]): - queries = ["*"] - - # Only search installed packages when listing duplicate packages - if QUERY_OPTS["duplicates"]: - QUERY_OPTS["includeInstalled"] = True - QUERY_OPTS["includePortTree"] = False - QUERY_OPTS["includeOverlayTree"] = False - - return queries - - def get_duplicates(matches): """Return only packages that have more than one version installed.""" @@ -129,8 +103,8 @@ def parse_module_options(module_opts): QUERY_OPTS['listAllPackages'] = True elif opt in ('-c', '--category'): QUERY_OPTS['categoryFilter'] = posarg - elif opt in ('-i', '--installed'): - QUERY_OPTS['includeInstalled'] = True + 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'): @@ -145,55 +119,13 @@ def parse_module_options(module_opts): QUERY_OPTS['duplicates'] = True -def print_sequence(seq): - """Print every item of a sequence.""" - - for item in seq: - print item - - -def sort_by_location(matches): - """Take a list of packages and sort them by location. - - @rtype: tuple - @return: - installed: list of all packages in matches that are in the vdb - overlay: list of all packages in matches that reside in an overlay - porttree: list of all packages that are not in the vdb or an overlay - """ - - all_installed_packages = set() - if QUERY_OPTS["includeInstalled"]: - all_installed_packages = set(Package(x) for x in get_installed_cpvs()) - - # Cache package sets - installed = [] - overlay = [] - porttree = [] - - for pkg in matches: - if QUERY_OPTS["includeInstalled"]: - if pkg in all_installed_packages: - installed.append(pkg) - continue - if pkg.is_overlay(): - if QUERY_OPTS["includeOverlayTree"]: - overlay.append(pkg) - continue - if QUERY_OPTS["includePortTree"]: - porttree.append(pkg) - - return installed, overlay, porttree - - def main(input_args): """Parse input and run the program""" - short_opts = "hc:defiIop" # -I was used to turn off -i when it was - # the default action, -e is now default + short_opts = "hc:defiIop" # -i, -e were options for default actions # 04/09: djanderson - # --exclude-installed 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', 'category=', 'installed', 'exclude-installed', 'portage-tree', 'overlay-tree', 'full-regex', 'exact-name', 'duplicates') @@ -207,7 +139,16 @@ def main(input_args): sys.exit(2) parse_module_options(module_opts) - queries = adjust_query_environment(queries) + + # Only search installed packages when listing duplicate packages + if QUERY_OPTS["duplicates"]: + QUERY_OPTS["includeInstalled"] = True + QUERY_OPTS["includePortTree"] = False + QUERY_OPTS["includeOverlayTree"] = False + + if not queries: + print_help() + sys.exit(2) first_run = True for query in queries: @@ -222,30 +163,29 @@ def main(input_args): matches.sort() - installed, overlay, porttree = sort_by_location(matches) - # # Output # - if QUERY_OPTS["includeInstalled"]: - print " * installed packages:" - if not Config["piping"]: - installed = format_package_names(installed, 1) - print_sequence(installed) - - if QUERY_OPTS["includePortTree"]: - portdir = pp.path(gentoolkit.settings["PORTDIR"]) - print " * Portage tree (%s):" % portdir - if not Config["piping"]: - porttree = format_package_names(porttree, 2) - print_sequence(porttree) - - if QUERY_OPTS["includeOverlayTree"]: - portdir_overlay = pp.path(gentoolkit.settings["PORTDIR_OVERLAY"]) - print " * overlay tree (%s):" % portdir_overlay - if not Config["piping"]: - overlay = format_package_names(overlay, 3) - print_sequence(overlay) + for pkg in matches: + if Config['verbose']: + pkgstr = PackageFormatter(pkg, format=True) + else: + pkgstr = PackageFormatter(pkg, 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 first_run = False diff --git a/pym/gentoolkit/equery/meta.py b/pym/gentoolkit/equery/meta.py index 8148329..a531537 100644 --- a/pym/gentoolkit/equery/meta.py +++ b/pym/gentoolkit/equery/meta.py @@ -27,7 +27,8 @@ from portage import settings import gentoolkit.pprinter as pp from gentoolkit import errors from gentoolkit.equery import format_options, mod_usage, Config -from gentoolkit.helpers2 import find_packages +from gentoolkit.helpers2 import find_packages, print_sequence, print_file, \ + uniqify from gentoolkit.textwrap_ import TextWrapper # ======= @@ -54,11 +55,6 @@ PORTDIR = [settings["PORTDIR"] or os.path.join(os.sep, "usr", "portage")] if settings["PORTDIR_OVERLAY"]: PORTDIR.extend(settings["PORTDIR_OVERLAY"].split()) -if not Config["piping"] and Config["verbosityLevel"] >= 3: - VERBOSE = True -else: - VERBOSE = False - # ========= # Functions # ========= @@ -91,7 +87,7 @@ def print_help(with_description=True): def call_get_functions(metadata_path, package_dir, QUERY_OPTS): """Call information gathering functions and display the results.""" - if VERBOSE: + if Config['verbose']: print get_overlay_name(package_dir) try: @@ -141,7 +137,7 @@ def call_get_functions(metadata_path, package_dir, QUERY_OPTS): print_sequence(format_list(useflags)) if QUERY_OPTS["xml"]: - print_file(meta) + print_file(metadata_path) def format_line(line, first="", subsequent="", force_quiet=False): @@ -233,7 +229,7 @@ def format_list(lst, first="", subsequent="", force_quiet=False): # We don't want to send a blank line to format_line() result.append("") else: - if VERBOSE: + if Config['verbose']: if force_quiet: result = None else: @@ -374,17 +370,16 @@ def _get_upstream_maintainer(node): maintainer = node.findall("maintainer") maint = [] for elem in maintainer: - name = elem.find("name") - email = elem.find("email") + if elem.find("name") != None: + maint.append(elem.find("name").text) + if elem.find("email") != None: + maint.append(elem.find("email").text) if elem.get("status") == "active": - status = "(%s)" % pp.output.green("active") + maint.append("(%s)" % pp.output.green("active")) elif elem.get("status") == "inactive": - status = "(%s)" % pp.output.red("inactive") - elif elem.get("status"): - status = "(" + elem.get("status") + ")" - else: - status = "" - maint.append(" ".join([name.text, email.text, status])) + maint.append("(%s)" % pp.output.red("inactive")) + elif elem.get("status") != None: + maint.append("(" + elem.get("status") + ")") return format_list(maint, "Maintainer: ", " " * 12, force_quiet=True) @@ -432,33 +427,6 @@ def get_upstream(xml_tree): return result -def print_sequence(seq): - """Print each element of a sequence.""" - - for elem in seq: - print elem - - -def uniqify(seq, preserve_order=True): - """Return a uniqified list. Optionally preserve order.""" - - if preserve_order: - seen = set() - result = [x for x in seq if x not in seen and not seen.add(x)] - else: - result = list(set(seq)) - - return result - - -def print_file(path): - """Display the contents of a file.""" - - with open(path) as open_file: - lines = open_file.read() - print lines.strip() - - def parse_module_options(module_opts): """Parse module options and update GLOBAL_OPTS""" @@ -526,4 +494,4 @@ def main(input_args): call_get_functions(metadata_path, package_dir, QUERY_OPTS) - first_run = False \ No newline at end of file + first_run = False diff --git a/pym/gentoolkit/equery/size.py b/pym/gentoolkit/equery/size.py index 59f45d8..9cb6bc9 100644 --- a/pym/gentoolkit/equery/size.py +++ b/pym/gentoolkit/equery/size.py @@ -78,10 +78,7 @@ def display_size(match_set): for pkg in match_set: (size, files, uncounted) = pkg.size() - if Config["piping"]: - info = "%s: total(%d), inaccessible(%d), size(%s)" - print info % (pkg.cpv, files, uncounted, size) - else: + if Config['verbose']: print " * %s" % pp.cpv(pkg.cpv) print "Total files : %s".rjust(25) % pp.number(str(files)) @@ -95,6 +92,9 @@ def display_size(match_set): size_str = "%s %s" % format_bytes(size) pp.print_info(0, "Total size : %s".rjust(25) % size_str) + else: + info = "%s: total(%d), inaccessible(%d), size(%s)" + print info % (pkg.cpv, files, uncounted, size) def format_bytes(bytes_, precision=2): diff --git a/pym/gentoolkit/equery/uses.py b/pym/gentoolkit/equery/uses.py index 2718613..56fc78b 100644 --- a/pym/gentoolkit/equery/uses.py +++ b/pym/gentoolkit/equery/uses.py @@ -82,9 +82,7 @@ def display_useflags(output): markers = ("-", "+") color = [pp.useflagoff, pp.useflagon] for in_makeconf, in_installed, flag, desc, restrict in output: - if Config["piping"]: - pp.print_info(0, markers[in_makeconf] + flag) - else: + if Config['verbose']: flag_name = "" if in_makeconf != in_installed: flag_name += pp.emph(" %s %s" % @@ -101,20 +99,22 @@ def display_useflags(output): restrict = "(%s %s)" % (pp.emph("Restricted to"), pp.cpv(restrict)) twrap.initial_indent = flag_name - pp.print_info(0, twrap.fill(restrict)) + print twrap.fill(restrict) if desc: twrap.initial_indent = twrap.subsequent_indent - pp.print_info(0, twrap.fill(desc)) + print twrap.fill(desc) else: - pp.print_info(0, " : ") + print " : " else: if desc: twrap.initial_indent = flag_name desc = twrap.fill(desc) - pp.print_info(0, desc) + print desc else: twrap.initial_indent = flag_name - pp.print_info(0, twrap.fill("")) + print twrap.fill("") + else: + print markers[in_makeconf] + flag def get_global_useflags(): @@ -277,15 +277,10 @@ def parse_module_options(module_opts): def print_legend(query): """Print a legend to explain the output format.""" - if not Config['piping']: - pp.print_info(3, " * Searching for packages matching %s ..." % - pp.pkgquery(query)) - pp.print_info(3, "[ Legend : %s - flag is set in make.conf ]" - % pp.emph("U")) - pp.print_info(3, "[ : %s - package is installed with flag ]" - % pp.emph("I")) - pp.print_info(3, "[ Colors : %s, %s ]" % - (pp.useflagon("set"), pp.useflagoff("unset"))) + 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.useflagon("set"), pp.useflagoff("unset")) def main(input_args): @@ -317,7 +312,8 @@ def main(input_args): if not first_run: print - print_legend(query) + if Config['verbose']: + print " * Searching for %s ..." % pp.pkgquery(query) matches = get_matches(query) matches.sort() @@ -327,14 +323,15 @@ def main(input_args): output = get_output_descriptions(pkg, global_usedesc) if output: - if not Config['piping']: - pp.print_info(3, "[ Found these USE flags for %s ]" % + if Config['verbose']: + print_legend(query) + print (" * Found these USE flags for %s:" % pp.cpv(pkg.cpv)) - pp.print_info(3, pp.emph(" U I")) + print pp.emph(" U I") display_useflags(output) else: - if not Config['piping']: - pp.print_info(3, "[ No USE flags found for %s ]" % + if Config['verbose']: + pp.print_warn("No USE flags found for %s" % pp.cpv(pkg.cpv)) first_run = False diff --git a/pym/gentoolkit/equery/which.py b/pym/gentoolkit/equery/which.py index 4cae712..828dae1 100644 --- a/pym/gentoolkit/equery/which.py +++ b/pym/gentoolkit/equery/which.py @@ -91,7 +91,7 @@ def main(input_args): pkg = sorted(matches).pop() ebuild_path = pkg.get_ebuild_path() if ebuild_path: - pp.print_info(0, os.path.normpath(ebuild_path)) + print os.path.normpath(ebuild_path) else: pp.print_warn("No ebuilds to satisfy %s" % pkg.name) else: diff --git a/pym/gentoolkit/errors.py b/pym/gentoolkit/errors.py index c635192..9843b6b 100644 --- a/pym/gentoolkit/errors.py +++ b/pym/gentoolkit/errors.py @@ -12,6 +12,7 @@ __all__ = [ 'GentoolkitInvalidPackageName', 'GentoolkitInvalidCPV', 'GentoolkitInvalidRegex', + 'GentoolkitInvalidVersion', 'GentoolkitNoMatches' ] @@ -77,6 +78,13 @@ class GentoolkitInvalidRegex(GentoolkitException): sys.exit(2) +class GentoolkitInvalidVersion(GentoolkitException): + """Got a malformed version""" + def __init__(self, version): + pp.print_error("Malformed version: '%s'" % version) + sys.exit(2) + + class GentoolkitNoMatches(GentoolkitException): """No packages were found matching the search query""" def __init__(self, query): diff --git a/pym/gentoolkit/glsa/__init__.py b/pym/gentoolkit/glsa/__init__.py index 1ddcd41..d283835 100644 --- a/pym/gentoolkit/glsa/__init__.py +++ b/pym/gentoolkit/glsa/__init__.py @@ -11,7 +11,7 @@ # - getting GLSAs from http/ftp servers (not really useful without the fixed ebuilds) # - GPG signing/verification (until key policy is clear) -__author__ = "Marius Mauch , Robert Buchholz " +__author__ = "Marius Mauch " import os import sys @@ -19,10 +19,10 @@ import urllib import codecs import re import operator -import xml.etree.cElementTree as ET +import xml.dom.minidom from StringIO import StringIO -if sys.version_info[0:2] < (2, 3): +if sys.version_info[0:2] < (2,3): raise NotImplementedError("Python versions below 2.3 have broken XML code " \ +"and are not supported") @@ -33,8 +33,8 @@ except ImportError: import portage # Note: the space for rgt and rlt is important !! -opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=", - "rge": ">=~", "rle": "<=~", "rgt": " >~", "rlt": " <~"} +opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=", + "rge": ">=~", "rle": "<=~", "rgt": " >~", "rlt": " <~"} NEWLINE_ESCAPE = "!;\\n" # some random string to mark newlines that should be preserved SPACE_ESCAPE = "!;_" # some random string to mark spaces that should be preserved @@ -164,15 +164,16 @@ def getListElements(listnode): """ Get all
  • elements for a given
      or
        node. - @type listnode: ElementTree + @type listnode: xml.dom.Node @param listnode:
          or
            list to get the elements for @rtype: List of Strings @return: a list that contains the value of the
          1. elements """ - if not listnode.tag in ["ul", "ol"]: + if not listnode.nodeName in ["ul", "ol"]: raise GlsaFormatException("Invalid function call: listnode is not
              or
                ") rValue = [getText(li, format="strip") \ - for li in listnode.getchildren()] + for li in listnode.childNodes \ + if li.nodeType == xml.dom.Node.ELEMENT_NODE] return rValue def getText(node, format, textfd = None): @@ -184,7 +185,7 @@ def getText(node, format, textfd = None): tabs and spaces. This function is only useful for the GLSA DTD, it's not applicable for other DTDs. - @type node: ElementTree + @type node: xml.dom.Node @param node: the root node to start with the parsing @type format: String @param format: this should be either I{strip}, I{keep} or I{xml} @@ -199,45 +200,45 @@ def getText(node, format, textfd = None): @return: the (formatted) content of the node and its subnodes except if textfd was not none """ - if node == None: - return "" if not textfd: textfd = StringIO() returnNone = False else: returnNone = True if format in ["strip", "keep"]: - if node.tag in ["uri", "mail"]: - textfd.write(node.text+": "+(node.get("link") or "")) + if node.nodeName in ["uri", "mail"]: + textfd.write(node.childNodes[0].data+": "+node.getAttribute("link")) else: - textfd.write(node.text) - for subnode in node.getchildren(): - getText(subnode, format, textfd) - textfd.write(subnode.tail) + for subnode in node.childNodes: + if subnode.nodeName == "#text": + textfd.write(subnode.data) + else: + getText(subnode, format, textfd) else: # format = "xml" - textfd.write(node.text) - for subnode in node.getchildren(): - if subnode.tag == "p": - ptext = subnode.text - for p_subnode in subnode.getchildren(): - ptext += (p_subnode.text or "").strip() - if p_subnode.tag in ["uri", "mail"]: - ptext += " <"+(p_subnode.get("link") or "")+">" - ptext += p_subnode.tail - textfd.write(ptext.strip()) + for subnode in node.childNodes: + if subnode.nodeName == "p": + for p_subnode in subnode.childNodes: + if p_subnode.nodeName == "#text": + textfd.write(p_subnode.data.strip()) + elif p_subnode.nodeName in ["uri", "mail"]: + textfd.write(p_subnode.childNodes[0].data) + textfd.write(" ( "+p_subnode.getAttribute("link")+" )") textfd.write(NEWLINE_ESCAPE) - elif subnode.tag == "ul": + elif subnode.nodeName == "ul": for li in getListElements(subnode): textfd.write("-"+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ") - elif subnode.tag == "ol": - for i, li in enumerate(getListElements(subnode)): - textfd.write(str(i+1)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ") - elif subnode.tag == "code": + elif subnode.nodeName == "ol": + i = 0 + for li in getListElements(subnode): + i = i+1 + textfd.write(str(i)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ") + elif subnode.nodeName == "code": textfd.write(getText(subnode, format="keep").lstrip().replace("\n", NEWLINE_ESCAPE)) textfd.write(NEWLINE_ESCAPE) + elif subnode.nodeName == "#text": + textfd.write(subnode.data) else: - raise GlsaFormatException("Invalid Tag found: ", subnode.tag) - textfd.write(subnode.tail) + raise GlsaFormatException("Invalid Tag found: ", subnode.nodeName) if returnNone: return None rValue = textfd.getvalue() @@ -251,7 +252,7 @@ def getMultiTagsText(rootnode, tagname, format): Returns a list with the text of all subnodes of type I{tagname} under I{rootnode} (which itself is not parsed) using the given I{format}. - @type rootnode: ElementTree + @type rootnode: xml.dom.Node @param rootnode: the node to search for I{tagname} @type tagname: String @param tagname: the name of the tags to search for @@ -261,7 +262,7 @@ def getMultiTagsText(rootnode, tagname, format): @return: a list containing the text of all I{tagname} childnodes """ rValue = [getText(e, format) \ - for e in rootnode.findall(tagname)] + for e in rootnode.getElementsByTagName(tagname)] return rValue def makeAtom(pkgname, versionNode): @@ -271,18 +272,22 @@ def makeAtom(pkgname, versionNode): @type pkgname: String @param pkgname: the name of the package for this atom - @type versionNode: ElementTree + @type versionNode: xml.dom.Node @param versionNode: a or Node that contains the version information for this atom @rtype: String @return: the portage atom """ - rValue = opMapping[versionNode.get("range")] \ + rValue = opMapping[versionNode.getAttribute("range")] \ + pkgname \ + "-" + getText(versionNode, format="strip") - slot = versionNode.get("slot") - if slot and slot != "*": - rValue += ":" + slot.strip() + try: + slot = versionNode.getAttribute("slot").strip() + except KeyError: + pass + else: + if slot and slot != "*": + rValue += ":" + slot return str(rValue) def makeVersion(versionNode): @@ -290,17 +295,21 @@ def makeVersion(versionNode): creates from the information in the I{versionNode} a version string (format ). - @type versionNode: ElementTree + @type versionNode: xml.dom.Node @param versionNode: a or Node that contains the version information for this atom @rtype: String @return: the version string """ - rValue = opMapping[versionNode.get("range")] \ + rValue = opMapping[versionNode.getAttribute("range")] \ +getText(versionNode, format="strip") - slot = versionNode.get("slot") - if slot and slot != "*": - rValue += ":" + slot.strip() + try: + slot = versionNode.getAttribute("slot").strip() + except KeyError: + pass + else: + if slot and slot != "*": + rValue += ":" + slot return rValue def match(atom, portdbname, match_type="default"): @@ -517,72 +526,81 @@ class Glsa: @rtype: None @returns: None """ - self.DOM = ET.parse(myfile) - #elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa.dtd": - #self.dtdversion = 0 - #elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa-2.dtd": - #self.dtdversion = 2 - #else: - #raise GlsaTypeException(self.DOM.doctype.systemId) - myroot = self.DOM.getroot() - if myroot.tag != "glsa": - raise GlsaFormatException("Root tag was not 'glsa', but '%s' in %s:" % (self.tag, self.nr)) - if self.type == "id" and myroot.get("id") != self.nr: - raise GlsaFormatException("filename and internal id don't match:" + myroot.get("id") + " != " + self.nr) + self.DOM = xml.dom.minidom.parse(myfile) + if not self.DOM.doctype: + raise GlsaTypeException(None) + elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa.dtd": + self.dtdversion = 0 + elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa-2.dtd": + self.dtdversion = 2 + else: + raise GlsaTypeException(self.DOM.doctype.systemId) + myroot = self.DOM.getElementsByTagName("glsa")[0] + if self.type == "id" and myroot.getAttribute("id") != self.nr: + raise GlsaFormatException("filename and internal id don't match:" + myroot.getAttribute("id") + " != " + self.nr) # the simple (single, required, top-level, #PCDATA) tags first - self.title = getText(myroot.find("title"), format="strip") - self.synopsis = getText(myroot.find("synopsis"), format="strip") - self.announced = format_date(getText(myroot.find("announced"), format="strip")) + self.title = getText(myroot.getElementsByTagName("title")[0], format="strip") + self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip") + self.announced = format_date(getText(myroot.getElementsByTagName("announced")[0], format="strip")) + count = 1 # Support both formats of revised: - # December 30, 2007: 02 (old style) - # 2007-12-30 (new style) - revisedEl = myroot.find("revised") + # December 30, 2007: 02 + # 2007-12-30 + revisedEl = myroot.getElementsByTagName("revised")[0] self.revised = getText(revisedEl, format="strip") - if (self.revised.find(":") >= 0): # old style + if (revisedEl.attributes.has_key("count")): + count = revisedEl.getAttribute("count") + elif (self.revised.find(":") >= 0): (self.revised, count) = self.revised.split(":") - else: #new style - count = revisedEl.get("count") - + self.revised = format_date(self.revised) try: self.count = int(count) - except (ValueError, TypeError): + except ValueError: # TODO should this rais a GlsaFormatException? self.count = 1 # now the optional and 0-n toplevel, #PCDATA tags and references - self.access = getText(myroot.find("access"), format="strip") - # TODO + try: + self.access = getText(myroot.getElementsByTagName("access")[0], format="strip") + except IndexError: + self.access = "" self.bugs = getMultiTagsText(myroot, "bug", format="strip") - self.references = getMultiTagsText(myroot.find("references"), "uri", format="keep") + self.references = getMultiTagsText(myroot.getElementsByTagName("references")[0], "uri", format="keep") # and now the formatted text elements - self.description = getText(myroot.find("description"), format="xml") - self.workaround = getText(myroot.find("workaround"), format="xml") - self.resolution = getText(myroot.find("resolution"), format="xml") - self.impact_text = getText(myroot.find("impact"), format="xml") - self.impact_type = myroot.find("impact").get("type") - self.background = getText(myroot.find("background"), format="xml") + self.description = getText(myroot.getElementsByTagName("description")[0], format="xml") + self.workaround = getText(myroot.getElementsByTagName("workaround")[0], format="xml") + self.resolution = getText(myroot.getElementsByTagName("resolution")[0], format="xml") + self.impact_text = getText(myroot.getElementsByTagName("impact")[0], format="xml") + self.impact_type = myroot.getElementsByTagName("impact")[0].getAttribute("type") + try: + self.background = getText(myroot.getElementsByTagName("background")[0], format="xml") + except IndexError: + self.background = "" # finally the interesting tags (product, affected, package) - self.glsatype = myroot.find("product").get("type") - self.product = getText(myroot.find("product"), format="strip") + self.glsatype = myroot.getElementsByTagName("product")[0].getAttribute("type") + self.product = getText(myroot.getElementsByTagName("product")[0], format="strip") + self.affected = myroot.getElementsByTagName("affected")[0] self.packages = {} - affected = myroot.find("affected") - for p in affected.findall("package"): - name = p.get("name") - new_entry = {} - new_entry["arch"] = p.get("arch") - new_entry["auto"] = (p.get("auto") == "yes") - new_entry["vul_vers"] = [makeVersion(v) for v in p.findall("vulnerable")] - new_entry["unaff_vers"] = [makeVersion(v) for v in p.findall("unaffected")] - new_entry["vul_atoms"] = [makeAtom(name, v) for v in p.findall("vulnerable")] - new_entry["unaff_atoms"] = [makeAtom(name, v) for v in p.findall("unaffected")] - package_entries = self.packages.setdefault(name, []) - package_entries.append(new_entry) + for p in self.affected.getElementsByTagName("package"): + name = p.getAttribute("name") + if not self.packages.has_key(name): + self.packages[name] = [] + tmp = {} + tmp["arch"] = p.getAttribute("arch") + tmp["auto"] = (p.getAttribute("auto") == "yes") + tmp["vul_vers"] = [makeVersion(v) for v in p.getElementsByTagName("vulnerable")] + tmp["unaff_vers"] = [makeVersion(v) for v in p.getElementsByTagName("unaffected")] + tmp["vul_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("vulnerable")] + tmp["unaff_atoms"] = [makeAtom(name, v) for v in p.getElementsByTagName("unaffected")] + self.packages[name].append(tmp) + # TODO: services aren't really used yet + self.services = self.affected.getElementsByTagName("service") return None def dump(self, outstream=sys.stdout, encoding="utf-8"): diff --git a/pym/gentoolkit/helpers.py b/pym/gentoolkit/helpers.py index 69acc1d..6d272d3 100644 --- a/pym/gentoolkit/helpers.py +++ b/pym/gentoolkit/helpers.py @@ -150,11 +150,12 @@ def split_package_name(name): r[0] = '' return r -def sort_package_list(pkglist): - """Returns the list ordered in the same way portage would do with lowest version - at the head of the list.""" - pkglist.sort(Package.compare_version) - return pkglist +# XXX: Defunct: use helpers2.compare_package_strings +#def sort_package_list(pkglist): +# """Returns the list ordered in the same way portage would do with lowest version +# at the head of the list.""" +# pkglist.sort(Package.compare_version) +# return pkglist if __name__ == "__main__": print "This module is for import only" diff --git a/pym/gentoolkit/helpers2.py b/pym/gentoolkit/helpers2.py index 1a2530a..f9d0c49 100644 --- a/pym/gentoolkit/helpers2.py +++ b/pym/gentoolkit/helpers2.py @@ -13,16 +13,19 @@ query matches one or more packages. This should be merged into helpers when a clean path is found. """ +# Move to Imports section after Python 2.6 is stable +from __future__ import with_statement + __all__ = ( 'compare_package_strings', + 'do_lookup', 'find_best_match', 'find_installed_packages', 'find_packages', 'get_cpvs', 'get_installed_cpvs', 'get_uninstalled_cpvs', - 'uses_globbing', - 'do_lookup' + 'uses_globbing' ) __author__ = 'Douglas Anderson' __docformat__ = 'epytext' @@ -40,17 +43,10 @@ from portage.util import unique_array import gentoolkit import gentoolkit.pprinter as pp -from gentoolkit import catpkgsplit, Config +from gentoolkit import Config, PORTDB, VARDB from gentoolkit import errors from gentoolkit.package import Package -# ======= -# Globals -# ======= - -PORTDB = portage.db[portage.root]["porttree"].dbapi -VARDB = portage.db[portage.root]["vartree"].dbapi - # ========= # Functions # ========= @@ -62,12 +58,12 @@ def compare_package_strings(pkg1, pkg2): An alternative is to use the Package descriptor from gentoolkit.package >>> pkgs = [Package(x) for x in package_list] >>> pkgs.sort() - + @see: >>> help(cmp) """ - pkg1 = catpkgsplit(pkg1) - pkg2 = catpkgsplit(pkg2) + pkg1 = portage.versions.catpkgsplit(pkg1) + pkg2 = portage.versions.catpkgsplit(pkg2) # Compare categories if pkg1[0] != pkg2[0]: return cmp(pkg1[0], pkg2[0]) @@ -79,75 +75,59 @@ def compare_package_strings(pkg1, pkg2): return portage.versions.pkgcmp(pkg1[1:], pkg2[1:]) -def find_best_match(query): - """Return the highest unmasked version of a package matching query. - - @type query: str - @param query: can be of the form: pkg, pkg-ver, cat/pkg, cat/pkg-ver, atom - @rtype: str or None - """ - - match = PORTDB.xmatch("bestmatch-visible", query) +def do_lookup(query, query_opts): + """A high-level wrapper around gentoolkit package-finder functions. - return Package(match) if match else None + @type query: str + @param query: pkg, cat/pkg, pkg-ver, cat/pkg-ver, atom or regex + @type query_opts: dict + @param query_opts: user-configurable options from the calling module + Currently supported options are: + categoryFilter = str or None + includeInstalled = bool + includePortTree = bool + includeOverlayTree = bool + isRegex = bool + printMatchInfo = bool # Print info about the search -def find_installed_packages(query): - """Return a list of Package objects that matched the search key.""" + @rtype: list + @return: Package objects matching query + """ - try: - matches = VARDB.match(query) - # catch the ambiguous package Exception - except ValueError, err: - if isinstance(err[0], list): - matches = [] - for pkgkey in err[0]: - matches.append(VARDB.match(pkgkey)) + if query_opts["includeInstalled"]: + if query_opts["includePortTree"] or query_opts["includeOverlayTree"]: + simple_package_finder = partial(find_packages, include_masked=True) + complex_package_finder = get_cpvs else: - raise ValueError(err) - except portage.exception.InvalidAtom, err: - pp.print_warn("Invalid Atom: '%s'" % str(err)) - return [] - - return [Package(x) for x in matches] - - -def uses_globbing(query): - """Check the query to see if it is using globbing. + simple_package_finder = find_installed_packages + complex_package_finder = get_installed_cpvs + elif query_opts["includePortTree"] or query_opts["includeOverlayTree"]: + simple_package_finder = partial(find_packages, include_masked=True) + complex_package_finder = get_uninstalled_cpvs + else: + pp.die(2, "Not searching in installed, portage tree or overlay." + " Nothing to do.") - @rtype: bool - @return: True if query uses globbing, else False - """ + is_simple_query = True + if query_opts["isRegex"] or uses_globbing(query): + is_simple_query = False - if set('!*?[]').intersection(set(query)): - if portage.dep.get_operator(query): - # Query may be an atom such as '=sys-apps/portage-2.2*' - pass - else: - return True + if is_simple_query: + matches = _do_simple_lookup(query, simple_package_finder, query_opts) + else: + matches = _do_complex_lookup(query, complex_package_finder, query_opts) - return False + return matches -def _do_complex_lookup(query, query_opts): +def _do_complex_lookup(query, package_finder, query_opts): """Find matches for a query which is a regex or includes globbing.""" # pylint: Too many branches (18/12) # pylint: disable-message=R0912 result = [] - if query_opts["includeInstalled"]: - if query_opts["includePortTree"] or query_opts["includeOverlayTree"]: - package_finder = get_cpvs - else: - package_finder = get_installed_cpvs - elif query_opts["includePortTree"] or query_opts["includeOverlayTree"]: - package_finder = get_uninstalled_cpvs - else: - pp.print_error("Not searching in installed, portage tree or overlay." + - " Nothing to do.") - pp.die(2, "This is an internal error. Please report this.") - if query_opts["printMatchInfo"] and not Config["piping"]: print_query_info(query, query_opts) @@ -194,28 +174,7 @@ def _do_complex_lookup(query, query_opts): return [Package(x) for x in result] -def print_query_info(query, query_opts): - """Print info about the query to the screen.""" - - cats = prepare_categories(query_opts["categoryFilter"]) - cat, pkg, ver, rev = split_query(query) - del ver, rev - if cats: - cat_str = "in %s " % ', '.join([pp.emph(x) for x in cats]) - elif cat and not query_opts["isRegex"]: - cat_str = "in %s " % pp.emph(cat) - else: - cat_str = "" - - if query_opts["isRegex"]: - pkg_str = query - else: - pkg_str = pkg - - print " * Searching for %s %s..." % (pp.emph(pkg_str), cat_str) - - -def _do_simple_lookup(query, query_opts): +def _do_simple_lookup(query, package_finder, query_opts): """Find matches for a query which is an atom or string.""" result = [] @@ -223,11 +182,6 @@ def _do_simple_lookup(query, query_opts): cats = prepare_categories(query_opts["categoryFilter"]) if query_opts["printMatchInfo"] and not Config["piping"]: print_query_info(query, query_opts) - - if query_opts["includePortTree"] or query_opts["includeOverlayTree"]: - package_finder = find_packages - else: - package_finder = find_installed_packages result = package_finder(query) if not query_opts["includeInstalled"]: @@ -239,36 +193,34 @@ def _do_simple_lookup(query, query_opts): return result -def do_lookup(query, query_opts): - """A high-level wrapper around gentoolkit package-finder functions. - +def find_best_match(query): + """Return the highest unmasked version of a package matching query. + @type query: str - @param query: pkg, cat/pkg, pkg-ver, cat/pkg-ver, atom or regex - @type query_opts: dict - @param query_opts: user-configurable options from the calling module - Currently supported options are: + @param query: can be of the form: pkg, pkg-ver, cat/pkg, cat/pkg-ver, atom + @rtype: str or None + """ - categoryFilter = str or None - includeInstalled = bool - includePortTree = bool - includeOverlayTree = bool - isRegex = bool - printMatchInfo = bool # Print info about the search + match = PORTDB.xmatch("bestmatch-visible", query) - @rtype: list - @return: Package objects matching query - """ + return Package(match) if match else None - is_simple_query = True - if query_opts["isRegex"] or uses_globbing(query): - is_simple_query = False - if is_simple_query: - matches = _do_simple_lookup(query, query_opts) - else: - matches = _do_complex_lookup(query, query_opts) +def find_installed_packages(query): + """Return a list of Package objects that matched the search key.""" - return matches + try: + matches = VARDB.match(query) + # catch the ambiguous package Exception + except portage.exception.AmbiguousPackageName, err: + matches = [] + for pkgkey in err[0]: + matches.extend(VARDB.match(pkgkey)) + except portage.exception.InvalidAtom, err: + pp.print_warn("Invalid Atom: '%s'" % str(err)) + return [] + + return [Package(x) for x in matches] def find_packages(query, include_masked=False): @@ -289,18 +241,6 @@ def find_packages(query, include_masked=False): else: matches = PORTDB.match(query) matches.extend(VARDB.match(query)) - # Catch ambiguous packages - except ValueError, err: - if isinstance(err[0], list): - matches = [] - for pkgkey in err[0]: - if include_masked: - matches.extend(PORTDB.xmatch("match-all", pkgkey)) - else: - matches.extend(PORTDB.match(pkgkey)) - matches.extend(VARDB.match(pkgkey)) - else: - raise ValueError(err) except portage.exception.InvalidAtom, err: raise errors.GentoolkitInvalidAtom(str(err)) @@ -400,6 +340,42 @@ def prepare_categories(category_filter): return tuple(good_cats) +def print_query_info(query, query_opts): + """Print info about the query to the screen.""" + + cats = prepare_categories(query_opts["categoryFilter"]) + cat, pkg, ver, rev = split_query(query) + del ver, rev + if cats: + cat_str = "in %s " % ', '.join([pp.emph(x) for x in cats]) + elif cat and not query_opts["isRegex"]: + cat_str = "in %s " % pp.emph(cat) + else: + cat_str = "" + + if query_opts["isRegex"]: + pkg_str = query + else: + pkg_str = pkg + + print " * Searching for %s %s..." % (pp.emph(pkg_str), cat_str) + + +def print_file(path): + """Display the contents of a file.""" + + with open(path) as open_file: + lines = open_file.read() + print lines.strip() + + +def print_sequence(seq): + """Print every item of a sequence.""" + + for item in seq: + print item + + def split_query(query): """Split a query into category, name, version and revision. @@ -410,16 +386,52 @@ def split_query(query): Each tuple element is a string or empty string (""). """ - cat = name = ver = rev = "" + result = portage.versions.catpkgsplit(query) - try: - (cat, name, ver, rev) = gentoolkit.split_package_name(query) - except ValueError, err: - # FIXME: Not hitting this error anymore... but we should be? - if str(err) == 'too many values to unpack': - pp.print_error("Too many slashes ('/').") - raise errors.GentoolkitInvalidPackageName(query) + if result: + result = list(result) + if result[0] == 'null': + result[0] = '' + if result[3] == 'r0': + result[3] = '' + else: + result = query.split("/") + if len(result) == 1: + result = ['', query, '', ''] else: - raise ValueError(err) - - return (cat, name, ver, rev) + result = result + ['', ''] + + if len(result) != 4: + pp.print_error("Too many slashes ('/').") + raise errors.GentoolkitInvalidPackageName(query) + + return tuple(result) + + +def uniqify(seq, preserve_order=True): + """Return a uniqified list. Optionally preserve order.""" + + if preserve_order: + seen = set() + result = [x for x in seq if x not in seen and not seen.add(x)] + else: + result = list(set(seq)) + + return result + + +def uses_globbing(query): + """Check the query to see if it is using globbing. + + @rtype: bool + @return: True if query uses globbing, else False + """ + + if set('!*?[]').intersection(set(query)): + if portage.dep.get_operator(query): + # Query may be an atom such as '=sys-apps/portage-2.2*' + pass + else: + return True + + return False diff --git a/pym/gentoolkit/package.py b/pym/gentoolkit/package.py index 340a512..857470a 100644 --- a/pym/gentoolkit/package.py +++ b/pym/gentoolkit/package.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#!/usr/bin/python # # Copyright(c) 2004, Karl Trygve Kalleberg # Copyright(c) 2004-2009, Gentoo Foundation @@ -14,18 +14,12 @@ import os import portage -from portage import catpkgsplit -from portage.versions import vercmp +from portage.versions import catpkgsplit, vercmp -from gentoolkit import * +import gentoolkit.pprinter as pp +from gentoolkit import settings, settingslock, PORTDB, VARDB from gentoolkit import errors - -# ======= -# Globals -# ======= - -PORTDB = portage.db[portage.root]["porttree"].dbapi -VARDB = portage.db[portage.root]["vartree"].dbapi +from gentoolkit.versionmatch import VersionMatch # ======= # Classes @@ -98,27 +92,33 @@ class Package(object): def __repr__(self): return "<%s %s @%#8x>" % (self.__class__.__name__, self._cpv, id(self)) - def __cmp__(self, other): - # FIXME: __cmp__ functions dissallowed in py3k; need __lt__, __gt__. + def __eq__(self, other): + return hash(self) == hash(other) + + def __ne__(self, other): + return hash(self) != hash(other) + + def __lt__(self, other): if not isinstance(other, self.__class__): raise TypeError("other isn't of %s type, is %s" % (self.__class__, other.__class__)) if self.category != other.category: - return cmp(self.category, other.category) + return self.category < other.category elif self.name != other.name: - return cmp(self.name, other.name) + return self.name < other.name else: # FIXME: this cmp() hack is for vercmp not using -1,0,1 # See bug 266493; this was fixed in portage-2.2_rc31 #return portage.vercmp(self.fullversion, other.fullversion) - return cmp(portage.vercmp(self.fullversion, other.fullversion), 0) - - def __eq__(self, other): - return hash(self) == hash(other) + result = cmp(portage.vercmp(self.fullversion, other.fullversion), 0) + if result == -1: + return True + else: + return False - def __ne__(self, other): - return hash(self) != hash(other) + def __gt__(self, other): + return not self.__lt__(other) def __hash__(self): return hash(self._cpv) @@ -144,11 +144,13 @@ class Package(object): def get_settings(self, key): """Returns the value of the given key for this package (useful for package.* files.""" - self._settingslock.acquire() - self._settings.setcpv(self.cpv) - v = self._settings[key] - self._settingslock.release() - return v + try: + self._settingslock.acquire() + self._settings.setcpv(self.cpv) + result = self._settings[key] + finally: + self._settingslock.release() + return result def get_cpv(self): """Returns full Category/Package-Version string""" @@ -156,14 +158,14 @@ class Package(object): def get_provide(self): """Return a list of provides, if any""" - if not self.is_installed(): + if self.is_installed(): + result = VARDB.get_provide(self.cpv) + else: try: - x = [self.get_env_var('PROVIDE')] + result = [self.get_env_var('PROVIDE')] except KeyError: - x = [] - return x - else: - return vartree.get_provide(self.cpv) + result = [] + return result def get_dependants(self): """Retrieves a list of CPVs for all packages depending on this one""" @@ -176,11 +178,10 @@ class Package(object): # Try to use the portage tree first, since emerge only uses the tree # when calculating dependencies try: - cd = self.get_env_var("RDEPEND", porttree).split() + rdepends = self.get_env_var("RDEPEND", PORTDB).split() except KeyError: - cd = self.get_env_var("RDEPEND", vartree).split() - r,i = self._parse_deps(cd) - return r + rdepends = self.get_env_var("RDEPEND", VARDB).split() + return self._parse_deps(rdepends)[0] def get_compiletime_deps(self): """Returns a linearised list of first-level compile time dependencies @@ -189,11 +190,10 @@ class Package(object): # Try to use the portage tree first, since emerge only uses the tree # when calculating dependencies try: - rd = self.get_env_var("DEPEND", porttree).split() + depends = self.get_env_var("DEPEND", PORTDB).split() except KeyError: - rd = self.get_env_var("DEPEND", vartree).split() - r,i = self._parse_deps(rd) - return r + depends = self.get_env_var("DEPEND", VARDB).split() + return self._parse_deps(depends)[0] def get_postmerge_deps(self): """Returns a linearised list of first-level post merge dependencies @@ -202,11 +202,10 @@ class Package(object): # Try to use the portage tree first, since emerge only uses the tree # when calculating dependencies try: - pd = self.get_env_var("PDEPEND", porttree).split() + postmerge_deps = self.get_env_var("PDEPEND", PORTDB).split() except KeyError: - pd = self.get_env_var("PDEPEND", vartree).split() - r,i = self._parse_deps(pd) - return r + postmerge_deps = self.get_env_var("PDEPEND", VARDB).split() + return self._parse_deps(postmerge_deps)[0] def intersects(self, other): """Check if a passed in package atom "intersects" this atom. @@ -221,7 +220,7 @@ class Package(object): ">=dev-lang/python-2.4" and "dev-lang/python" but not "2) return ( - VersionMatch(frompkg=other).match(ranged) and - VersionMatch(frompkg=ranged).match(other)) + VersionMatch(other).match(ranged) and + VersionMatch(ranged).match(other)) if other.operator == '~': # Other definitely matches its own version. If ranged also # does we're done: - if VersionMatch(frompkg=ranged).match(other): + if VersionMatch(ranged).match(other): return True # The only other case where we intersect is if ranged is a # > or >= on other's version and a nonzero revision. In @@ -362,8 +361,8 @@ class Package(object): def is_overlay(self): """Returns True if the package is in an overlay.""" - dir,ovl = portage.portdb.findname2(self.cpv) - return ovl != self._portdir_path + ebuild, tree = portage.portdb.findname2(self.cpv) + return tree != self._portdir_path def is_masked(self): """Returns true if this package is masked against installation. @@ -372,80 +371,52 @@ class Package(object): unmasked = portage.portdb.xmatch("match-visible", self.cpv) return self.cpv not in unmasked - def get_ebuild_path(self,in_vartree=0): + def get_ebuild_path(self, in_vartree=False): """Returns the complete path to the .ebuild file""" if in_vartree: - return vartree.getebuildpath(self.cpv) - else: - return portage.portdb.findname(self.cpv) + return VARDB.getebuildpath(self.cpv) + return PORTDB.findname(self.cpv) def get_package_path(self): """Returns the path to where the ChangeLog, Manifest, .ebuild files reside""" - p = self.get_ebuild_path() - sp = p.split("/") - if sp: - # FIXME: use os.path.join - return "/".join(sp[:-1]) + ebuild_path = self.get_ebuild_path() + path_split = ebuild_path.split("/") + if path_split: + return os.sep.join(path_split[:-1]) - def get_env_var(self, var, tree=""): + def get_env_var(self, var, tree=None): """Returns one of the predefined env vars DEPEND, RDEPEND, SRC_URI,....""" - if tree == "": - mytree = vartree + if tree == None: + tree = VARDB if not self.is_installed(): - mytree = porttree - else: - mytree = tree - try: - r = mytree.dbapi.aux_get(self.cpv,[var]) - except KeyError: - # aux_get raises KeyError if it encounters a bad digest, etc - raise - if not r: + tree = PORTDB + result = tree.aux_get(self.cpv, [var]) + if not result: raise errors.GentoolkitFatalError("Could not find the package tree") - if len(r) != 1: + if len(result) != 1: raise errors.GentoolkitFatalError("Should only get one element!") - return r[0] + return result[0] def get_use_flags(self): """Returns the USE flags active at time of installation""" self._initdb() if self.is_installed(): return self._db.getfile("USE") - return "" def get_contents(self): """Returns the full contents, as a dictionary, in the form - [ '/bin/foo' : [ 'obj', '1052505381', '45ca8b89751...' ], ... ]""" + ['/bin/foo' : [ 'obj', '1052505381', '45ca8b89751...' ], ... ]""" self._initdb() if self.is_installed(): return self._db.getcontents() return {} - # XXX > - def compare_version(self,other): - """Compares this package's version to another's CPV; returns -1, 0, 1. - - Deprecated in favor of __cmp__. - """ - v1 = self.cpv_split - v2 = catpkgsplit(other.get_cpv()) - # if category is different - if v1[0] != v2[0]: - return cmp(v1[0],v2[0]) - # if name is different - elif v1[1] != v2[1]: - return cmp(v1[1],v2[1]) - # Compare versions - else: - return portage.pkgcmp(v1[1:],v2[1:]) - # < XXX - def size(self): """Estimates the installed size of the contents of this package, if possible. - Returns [size, number of files in total, number of uncounted files] + Returns (size, number of files in total, number of uncounted files) """ contents = self.get_contents() size = 0 @@ -457,7 +428,7 @@ class Package(object): files += 1 except OSError: uncounted += 1 - return [size, files, uncounted] + return (size, files, uncounted) def _initdb(self): """Internal helper function; loads package information from disk, @@ -470,113 +441,106 @@ class Package(object): settings["ROOT"], settings ) - -class VersionMatch(object): - """Package restriction implementing Gentoo ebuild version comparison rules. - From pkgcore.ebuild.atom_restricts. - Any overriding of this class *must* maintain numerical order of - self.vals, see intersect for reason why. vals also must be a tuple. +class PackageFormatter(object): + """When applied to a L{gentoolkit.package.Package} object, determine the + location (Portage Tree vs. overlay), install status and masked status. That + information can then be easily formatted and displayed. + + Example usage: + >>> from gentoolkit.helpers2 import find_packages + >>> from gentoolkit.package import PackageFormatter + >>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')] + >>> for pkg in pkgs: + ... # Only print packages that are installed and from the Portage + ... # tree + ... if set('IP').issubset(pkg.location): + ... print pkg + ... + [IP-] [ ] sys-devel/gcc-4.3.2-r3 (4.3) + + @type pkg: L{gentoolkit.package.Package} + @param pkg: package to format + @type format: L{bool} + @param format: Whether to format the package name or not. + Essentially C{format} should be set to False when piping or when + quiet output is desired. If C{format} is False, only the location + attribute will be created to save time. """ - _convert_op2int = {(-1,):"<", (-1, 0): "<=", (0,):"=", - (0, 1):">=", (1,):">"} - - _convert_int2op = dict([(v, k) for k, v in _convert_op2int.iteritems()]) - del k, v - - def __init__(self, **kwargs): - """This class will either create a VersionMatch instance out of - a Package instance, or from explicitly passed in operator, version, - and revision. - Possible args: - frompkg= instance + def __init__(self, pkg, format=True): + location = '' + maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-'] - OR + self.pkg = pkg + self.format = format + if format: + self.arch = settings["ARCH"] + self.mask = maskmodes[self.get_mask_status()] + self.slot = pkg.get_env_var("SLOT") + self.location = self.get_package_location() - op=str: version comparison to do, - valid operators are ('<', '<=', '=', '>=', '>', '~') - ver=str: version to base comparison on - rev=str: revision to base comparison on - """ - if 'frompkg' in kwargs and kwargs['frompkg']: - self.operator = kwargs['frompkg'].operator - self.version = kwargs['frompkg'].version - self.revision = kwargs['frompkg'].revision - self.fullversion = kwargs['frompkg'].fullversion - elif set(('op', 'ver', 'rev')) == set(kwargs): - self.operator = kwargs['op'] - self.version = kwargs['ver'] - self.revision = kwargs['rev'] - if not self.revision: - self.fullversion = self.version - else: - self.fullversion = "%s-%s" % (self.version, self.revision) - else: - raise TypeError('__init__() takes either a Package instance ' - 'via frompkg= or op=, ver= and rev= all passed in') - - if self.operator != "~" and self.operator not in self._convert_int2op: - # FIXME: change error - raise errors.InvalidVersion(self.ver, self.rev, - "invalid operator, '%s'" % operator) - - if self.operator == "~": - if not self.version: - raise ValueError( - "for ~ op, version must be specified") - self.droprevision = True - self.values = (0,) - else: - self.droprevision = False - self.values = self._convert_int2op[self.operator] - - def match(self, pkginst): - if self.droprevision: - ver1, ver2 = self.version, pkginst.version - else: - ver1, ver2 = self.fullversion, pkginst.fullversion - - #print "== VersionMatch.match DEBUG START ==" - #print "ver1:", ver1 - #print "ver2:", ver2 - #print "vercmp(ver2, ver1):", vercmp(ver2, ver1) - #print "self.values:", self.values - #print "vercmp(ver2, ver1) in values?", - #print "vercmp(ver2, ver1) in self.values" - #print "== VersionMatch.match DEBUG END ==" - - return vercmp(ver2, ver1) in self.values + def __repr__(self): + return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self)) def __str__(self): - s = self._convert_op2int[self.values] + if self.format: + return "[%(location)s] [%(mask)s] %(package)s (%(slot)s)" % { + 'location': self.location, + 'mask': pp.maskflag(self.mask), + 'package': pp.cpv(self.pkg.cpv), + 'slot': self.slot + } + else: + return self.pkg.cpv - if self.droprevision or not self.revision: - return "ver %s %s" % (s, self.version) - return "ver-rev %s %s-%s" % (s, self.version, self.revision) + def get_package_location(self): + """Get the physical location of a package on disk. - def __repr__(self): - return "<%s %s @%#8x>" % (self.__class__.__name__, str(self), id(self)) + @rtype: str + @return: one of: + '-P-' : Not installed and from the Portage tree + '--O' : Not installed and from an overlay + 'IP-' : Installed and from the Portage tree + 'I-O' : Installed and from an overlay + """ - @staticmethod - def _convert_ops(inst): - if inst.droprevision: - return inst.values - return tuple(sorted(set((-1, 0, 1)).difference(inst.values))) + result = ['-', '-', '-'] - def __eq__(self, other): - if self is other: - return True - if isinstance(other, self.__class__): - if (self.droprevsion != other.droprevsion or - self.version != other.version or - self.revision != other.revision): - return False - return self._convert_ops(self) == self._convert_ops(other) + if self.pkg.is_installed(): + result[0] = 'I' + if self.pkg.is_overlay(): + result[2] = 'O' + else: + result[1] = 'P' + + return ''.join(result) + + def get_mask_status(self): + """Get the mask status of a given package. + + @type pkg: L{gentoolkit.package.Package} + @param pkg: pkg to get mask status of + @type arch: str + @param arch: output of gentoolkit.settings["ARCH"] + @rtype: int + @return: an index for this list: [" ", " ~", " -", "M ", "M~", "M-"] + 0 = not masked + 1 = keyword masked + 2 = arch masked + 3 = hard masked + 4 = hard and keyword masked, + 5 = hard and arch masked + """ - return False + keywords = self.pkg.get_env_var("KEYWORDS").split() + mask_status = 0 + if self.pkg.is_masked(): + mask_status += 3 + if ("~%s" % self.arch) in keywords: + mask_status += 1 + elif ("-%s" % self.arch) in keywords or "-*" in keywords: + mask_status += 2 - def __hash__(self): - return hash((self.droprevision, self.version, self.revision, - self.values)) + return mask_status diff --git a/pym/gentoolkit/versionmatch.py b/pym/gentoolkit/versionmatch.py new file mode 100644 index 0000000..0048882 --- /dev/null +++ b/pym/gentoolkit/versionmatch.py @@ -0,0 +1,190 @@ +#! /usr/bin/python +# +# Copyright(c) 2009, Gentoo Foundation +# Licensed under the GNU General Public License, v2 +# +# $Header$ + +"""Gentoo package version comparison object from pkgcore.ebuild.atom_restricts. + +The VersionMatch class allows you to compare package versions according to +Gentoo's versioning rules. + +The simplest way to use it is to test simple equality. In this example I've +passed in the keyword arguments op (operator), ver (version), and +rev (revision) explicitly: +>>> from gentoolkit.versionmatch import VersionMatch +>>> VersionMatch(op='=',ver='1',rev='') == VersionMatch(op='=',ver='1',rev='') +True +>>> VersionMatch(op='=',ver='1',rev='') == VersionMatch(op='=',ver='2',rev='') +False + +A more flexible way to use it is to pass it a single gentoolkit.package.Package +instance which it uses to determine op, ver and rev: +>>> from gentoolkit.package import Package +>>> from gentoolkit.versionmatch import VersionMatch +>>> pkg1 = Package('sys-apps/portage-2.2') +>>> pkg2 = Package('sys-apps/portage-1.6') +>>> VersionMatch(pkg1) == VersionMatch(pkg2) +False + +Simple equality tests aren't actually very useful because they don't understand +different operators: +>>> VersionMatch(op='>', ver='1.5', rev='') == \ +... VersionMatch(op='=', ver='2', rev='') +False + +For more complicated comparisons, we can use the match method: +>>> from gentoolkit.package import Package +>>> from gentoolkit.versionmatch import VersionMatch +>>> pkg1 = Package('>=sys-apps/portage-2.2') +>>> pkg2 = Package('=sys-apps/portage-2.2_rc30') +>>> # An "rc" (release candidate) version compares less than a non "rc" version +... VersionMatch(pkg1).match(pkg2) +False +>>> pkg2 = Package('=sys-apps/portage-2.2-r6') +>>> # But an "r" (revision) version compares greater than a non "r" version +... VersionMatch(pkg1).match(pkg2) +True + +@see: gentoolkit.equery.changes for examples of use in gentoolkit. +@see: gentoolkit.package.Package.intersects for a higher level version + comparison method. +""" + +# ======= +# Imports +# ======= + +from portage.versions import vercmp + +import gentoolkit +from gentoolkit import errors + +# ======= +# Classes +# ======= + +class VersionMatch(object): + """Gentoo package version comparison object from pkgcore.ebuild.atom_restricts. + + Any overriding of this class *must* maintain numerical order of + self.vals, see intersect for reason why. vals also must be a tuple. + """ + _convert_op2int = {(-1,):"<", (-1, 0): "<=", (0,):"=", + (0, 1):">=", (1,):">"} + + _convert_int2op = dict([(v, k) for k, v in _convert_op2int.iteritems()]) + del k, v + + def __init__(self, *args, **kwargs): + """This class will either create a VersionMatch instance out of + a Package instance, or from explicitly passed in operator, version, + and revision. + + Takes EITHER one arg: + instance + + OR + + three keyword args: + op=str: version comparison to do, + valid operators are ('<', '<=', '=', '>=', '>', '~') + ver=str: version to base comparison on + rev=str: revision to base comparison on + """ + if args and isinstance(args[0], (gentoolkit.package.Package, + self.__class__)): + self.operator = args[0].operator + self.version = args[0].version + self.revision = args[0].revision + self.fullversion = args[0].fullversion + elif set(('op', 'ver', 'rev')) == set(kwargs): + self.operator = kwargs['op'] + self.version = kwargs['ver'] + self.revision = kwargs['rev'] + if not self.revision: + self.fullversion = self.version + else: + self.fullversion = "%s-%s" % (self.version, self.revision) + else: + raise TypeError('__init__() takes either a Package instance ' + 'argument or op=, ver= and rev= all passed in as keyword args') + + if self.operator != "~" and self.operator not in self._convert_int2op: + raise errors.GentoolkitInvalidVersion( + "invalid operator '%s'" % self.operator) + + if self.operator == "~": + if not self.version: + raise errors.GentoolkitInvalidVersion( + "for ~ op, version must be specified") + self.droprevision = True + self.values = (0,) + else: + self.droprevision = False + self.values = self._convert_int2op[self.operator] + + def match(self, pkginst): + """See whether a passed in VersionMatch or Package instance matches + self. + + Example usage: + >>> from gentoolkit.versionmatch import VersionMatch + >>> VersionMatch(op='>',ver='1.5',rev='').match( + ... VersionMatch(op='=',ver='2.0',rev='')) + True + + @type pkginst: gentoolkit.versionmatch.VersionMatch OR + gentoolkit.package.Package + @param pkginst: version to compare with self's version + @rtype: bool + """ + + if self.droprevision: + ver1, ver2 = self.version, pkginst.version + else: + ver1, ver2 = self.fullversion, pkginst.fullversion + + #print "== VersionMatch.match DEBUG START ==" + #print "ver1:", ver1 + #print "ver2:", ver2 + #print "vercmp(ver2, ver1):", vercmp(ver2, ver1) + #print "self.values:", self.values + #print "vercmp(ver2, ver1) in values?", + #print "vercmp(ver2, ver1) in self.values" + #print "== VersionMatch.match DEBUG END ==" + + return vercmp(ver2, ver1) in self.values + + def __str__(self): + operator = self._convert_op2int[self.values] + + if self.droprevision or not self.revision: + return "ver %s %s" % (operator, self.version) + return "ver-rev %s %s-%s" % (operator, self.version, self.revision) + + def __repr__(self): + return "<%s %s @%#8x>" % (self.__class__.__name__, str(self), id(self)) + + @staticmethod + def _convert_ops(inst): + if inst.droprevision: + return inst.values + return tuple(sorted(set((-1, 0, 1)).difference(inst.values))) + + def __eq__(self, other): + if self is other: + return True + if isinstance(other, self.__class__): + if (self.droprevision != other.droprevision or + self.version != other.version or + self.revision != other.revision): + return False + return self._convert_ops(self) == self._convert_ops(other) + + return False + + def __hash__(self): + return hash((self.droprevision, self.version, self.revision, + self.values))