#
# $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)
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"
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.
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"""
# 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
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()
print_help(with_description=False)
sys.exit(2)
-
# Parse global options
need_help = parse_global_options(global_opts, args)
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:
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
query_re = prepare_search_regex(queries)
- if not Config["piping"]:
+ if Config['verbose']:
pp.print_info(3, " * Searching for %s ... "
% (pp.regexpquery(",".join(queries))))
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
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
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
# 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))
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)
#
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
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
"includePortTree": False,
"includeOverlayTree": False,
"isRegex": False,
- "matchExact": True,
"onlyDirect": True,
- "onlyInstalled": True,
- "printMatchInfo": True,
+ "printMatchInfo": (not Config['quiet']),
"indentLevel": 0,
"depth": -1
}
"""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
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 + ")"
@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()
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)
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
"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
# =========
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)"
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"])), ") ]"])
"isRegex": False,
"matchExact": True,
"outputTree": False,
- "printMatchInfo": True,
+ "printMatchInfo": (not Config['quiet']),
"showType": False,
"showTimestamp": False,
"showMD5": False,
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
# =========
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))
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
QUERY_OPTS = {
"categoryFilter": None,
- "includeInstalled": False,
+ "includeInstalled": True,
"includePortTree": False,
"includeOverlayTree": False,
"includeMasked": 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")
))
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')
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()
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
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
QUERY_OPTS = {
"categoryFilter": None,
"duplicates": False,
- "includeInstalled": False,
+ "includeInstalled": True,
"includePortTree": False,
"includeOverlayTree": False,
"includeMasked": True,
"isRegex": False,
- "printMatchInfo": True
+ "printMatchInfo": (not Config['quiet'])
}
# =========
(" -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."""
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'):
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')
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:
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
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
# =======
if settings["PORTDIR_OVERLAY"]:
PORTDIR.extend(settings["PORTDIR_OVERLAY"].split())
-if not Config["piping"] and Config["verbosityLevel"] >= 3:
- VERBOSE = True
-else:
- VERBOSE = False
-
# =========
# Functions
# =========
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:
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):
# 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:
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)
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"""
call_get_functions(metadata_path, package_dir, QUERY_OPTS)
- first_run = False
\ No newline at end of file
+ first_run = False
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))
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):
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" %
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, " : <unknown>")
+ print " : <unknown>"
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("<unknown>"))
+ print twrap.fill("<unknown>")
+ else:
+ print markers[in_makeconf] + flag
def get_global_useflags():
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):
if not first_run:
print
- print_legend(query)
+ if Config['verbose']:
+ print " * Searching for %s ..." % pp.pkgquery(query)
matches = get_matches(query)
matches.sort()
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
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:
'GentoolkitInvalidPackageName',
'GentoolkitInvalidCPV',
'GentoolkitInvalidRegex',
+ 'GentoolkitInvalidVersion',
'GentoolkitNoMatches'
]
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):
# - getting GLSAs from http/ftp servers (not really useful without the fixed ebuilds)
# - GPG signing/verification (until key policy is clear)
-__author__ = "Marius Mauch <genone@gentoo.org>, Robert Buchholz <rbu@gentoo.org>"
+__author__ = "Marius Mauch <genone@gentoo.org>"
import os
import sys
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")
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
"""
Get all <li> elements for a given <ol> or <ul> node.
- @type listnode: ElementTree
+ @type listnode: xml.dom.Node
@param listnode: <ul> or <ol> list to get the elements for
@rtype: List of Strings
@return: a list that contains the value of the <li> elements
"""
- if not listnode.tag in ["ul", "ol"]:
+ if not listnode.nodeName in ["ul", "ol"]:
raise GlsaFormatException("Invalid function call: listnode is not <ul> or <ol>")
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):
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}
@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()
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
@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):
@type pkgname: String
@param pkgname: the name of the package for this atom
- @type versionNode: ElementTree
+ @type versionNode: xml.dom.Node
@param versionNode: a <vulnerable> or <unaffected> 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):
creates from the information in the I{versionNode} a
version string (format <op><version>).
- @type versionNode: ElementTree
+ @type versionNode: xml.dom.Node
@param versionNode: a <vulnerable> or <unaffected> 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"):
@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:
- # <revised>December 30, 2007: 02</revised> (old style)
- # <revised count="2">2007-12-30</revised> (new style)
- revisedEl = myroot.find("revised")
+ # <revised>December 30, 2007: 02</revised>
+ # <revised count="2">2007-12-30</revised>
+ 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"):
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"
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'
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
# =========
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])
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)
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 = []
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"]:
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):
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))
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.
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
-#! /usr/bin/python
+#!/usr/bin/python
#
# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
# Copyright(c) 2004-2009, Gentoo Foundation
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
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)
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"""
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"""
# 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
# 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
# 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.
">=dev-lang/python-2.4" and "dev-lang/python" but not
"<dev-lang/python-2.3"
- @type other: gentoolkit.package.Package
+ @type other: L{gentoolkit.package.Package}
@param other: other package to compare
@see: pkgcore.ebuild.atom.py
"""
if self.operator == '=':
if other.operator == '=*':
return self.fullversion.startswith(other.fullversion)
- return VersionMatch(frompkg=other).match(self)
+ return VersionMatch(other).match(self)
if other.operator == '=':
if self.operator == '=*':
return other.fullversion.startswith(self.fullversion)
- return VersionMatch(frompkg=self).match(other)
+ return VersionMatch(self).match(other)
# If we are both ~ matches we match if we are identical:
if self.operator == other.operator == '~':
# match the other's endpoint (just checking one endpoint
# is not enough, it would give a false positive on <=2 vs >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
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.
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
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,
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=<gentoolkit.package.Package> 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
--- /dev/null
+#! /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:
+ <gentoolkit.package.Package> 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))