summaryrefslogtreecommitdiff
path: root/pym
diff options
context:
space:
mode:
authorfuzzyray <fuzzyray@gentoo.org>2009-12-08 21:53:45 +0000
committerfuzzyray <fuzzyray@gentoo.org>2009-12-08 21:53:45 +0000
commitacdf616efa73b77936963eaa8b5c715db97646d2 (patch)
treed08ef2efee8b7edbf8c1df1a8de26439d6b42bd3 /pym
parent8a7c93709f74e29b81b5e7ad5310530d86cfe87c (diff)
downloadgentoolkit-acdf616efa73b77936963eaa8b5c715db97646d2.tar.gz
Merge rev 113 from djanderson's genscripts repo
svn path=/trunk/gentoolkit/; revision=703
Diffstat (limited to 'pym')
-rw-r--r--pym/gentoolkit/__init__.py43
-rw-r--r--pym/gentoolkit/atom.py195
-rw-r--r--pym/gentoolkit/cpv.py144
-rw-r--r--pym/gentoolkit/dbapi.py17
-rw-r--r--pym/gentoolkit/dependencies.py317
-rw-r--r--pym/gentoolkit/deprecated/helpers.py179
-rw-r--r--pym/gentoolkit/equery/__init__.py153
-rw-r--r--pym/gentoolkit/equery/belongs.py132
-rw-r--r--pym/gentoolkit/equery/changes.py236
-rw-r--r--pym/gentoolkit/equery/check.py323
-rw-r--r--pym/gentoolkit/equery/depends.py242
-rw-r--r--pym/gentoolkit/equery/depgraph.py198
-rw-r--r--pym/gentoolkit/equery/files.py90
-rw-r--r--pym/gentoolkit/equery/hasuse.py78
-rw-r--r--pym/gentoolkit/equery/list_.py89
-rw-r--r--pym/gentoolkit/equery/meta.py505
-rw-r--r--pym/gentoolkit/equery/size.py70
-rw-r--r--pym/gentoolkit/equery/uses.py145
-rw-r--r--pym/gentoolkit/equery/which.py14
-rw-r--r--pym/gentoolkit/errors.py110
-rw-r--r--pym/gentoolkit/glsa/__init__.py144
-rw-r--r--pym/gentoolkit/helpers.py843
-rw-r--r--pym/gentoolkit/metadata.py303
-rw-r--r--pym/gentoolkit/package.py666
-rw-r--r--pym/gentoolkit/pprinter.py181
-rw-r--r--pym/gentoolkit/test/__init__.py6
-rw-r--r--pym/gentoolkit/test/equery/__init__.py6
-rw-r--r--pym/gentoolkit/test/equery/test_init.py52
-rw-r--r--pym/gentoolkit/test/test_helpers.py107
-rw-r--r--pym/gentoolkit/test/test_syntax.py31
-rw-r--r--pym/gentoolkit/textwrap_.py108
-rw-r--r--pym/gentoolkit/versionmatch.py146
32 files changed, 3714 insertions, 2159 deletions
diff --git a/pym/gentoolkit/__init__.py b/pym/gentoolkit/__init__.py
index 35d89ac..03382b7 100644
--- a/pym/gentoolkit/__init__.py
+++ b/pym/gentoolkit/__init__.py
@@ -1,42 +1,23 @@
#!/usr/bin/python
#
# Copyright 2003-2004 Karl Trygve Kalleberg
-# Copyright 2003-2009 Gentoo Technologies, Inc.
+# Copyright 2003-2009 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#
# $Header$
-# =======
-# Imports
-# =======
+"""Gentoolkit is a collection of administration scripts for Gentoo"""
-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(object):
- def acquire(self):
- pass
- def release(self):
- pass
+import sys
-# =======
-# Globals
-# =======
-
-PORTDB = portage.db[portage.root]["porttree"].dbapi
-VARDB = portage.db[portage.root]["vartree"].dbapi
-VIRTUALS = portage.db[portage.root]["virtuals"]
-
-Config = {
- "verbosityLevel": 3
+CONFIG = {
+ # Color handling: -1: Use Portage settings, 0: Force off, 1: Force on
+ 'color': -1,
+ # Guess piping output:
+ 'piping': False if sys.stdout.isatty() else True,
+ # Set some defaults:
+ 'quiet': False,
+ 'debug': False
}
-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)
+# vim: set ts=8 sw=4 tw=79:
diff --git a/pym/gentoolkit/atom.py b/pym/gentoolkit/atom.py
new file mode 100644
index 0000000..ff150c0
--- /dev/null
+++ b/pym/gentoolkit/atom.py
@@ -0,0 +1,195 @@
+#!/usr/bin/python
+#
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2
+#
+# $Header$
+
+"""Subclasses portage.dep.Atom to provide methods on a Gentoo atom string."""
+
+__all__ = ('Atom',)
+
+# =======
+# Imports
+# =======
+
+import weakref
+
+import portage
+
+from gentoolkit.cpv import CPV
+from gentoolkit.versionmatch import VersionMatch
+from gentoolkit import errors
+
+# =======
+# Classes
+# =======
+
+class Atom(portage.dep.Atom, CPV):
+ """Portage's Atom class with an improved intersects method from pkgcore.
+
+ portage.dep.Atom provides the following instance variables:
+
+ @type operator: str
+ @ivar operator: one of ('=', '=*', '<', '>', '<=', '>=', '~', None)
+ @type cp: str
+ @ivar cp: cat/pkg
+ @type cpv: str
+ @ivar cpv: cat/pkg-ver (if ver)
+ @type slot: str or None
+ @ivar slot: slot passed in as cpv:#
+ """
+
+ # Necessary for Portage versions < 2.1.7
+ _atoms = weakref.WeakValueDictionary()
+
+ def __init__(self, atom):
+ self.atom = atom
+
+ try:
+ portage.dep.Atom.__init__(self, atom)
+ except portage.exception.InvalidAtom, err:
+ raise errors.GentoolkitInvalidAtom(err)
+
+ # Make operator compatible with intersects
+ if self.operator is None:
+ self.operator = '='
+
+ self.cpv = CPV(self.cpv)
+
+ # use_conditional is USE flag condition for this Atom to be required:
+ # For: !build? ( >=sys-apps/sed-4.0.5 ), use_conditional = '!build'
+ self.use_conditional = None
+
+ def __repr__(self):
+ uc = self.use_conditional
+ uc = "%s? " % uc if uc is not None else ''
+ return "<%s %r>" % (self.__class__.__name__, "%s%s" % (uc, self.atom))
+
+ def __setattr__(self, name, value):
+ object.__setattr__(self, name, value)
+
+ #R0911:121:Atom.intersects: Too many return statements (20/6)
+ #R0912:121:Atom.intersects: Too many branches (23/12)
+ # pylint: disable-msg=R0911,R0912
+ def intersects(self, other):
+ """Check if a passed in package atom "intersects" this atom.
+
+ Lifted from pkgcore.
+
+ Two atoms "intersect" if a package can be constructed that
+ matches both:
+ - if you query for just "dev-lang/python" it "intersects" both
+ "dev-lang/python" and ">=dev-lang/python-2.4"
+ - if you query for "=dev-lang/python-2.4" it "intersects"
+ ">=dev-lang/python-2.4" and "dev-lang/python" but not
+ "<dev-lang/python-2.3"
+
+ @type other: Any "Intersectable" object
+ @param other: other package to compare
+ @see: L{pkgcore.ebuild.atom}
+ """
+ # Our "cp" (cat/pkg) must match exactly:
+ if self.cpv.cp != other.cpv.cp:
+ # Check to see if one is name only:
+ # Avoid slow partitioning if we're definitely not matching
+ # (yes, this is hackish, but it's faster):
+ if self.cpv.cp[-1:] != other.cpv.cp[-1:]:
+ return False
+
+ if ((not self.cpv.category and self.cpv.name == other.cpv.name) or
+ (not other.cpv.category and other.cpv.name == self.cpv.name)):
+ return True
+ return False
+
+ # If one of us is unversioned we intersect:
+ if not self.operator or not other.operator:
+ return True
+
+ # If we are both "unbounded" in the same direction we intersect:
+ if (('<' in self.operator and '<' in other.operator) or
+ ('>' in self.operator and '>' in other.operator)):
+ return True
+
+ # If one of us is an exact match we intersect if the other matches it:
+ if self.operator == '=':
+ if other.operator == '=*':
+ return self.cpv.fullversion.startswith(other.cpv.fullversion)
+ return VersionMatch(other.cpv, op=other.operator).match(self.cpv)
+ if other.operator == '=':
+ if self.operator == '=*':
+ return other.cpv.fullversion.startswith(self.cpv.fullversion)
+ return VersionMatch(self.cpv, op=self.operator).match(other.cpv)
+
+ # If we are both ~ matches we match if we are identical:
+ if self.operator == other.operator == '~':
+ return (self.cpv.version == other.cpv.version and
+ self.cpv.revision == other.cpv.revision)
+
+ # If we are both glob matches we match if one of us matches the other.
+ if self.operator == other.operator == '=*':
+ return (self.cpv.fullversion.startswith(other.cpv.fullversion) or
+ other.cpv.fullversion.startswith(self.cpv.fullversion))
+
+ # If one of us is a glob match and the other a ~ we match if the glob
+ # matches the ~ (ignoring a revision on the glob):
+ if self.operator == '=*' and other.operator == '~':
+ return other.cpv.fullversion.startswith(self.cpv.version)
+ if other.operator == '=*' and self.operator == '~':
+ return self.cpv.fullversion.startswith(other.cpv.version)
+
+ # If we get here at least one of us is a <, <=, > or >=:
+ if self.operator in ('<', '<=', '>', '>='):
+ ranged, other = self, other
+ ranged.operator = self.operator
+ else:
+ ranged, other = other, self
+ ranged.operator = other.operator
+
+ if '<' in other.operator or '>' in other.operator:
+ # We are both ranged, and in the opposite "direction" (or
+ # we would have matched above). We intersect if we both
+ # match the other's endpoint (just checking one endpoint
+ # is not enough, it would give a false positive on <=2 vs >2)
+ return (VersionMatch(other.cpv, op=other.operator).match(ranged) and
+ VersionMatch(ranged.cpv, op=ranged.operator).match(other.cpv))
+
+ if other.operator == '~':
+ # Other definitely matches its own version. If ranged also
+ # does we're done:
+ if VersionMatch(ranged.cpv, op=ranged.operator).match(other.cpv):
+ return True
+ # The only other case where we intersect is if ranged is a
+ # > or >= on other's version and a nonzero revision. In
+ # that case other will match ranged. Be careful not to
+ # give a false positive for ~2 vs <2 here:
+ return (ranged.operator in ('>', '>=') and
+ VersionMatch(other.cpv, op=other.operator).match(ranged.cpv))
+
+ if other.operator == '=*':
+ # a glob match definitely matches its own version, so if
+ # ranged does too we're done:
+ if VersionMatch(ranged.cpv, op=ranged.operator).match(other.cpv):
+ return True
+ if '<' in ranged.operator:
+ # If other.revision is not defined then other does not
+ # match anything smaller than its own fullversion:
+ if not other.cpv.revision:
+ return False
+
+ # If other.revision is defined then we can always
+ # construct a package smaller than other.fullversion by
+ # tagging e.g. an _alpha1 on.
+ return ranged.cpv.fullversion.startswith(other.cpv.version)
+ else:
+ # Remaining cases where this intersects: there is a
+ # package greater than ranged.fullversion and
+ # other.fullversion that they both match.
+ return ranged.cpv.fullversion.startswith(other.cpv.version)
+
+ # Handled all possible ops.
+ raise NotImplementedError(
+ 'Someone added an operator without adding it to intersects')
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/cpv.py b/pym/gentoolkit/cpv.py
new file mode 100644
index 0000000..7dc54e5
--- /dev/null
+++ b/pym/gentoolkit/cpv.py
@@ -0,0 +1,144 @@
+#!/usr/bin/python
+#
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2
+#
+# $Header$
+
+"""Provides attributes and methods for a category/package-version string."""
+
+__all__ = ('CPV',)
+
+# =======
+# Imports
+# =======
+
+from portage.versions import catpkgsplit, vercmp
+
+from gentoolkit import errors
+
+# =======
+# Classes
+# =======
+
+class CPV(object):
+ """Provides methods on a category/package-version string.
+
+ Will also correctly split just a package or package-version string.
+
+ Example usage:
+ >>> from gentoolkit.cpv import CPV
+ >>> cpv = CPV('sys-apps/portage-2.2-r1')
+ >>> cpv.category, cpv.name, cpv.fullversion
+ ('sys-apps', 'portage', '2.2-r1')
+ >>> str(cpv)
+ 'sys-apps/portage-2.2-r1'
+ >>> # An 'rc' (release candidate) version is less than non 'rc' version:
+ ... CPV('sys-apps/portage-2') > CPV('sys-apps/portage-2_rc10')
+ """
+
+ def __init__(self, cpv):
+ if not cpv:
+ raise errors.GentoolkitInvalidCPV(cpv)
+ self.scpv = cpv
+
+ values = split_cpv(cpv)
+ self.category = values[0]
+ self.name = values[1]
+ self.version = values[2]
+ self.revision = values[3]
+ del values
+
+ if not self.name:
+ raise errors.GentoolkitInvalidCPV(cpv)
+
+ sep = '/' if self.category else ''
+ self.cp = sep.join((self.category, self.name))
+
+ sep = '-' if self.revision else ''
+ self.fullversion = sep.join((self.version, self.revision))
+ del sep
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ raise TypeError("other isn't of %s type, is %s" % (
+ self.__class__, other.__class__)
+ )
+ return self.scpv == other.scpv
+
+ def __ne__(self, other):
+ if not isinstance(other, self.__class__):
+ raise TypeError("other isn't of %s type, is %s" % (
+ self.__class__, other.__class__)
+ )
+ return not self == 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 self.category < other.category
+ elif 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 vercmp(self.fullversion, other.fullversion)
+ result = cmp(vercmp(self.fullversion, other.fullversion), 0)
+ if result == -1:
+ return True
+ else:
+ return False
+
+ def __gt__(self, other):
+ if not isinstance(other, self.__class__):
+ raise TypeError("other isn't of %s type, is %s" % (
+ self.__class__, other.__class__)
+ )
+ return not self < other and not self == other
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, str(self))
+
+ def __str__(self):
+ return self.scpv
+
+
+# =========
+# Functions
+# =========
+
+def split_cpv(cpv):
+ """Split a cpv into category, name, version and revision.
+
+ Inlined from helpers because of circular imports.
+
+ @type cpv: str
+ @param cpv: pkg, cat/pkg, pkg-ver, cat/pkg-ver, atom or regex
+ @rtype: tuple
+ @return: (category, pkg_name, version, revision)
+ Each tuple element is a string or empty string ("").
+ """
+
+ result = catpkgsplit(cpv)
+
+ if result:
+ result = list(result)
+ if result[0] == 'null':
+ result[0] = ''
+ if result[3] == 'r0':
+ result[3] = ''
+ else:
+ result = cpv.split("/")
+ if len(result) == 1:
+ result = ['', cpv, '', '']
+ else:
+ result = result + ['', '']
+
+ return tuple(result)
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/dbapi.py b/pym/gentoolkit/dbapi.py
new file mode 100644
index 0000000..2866214
--- /dev/null
+++ b/pym/gentoolkit/dbapi.py
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+#
+# Copyright 2009 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# $Header$
+
+"""Provides access to Portage database api"""
+
+import portage
+
+#bindb = portage.db[portage.root]["bintree"].dbapi
+PORTDB = portage.db[portage.root]["porttree"].dbapi
+VARDB = portage.db[portage.root]["vartree"].dbapi
+#virtuals = portage.db[portage.root]["virtuals"]
+
+# vim: set ts=8 sw=4 tw=79:
diff --git a/pym/gentoolkit/dependencies.py b/pym/gentoolkit/dependencies.py
new file mode 100644
index 0000000..632ca1e
--- /dev/null
+++ b/pym/gentoolkit/dependencies.py
@@ -0,0 +1,317 @@
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2
+#
+# $Header: $
+
+"""Provides a class for easy calculating dependencies for a given CPV."""
+
+__docformat__ = 'epytext'
+__all__ = ('Dependencies',)
+
+# =======
+# Imports
+# =======
+
+import portage
+from portage.dep import paren_reduce
+
+from gentoolkit import errors
+from gentoolkit.atom import Atom
+from gentoolkit.cpv import CPV
+from gentoolkit.helpers import find_best_match, uniqify
+from gentoolkit.dbapi import PORTDB, VARDB
+
+# =======
+# Classes
+# =======
+
+class Dependencies(CPV):
+ """Access a package's dependencies and reverse dependencies.
+
+ Example usage:
+ >>> from gentoolkit.dependencies import Dependencies
+ >>> portage = Dependencies('sys-apps/portage-2.1.6.13')
+ >>> portage
+ <Dependencies 'sys-apps/portage-2.1.6.13'>
+ >>> # All methods return gentoolkit.atom.Atom instances
+ ... portage.get_depend()
+ [<Atom '>=dev-lang/python-2.5'>, <Atom '<dev-lang/python-3.0'>, ...]
+
+ """
+ def __init__(self, cpv, op='', parser=None):
+ if isinstance(cpv, CPV):
+ self.cpv = cpv
+ else:
+ self.cpv = CPV(cpv)
+
+ self.operator = op
+ self.atom = self.operator + str(self.cpv)
+ self.use = []
+ self.depatom = str()
+
+ # Allow a custom parser function:
+ self.parser = parser if parser else self._parser
+
+ def __eq__(self, other):
+ if self.atom != other.atom:
+ return False
+ else:
+ return True
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash((self.atom, self.depatom, tuple(self.use)))
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.atom)
+
+ def get_env_vars(self, envvars):
+ """Returns predefined env vars DEPEND, SRC_URI, etc."""
+
+ # Try to use the Portage tree first, since emerge only uses the tree
+ # when calculating dependencies
+ try:
+ result = PORTDB.aux_get(str(self.cpv), envvars)
+ except KeyError:
+ result = VARDB.aux_get(str(self.cpv), envvars)
+ return result
+
+ def get_depend(self):
+ """Get the contents of DEPEND and parse it with self.parser."""
+
+ try:
+ return self.parser(self.get_env_vars(('DEPEND',))[0])
+ except portage.exception.InvalidPackageName, err:
+ raise errors.GentoolkitInvalidCPV(err)
+
+ def get_pdepend(self):
+ """Get the contents of PDEPEND and parse it with self.parser."""
+
+ try:
+ return self.parser(self.get_env_vars(('PDEPEND',))[0])
+ except portage.exception.InvalidPackageName, err:
+ raise errors.GentoolkitInvalidCPV(err)
+
+ def get_rdepend(self):
+ """Get the contents of RDEPEND and parse it with self.parser."""
+
+ try:
+ return self.parser(self.get_env_vars(('RDEPEND',))[0])
+ except portage.exception.InvalidPackageName, err:
+ raise errors.GentoolkitInvalidCPV(err)
+
+ def get_all_depends(self):
+ """Get the contents of ?DEPEND and parse it with self.parser."""
+
+ env_vars = ('DEPEND', 'PDEPEND', 'RDEPEND')
+ try:
+ return self.parser(' '.join(self.get_env_vars(env_vars)))
+ except portage.exception.InvalidPackageName, err:
+ raise errors.GentoolkitInvalidCPV(err)
+
+ def graph_depends(
+ self,
+ max_depth=1,
+ printer_fn=None,
+ # The rest of these are only used internally:
+ depth=0,
+ seen=None,
+ depcache=None,
+ result=None
+ ):
+ """Graph direct dependencies for self.
+
+ Optionally gather indirect dependencies.
+
+ @type max_depth: int
+ @param max_depth: Maximum depth to recurse if.
+ <1 means no maximum depth
+ >0 means recurse only this depth;
+ @type printer_fn: callable
+ @param printer_fn: If None, no effect. If set, it will be applied to
+ each result.
+ @rtype: list
+ @return: [(depth, pkg), ...]
+ """
+ if seen is None:
+ seen = set()
+ if depcache is None:
+ depcache = dict()
+ if result is None:
+ result = list()
+
+ pkgdep = None
+ deps = self.get_all_depends()
+ for dep in deps:
+ if dep.atom in depcache:
+ continue
+ try:
+ pkgdep = depcache[dep.atom]
+ except KeyError:
+ pkgdep = find_best_match(dep.atom)
+ depcache[dep.atom] = pkgdep
+ if pkgdep and str(pkgdep.cpv) in seen:
+ continue
+ if depth < max_depth or max_depth <= 0:
+
+ if printer_fn is not None:
+ printer_fn(depth, pkgdep, dep)
+ if not pkgdep:
+ continue
+
+ seen.add(str(pkgdep.cpv))
+ result.append((
+ depth,
+ pkgdep.deps.graph_depends(
+ max_depth=max_depth,
+ printer_fn=printer_fn,
+ # The rest of these are only used internally:
+ depth=depth+1,
+ seen=seen,
+ depcache=depcache,
+ result=result
+ )
+ ))
+
+ if depth == 0:
+ return result
+ return pkgdep
+
+ def graph_reverse_depends(
+ self,
+ pkgset=None,
+ max_depth=-1,
+ only_direct=True,
+ printer_fn=None,
+ # The rest of these are only used internally:
+ depth=0,
+ depcache=None,
+ seen=None,
+ result=None
+ ):
+ """Graph direct reverse dependencies for self.
+
+ Example usage:
+ >>> from gentoolkit.dependencies import Dependencies
+ >>> ffmpeg = Dependencies('media-video/ffmpeg-0.5_p20373')
+ >>> # I only care about installed packages that depend on me:
+ ... from gentoolkit.helpers import get_installed_cpvs
+ >>> # I want to pass in a sorted list. We can pass strings or
+ ... # Package or Atom types, so I'll use Package to sort:
+ ... from gentoolkit.package import Package
+ >>> installed = sorted(Package(x) for x in get_installed_cpvs())
+ >>> deptree = ffmpeg.graph_reverse_depends(
+ ... only_direct=False, # Include indirect revdeps
+ ... pkgset=installed) # from installed pkgset
+ >>> len(deptree)
+ 44
+
+ @type pkgset: iterable
+ @param pkgset: sorted pkg cpv strings or any 'intersectable' objects to
+ use for calculate our revdep graph.
+ @type max_depth: int
+ @param max_depth: Maximum depth to recurse if only_direct=False.
+ -1 means no maximum depth;
+ 0 is the same as only_direct=True;
+ >0 means recurse only this many times;
+ @type only_direct: bool
+ @param only_direct: to recurse or not to recurse
+ @type printer_fn: callable
+ @param printer_fn: If None, no effect. If set, it will be applied to
+ each L{gentoolkit.atom.Atom} object as it is added to
+ the results.
+ @rtype: list
+ @return: L{gentoolkit.dependencies.Dependencies} objects
+ """
+ if not pkgset:
+ err = ("%s kwarg 'pkgset' must be set. "
+ "Can be list of cpv strings or any 'intersectable' object.")
+ raise errors.GentoolkitFatalError(err % (self.__class__.__name__,))
+
+ if depcache is None:
+ depcache = dict()
+ if seen is None:
+ seen = set()
+ if result is None:
+ result = list()
+
+ if depth == 0:
+ pkgset = tuple(Dependencies(x) for x in pkgset)
+
+ pkgdep = None
+ for pkgdep in pkgset:
+ try:
+ all_depends = depcache[pkgdep]
+ except KeyError:
+ all_depends = uniqify(pkgdep.get_all_depends())
+ depcache[pkgdep] = all_depends
+
+ dep_is_displayed = False
+ for dep in all_depends:
+ # TODO: Add ability to determine if dep is enabled by USE flag.
+ # Check portage.dep.use_reduce
+ if dep.intersects(self):
+ pkgdep.depth = depth
+ pkgdep.matching_dep = dep
+ if printer_fn is not None:
+ printer_fn(pkgdep, dep_is_displayed=dep_is_displayed)
+ result.append(pkgdep)
+ dep_is_displayed = True
+
+ # if --indirect specified, call ourselves again with the dep
+ # Do not call if we have already called ourselves.
+ if (
+ dep_is_displayed and not only_direct and
+ str(pkgdep.cpv) not in seen and
+ (depth < max_depth or max_depth == -1)
+ ):
+
+ seen.add(str(pkgdep.cpv))
+ result.append(
+ pkgdep.graph_reverse_depends(
+ pkgset=pkgset,
+ max_depth=max_depth,
+ only_direct=only_direct,
+ printer_fn=printer_fn,
+ depth=depth+1,
+ depcache=depcache,
+ seen=seen,
+ result=result
+ )
+ )
+
+ if depth == 0:
+ return result
+ return pkgdep
+
+ def _parser(self, deps, use_conditional=None, depth=0):
+ """?DEPEND file parser.
+
+ @rtype: list
+ @return: L{gentoolkit.atom.Atom} objects
+ """
+ result = []
+
+ if depth == 0:
+ deps = paren_reduce(deps)
+ for tok in deps:
+ if tok == '||':
+ continue
+ if tok[-1] == '?':
+ use_conditional = tok[:-1]
+ continue
+ if isinstance(tok, list):
+ asdf = self._parser(tok, use_conditional, depth=depth+1)
+ result.extend(asdf)
+ continue
+ atom = Atom(tok)
+ if use_conditional is not None:
+ atom.use_conditional = use_conditional
+ result.append(atom)
+
+ return result
+
+# vim: set ts=4 sw=4 tw=0:
diff --git a/pym/gentoolkit/deprecated/helpers.py b/pym/gentoolkit/deprecated/helpers.py
new file mode 100644
index 0000000..df158f2
--- /dev/null
+++ b/pym/gentoolkit/deprecated/helpers.py
@@ -0,0 +1,179 @@
+#!/usr/bin/python2
+#
+# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2
+#
+# $Header$
+
+"""The functions in this module have been deprecated and are not even
+guaranteed to work. Improved functions can be found in helpers2.py"""
+
+import warnings
+
+import portage
+from gentoolkit import *
+from package import *
+from pprinter import warn
+try:
+ from portage.util import unique_array
+except ImportError:
+ from portage_util import unique_array
+
+def find_packages(search_key, masked=False):
+ """Returns a list of Package objects that matched the search key."""
+ warnings.warn("Deprecated. Use helpers2.find_packages.", DeprecationWarning)
+ try:
+ if masked:
+ t = portage.db["/"]["porttree"].dbapi.xmatch("match-all", search_key)
+ t += portage.db["/"]["vartree"].dbapi.match(search_key)
+ else:
+ t = portage.db["/"]["porttree"].dbapi.match(search_key)
+ t += portage.db["/"]["vartree"].dbapi.match(search_key)
+ # catch the "amgigous package" Exception
+ except ValueError, e:
+ if isinstance(e[0],list):
+ t = []
+ for cp in e[0]:
+ if masked:
+ t += portage.db["/"]["porttree"].dbapi.xmatch("match-all", cp)
+ t += portage.db["/"]["vartree"].dbapi.match(cp)
+ else:
+ t += portage.db["/"]["porttree"].dbapi.match(cp)
+ t += portage.db["/"]["vartree"].dbapi.match(cp)
+ else:
+ raise ValueError(e)
+ except portage_exception.InvalidAtom, e:
+ print warn("Invalid Atom: '%s'" % str(e))
+ return []
+ # Make the list of packages unique
+ t = unique_array(t)
+ t.sort()
+ return [Package(x) for x in t]
+
+def find_installed_packages(search_key, masked=False):
+ """Returns a list of Package objects that matched the search key."""
+ warnings.warn("Deprecated. Use helpers2.find_installed_packages.",
+ DeprecationWarning)
+ try:
+ t = portage.db["/"]["vartree"].dbapi.match(search_key)
+ # catch the "amgigous package" Exception
+ except ValueError, e:
+ if isinstance(e[0],list):
+ t = []
+ for cp in e[0]:
+ t += portage.db["/"]["vartree"].dbapi.match(cp)
+ else:
+ raise ValueError(e)
+ except portage_exception.InvalidAtom, e:
+ print warn("Invalid Atom: '%s'" % str(e))
+ return []
+ return [Package(x) for x in t]
+
+def find_best_match(search_key):
+ """Returns a Package object for the best available candidate that
+ matched the search key."""
+ warnings.warn("Deprecated. Use helpers2.find_best_match.",
+ DeprecationWarning)
+ t = portage.db["/"]["porttree"].dep_bestmatch(search_key)
+ if t:
+ return Package(t)
+ return None
+
+def find_system_packages(prefilter=None):
+ """Returns a tuple of lists, first list is resolved system packages,
+ second is a list of unresolved packages."""
+ pkglist = settings.packages
+ resolved = []
+ unresolved = []
+ for x in pkglist:
+ cpv = x.strip()
+ if len(cpv) and cpv[0] == "*":
+ pkg = find_best_match(cpv)
+ if pkg:
+ resolved.append(pkg)
+ else:
+ unresolved.append(cpv)
+ return (resolved, unresolved)
+
+def find_world_packages(prefilter=None):
+ """Returns a tuple of lists, first list is resolved world packages,
+ seond is unresolved package names."""
+ f = open(portage.root+portage.WORLD_FILE)
+ pkglist = f.readlines()
+ resolved = []
+ unresolved = []
+ for x in pkglist:
+ cpv = x.strip()
+ if len(cpv) and cpv[0] != "#":
+ pkg = find_best_match(cpv)
+ if pkg:
+ resolved.append(pkg)
+ else:
+ unresolved.append(cpv)
+ return (resolved,unresolved)
+
+def find_all_installed_packages(prefilter=None):
+ """Returns a list of all installed packages, after applying the prefilter
+ function"""
+ warnings.warn("Deprecated. Use helpers2.get_installed_cpvs.",
+ DeprecationWarning)
+ t = vartree.dbapi.cpv_all()
+ if prefilter:
+ t = filter(prefilter,t)
+ return [Package(x) for x in t]
+
+def find_all_uninstalled_packages(prefilter=None):
+ """Returns a list of all uninstalled packages, after applying the prefilter
+ function"""
+ warnings.warn("Deprecated. Use helpers2.get_uninstalled_cpvs.",
+ DeprecationWarning)
+ alist = find_all_packages(prefilter)
+ return [x for x in alist if not x.is_installed()]
+
+def find_all_packages(prefilter=None):
+ """Returns a list of all known packages, installed or not, after applying
+ the prefilter function"""
+ warnings.warn("Deprecated. Use helpers2.get_cpvs.", DeprecationWarning)
+ t = porttree.dbapi.cp_all()
+ t += vartree.dbapi.cp_all()
+ if prefilter:
+ t = filter(prefilter,t)
+ t = unique_array(t)
+ t2 = []
+ for x in t:
+ t2 += porttree.dbapi.cp_list(x)
+ t2 += vartree.dbapi.cp_list(x)
+ t2 = unique_array(t2)
+ return [Package(x) for x in t2]
+
+def split_package_name(name):
+ """Returns a list on the form [category, name, version, revision]. Revision will
+ be 'r0' if none can be inferred. Category and version will be empty, if none can
+ be inferred."""
+ warnings.warn("Deprecated. Just use portage.catpkgsplit or apply "
+ "gentoolkit.package.Package to access pkg.category, pkg.revision, etc.",
+ DeprecationWarning)
+ r = portage.catpkgsplit(name)
+ if not r:
+ r = name.split("/")
+ if len(r) == 1:
+ return ["", name, "", "r0"]
+ else:
+ return r + ["", "r0"]
+ else:
+ r = list(r)
+ if r[0] == 'null':
+ r[0] = ''
+ return r
+
+# 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/equery/__init__.py b/pym/gentoolkit/equery/__init__.py
index c205938..13ff6ba 100644
--- a/pym/gentoolkit/equery/__init__.py
+++ b/pym/gentoolkit/equery/__init__.py
@@ -15,27 +15,51 @@ __all__ = (
'mod_usage'
)
__docformat__ = 'epytext'
+# version is dynamically set by distutils sdist
+__version__ = "svn"
# =======
# Imports
# =======
import errno
+import os
import sys
import time
from getopt import getopt, GetoptError
-from portage import exception
+import portage
import gentoolkit
-import gentoolkit.pprinter as pp
-from gentoolkit import settings, Config
+from gentoolkit import CONFIG
+from gentoolkit import errors
+from gentoolkit import pprinter as pp
from gentoolkit.textwrap_ import TextWrapper
__productname__ = "equery"
-__authors__ = """\
-Karl Trygve Kalleberg - Original author
-Douglas Anderson - Modular redesign; author of meta, changes"""
+__authors__ = (
+ 'Karl Trygve Kalleberg - Original author',
+ 'Douglas Anderson - Modular redesign; author of meta, changes'
+)
+
+# =======
+# Globals
+# =======
+
+NAME_MAP = {
+ 'b': 'belongs',
+ 'c': 'changes',
+ 'k': 'check',
+ 'd': 'depends',
+ 'g': 'depgraph',
+ 'f': 'files',
+ 'h': 'hasuse',
+ 'l': 'list_',
+ 'm': 'meta',
+ 's': 'size',
+ 'u': 'uses',
+ 'w': 'which'
+}
# =========
# Functions
@@ -48,7 +72,7 @@ def print_help(with_description=True):
"""
if with_description:
- print __doc__
+ print __doc__
print main_usage()
print
print pp.globaloption("global options")
@@ -78,30 +102,15 @@ def print_help(with_description=True):
def expand_module_name(module_name):
- """Returns one of the values of name_map or raises KeyError"""
-
- name_map = {
- 'b': 'belongs',
- 'c': 'changes',
- 'k': 'check',
- 'd': 'depends',
- 'g': 'depgraph',
- 'f': 'files',
- 'h': 'hasuse',
- 'l': 'list_',
- 'm': 'meta',
- 's': 'size',
- 'u': 'uses',
- 'w': 'which'
- }
+ """Returns one of the values of NAME_MAP or raises KeyError"""
if module_name == 'list':
# list is a Python builtin type, so we must rename our module
return 'list_'
- elif module_name in name_map.values():
+ elif module_name in NAME_MAP.values():
return module_name
else:
- return name_map[module_name]
+ return NAME_MAP[module_name]
def format_options(options):
@@ -114,21 +123,21 @@ def format_options(options):
"""
result = []
- twrap = TextWrapper(width=Config['termWidth'])
+ twrap = TextWrapper(width=CONFIG['termWidth'])
opts = (x[0] for x in options)
descs = (x[1] for x in options)
for opt, desc in zip(opts, descs):
twrap.initial_indent = pp.emph(opt.ljust(25))
twrap.subsequent_indent = " " * 25
result.append(twrap.fill(desc))
-
+
return '\n'.join(result)
def format_filetype(path, fdesc, show_type=False, show_md5=False,
show_timestamp=False):
"""Format a path for printing.
-
+
@type path: str
@param path: the path
@type fdesc: list
@@ -141,7 +150,7 @@ def format_filetype(path, fdesc, show_type=False, show_md5=False,
@type show_md5: bool
@param show_md5: if True, append MD5 sum to the formatted string
@type show_timestamp: bool
- @param show_timestamp: if True, append time-of-creation after pathname
+ @param show_timestamp: if True, append time-of-creation after pathname
@rtype: str
@return: formatted pathname with optional added information
"""
@@ -160,7 +169,7 @@ def format_filetype(path, fdesc, show_type=False, show_md5=False,
ftype = "sym"
stamp = format_timestamp(fdesc[1])
tgt = fdesc[2].split()[0]
- if Config["piping"]:
+ if CONFIG["piping"]:
fpath = path
else:
fpath = pp.path_symlink(path + " -> " + tgt)
@@ -168,7 +177,9 @@ def format_filetype(path, fdesc, show_type=False, show_md5=False,
ftype = "dev"
fpath = path
else:
- pp.print_error("%s has unknown type: %s" % (path, fdesc[0]))
+ sys.stderr.write(
+ pp.error("%s has unknown type: %s" % (path, fdesc[0]))
+ )
result = ""
if show_type:
@@ -198,24 +209,14 @@ def initialize_configuration():
term_width = 80
# Terminal size, minus a 1-char margin for text wrapping
- Config['termWidth'] = term_width - 1
-
- # Color handling: -1: Use Portage settings, 0: Force off, 1: Force on
- Config['color'] = -1
-
- Config['quiet'] = False
+ CONFIG['termWidth'] = term_width - 1
# Guess color output
- if (Config['color'] == -1 and (not sys.stdout.isatty() or
- settings["NOCOLOR"] in ("yes", "true")) or
- Config['color'] == 0):
+ if (CONFIG['color'] == -1 and (not sys.stdout.isatty() or
+ os.getenv("NOCOLOR") in ("yes", "true")) or CONFIG['color'] == 0):
pp.output.nocolor()
- # Guess piping output
- if not sys.stdout.isatty():
- Config["piping"] = True
- else:
- Config["piping"] = False
+ CONFIG['verbose'] = not CONFIG['piping']
def main_usage():
@@ -232,7 +233,7 @@ def main_usage():
def mod_usage(mod_name="module", arg="pkgspec", optional=False):
"""Provide a consistant usage message to the calling module.
-
+
@type arg: string
@param arg: what kind of argument the module takes (pkgspec, filename, etc)
@type optional: bool
@@ -262,88 +263,88 @@ def parse_global_options(global_opts, args):
print_help()
sys.exit(0)
elif opt in ('-q','--quiet'):
- Config["quiet"] = True
+ CONFIG['quiet'] = True
elif opt in ('-C', '--no-color', '--nocolor'):
- Config['color'] = 0
+ CONFIG['color'] = 0
pp.output.nocolor()
elif opt in ('-N', '--no-pipe'):
- Config["piping"] = False
+ CONFIG['piping'] = False
elif opt in ('-V', '--version'):
print_version()
sys.exit(0)
-
+ elif opt in ('--debug'):
+ CONFIG['debug'] = True
+
return need_help
-
+
def print_version():
"""Print the version of this tool to the console."""
- try:
- with open('/usr/share/gentoolkit/VERSION') as gentoolkit_version:
- version = gentoolkit_version.read().strip()
- except IOError, err:
- pp.die(2, str(err))
-
print "%(product)s (%(version)s) - %(docstring)s" % {
"product": pp.productname(__productname__),
- "version": version,
+ "version": __version__,
"docstring": __doc__
}
def split_arguments(args):
"""Separate module name from module arguments"""
-
+
return args.pop(0), args
def main():
"""Parse input and run the program."""
- initialize_configuration()
-
short_opts = "hqCNV"
- long_opts = ('help', 'quiet', 'nocolor', 'no-color', 'no-pipe', 'version')
+ long_opts = (
+ 'help', 'quiet', 'nocolor', 'no-color', 'no-pipe', 'version', 'debug'
+ )
+
+ initialize_configuration()
try:
global_opts, args = getopt(sys.argv[1:], short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Global %s" % err)
+ sys.stderr.write(pp.error("Global %s" % err))
print_help(with_description=False)
sys.exit(2)
# Parse global options
need_help = parse_global_options(global_opts, args)
+ # FIXME: There are a few places that make use of both quiet and verbose.
+ # Consider combining.
+ if CONFIG['quiet']:
+ CONFIG['verbose'] = False
+
try:
module_name, module_args = split_arguments(args)
except IndexError:
print_help()
sys.exit(2)
-
+
if need_help:
module_args.append('--help')
- if Config['piping'] or Config['quiet']:
- Config['verbose'] = False
- else:
- Config['verbose'] = True
-
try:
expanded_module_name = expand_module_name(module_name)
except KeyError:
- pp.print_error("Unknown module '%s'" % module_name)
+ sys.stderr.write(pp.error("Unknown module '%s'" % module_name))
print_help(with_description=False)
sys.exit(2)
try:
- loaded_module = __import__(expanded_module_name, globals(),
- locals(), [], -1)
+ loaded_module = __import__(
+ expanded_module_name, globals(), locals(), [], -1
+ )
loaded_module.main(module_args)
- except exception.AmbiguousPackageName, err:
- pp.print_error("Ambiguous package name. Use one of: ")
- while err[0]:
- print " " + err[0].pop()
+ except portage.exception.AmbiguousPackageName, err:
+ raise errors.GentoolkitAmbiguousPackage(err)
except IOError, err:
if err.errno != errno.EPIPE:
raise
+
+if __name__ == '__main__':
+ main()
diff --git a/pym/gentoolkit/equery/belongs.py b/pym/gentoolkit/equery/belongs.py
index d4da36f..e1367cf 100644
--- a/pym/gentoolkit/equery/belongs.py
+++ b/pym/gentoolkit/equery/belongs.py
@@ -16,15 +16,13 @@ __docformat__ = 'epytext'
# Imports
# =======
-import re
import sys
from getopt import gnu_getopt, GetoptError
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_filetype, format_options, mod_usage, \
- Config
-from gentoolkit.helpers2 import get_installed_cpvs
-from gentoolkit.package import Package
+from gentoolkit.equery import (format_filetype, format_options, mod_usage,
+ CONFIG)
+from gentoolkit.helpers import FileOwner
# =======
# Globals
@@ -36,27 +34,60 @@ QUERY_OPTS = {
"nameOnly": False
}
+# =======
+# Classes
+# =======
+
+class BelongsPrinter(object):
+ """Outputs a formatted list of packages that claim to own a files."""
+
+ def __init__(self, verbose=True, name_only=False):
+ if verbose:
+ self.print_fn = self.print_verbose
+ else:
+ self.print_fn = self.print_quiet
+
+ self.name_only = name_only
+
+ def __call__(self, pkg, cfile):
+ self.print_fn(pkg, cfile)
+
+ # W0613: *Unused argument %r*
+ # pylint: disable-msg=W0613
+ def print_quiet(self, pkg, cfile):
+ "Format for minimal output."
+ if self.name_only:
+ name = pkg.cpv.cp
+ else:
+ name = str(pkg.cpv)
+ print name
+
+ def print_verbose(self, pkg, cfile):
+ "Format for full output."
+ file_str = pp.path(format_filetype(cfile, pkg.get_contents()[cfile]))
+ if self.name_only:
+ name = pkg.cpv.cp
+ else:
+ name = str(pkg.cpv)
+ print pp.cpv(name), "(" + file_str + ")"
+
+
# =========
# Functions
# =========
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
for opt in opts:
if opt in ('-h','--help'):
print_help()
sys.exit(0)
- elif opt in ('-c', '--category'):
- # Remove this warning after a reasonable amount of time
- # (djanderson, 2/2009)
- pp.print_warn("Module option -c, --category not implemented")
- print
elif opt in ('-e', '--early-out', '--earlyout'):
if opt == '--earlyout':
- pp.print_warn("Use of --earlyout is deprecated.")
- pp.print_warn("Please use --early-out.")
+ sys.stderr.write(pp.warn("Use of --earlyout is deprecated."))
+ sys.stderr.write(pp.warn("Please use --early-out."))
print
QUERY_OPTS['earlyOut'] = True
elif opt in ('-f', '--full-regex'):
@@ -65,31 +96,9 @@ def parse_module_options(module_opts):
QUERY_OPTS['nameOnly'] = True
-def prepare_search_regex(queries):
- """Create a regex out of the queries"""
-
- if QUERY_OPTS["fullRegex"]:
- result = queries
- else:
- result = []
- # Trim trailing and multiple slashes from queries
- slashes = re.compile('/+')
- for query in queries:
- query = slashes.sub('/', query).rstrip('/')
- if query.startswith('/'):
- query = "^%s$" % re.escape(query)
- else:
- query = "/%s$" % re.escape(query)
- result.append(query)
-
- result = "|".join(result)
-
- return re.compile(result)
-
-
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -111,15 +120,14 @@ def print_help(with_description=True):
def main(input_args):
"""Parse input and run the program"""
- # -c, --category is not implemented
- short_opts = "hc:fen"
- long_opts = ('help', 'category=', 'full-regex', 'early-out', 'earlyout',
+ short_opts = "h:fen"
+ long_opts = ('help', 'full-regex', 'early-out', 'earlyout',
'name-only')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -130,31 +138,19 @@ def main(input_args):
print_help()
sys.exit(2)
- query_re = prepare_search_regex(queries)
-
- if Config['verbose']:
- pp.print_info(3, " * Searching for %s ... "
- % (pp.regexpquery(",".join(queries))))
-
- matches = get_installed_cpvs()
-
- # Print matches to screen or pipe
- found_match = False
- for pkg in [Package(x) for x in matches]:
- files = pkg.get_contents()
- for cfile in files:
- if query_re.search(cfile):
- if QUERY_OPTS["nameOnly"]:
- pkg_str = pkg.key
- else:
- pkg_str = pkg.cpv
- if Config['verbose']:
- file_str = pp.path(format_filetype(cfile, files[cfile]))
- print "%s (%s)" % (pkg_str, file_str)
- else:
- print pkg_str
-
- found_match = True
-
- if found_match and QUERY_OPTS["earlyOut"]:
- break
+ if CONFIG['verbose']:
+ print " * Searching for %s ... " % (pp.regexpquery(",".join(queries)))
+
+ printer_fn = BelongsPrinter(
+ verbose=CONFIG['verbose'], name_only=QUERY_OPTS['nameOnly']
+ )
+
+ find_owner = FileOwner(
+ is_regex=QUERY_OPTS['fullRegex'],
+ early_out=QUERY_OPTS['earlyOut'],
+ printer_fn=printer_fn
+ )
+
+ find_owner(queries)
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/changes.py b/pym/gentoolkit/equery/changes.py
index 604bb45..09cccca 100644
--- a/pym/gentoolkit/equery/changes.py
+++ b/pym/gentoolkit/equery/changes.py
@@ -4,14 +4,13 @@
#
# $Header: $
-"""Display the Gentoo ChangeLog entry for the latest installable version of a
-given package
+"""Display the Gentoo ChangeLog entry for the latest installable version of a
+given package.
"""
# Move to Imports sections when Python 2.6 is stable
from __future__ import with_statement
-__author__ = 'Douglas Anderson'
__docformat__ = 'epytext'
# =======
@@ -22,14 +21,11 @@ import os
import sys
from getopt import gnu_getopt, GetoptError
-from portage.versions import pkgsplit
-
import gentoolkit.pprinter as pp
from gentoolkit import errors
+from gentoolkit.atom import Atom
from gentoolkit.equery import format_options, mod_usage
-from gentoolkit.helpers2 import find_best_match, find_packages
-from gentoolkit.package import Package
-from gentoolkit.versionmatch import VersionMatch
+from gentoolkit.helpers import ChangeLog, find_best_match, find_packages
# =======
# Globals
@@ -49,7 +45,7 @@ QUERY_OPTS = {
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -78,71 +74,26 @@ def print_help(with_description=True):
))
-def get_logpath(pkg):
- """Test that the package's ChangeLog path is valid and readable, else
- die.
-
- @type pkg: gentoolkit.package.Package
- @param pkg: package to find logpath for
- @rtype: str
- @return: a path to a readable ChangeLog
- """
-
- logpath = os.path.join(pkg.get_package_path(), 'ChangeLog')
- if not os.path.isfile(logpath) or not os.access(logpath, os.R_OK):
- raise errors.GentoolkitFatalError("%s does not exist or is unreadable"
- % pp.path(logpath))
-
- return logpath
-
-
def get_match(query):
- """Find a valid package to get the ChangeLog path from or raise
- GentoolkitNoMatches.
+ """Find a valid package from which to get the ChangeLog path.
+
+ @raise GentoolkitNoMatches: if no matches found
"""
match = matches = None
match = find_best_match(query)
-
+
if not match:
matches = find_packages(query, include_masked=True)
else:
matches = [match]
if not matches:
- pp.print_warn("Try using an unversioned query with "
- "--from and --to.")
raise errors.GentoolkitNoMatches(query)
return matches[0]
-def index_changelog(entries):
- """Convert the list from split_changelog into a dict with VersionMatch
- instance as the index.
-
- @todo: UPDATE THIS
- @type entries: list
- @param entries: output of split_changelog
- @rtype: dict
- @return: dict with gentoolkit.package.Package instances as keys and the
- corresponding ChangeLog entree as its value
- """
-
- result = []
- for entry in 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))
-
- return result
-
-
def is_ranged(atom):
"""Return True if an atom string appears to be ranged, else False."""
@@ -150,7 +101,7 @@ def is_ranged(atom):
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
posargs = (x[1] for x in module_opts)
@@ -165,126 +116,39 @@ def parse_module_options(module_opts):
elif opt in ('--limit',):
set_limit(posarg)
elif opt in ('--from',):
- set_from(posarg)
+ QUERY_OPTS['from'] = posarg
elif opt in ('--to',):
- set_to(posarg)
-
-
-def print_matching_entries(indexed_entries, pkg, first_run):
- """Print only the entries which interect with the pkg version."""
-
- from_restriction = QUERY_OPTS['from']
- to_restriction = QUERY_OPTS['to']
-
- for entry_set in indexed_entries:
- i, entry = entry_set
- # a little hackery, since versionmatch doesn't store the
- # package key, but intersects checks that it matches.
- i.key = pkg.key
- if from_restriction or to_restriction:
- if from_restriction and not from_restriction.match(i):
- continue
- if to_restriction and not to_restriction.match(i):
- continue
- elif not pkg.intersects(i):
- continue
-
- if not first_run:
- print "\n"
- print entry.strip()
- first_run = False
-
- return first_run
+ QUERY_OPTS['to'] = posarg
-def set_from(posarg):
- """Set a starting version to filter the ChangeLog with or die if posarg
- is not a valid version.
- """
-
- pkg_split = pkgsplit('null-%s' % posarg)
+def print_entries(entries):
+ """Print entries and strip trailing whitespace from the last entry."""
- if pkg_split and not is_ranged(posarg):
- ver_match = VersionMatch(
- op=">=",
- ver=pkg_split[1],
- rev=pkg_split[2] if pkg_split[2] != 'r0' else '')
- QUERY_OPTS['from'] = ver_match
- else:
- err = "Module option --from requires valid unranged version (got '%s')"
- pp.print_error(err % posarg)
- print
- print_help(with_description=False)
- sys.exit(2)
+ len_entries = len(entries)
+ for i, entry in enumerate(entries): # , start=1): in py2.6
+ i += 1
+ if i < len_entries:
+ print entry
+ else:
+ print entry.strip()
def set_limit(posarg):
- """Set a limit in QUERY_OPTS on how many ChangeLog entries to display or
- die if posarg is not an integer.
+ """Set a limit in QUERY_OPTS on how many ChangeLog entries to display.
+
+ Die if posarg is not an integer.
"""
if posarg.isdigit():
QUERY_OPTS['limit'] = int(posarg)
else:
err = "Module option --limit requires integer (got '%s')"
- pp.print_error(err % posarg)
+ sys.stderr.write(pp.error(err % posarg))
print
print_help(with_description=False)
sys.exit(2)
-def set_to(posarg):
- """Set an ending version to filter the ChangeLog with or die if posarg
- is not a valid version.
- """
-
- pkg_split = pkgsplit('null-%s' % posarg)
- if pkg_split and not is_ranged(posarg):
- ver_match = VersionMatch(
- op="<=",
- ver=pkg_split[1],
- rev=pkg_split[2] if pkg_split[2] != 'r0' else '')
- QUERY_OPTS['to'] = ver_match
- else:
- err = "Module option --to requires valid unranged version (got '%s')"
- pp.print_error(err % posarg)
- print
- print_help(with_description=False)
- sys.exit(2)
-
-
-def split_changelog(logpath):
- """Split the changelog up into individual entries.
-
- @type logpath: str
- @param logpath: valid path to ChangeLog file
- @rtype: list
- @return: individual ChangeLog entrees
- """
-
- result = []
- partial_entries = []
- with open(logpath) as log:
- for line in log:
- if line.startswith('#'):
- continue
- elif line.startswith('*'):
- # Append last entry to result...
- entry = ''.join(partial_entries)
- if entry and not entry.isspace():
- result.append(entry)
- # ... and start a new entry
- partial_entries = [line]
- else:
- partial_entries.append(line)
- else:
- # Append the final entry
- entry = ''.join(partial_entries)
- result.append(entry)
-
- return result
-
-
def main(input_args):
"""Parse input and run the program"""
@@ -294,7 +158,7 @@ def main(input_args):
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -310,36 +174,34 @@ def main(input_args):
if not first_run:
print
- ranged_query = None
- if is_ranged(query):
- # Raises GentoolkitInvalidCPV here if invalid
- ranged_query = Package(query)
-
- 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)
+ match = get_match(query)
+ changelog_path = os.path.join(match.get_package_path(), 'ChangeLog')
+ changelog = ChangeLog(changelog_path)
#
# Output
#
- if QUERY_OPTS['onlyLatest']:
- print log_entries[0].strip()
- elif QUERY_OPTS['showFullLog']:
- end = QUERY_OPTS['limit'] or len(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()
+ if (QUERY_OPTS['onlyLatest'] or (
+ changelog.entries and not changelog.indexed_entries
+ )):
+ print changelog.latest.strip()
else:
- if ranged_query:
- pkg = ranged_query
- first_run = print_matching_entries(indexed_entries, pkg, first_run)
+ end = QUERY_OPTS['limit'] or len(changelog.indexed_entries)
+ if QUERY_OPTS['to'] or QUERY_OPTS['from']:
+ print_entries(
+ changelog.entries_matching_range(
+ from_ver=QUERY_OPTS['from'],
+ to_ver=QUERY_OPTS['to']
+ )[:end]
+ )
+ elif QUERY_OPTS['showFullLog']:
+ print_entries(changelog.full[:end])
+ else:
+ # Raises GentoolkitInvalidAtom here if invalid
+ atom = Atom(query) if is_ranged(query) else '=' + str(match.cpv)
+ print_entries(changelog.entries_matching_atom(atom)[:end])
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/check.py b/pym/gentoolkit/equery/check.py
index 2531970..011fd5d 100644
--- a/pym/gentoolkit/equery/check.py
+++ b/pym/gentoolkit/equery/check.py
@@ -4,7 +4,7 @@
#
# $Header: $
-"""Check timestamps and MD5sums for files owned by a given installed package"""
+"""Check timestamps and MD5 sums for files owned by a given installed package"""
__docformat__ = 'epytext'
@@ -14,43 +14,166 @@ __docformat__ = 'epytext'
import os
import sys
+from functools import partial
from getopt import gnu_getopt, GetoptError
-try:
- import portage.checksum as checksum
-except ImportError:
- import portage_checksum as checksum
+import portage.checksum as checksum
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import do_lookup
+from gentoolkit import errors
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import do_lookup
# =======
# Globals
# =======
QUERY_OPTS = {
- "categoryFilter": None,
- "includeInstalled": False,
+ "includeInstalled": True,
"includeOverlayTree": False,
"includePortTree": False,
"checkMD5sum": True,
"checkTimestamp" : True,
"isRegex": False,
- "matchExact": True,
+ "onlyFailures": False,
"printMatchInfo": False,
"showSummary" : True,
"showPassedFiles" : False,
"showFailedFiles" : True
}
+# =======
+# Classes
+# =======
+
+class VerifyContents(object):
+ """Verify installed packages' CONTENTS files.
+
+ The CONTENTS file contains timestamps and MD5 sums for each file owned
+ by a package.
+ """
+ def __init__(self, printer_fn=None):
+ """Create a VerifyObjects instance.
+
+ @type printer_fn: callable
+ @param printer_fn: if defined, will be applied to each result as found
+ """
+ self.check_sums = True
+ self.check_timestamps = True
+ self.printer_fn = printer_fn
+
+ self.is_regex = False
+
+ def __call__(
+ self,
+ pkgs,
+ is_regex=False,
+ check_sums=True,
+ check_timestamps=True
+ ):
+ self.is_regex = is_regex
+ self.check_sums = check_sums
+ self.check_timestamps = check_timestamps
+
+ result = {}
+ for pkg in pkgs:
+ # _run_checks returns tuple(n_passed, n_checked, err)
+ check_results = self._run_checks(pkg.get_contents())
+ result[pkg.cpv] = check_results
+ if self.printer_fn is not None:
+ self.printer_fn(pkg.cpv, check_results)
+
+ return result
+
+ def _run_checks(self, files):
+ """Run some basic sanity checks on a package's contents.
+
+ If the file type (ftype) is not a directory or symlink, optionally
+ verify MD5 sums or mtimes via L{self._verify_obj}.
+
+ @see: gentoolkit.packages.get_contents()
+ @type files: dict
+ @param files: in form {'PATH': ['TYPE', 'TIMESTAMP', 'MD5SUM']}
+ @rtype: tuple
+ @return:
+ n_passed (int): number of files that passed all checks
+ n_checked (int): number of files checked
+ errs (list): check errors' descriptions
+ """
+ n_checked = 0
+ n_passed = 0
+ errs = []
+ for cfile in files:
+ n_checked += 1
+ ftype = files[cfile][0]
+ if not os.path.exists(cfile):
+ errs.append("%s does not exist" % cfile)
+ continue
+ elif ftype == "dir":
+ if not os.path.isdir(cfile):
+ err = "%(cfile)s exists, but is not a directory"
+ errs.append(err % locals())
+ continue
+ elif ftype == "obj":
+ obj_errs = self._verify_obj(files, cfile, errs)
+ if len(obj_errs) > len(errs):
+ errs = obj_errs[:]
+ continue
+ elif ftype == "sym":
+ target = files[cfile][2].strip()
+ if not os.path.islink(cfile):
+ err = "%(cfile)s exists, but is not a symlink"
+ errs.append(err % locals())
+ continue
+ tgt = os.readlink(cfile)
+ if tgt != target:
+ err = "%(cfile)s does not point to %(target)s"
+ errs.append(err % locals())
+ continue
+ else:
+ err = "%(cfile)s has unknown type %(ftype)s"
+ errs.append(err % locals())
+ continue
+ n_passed += 1
+
+ return n_passed, n_checked, errs
+
+ def _verify_obj(self, files, cfile, errs):
+ """Verify the MD5 sum and/or mtime and return any errors."""
+
+ obj_errs = errs[:]
+ if self.check_sums:
+ md5sum = files[cfile][2]
+ try:
+ cur_checksum = checksum.perform_md5(cfile, calc_prelink=1)
+ except IOError:
+ err = "Insufficient permissions to read %(cfile)s"
+ obj_errs.append(err % locals())
+ return obj_errs
+ if cur_checksum != md5sum:
+ err = "%(cfile)s has incorrect MD5sum"
+ obj_errs.append(err % locals())
+ return obj_errs
+ if self.check_timestamps:
+ mtime = int(files[cfile][1])
+ st_mtime = int(os.lstat(cfile).st_mtime)
+ if st_mtime != mtime:
+ err = (
+ "%(cfile)s has wrong mtime (is %(st_mtime)d, should be "
+ "%(mtime)d)"
+ )
+ obj_errs.append(err % locals())
+ return obj_errs
+
+ return obj_errs
+
# =========
# Functions
# =========
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -60,9 +183,14 @@ def print_help(with_description=True):
print
# Deprecation warning added by djanderson, 12/2008
- pp.print_warn("Default action for this module has changed in Gentoolkit 0.3.")
- pp.print_warn("Use globbing to simulate the old behavior (see man equery).")
- pp.print_warn("Use '*' to check all installed packages.")
+ depwarning = (
+ "Default action for this module has changed in Gentoolkit 0.3.",
+ "Use globbing to simulate the old behavior (see man equery).",
+ "Use '*' to check all installed packages.",
+ "Use 'foo-bar/*' to filter by category."
+ )
+ for line in depwarning:
+ sys.stderr.write(pp.warn(line))
print
print mod_usage(mod_name="check")
@@ -70,135 +198,73 @@ def print_help(with_description=True):
print pp.command("options")
print format_options((
(" -h, --help", "display this help message"),
- (" -c, --category CAT", "only check files from packages in CAT"),
(" -f, --full-regex", "query is a regular expression"),
+ (" -o, --only-failures", "only display packages that do not pass"),
))
+def checks_printer(cpv, data, verbose=True, only_failures=False):
+ """Output formatted results of pkg file(s) checks"""
+ seen = []
+
+ n_passed, n_checked, errs = data
+ n_failed = n_checked - n_passed
+ if only_failures and not n_failed:
+ return
+ else:
+ if verbose:
+ if not cpv in seen:
+ print "* Checking %s ..." % (pp.emph(str(cpv)))
+ seen.append(cpv)
+ else:
+ print "%s:" % cpv,
+
+ if verbose:
+ for err in errs:
+ sys.stderr.write(pp.error(err))
+
+ if verbose:
+ n_passed = pp.number(str(n_passed))
+ n_checked = pp.number(str(n_checked))
+ info = " %(n_passed)s out of %(n_checked)s files passed"
+ print info % locals()
+ else:
+ print "failed(%s)" % n_failed
+
+
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
- posargs = (x[1] for x in module_opts)
- for opt, posarg in zip(opts, posargs):
+ for opt in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
- elif opt in ('-c', '--category'):
- QUERY_OPTS['categoryFilter'] = posarg
elif opt in ('-f', '--full-regex'):
QUERY_OPTS['isRegex'] = True
-
-
-def run_checks(files):
- """Run some basic sanity checks on a package's contents.
-
- If the file type (ftype) is not a directory or symlink, optionally
- verify MD5 sums or mtimes via verify_obj().
-
- @see: gentoolkit.packages.get_contents()
- @type files: dict
- @param files: in form {'PATH': ['TYPE', 'TIMESTAMP', 'MD5SUM']}
- @rtype: tuple
- @return:
- passed (int): number of files that passed all checks
- checked (int): number of files checked
- errs (list): check errors' descriptions
- """
-
- checked = 0
- passed = 0
- errs = []
- for cfile in files:
- checked += 1
- ftype = files[cfile][0]
- if not os.path.exists(cfile):
- errs.append("%s does not exist" % cfile)
- continue
- elif ftype == "dir":
- if not os.path.isdir(cfile):
- err = "%(cfile)s exists, but is not a directory"
- errs.append(err % locals())
- continue
- elif ftype == "obj":
- new_errs = verify_obj(files, cfile, errs)
- if new_errs != errs:
- errs = new_errs
- continue
- elif ftype == "sym":
- target = files[cfile][2].strip()
- if not os.path.islink(cfile):
- err = "%(cfile)s exists, but is not a symlink"
- errs.append(err % locals())
- continue
- tgt = os.readlink(cfile)
- if tgt != target:
- err = "%(cfile)s does not point to %(target)s"
- errs.append(err % locals())
- continue
- else:
- err = "%(cfile)s has unknown type %(ftype)s"
- errs.append(err % locals())
- continue
- passed += 1
-
- return passed, checked, errs
-
-
-def verify_obj(files, cfile, errs):
- """Verify the MD5 sum and/or mtime and return any errors."""
-
- if QUERY_OPTS["checkMD5sum"]:
- md5sum = files[cfile][2]
- try:
- cur_checksum = checksum.perform_md5(cfile, calc_prelink=1)
- except IOError:
- err = "Insufficient permissions to read %(cfile)s"
- errs.append(err % locals())
- return errs
- if cur_checksum != md5sum:
- err = "%(cfile)s has incorrect MD5sum"
- errs.append(err % locals())
- return errs
- if QUERY_OPTS["checkTimestamp"]:
- mtime = int(files[cfile][1])
- st_mtime = os.lstat(cfile).st_mtime
- if st_mtime != mtime:
- err = "%(cfile)s has wrong mtime (is %(st_mtime)d, " + \
- "should be %(mtime)d)"
- errs.append(err % locals())
- return errs
-
- return errs
+ elif opt in ('-o', '--only-failures'):
+ QUERY_OPTS['onlyFailures'] = True
def main(input_args):
"""Parse input and run the program"""
- short_opts = "hac:f"
- long_opts = ('help', 'all', 'category=', 'full-regex')
+ short_opts = "hof"
+ long_opts = ('help', 'only-failures', 'full-regex')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
- if not queries and not QUERY_OPTS["includeInstalled"]:
+
+ if not queries:
print_help()
sys.exit(2)
- elif queries and not QUERY_OPTS["includeInstalled"]:
- QUERY_OPTS["includeInstalled"] = True
- elif QUERY_OPTS["includeInstalled"]:
- queries = ["*"]
-
- #
- # Output
- #
first_run = True
for query in queries:
@@ -208,25 +274,18 @@ def main(input_args):
matches = do_lookup(query, QUERY_OPTS)
if not matches:
- pp.print_error("No package found matching %s" % query)
+ raise errors.GentoolkitNoMatches(query, in_installed=True)
matches.sort()
- for pkg in matches:
- if Config['verbose']:
- print " * Checking %s ..." % pp.emph(pkg.cpv)
- else:
- print "%s:" % pkg.cpv
-
- passed, checked, errs = run_checks(pkg.get_contents())
-
- if Config['verbose']:
- for err in errs:
- pp.print_error(err)
+ printer = partial(
+ checks_printer,
+ verbose=CONFIG['verbose'],
+ only_failures=QUERY_OPTS['onlyFailures']
+ )
+ check = VerifyContents(printer_fn=printer)
+ check(matches)
- passed = pp.number(str(passed))
- checked = pp.number(str(checked))
- info = " %(passed)s out of %(checked)s files passed"
- print info % locals()
+ first_run = False
- first_run = False
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/depends.py b/pym/gentoolkit/equery/depends.py
index a1a0d20..df2de9f 100644
--- a/pym/gentoolkit/equery/depends.py
+++ b/pym/gentoolkit/equery/depends.py
@@ -4,7 +4,7 @@
#
# $Header: $
-"""List all direct dependencies matching a given query"""
+"""List all packages that depend on a given query"""
__docformat__ = 'epytext'
@@ -15,34 +15,82 @@ __docformat__ = 'epytext'
import sys
from getopt import gnu_getopt, GetoptError
-from portage.util import unique_array
-
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import compare_package_strings, do_lookup, \
- find_packages, get_cpvs, get_installed_cpvs
-from gentoolkit.package import Package
+from gentoolkit.dependencies import Dependencies
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import (get_cpvs, get_installed_cpvs,
+ compare_package_strings)
# =======
# Globals
# =======
QUERY_OPTS = {
- "categoryFilter": None,
- "includeInstalled": True,
- "includePortTree": False,
- "includeOverlayTree": False,
- "isRegex": False,
+ "includeMasked": False,
"onlyDirect": True,
- "printMatchInfo": (not Config['quiet']),
- "indentLevel": 0,
- "depth": -1
+ "maxDepth": -1,
}
-# Used to cache and detect looping
-PKGSEEN = set()
-PKGDEPS = {}
-DEPPKGS = {}
+# =======
+# Classes
+# =======
+
+class DependPrinter(object):
+ """Output L{gentoolkit.dependencies.Dependencies} objects."""
+ def __init__(self, verbose=True):
+ if verbose:
+ self.print_fn = self.print_verbose
+ else:
+ self.print_fn = self.print_quiet
+
+ def __call__(self, dep, dep_is_displayed=False):
+ self.format_depend(dep, dep_is_displayed)
+
+ @staticmethod
+ def print_verbose(indent, cpv, use_conditional, depatom):
+ """Verbosely prints a set of dep strings."""
+
+ sep = ' ? ' if (depatom and use_conditional) else ''
+ print indent + pp.cpv(cpv), "(" + use_conditional + sep + depatom + ")"
+
+ # W0613: *Unused argument %r*
+ # pylint: disable-msg=W0613
+ @staticmethod
+ def print_quiet(indent, cpv, use_conditional, depatom):
+ """Quietly prints a subset set of dep strings."""
+
+ print indent + pp.cpv(cpv)
+
+ def format_depend(self, dep, dep_is_displayed):
+ """Format a dependency for printing.
+
+ @type dep: L{gentoolkit.dependencies.Dependencies}
+ @param dep: the dependency to display
+ """
+
+ depth = getattr(dep, 'depth', 0)
+ indent = " " * depth
+ mdep = dep.matching_dep
+ use_conditional = ""
+ if mdep.use_conditional:
+ use_conditional = " & ".join(
+ pp.useflag(u) for u in mdep.use_conditional.split()
+ )
+ if mdep.operator == '=*':
+ formatted_dep = '=%s*' % str(mdep.cpv)
+ else:
+ formatted_dep = mdep.operator + str(mdep.cpv)
+ if mdep.slot:
+ formatted_dep += pp.emph(':') + pp.slot(mdep.slot)
+ if mdep.use:
+ useflags = pp.useflag(','.join(mdep.use.tokens))
+ formatted_dep += (pp.emph('[') + useflags + pp.emph(']'))
+
+ if dep_is_displayed:
+ indent = indent + " " * len(str(dep.cpv))
+ self.print_fn(indent, '', use_conditional, formatted_dep)
+ else:
+ self.print_fn(indent, str(dep.cpv), use_conditional, formatted_dep)
# =========
# Functions
@@ -50,7 +98,7 @@ DEPPKGS = {}
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -63,125 +111,16 @@ def print_help(with_description=True):
print pp.command("options")
print format_options((
(" -h, --help", "display this help message"),
- (" -a, --all-packages",
- "include packages that are not installed (slow)"),
+ (" -a, --all-packages",
+ "include dependencies that are not installed (slow)"),
(" -D, --indirect",
"search both direct and indirect dependencies"),
(" --depth=N", "limit indirect dependency tree to specified depth")
))
-def cache_package_list(pkg_cache=None):
- """Ensure that the package cache is set."""
-
- if not pkg_cache:
- if QUERY_OPTS['includePortTree']:
- packages = [Package(x) for x in get_cpvs()]
- else:
- packages = [Package(x) for x in get_installed_cpvs()]
- packages.sort()
- pkg_cache = packages
- else:
- packages = pkg_cache
-
- return packages
-
-
-def display_dependencies(cpv_is_displayed, dependency, cpv):
- """Output dependencies calculated by find_dependencies.
-
- @type cpv_is_displayed: bool
- @param cpv_is_displayed: if True, the cpv has already been printed
- @see: gentoolkit.package.get_*_deps()
- @type dependency: tuple
- @param dependency: (comparator, [use flags], cpv)
- @type cpv: string
- @param cpv: cat/pkg-ver
- """
-
- atom = pp.pkgquery(dependency[0] + dependency[2])
- indent = " " * (QUERY_OPTS["indentLevel"] * 2)
- useflags = pp.useflag(" & ".join(dependency[1]))
-
- if not cpv_is_displayed:
- if dependency[1]:
- if Config['verbose']:
- print indent + pp.cpv(cpv),
- print "(" + useflags + " ? " + atom + ")"
- else:
- print indent + cpv
- else:
- if Config['verbose']:
- print indent + pp.cpv(cpv),
- print "(" + atom + ")"
- else:
- print indent + cpv
- elif Config['verbose']:
- indent = indent + " " * len(cpv)
- if dependency[1]:
- print indent + " (" + useflags + " ? " + atom + ")"
- else:
- print indent + " (" + atom + ")"
-
-
-def find_dependencies(matches, pkg_cache):
- """Find dependencies for the packaged named in queries.
-
- @type queries: list
- @param queries: packages to find the dependencies for
- """
-
- for pkg in cache_package_list(pkg_cache):
- if not pkg.cpv in PKGDEPS:
- try:
- deps = pkg.get_runtime_deps() + pkg.get_compiletime_deps()
- deps.extend(pkg.get_postmerge_deps())
- except KeyError:
- # If the ebuild is not found...
- continue
- # Remove duplicate deps
- deps = unique_array(deps)
- PKGDEPS[pkg.cpv] = deps
- else:
- deps = PKGDEPS[pkg.cpv]
-
- cpv_is_displayed = False
- for dependency in deps:
- # TODO: (old) determine if dependency is enabled by USE flag
- # Find all packages matching the dependency
- depstr = dependency[0] + dependency[2]
- if not depstr in DEPPKGS:
- depcpvs = find_packages(depstr,
- include_masked=QUERY_OPTS["includePortTree"])
- DEPPKGS[depstr] = depcpvs
- else:
- depcpvs = DEPPKGS[depstr]
-
- for depcpv in depcpvs:
- is_match = False
- if depcpv in matches:
- is_match = True
-
- if is_match:
- display_dependencies(cpv_is_displayed, dependency, pkg.cpv)
- cpv_is_displayed = True
- break
-
- # if --indirect specified, call ourselves again with the dependency
- # Do not call if we have already called ourselves.
- if (cpv_is_displayed and not QUERY_OPTS["onlyDirect"] and
- pkg not in PKGSEEN and
- (QUERY_OPTS["indentLevel"] < QUERY_OPTS["depth"] or
- QUERY_OPTS["depth"] == -1)):
-
- PKGSEEN.add(pkg)
- QUERY_OPTS["indentLevel"] += 1
- find_dependencies([pkg], pkg_cache)
- QUERY_OPTS["indentLevel"] -= 1
-
-
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
posargs = (x[1] for x in module_opts)
@@ -190,9 +129,7 @@ def parse_module_options(module_opts):
print_help()
sys.exit(0)
elif opt in ('-a', '--all-packages'):
- QUERY_OPTS['includePortTree'] = True
- elif opt in ('-d', '--direct'):
- continue
+ QUERY_OPTS['includeMasked'] = True
elif opt in ('-D', '--indirect'):
QUERY_OPTS['onlyDirect'] = False
elif opt in ('--depth'):
@@ -200,29 +137,28 @@ def parse_module_options(module_opts):
depth = int(posarg)
else:
err = "Module option --depth requires integer (got '%s')"
- pp.print_error(err % posarg)
+ sys.stdout.write(pp.error(err % posarg))
print
print_help(with_description=False)
sys.exit(2)
- QUERY_OPTS["depth"] = depth
+ QUERY_OPTS["maxDepth"] = depth
def main(input_args):
"""Parse input and run the program"""
-
short_opts = "hadD" # -d, --direct was old option for default action
long_opts = ('help', 'all-packages', 'direct', 'indirect', 'depth=')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
+
if not queries:
print_help()
sys.exit(2)
@@ -231,21 +167,27 @@ def main(input_args):
# Output
#
+ dep_print = DependPrinter(verbose=CONFIG['verbose'])
first_run = True
for query in queries:
if not first_run:
print
- matches = do_lookup(query, QUERY_OPTS)
-
- if matches:
- find_dependencies(matches, None)
+ pkg = Dependencies(query)
+ if QUERY_OPTS['includeMasked']:
+ pkggetter = get_cpvs
else:
- if QUERY_OPTS['includePortTree']:
- pp.print_error("No matching package found for %s" % query)
- else:
- pp.print_error(
- "No matching package or all versions masked for %s" % query
- )
+ pkggetter = get_installed_cpvs
+
+ if CONFIG['verbose']:
+ print " * These packages depend on %s:" % pp.emph(str(pkg.cpv))
+ pkg.graph_reverse_depends(
+ pkgset=sorted(pkggetter(), cmp=compare_package_strings),
+ max_depth=QUERY_OPTS["maxDepth"],
+ only_direct=QUERY_OPTS["onlyDirect"],
+ printer_fn=dep_print
+ )
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/depgraph.py b/pym/gentoolkit/equery/depgraph.py
index b9cd0a1..18d19ba 100644
--- a/pym/gentoolkit/equery/depgraph.py
+++ b/pym/gentoolkit/equery/depgraph.py
@@ -4,7 +4,7 @@
#
# $Header: $
-"""Display a dependency graph for a given package"""
+"""Display a direct dependency graph for a given package"""
__docformat__ = 'epytext'
@@ -13,30 +13,30 @@ __docformat__ = 'epytext'
# =======
import sys
+from functools import partial
from getopt import gnu_getopt, GetoptError
-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, find_best_match
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import do_lookup
# =======
# Globals
# =======
QUERY_OPTS = {
- "categoryFilter": None,
- "depth": 0,
- "displayUseflags": True,
- "fancyFormat": True,
+ "depth": 1,
+ "noAtom": False,
+ "noIndent": False,
+ "noUseflags": False,
"includeInstalled": True,
"includePortTree": True,
"includeOverlayTree": True,
"includeMasked": True,
"isRegex": False,
"matchExact": True,
- "printMatchInfo": (not Config['quiet'])
+ "printMatchInfo": (not CONFIG['quiet'])
}
# =========
@@ -45,7 +45,7 @@ QUERY_OPTS = {
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -53,66 +53,22 @@ def print_help(with_description=True):
if with_description:
print __doc__.strip()
print
+ print "Default depth is set to 1 (direct only). Use --depth=0 for no max."
+ print
print mod_usage(mod_name="depgraph")
print
print pp.command("options")
print format_options((
(" -h, --help", "display this help message"),
+ (" -A, --no-atom", "do not show dependency atom"),
(" -U, --no-useflags", "do not show USE flags"),
- (" -l, --linear", "do not use fancy formatting"),
+ (" -l, --linear", "do not format the graph by indenting dependencies"),
(" --depth=N", "limit dependency graph to specified depth")
))
-def display_graph(pkg, stats, level=0, seen_pkgs=None, suffix=""):
- """Display a dependency graph for a package
-
- @type pkg: gentoolkit.package.Package
- @param pkg: package to check dependencies of
- @type level: int
- @param level: current depth level
- @type seen_pkgs: set
- @param seen_pkgs: a set of all packages that have had their deps graphed
- """
-
- if not seen_pkgs:
- seen_pkgs = set()
-
- stats["packages"] += 1
- stats["maxdepth"] = max(stats["maxdepth"], level)
-
- pfx = ""
- if QUERY_OPTS["fancyFormat"]:
- pfx = (level * " ") + "`-- "
- pp.print_info(0, pfx + pkg.cpv + suffix)
-
- seen_pkgs.add(pkg.cpv)
-
- deps = pkg.get_runtime_deps() + pkg.get_compiletime_deps()
- deps.extend(pkg.get_postmerge_deps())
- for dep in deps:
- suffix = ""
- depcpv = dep[2]
- deppkg = find_best_match(dep[0] + depcpv)
- if not deppkg:
- print (pfx + dep[0] + depcpv),
- print "(unable to resolve: package masked or removed)"
- continue
- if deppkg.get_cpv() in seen_pkgs:
- continue
- if depcpv.find("virtual") == 0:
- suffix += " (%s)" % pp.cpv(depcpv)
- if dep[1] and QUERY_OPTS["displayUseflags"]:
- suffix += " [%s]" % pp.useflagon(' '.join(dep[1]))
- if (level < QUERY_OPTS["depth"] or QUERY_OPTS["depth"] <= 0):
- seen_pkgs, stats = display_graph(deppkg, stats, level+1,
- seen_pkgs, suffix)
-
- return seen_pkgs, stats
-
-
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
posargs = (x[1] for x in module_opts)
@@ -120,38 +76,112 @@ def parse_module_options(module_opts):
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
+ if opt in ('-A', '--no-atom'):
+ QUERY_OPTS["noAtom"] = True
if opt in ('-U', '--no-useflags'):
- QUERY_OPTS["displayUseflags"] = False
+ QUERY_OPTS["noUseflags"] = True
if opt in ('-l', '--linear'):
- QUERY_OPTS["fancyFormat"] = False
+ QUERY_OPTS["noIndent"] = True
if opt in ('--depth'):
if posarg.isdigit():
depth = int(posarg)
else:
- err = "Module option --depth requires integer (got '%s')"
- pp.print_error(err % posarg)
+ err = "Module option --depth requires integer (got '%s')"
+ sys.stderr.write(pp.error(err % posarg))
print
print_help(with_description=False)
sys.exit(2)
QUERY_OPTS["depth"] = depth
+def depgraph_printer(
+ depth,
+ pkg,
+ dep,
+ no_use=False,
+ no_atom=False,
+ no_indent=False,
+ initial_pkg=False
+):
+ """Display L{gentoolkit.dependencies.Dependencies.graph_depends} results.
+
+ @type depth: int
+ @param depth: depth of indirection, used to calculate indent
+ @type pkg: L{gentoolkit.package.Package}
+ @param pkg: "best match" package matched by B{dep}
+ @type dep: L{gentoolkit.atom.Atom}
+ @param dep: dependency that matched B{pkg}
+ @type no_use: bool
+ @param no_use: don't output USE flags
+ @type no_atom: bool
+ @param no_atom: don't output dep atom
+ @type no_indent: bool
+ @param no_indent: don't output indent based on B{depth}
+ @type initial_pkg: bool
+ @param initial_pkg: somewhat of a hack used to print the root package of
+ the graph with absolutely no indent
+ """
+ indent = '' if no_indent or initial_pkg else ' ' + (' ' * depth)
+ decorator = '[%3d] ' % depth if no_indent else '`-- '
+ use = ''
+ try:
+ atom = '' if no_atom else ' (%s)' % dep.atom
+ if not no_use and dep is not None and dep.use:
+ use = ' [%s]' % ' '.join(
+ pp.useflag(x, enabled=True) for x in dep.use.tokens
+ )
+ except AttributeError:
+ # 'NoneType' object has no attribute 'atom'
+ atom = ''
+ try:
+ print ''.join((indent, decorator, pp.cpv(str(pkg.cpv)), atom, use))
+ except AttributeError:
+ # 'NoneType' object has no attribute 'cpv'
+ print ''.join((indent, decorator, "(no match for %r)" % dep.atom))
+
+
+def make_depgraph(pkg, printer_fn):
+ """Create and display depgraph for each package."""
+
+ if CONFIG['verbose']:
+ print " * direct dependency graph for %s:" % pp.cpv(str(pkg.cpv))
+ else:
+ print "%s:" % str(pkg.cpv)
+
+ # Print out the first package
+ printer_fn(0, pkg, None, initial_pkg=True)
+
+ deps = pkg.deps.graph_depends(
+ max_depth=QUERY_OPTS['depth'],
+ printer_fn=printer_fn,
+ # Use this to set this pkg as the graph's root; better way?
+ result=[(0, pkg)]
+ )
+
+ if CONFIG['verbose']:
+ pkgname = pp.cpv(str(pkg.cpv))
+ n_packages = pp.number(str(len(deps)))
+ max_seen = pp.number(str(max(x[0] for x in deps)))
+ info = "[ %s stats: packages (%s), max depth (%s) ]"
+ print info % (pkgname, n_packages, max_seen)
+
+
def main(input_args):
"""Parse input and run the program"""
- short_opts = "hUl"
- long_opts = ('help', 'no-useflags', 'depth=')
+ short_opts = "hAUl"
+ long_opts = ('help', 'no-atom', 'no-useflags', 'depth=')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
+
if not queries:
print_help()
sys.exit(2)
@@ -168,22 +198,26 @@ def main(input_args):
matches = do_lookup(query, QUERY_OPTS)
if not matches:
- errors.GentoolkitNoMatches(query)
+ raise errors.GentoolkitNoMatches(query)
+
+ if CONFIG['verbose']:
+ printer = partial(
+ depgraph_printer,
+ no_atom=QUERY_OPTS['noAtom'],
+ no_indent=QUERY_OPTS['noIndent'],
+ no_use=QUERY_OPTS['noUseflags']
+ )
+ else:
+ printer = partial(
+ depgraph_printer,
+ no_atom=True,
+ no_indent=True,
+ no_use=True
+ )
for pkg in matches:
- stats = {"maxdepth": 0, "packages": 0}
-
- 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 Config['verbose']:
- info = ''.join(["[ ", pp.cpv(pkg.cpv), " stats: packages (",
- pp.number(str(stats["packages"])), "), max depth (",
- pp.number(str(stats["maxdepth"])), ") ]"])
- pp.print_info(0, info)
+ make_depgraph(pkg, printer)
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/files.py b/pym/gentoolkit/equery/files.py
index 80caf1d..4f588bb 100644
--- a/pym/gentoolkit/equery/files.py
+++ b/pym/gentoolkit/equery/files.py
@@ -16,11 +16,12 @@ import os
import sys
from getopt import gnu_getopt, GetoptError
-import gentoolkit
+import portage
+
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_filetype, format_options, mod_usage, \
- Config
-from gentoolkit.helpers2 import do_lookup
+from gentoolkit.equery import (format_filetype, format_options, mod_usage,
+ CONFIG)
+from gentoolkit.helpers import do_lookup
# =======
# Globals
@@ -35,15 +36,16 @@ QUERY_OPTS = {
"isRegex": False,
"matchExact": True,
"outputTree": False,
- "printMatchInfo": (not Config['quiet']),
+ "printMatchInfo": (not CONFIG['quiet']),
"showType": False,
"showTimestamp": False,
"showMD5": False,
"typeFilter": None
}
-FILTER_RULES = ('dir', 'obj', 'sym', 'dev', 'path', 'conf', 'cmd', 'doc',
- 'man', 'info')
+FILTER_RULES = (
+ 'dir', 'obj', 'sym', 'dev', 'path', 'conf', 'cmd', 'doc', 'man', 'info'
+)
# =========
# Functions
@@ -51,7 +53,7 @@ FILTER_RULES = ('dir', 'obj', 'sym', 'dev', 'path', 'conf', 'cmd', 'doc',
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -69,12 +71,14 @@ def print_help(with_description=True):
(" -t, --type", "include file type in output"),
(" --tree", "display results in a tree (turns off other options)"),
(" -f, --filter=RULES", "filter output by file type"),
- (" RULES",
+ (" RULES",
"a comma-separated list (no spaces); choose from:")
))
print " " * 24, ', '.join(pp.emph(x) for x in FILTER_RULES)
+# R0912: *Too many branches (%s/%s)*
+# pylint: disable-msg=R0912
def display_files(contents):
"""Display the content of an installed package.
@@ -89,40 +93,43 @@ def display_files(contents):
for name in filenames:
if QUERY_OPTS["outputTree"]:
- basename = name.split("/")[1:]
+ dirdepth = name.count('/')
+ indent = " "
+ if dirdepth == 2:
+ indent = " "
+ elif dirdepth > 2:
+ indent = " " * (dirdepth - 1)
+
+ basename = name.rsplit("/", dirdepth - 1)
if contents[name][0] == "dir":
if len(last) == 0:
last = basename
- print pp.path(" /" + basename[0])
+ print pp.path(indent + basename[0])
continue
- numol = 0
for i, directory in enumerate(basename):
try:
if directory in last[i]:
- numol = i + 1
continue
- # W0704: Except doesn't do anything
- # pylint: disable-msg=W0704
except IndexError:
pass
last = basename
if len(last) == 1:
- print pp.path(" " + last[0])
+ print pp.path(indent + last[0])
continue
- ind = " " * (numol * 3)
- print pp.path(ind + "> " + "/" + last[-1])
+ print pp.path(indent + "> /" + last[-1])
elif contents[name][0] == "sym":
- print pp.path(" " * (len(last) * 3) + "+"),
+ print pp.path(indent + "+"),
print pp.path_symlink(basename[-1] + " -> " + contents[name][2])
- else:
- print pp.path(" " * (len(last) * 3) + "+ ") + basename[-1]
+ else:
+ print pp.path(indent + "+ ") + basename[-1]
else:
- pp.print_info(0, format_filetype(
+ print format_filetype(
name,
contents[name],
show_type=QUERY_OPTS["showType"],
show_md5=QUERY_OPTS["showMD5"],
- show_timestamp=QUERY_OPTS["showTimestamp"]))
+ show_timestamp=QUERY_OPTS["showTimestamp"]
+ )
def filter_by_doc(contents, content_filter):
@@ -136,7 +143,7 @@ def filter_by_doc(contents, content_filter):
for path in contents:
if contents[path][0] == 'obj' and path.startswith(docpath):
filtered_content[path] = contents[path]
-
+
return filtered_content
@@ -150,7 +157,7 @@ def filter_by_command(contents):
if (contents[path][0] in ['obj', 'sym'] and
os.path.dirname(path) in userpath):
filtered_content[path] = contents[path]
-
+
return filtered_content
@@ -172,7 +179,7 @@ def filter_by_path(contents):
if check_subdirs:
while (paths and paths[-1].startswith(basepath)):
paths.pop()
-
+
return filtered_content
@@ -180,9 +187,9 @@ def filter_by_conf(contents):
"""Return a copy of content filtered by configuration files."""
filtered_content = {}
- conf_path = gentoolkit.settings["CONFIG_PROTECT"].split()
+ conf_path = portage.settings["CONFIG_PROTECT"].split()
conf_path = tuple(os.path.normpath(x) for x in conf_path)
- conf_mask_path = gentoolkit.settings["CONFIG_PROTECT_MASK"].split()
+ conf_mask_path = portage.settings["CONFIG_PROTECT_MASK"].split()
conf_mask_path = tuple(os.path.normpath(x) for x in conf_mask_path)
for path in contents:
if contents[path][0] == 'obj' and path.startswith(conf_path):
@@ -206,7 +213,7 @@ def filter_contents(contents):
content_filter = QUERY_OPTS['typeFilter']
else:
return contents
-
+
filtered_content = {}
if frozenset(('dir', 'obj', 'sym', 'dev')).intersection(content_filter):
# Filter elements by type (as recorded in CONTENTS)
@@ -221,12 +228,12 @@ def filter_contents(contents):
filtered_content.update(filter_by_conf(contents))
if frozenset(('doc' ,'man' ,'info')).intersection(content_filter):
filtered_content.update(filter_by_doc(contents, content_filter))
-
+
return filtered_content
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
content_filter = []
opts = (x[0] for x in module_opts)
@@ -250,7 +257,9 @@ def parse_module_options(module_opts):
content_filter.extend(x.lstrip('=') for x in f_split)
for rule in content_filter:
if not rule in FILTER_RULES:
- pp.print_error("Invalid filter rule '%s'" % rule)
+ sys.stderr.write(
+ pp.error("Invalid filter rule '%s'" % rule)
+ )
print
print_help(with_description=False)
sys.exit(2)
@@ -260,20 +269,21 @@ def parse_module_options(module_opts):
def main(input_args):
"""Parse input and run the program"""
+ # -e, --exact-name is legacy option. djanderson '09
short_opts = "hemstf:"
long_opts = ('help', 'exact-name', 'md5sum', 'timestamp', 'type', 'tree',
- 'filter=')
+ 'filter=')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
+
if not queries:
print_help()
sys.exit(2)
@@ -294,13 +304,17 @@ def main(input_args):
matches = do_lookup(query, QUERY_OPTS)
if not matches:
- pp.print_error("No matching packages found for %s" % query)
+ sys.stderr.write(
+ pp.error("No matching packages found for %s" % query)
+ )
for pkg in matches:
- if Config['verbose']:
- print " * Contents of %s:" % pp.cpv(pkg.cpv)
+ if CONFIG['verbose']:
+ print " * Contents of %s:" % pp.cpv(str(pkg.cpv))
contents = pkg.get_contents()
display_files(filter_contents(contents))
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/hasuse.py b/pym/gentoolkit/equery/hasuse.py
index c110a22..82b2e29 100644
--- a/pym/gentoolkit/equery/hasuse.py
+++ b/pym/gentoolkit/equery/hasuse.py
@@ -15,11 +15,11 @@ __docformat__ = 'epytext'
import sys
from getopt import gnu_getopt, GetoptError
-import gentoolkit
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import do_lookup, get_installed_cpvs, print_sequence
-from gentoolkit.package import Package, PackageFormatter
+from gentoolkit import errors
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import do_lookup
+from gentoolkit.package import PackageFormatter
# =======
# Globals
@@ -41,7 +41,7 @@ QUERY_OPTS = {
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -61,8 +61,42 @@ def print_help(with_description=True):
))
+def display_useflags(query, pkg):
+ """Display USE flag information for a given package."""
+
+ try:
+ useflags = [x.lstrip("+-") for x in pkg.get_env_var("IUSE").split()]
+ except errors.GentoolkitFatalError:
+ # aux_get KeyError or other unexpected result
+ return
+
+ if query not in useflags:
+ return
+
+ if CONFIG['verbose']:
+ fmt_pkg = PackageFormatter(pkg, do_format=True)
+ else:
+ fmt_pkg = PackageFormatter(pkg, do_format=False)
+
+ if (QUERY_OPTS["includeInstalled"] and
+ not QUERY_OPTS["includePortTree"] and
+ not QUERY_OPTS["includeOverlayTree"]):
+ if not 'I' in fmt_pkg.location:
+ return
+ if (QUERY_OPTS["includePortTree"] and
+ not QUERY_OPTS["includeOverlayTree"]):
+ if not 'P' in fmt_pkg.location:
+ return
+ if (QUERY_OPTS["includeOverlayTree"] and
+ not QUERY_OPTS["includePortTree"]):
+ if not 'O' in fmt_pkg.location:
+ return
+ print fmt_pkg
+
+
+
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
# Parse module options
opts = (x[0] for x in module_opts)
@@ -89,7 +123,7 @@ def main(input_args):
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -112,33 +146,11 @@ def main(input_args):
if not first_run:
print
- if Config['verbose']:
+ 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
-
+ display_useflags(query, pkg)
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/list_.py b/pym/gentoolkit/equery/list_.py
index dd13029..32f8eff 100644
--- a/pym/gentoolkit/equery/list_.py
+++ b/pym/gentoolkit/equery/list_.py
@@ -17,8 +17,8 @@ from getopt import gnu_getopt, GetoptError
import gentoolkit
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import do_lookup, get_installed_cpvs
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import do_lookup, get_installed_cpvs
from gentoolkit.package import Package, PackageFormatter
# =======
@@ -26,14 +26,14 @@ from gentoolkit.package import Package, PackageFormatter
# =======
QUERY_OPTS = {
- "categoryFilter": None,
"duplicates": False,
"includeInstalled": True,
"includePortTree": False,
"includeOverlayTree": False,
"includeMasked": True,
+ "includeMaskReason": False,
"isRegex": False,
- "printMatchInfo": (not Config['quiet'])
+ "printMatchInfo": (not CONFIG['quiet'])
}
# =========
@@ -42,7 +42,7 @@ QUERY_OPTS = {
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -50,11 +50,16 @@ def print_help(with_description=True):
if with_description:
print __doc__.strip()
print
- # Deprecation warning added 04/09: djanderson
- pp.print_warn("Default action for this module has changed in Gentoolkit 0.3.")
- pp.print_warn("-e, --exact-name is now the default behavior.")
- pp.print_warn("Use globbing to simulate the old behavior (see man equery).")
- pp.print_warn("Use '*' to check all installed packages.")
+
+ # Deprecation warning added by djanderson, 12/2008
+ depwarning = (
+ "Default action for this module has changed in Gentoolkit 0.3.",
+ "Use globbing to simulate the old behavior (see man equery).",
+ "Use '*' to check all installed packages.",
+ "Use 'foo-bar/*' to filter by category."
+ )
+ for line in depwarning:
+ sys.stderr.write(pp.warn(line))
print
print mod_usage(mod_name="list")
@@ -62,9 +67,9 @@ def print_help(with_description=True):
print pp.command("options")
print format_options((
(" -h, --help", "display this help message"),
- (" -c, --category CAT", "only search in the category CAT"),
(" -d, --duplicates", "list only installed duplicate packages"),
(" -f, --full-regex", "query is a regular expression"),
+ (" -m, --mask-reason", "include reason for package mask"),
(" -I, --exclude-installed",
"exclude installed packages from output"),
(" -o, --overlay-tree", "list packages in overlays"),
@@ -78,10 +83,10 @@ def get_duplicates(matches):
dups = {}
result = []
for pkg in matches:
- if pkg.key in dups:
- dups[pkg.key].append(pkg)
+ if pkg.cp in dups:
+ dups[pkg.cp].append(pkg)
else:
- dups[pkg.key] = [pkg]
+ dups[pkg.cp] = [pkg]
for cpv in dups.values():
if len(cpv) > 1:
@@ -91,7 +96,7 @@ def get_duplicates(matches):
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
posargs = (x[1] for x in module_opts)
@@ -99,10 +104,6 @@ def parse_module_options(module_opts):
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
- elif opt in ('-a', '--all'):
- QUERY_OPTS['listAllPackages'] = True
- elif opt in ('-c', '--category'):
- QUERY_OPTS['categoryFilter'] = posarg
elif opt in ('-I', '--exclude-installed'):
QUERY_OPTS['includeInstalled'] = False
elif opt in ('-p', '--portage-tree'):
@@ -111,9 +112,13 @@ def parse_module_options(module_opts):
QUERY_OPTS['includeOverlayTree'] = True
elif opt in ('-f', '--full-regex'):
QUERY_OPTS['isRegex'] = True
+ elif opt in ('-m', '--mask-reason'):
+ QUERY_OPTS['includeMaskReason'] = True
elif opt in ('-e', '--exact-name'):
- pp.print_warn("-e, --exact-name is now default.")
- pp.print_warn("Use globbing to simulate the old behavior.")
+ sys.stderr.write(pp.warn("-e, --exact-name is now default."))
+ sys.stderr.write(
+ pp.warn("Use globbing to simulate the old behavior.")
+ )
print
elif opt in ('-d', '--duplicates'):
QUERY_OPTS['duplicates'] = True
@@ -122,18 +127,20 @@ def parse_module_options(module_opts):
def main(input_args):
"""Parse input and run the program"""
- short_opts = "hc:defiIop" # -i, -e were options for default actions
+ short_opts = "hdefiImop" # -i, -e were options for default actions
# 04/09: djanderson
+ # --all is no longer needed. Kept for compatibility.
# --installed is no longer needed. Kept for compatibility.
# --exact-name is no longer needed. Kept for compatibility.
- long_opts = ('help', 'all', 'category=', 'installed', 'exclude-installed',
- 'portage-tree', 'overlay-tree', 'full-regex', 'exact-name', 'duplicates')
+ long_opts = ('help', 'all', 'installed', 'exclude-installed',
+ 'mask-reason', 'portage-tree', 'overlay-tree', 'full-regex', 'exact-name',
+ 'duplicates')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -145,6 +152,7 @@ def main(input_args):
QUERY_OPTS["includeInstalled"] = True
QUERY_OPTS["includePortTree"] = False
QUERY_OPTS["includeOverlayTree"] = False
+ QUERY_OPTS["includeMaskReason"] = False
if not queries:
print_help()
@@ -168,10 +176,10 @@ def main(input_args):
#
for pkg in matches:
- if Config['verbose']:
- pkgstr = PackageFormatter(pkg, format=True)
+ if CONFIG['verbose']:
+ pkgstr = PackageFormatter(pkg, do_format=True)
else:
- pkgstr = PackageFormatter(pkg, format=False)
+ pkgstr = PackageFormatter(pkg, do_format=False)
if (QUERY_OPTS["includeInstalled"] and
not QUERY_OPTS["includePortTree"] and
@@ -188,4 +196,29 @@ def main(input_args):
continue
print pkgstr
+ if QUERY_OPTS["includeMaskReason"]:
+ ms_int, ms_orig = pkgstr.format_mask_status()
+ if not ms_int > 2:
+ # ms_int is a number representation of mask level.
+ # Only 2 and above are "hard masked" and have reasons.
+ continue
+ mask_reason = pkg.get_mask_reason()
+ if not mask_reason:
+ # Package not on system or not masked
+ continue
+ elif not any(mask_reason):
+ print " * No mask reason given"
+ else:
+ status = ', '.join(ms_orig)
+ explanation = mask_reason[0]
+ mask_location = mask_reason[1]
+ print " * Masked by %r" % status
+ print " * %s:" % mask_location
+ print '\n'.join(
+ [' * %s' % line.lstrip(' #')
+ for line in explanation.splitlines()]
+ )
+
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/meta.py b/pym/gentoolkit/equery/meta.py
index d847f56..fc38bff 100644
--- a/pym/gentoolkit/equery/meta.py
+++ b/pym/gentoolkit/equery/meta.py
@@ -9,7 +9,6 @@
# Move to Imports section after Python-2.6 is stable
from __future__ import with_statement
-__author__ = "Douglas Anderson"
__docformat__ = 'epytext'
# =======
@@ -17,18 +16,14 @@ __docformat__ = 'epytext'
# =======
import os
-import re
+import re
import sys
-import xml.etree.cElementTree as ET
from getopt import gnu_getopt, GetoptError
-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, print_sequence, print_file, \
- uniqify
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import find_packages, print_sequence, print_file
from gentoolkit.textwrap_ import TextWrapper
# =======
@@ -40,28 +35,23 @@ from gentoolkit.textwrap_ import TextWrapper
# pylint: disable-msg=E1101
QUERY_OPTS = {
- "current": False,
- "description": False,
- "herd": False,
- "maintainer": False,
- "useflags": False,
- "upstream": False,
- "xml": False
-}
-
-# Get the location of the main Portage tree
-PORTDIR = [settings["PORTDIR"] or os.path.join(os.sep, "usr", "portage")]
-# Check for overlays
-if settings["PORTDIR_OVERLAY"]:
- PORTDIR.extend(settings["PORTDIR_OVERLAY"].split())
+ 'current': False,
+ 'description': False,
+ 'herd': False,
+ 'keywords': False,
+ 'maintainer': False,
+ 'useflags': False,
+ 'upstream': False,
+ 'xml': False
+}
# =========
# Functions
# =========
-def print_help(with_description=True):
+def print_help(with_description=True, with_usage=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -69,83 +59,207 @@ def print_help(with_description=True):
if with_description:
print __doc__.strip()
print
- print mod_usage(mod_name="meta")
- print
+ if with_usage:
+ print mod_usage(mod_name="meta")
+ print
print pp.command("options")
print format_options((
(" -h, --help", "display this help message"),
- (" -c, --current", "parse metadata.xml in the current directory"),
(" -d, --description", "show an extended package description"),
(" -H, --herd", "show the herd(s) for the package"),
+ (" -k, --keywords", "show keywords for all matching package versions"),
(" -m, --maintainer", "show the maintainer(s) for the package"),
(" -u, --useflags", "show per-package USE flag descriptions"),
(" -U, --upstream", "show package's upstream information"),
- (" -x, --xml", "show the plain XML file")
+ (" -x, --xml", "show the plain metadata.xml file")
))
-def call_get_functions(metadata_path, package_dir, QUERY_OPTS):
+def format_herds(herds):
+ """Format herd information for display."""
+
+ result = []
+ for herd in herds:
+ herdstr = ''
+ email = "(%s)" % herd[1] if herd[1] else ''
+ herdstr = herd[0]
+ if CONFIG['verbose']:
+ herdstr += " %s" % (email,)
+ result.append(herdstr)
+
+ return result
+
+
+def format_maintainers(maints):
+ """Format maintainer information for display."""
+
+ result = []
+ for maint in maints:
+ maintstr = ''
+ maintstr = maint.email
+ if CONFIG['verbose']:
+ maintstr += " (%s)" % (maint.name,) if maint.name else ''
+ maintstr += "\n%s" % (maint.description,) \
+ if maint.description else ''
+ result.append(maintstr)
+
+ return result
+
+
+def format_upstream(upstream):
+ """Format upstream information for display."""
+
+ def _format_upstream_docs(docs):
+ result = []
+ for doc in docs:
+ doc_location = doc[0]
+ doc_lang = doc[1]
+ docstr = doc_location
+ if doc_lang is not None:
+ docstr += " (%s)" % (doc_lang,)
+ result.append(docstr)
+ return result
+
+ def _format_upstream_ids(ids):
+ result = []
+ for id_ in ids:
+ site = id_[0]
+ proj_id = id_[1]
+ idstr = "%s ID: %s" % (site, proj_id)
+ result.append(idstr)
+ return result
+
+ result = []
+ for up in upstream:
+ upmaints = format_maintainers(up.maintainers)
+ for upmaint in upmaints:
+ result.append(format_line(upmaint, "Maintainer: ", " " * 13))
+
+ for upchange in up.changelogs:
+ result.append(format_line(upchange, "ChangeLog: ", " " * 13))
+
+ updocs = _format_upstream_docs(up.docs)
+ for updoc in updocs:
+ result.append(format_line(updoc, "Docs: ", " " * 13))
+
+ for upbug in up.bugtrackers:
+ result.append(format_line(upbug, "Bugs-to: ", " " * 13))
+
+ upids = _format_upstream_ids(up.remoteids)
+ for upid in upids:
+ result.append(format_line(upid, "Remote-ID: ", " " * 13))
+
+ return result
+
+
+def format_useflags(useflags):
+ """Format USE flag information for display."""
+
+ result = []
+ for flag in useflags:
+ result.append(pp.useflag(flag.name))
+ result.append(flag.description)
+ result.append("")
+
+ return result
+
+
+def format_keywords(match):
+ """Format keywords information for display."""
+
+ kwsplit = match.get_env_var('KEYWORDS').split()
+ ver = match.cpv.fullversion
+ keywords = ''
+ for kw in kwsplit:
+ if kw.startswith('~'):
+ keywords += " %s" % pp.useflag(kw, enabled=True)
+ else:
+ keywords += " %s" % pp.useflag(kw, enabled=False)
+
+ if CONFIG['verbose']:
+ result = format_line(
+ keywords, "%s: " % pp.cpv(ver), " " * (len(ver) + 2)
+ )
+ else:
+ result = "%s:%s" % (ver, keywords)
+
+ return result
+
+# R0912: *Too many branches (%s/%s)*
+# pylint: disable-msg=R0912
+def call_format_functions(matches):
"""Call information gathering functions and display the results."""
-
- if Config['verbose']:
- print get_overlay_name(package_dir)
- try:
- xml_tree = ET.parse(metadata_path)
- except IOError:
- pp.print_error("No metadata available")
- first_run = False
- return
+ # Choose a good package to reference metadata from
+ ref_pkg = get_reference_pkg(matches)
+
+ if CONFIG['verbose']:
+ repo = ref_pkg.get_repo_name()
+ print " * %s [%s]" % (pp.cpv(ref_pkg.cpv.cp), pp.section(repo))
got_opts = False
- if (QUERY_OPTS["herd"] or QUERY_OPTS["description"] or
- QUERY_OPTS["useflags"] or QUERY_OPTS["maintainer"] or
- QUERY_OPTS["upstream"] or QUERY_OPTS["xml"]):
+ if any(QUERY_OPTS.values()):
# Specific information requested, less formatting
got_opts = True
+ if not got_opts:
+ pkg_loc = ref_pkg.get_package_path()
+ print format_line(pkg_loc, "Location: ", " " * 13)
+
if QUERY_OPTS["herd"] or not got_opts:
- herd = get_herd(xml_tree)
+ herds = format_herds(ref_pkg.metadata.get_herds(include_email=True))
if QUERY_OPTS["herd"]:
- herd = format_list(herd)
+ print_sequence(format_list(herds))
else:
- herd = format_list(herd, "Herd: ", " " * 13)
- print_sequence(herd)
+ for herd in herds:
+ print format_line(herd, "Herd: ", " " * 13)
if QUERY_OPTS["maintainer"] or not got_opts:
- maint = get_maitainer(xml_tree)
+ maints = format_maintainers(ref_pkg.metadata.get_maintainers())
if QUERY_OPTS["maintainer"]:
- maint = format_list(maint)
+ print_sequence(format_list(maints))
else:
- maint = format_list(maint, "Maintainer: ", " " * 13)
- print_sequence(maint)
+ if not maints:
+ print format_line([], "Maintainer: ", " " * 13)
+ else:
+ for maint in maints:
+ print format_line(maint, "Maintainer: ", " " * 13)
if QUERY_OPTS["upstream"] or not got_opts:
- upstream = get_upstream(xml_tree)
+ upstream = format_upstream(ref_pkg.metadata.get_upstream())
if QUERY_OPTS["upstream"]:
upstream = format_list(upstream)
else:
upstream = format_list(upstream, "Upstream: ", " " * 13)
print_sequence(upstream)
+ if QUERY_OPTS["keywords"] or not got_opts:
+ for match in matches:
+ kwds = format_keywords(match)
+ if QUERY_OPTS["keywords"]:
+ print kwds
+ else:
+ indent = " " * (15 + len(match.cpv.fullversion))
+ print format_line(kwds, "Keywords: ", indent)
+
if QUERY_OPTS["description"]:
- desc = get_description(xml_tree)
+ desc = ref_pkg.metadata.get_descriptions()
print_sequence(format_list(desc))
if QUERY_OPTS["useflags"]:
- useflags = get_useflags(xml_tree)
+ useflags = format_useflags(ref_pkg.metadata.get_useflags())
print_sequence(format_list(useflags))
if QUERY_OPTS["xml"]:
- print_file(metadata_path)
+ print_file(os.path.join(ref_pkg.get_package_path(), 'metadata.xml'))
def format_line(line, first="", subsequent="", force_quiet=False):
"""Wrap a string at word boundaries and optionally indent the first line
and/or subsequent lines with custom strings.
- Preserve newlines if the longest line is not longer than
- Config['termWidth']. To force the preservation of newlines and indents,
+ Preserve newlines if the longest line is not longer than
+ CONFIG['termWidth']. To force the preservation of newlines and indents,
split the string into a list and feed it to format_line via format_list.
@see: format_list()
@@ -161,7 +275,7 @@ def format_line(line, first="", subsequent="", force_quiet=False):
"""
if line:
- line = line.expandtabs().strip("\n").splitlines()
+ line = line.expandtabs().strip("\n").splitlines()
else:
if force_quiet:
return
@@ -172,18 +286,18 @@ def format_line(line, first="", subsequent="", force_quiet=False):
wider_indent = first
else:
wider_indent = subsequent
-
+
widest_line_len = len(max(line, key=len)) + len(wider_indent)
-
- if widest_line_len > Config['termWidth']:
- twrap = TextWrapper(width=Config['termWidth'], expand_tabs=False,
+
+ if widest_line_len > CONFIG['termWidth']:
+ twrap = TextWrapper(width=CONFIG['termWidth'], expand_tabs=False,
initial_indent=first, subsequent_indent=subsequent)
line = " ".join(line)
line = re.sub("\s+", " ", line)
line = line.lstrip()
result = twrap.fill(line)
else:
- # line will fit inside Config['termWidth'], so preserve whitespace and
+ # line will fit inside CONFIG['termWidth'], so preserve whitespace and
# newlines
line[0] = first + line[0] # Avoid two newlines if len == 1
@@ -212,7 +326,7 @@ def format_list(lst, first="", subsequent="", force_quiet=False):
@type subsequent: string
@param subsequent: text to prepend to subsequent lines
@rtype: list
- @return: list with element text wrapped at Config['termWidth']
+ @return: list with element text wrapped at CONFIG['termWidth']
"""
result = []
@@ -229,7 +343,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 Config['verbose']:
+ if CONFIG['verbose']:
if force_quiet:
result = None
else:
@@ -239,238 +353,34 @@ def format_list(lst, first="", subsequent="", force_quiet=False):
return result
-def get_herd(xml_tree):
- """Return a list of text nodes for <herd>."""
-
- result = []
- for elem in xml_tree.findall("herd"):
- herd_mail = get_herd_email(elem.text)
- if herd_mail and Config['verbose']:
- result.append("%s (%s)" % (elem.text, herd_mail))
- else:
- result.append(elem.text)
+def get_reference_pkg(matches):
+ """Find a package in the Portage tree to reference."""
- return result
-
-
-def get_herd_email(herd):
- """Return the email of the given herd if it's in herds.xml, else None."""
-
- herds_path = os.path.join(PORTDIR[0], "metadata/herds.xml")
-
- try:
- herds_tree = ET.parse(herds_path)
- except IOError, err:
- pp.print_error(str(err))
- return None
-
- # Some special herds are not listed in herds.xml
- if herd in ('no-herd', 'maintainer-wanted', 'maintainer-needed'):
- return None
-
- for node in herds_tree.getiterator("herd"):
- if node.findtext("name") == herd:
- return node.findtext("email")
-
-
-def get_description(xml_tree):
- """Return a list of text nodes for <longdescription>.
-
- @todo: Support the `lang' attribute
- """
-
- return [e.text for e in xml_tree.findall("longdescription")]
-
-
-def get_maitainer(xml_tree):
- """Return a parsable tree of all maintainer elements and sub-elements."""
-
- first_run = True
- result = []
- for node in xml_tree.findall("maintainer"):
- if not first_run:
- result.append("")
- restrict = node.get("restrict")
- if restrict:
- result.append("(%s %s)" %
- (pp.emph("Restrict to"), pp.output.green(restrict)))
- result.extend(e.text for e in node)
- first_run = False
-
- return result
-
-
-def get_overlay_name(p_dir):
- """Determine the overlay name and return a formatted string."""
-
- result = []
- cat_pkg = '/'.join(p_dir.split('/')[-2:])
- result.append(" * %s" % pp.cpv(cat_pkg))
- o_dir = '/'.join(p_dir.split('/')[:-2])
- if o_dir != PORTDIR[0]:
- # o_dir is an overlay
- o_name = o_dir.split('/')[-1]
- o_name = ("[", o_name, "]")
- result.append(pp.output.turquoise("".join(o_name)))
-
- return ' '.join(result)
-
-
-def get_package_directory(query):
- """Find a package's portage directory."""
-
- matches = find_packages(query, include_masked=True)
- # Prefer a package that's in the Portage tree over one in an
- # overlay. Start with oldest first.
pkg = None
while list(reversed(matches)):
pkg = matches.pop()
if not pkg.is_overlay():
break
-
- return pkg.get_package_path() if pkg else None
-
-
-def get_useflags(xml_tree):
- """Return a list of formatted <useflag> lines, including blank elements
- where blank lines should be printed."""
-
- first_run = True
- result = []
- for node in xml_tree.getiterator("flag"):
- if not first_run:
- result.append("")
- flagline = pp.useflag(node.get("name"))
- restrict = node.get("restrict")
- if restrict:
- result.append("%s (%s %s)" %
- (flagline, pp.emph("Restrict to"), pp.output.green(restrict)))
- else:
- result.append(flagline)
- # ElementTree handles nested element text in a funky way.
- # So we need to dump the raw XML and parse it manually.
- flagxml = ET.tostring(node)
- flagxml = re.sub("\s+", " ", flagxml)
- flagxml = re.sub("\n\t", "", flagxml)
- flagxml = re.sub("<(pkg|cat)>(.*?)</(pkg|cat)>",
- pp.cpv(r"\2"), flagxml)
- flagtext = re.sub("<.*?>", "", flagxml)
- result.append(flagtext)
- first_run = False
-
- return result
-
-
-def _get_upstream_bugtracker(node):
- """Extract and format upstream bugtracker information."""
-
- bt_loc = [e.text for e in node.findall("bugs-to")]
-
- return format_list(bt_loc, "Bugs to: ", " " * 12, force_quiet=True)
-
-
-def _get_upstream_changelog(node):
- """Extract and format upstream changelog information."""
-
- cl_paths = [e.text for e in node.findall("changelog")]
-
- return format_list(cl_paths, "Changelog: ", " " * 12, force_quiet=True)
-
-
-def _get_upstream_documentation(node):
- """Extract and format upstream documentation information."""
-
- doc = []
- for elem in node.findall("doc"):
- lang = elem.get("lang")
- if lang:
- lang = "(%s)" % pp.output.yellow(lang)
- else:
- lang = ""
- doc.append(" ".join([elem.text, lang]))
-
- return format_list(doc, "Docs: ", " " * 12, force_quiet=True)
-
-def _get_upstream_maintainer(node):
- """Extract and format upstream maintainer information."""
-
- maintainer = node.findall("maintainer")
- maint = []
- for elem in maintainer:
- 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":
- maint.append("(%s)" % pp.output.green("active"))
- elif elem.get("status") == "inactive":
- 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)
-
-
-def _get_upstream_remoteid(node):
- """Extract and format upstream remote ID."""
-
- r_id = [e.get("type") + ": " + e.text for e in node.findall("remote-id")]
-
- return format_list(r_id, "Remote ID: ", " " * 12, force_quiet=True)
-
-
-def get_upstream(xml_tree):
- """Return a list of formatted <upstream> lines, including blank elements
- where blank lines should be printed."""
-
- first_run = True
- result = []
- for node in xml_tree.findall("upstream"):
- if not first_run:
- result.append("")
-
- maint = _get_upstream_maintainer(node)
- if maint:
- result.append("\n".join(maint))
-
- changelog = _get_upstream_changelog(node)
- if changelog:
- result.append("\n".join(changelog))
-
- documentation = _get_upstream_documentation(node)
- if documentation:
- result.append("\n".join(documentation))
-
- bugs_to = _get_upstream_bugtracker(node)
- if bugs_to:
- result.append("\n".join(bugs_to))
-
- remote_id = _get_upstream_remoteid(node)
- if remote_id:
- result.append("\n".join(remote_id))
-
- first_run = False
-
- return result
+ return pkg
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
for opt in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
- elif opt in ('-c', '--current'):
- QUERY_OPTS["current"] = True
elif opt in ('-d', '--description'):
QUERY_OPTS["description"] = True
elif opt in ('-H', '--herd'):
QUERY_OPTS["herd"] = True
elif opt in ('-m', '--maintainer'):
QUERY_OPTS["maintainer"] = True
+ elif opt in ('-k', '--keywords'):
+ QUERY_OPTS["keywords"] = True
elif opt in ('-u', '--useflags'):
QUERY_OPTS["useflags"] = True
elif opt in ('-U', '--upstream'):
@@ -482,44 +392,37 @@ def parse_module_options(module_opts):
def main(input_args):
"""Parse input and run the program."""
- short_opts = "hcdHmuUx"
- long_opts = ('help', 'current', 'description', 'herd', 'maintainer',
+ short_opts = "hdHkmuUx"
+ long_opts = ('help', 'description', 'herd', 'keywords', 'maintainer',
'useflags', 'upstream', 'xml')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
+
# Find queries' Portage directory and throw error if invalid
- if not queries and not QUERY_OPTS["current"]:
+ if not queries:
print_help()
sys.exit(2)
-
- if QUERY_OPTS["current"]:
- package_dir = os.getcwd()
- metadata_path = os.path.join(package_dir, "metadata.xml")
- call_get_functions(metadata_path, package_dir, QUERY_OPTS)
- else:
- first_run = True
- for query in queries:
- package_dir = get_package_directory(query)
- if not package_dir:
- raise errors.GentoolkitNoMatches(query)
- metadata_path = os.path.join(package_dir, "metadata.xml")
-
- # --------------------------------
- # Check options and call functions
- # --------------------------------
-
- if not first_run:
- print
-
- call_get_functions(metadata_path, package_dir, QUERY_OPTS)
-
- first_run = False
+
+ first_run = True
+ for query in queries:
+ matches = find_packages(query, include_masked=True)
+ if not matches:
+ raise errors.GentoolkitNoMatches(query)
+
+ if not first_run:
+ print
+
+ matches.sort()
+ call_format_functions(matches)
+
+ first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/size.py b/pym/gentoolkit/equery/size.py
index 9cb6bc9..8570a6a 100644
--- a/pym/gentoolkit/equery/size.py
+++ b/pym/gentoolkit/equery/size.py
@@ -16,16 +16,15 @@ import sys
from getopt import gnu_getopt, GetoptError
import gentoolkit.pprinter as pp
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import do_lookup
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import do_lookup
# =======
# Globals
# =======
QUERY_OPTS = {
- "categoryFilter": None,
- "includeInstalled": False,
+ "includeInstalled": True,
"includePortTree": False,
"includeOverlayTree": False,
"includeMasked": True,
@@ -41,7 +40,7 @@ QUERY_OPTS = {
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -50,11 +49,15 @@ def print_help(with_description=True):
print __doc__.strip()
print
- # Deprecation warning added 04/09: djanderson
- pp.print_warn("Default action for this module has changed in Gentoolkit 0.3.")
- pp.print_warn("-e, --exact-name is now the default behavior.")
- pp.print_warn("Use globbing to simulate the old behavior (see man equery).")
- pp.print_warn("Use '*' to check all installed packages.")
+ # Deprecation warning added by djanderson, 12/2008
+ depwarning = (
+ "Default action for this module has changed in Gentoolkit 0.3.",
+ "Use globbing to simulate the old behavior (see man equery).",
+ "Use '*' to check all installed packages.",
+ "Use 'foo-bar/*' to filter by category."
+ )
+ for line in depwarning:
+ sys.stderr.write(pp.warn(line))
print
print mod_usage(mod_name="size")
@@ -63,7 +66,6 @@ def print_help(with_description=True):
print format_options((
(" -h, --help", "display this help message"),
(" -b, --bytes", "report size in bytes"),
- (" -c, --category CAT", "only search in the category CAT"),
(" -f, --full-regex", "query is a regular expression")
))
@@ -76,14 +78,14 @@ def display_size(match_set):
"""
for pkg in match_set:
- (size, files, uncounted) = pkg.size()
+ size, files, uncounted = pkg.get_size()
- if Config['verbose']:
- print " * %s" % pp.cpv(pkg.cpv)
+ if CONFIG['verbose']:
+ print " * %s" % pp.cpv(str(pkg.cpv))
print "Total files : %s".rjust(25) % pp.number(str(files))
if uncounted:
- pp.print_info(0, "Inaccessible files : %s".rjust(25) %
+ print ("Inaccessible files : %s".rjust(25) %
pp.number(str(uncounted)))
if QUERY_OPTS["sizeInBytes"]:
@@ -91,10 +93,10 @@ def display_size(match_set):
else:
size_str = "%s %s" % format_bytes(size)
- pp.print_info(0, "Total size : %s".rjust(25) % size_str)
+ print "Total size : %s".rjust(25) % size_str
else:
info = "%s: total(%d), inaccessible(%d), size(%s)"
- print info % (pkg.cpv, files, uncounted, size)
+ print info % (str(pkg.cpv), files, uncounted, size)
def format_bytes(bytes_, precision=2):
@@ -134,21 +136,19 @@ def format_bytes(bytes_, precision=2):
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
- posargs = (x[1] for x in module_opts)
- for opt, posarg in zip(opts, posargs):
+ for opt in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit(0)
elif opt in ('-b', '--bytes'):
QUERY_OPTS["sizeInBytes"] = True
- elif opt in ('-c', '--category'):
- QUERY_OPTS['categoryFilter'] = posarg
elif opt in ('-e', '--exact-name'):
- pp.print_warn("-e, --exact-name is now default.")
- pp.print_warn("Use globbing to simulate the old behavior.")
+ sys.stderr.write(pp.warn("-e, --exact-name is now default."))
+ warning = pp.warn("Use globbing to simulate the old behavior.")
+ sys.stderr.write(warning)
print
elif opt in ('-f', '--full-regex'):
QUERY_OPTS['isRegex'] = True
@@ -159,30 +159,22 @@ def main(input_args):
# -e, --exact-name is no longer needed. Kept for compatibility.
# 04/09 djanderson
- short_opts = "hbc:fe"
- long_opts = ('help', 'bytes', 'category=', 'full-regex', 'exact-name')
+ short_opts = "hbfe"
+ long_opts = ('help', 'bytes', 'full-regex', 'exact-name')
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
parse_module_options(module_opts)
-
- if not queries and not QUERY_OPTS["includeInstalled"]:
+
+ if not queries:
print_help()
sys.exit(2)
- elif queries and not QUERY_OPTS["includeInstalled"]:
- QUERY_OPTS["includeInstalled"] = True
- elif QUERY_OPTS["includeInstalled"]:
- queries = ["*"]
-
- #
- # Output
- #
first_run = True
for query in queries:
@@ -192,8 +184,10 @@ def main(input_args):
matches = do_lookup(query, QUERY_OPTS)
if not matches:
- pp.print_error("No package found matching %s" % query)
+ sys.stderr.write(pp.error("No package found matching %s" % query))
display_size(matches)
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/uses.py b/pym/gentoolkit/equery/uses.py
index 56fc78b..88a7a87 100644
--- a/pym/gentoolkit/equery/uses.py
+++ b/pym/gentoolkit/equery/uses.py
@@ -16,20 +16,17 @@ __docformat__ = 'epytext'
# =======
import os
-import re
import sys
+from functools import partial
from getopt import gnu_getopt, GetoptError
from glob import glob
-import xml.etree.cElementTree as ET
-from portage.util import unique_array
+from portage import settings
-import gentoolkit
import gentoolkit.pprinter as pp
from gentoolkit import errors
-from gentoolkit.equery import format_options, mod_usage, Config
-from gentoolkit.helpers2 import compare_package_strings, find_best_match, \
- find_packages
+from gentoolkit.equery import format_options, mod_usage, CONFIG
+from gentoolkit.helpers import find_best_match, find_packages, uniqify
from gentoolkit.textwrap_ import TextWrapper
# =======
@@ -44,7 +41,7 @@ QUERY_OPTS = {"allVersions" : False}
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -66,29 +63,29 @@ def display_useflags(output):
@type output: list
@param output: [(inuse, inused, flag, desc, restrict), ...]
- inuse (int) = 0 or 1; if 1, flag is set in make.conf
- inused (int) = 0 or 1; if 1, package is installed with flag enabled
- flag (str) = the name of the USE flag
- desc (str) = the flag's description
- restrict (str) = corresponds to the text of restrict in metadata
+ inuse (int) = 0 or 1; if 1, flag is set in make.conf
+ inused (int) = 0 or 1; if 1, package is installed with flag enabled
+ flag (str) = the name of the USE flag
+ desc (str) = the flag's description
+ restrict (str) = corresponds to the text of restrict in metadata
"""
maxflag_len = len(max([t[2] for t in output], key=len))
twrap = TextWrapper()
- twrap.width = Config['termWidth']
+ twrap.width = CONFIG['termWidth']
twrap.subsequent_indent = " " * (maxflag_len + 8)
markers = ("-", "+")
- color = [pp.useflagoff, pp.useflagon]
+ color = [pp.useflag, partial(pp.useflag, enabled=True)]
for in_makeconf, in_installed, flag, desc, restrict in output:
- if Config['verbose']:
+ if CONFIG['verbose']:
flag_name = ""
if in_makeconf != in_installed:
- flag_name += pp.emph(" %s %s" %
+ flag_name += pp.emph(" %s %s" %
(markers[in_makeconf], markers[in_installed]))
else:
- flag_name += (" %s %s" %
+ flag_name += (" %s %s" %
(markers[in_makeconf], markers[in_installed]))
flag_name += " " + color[in_makeconf](flag.ljust(maxflag_len))
@@ -96,7 +93,7 @@ def display_useflags(output):
# print description
if restrict:
- restrict = "(%s %s)" % (pp.emph("Restricted to"),
+ restrict = "(%s %s)" % (pp.emph("Restricted to"),
pp.cpv(restrict))
twrap.initial_indent = flag_name
print twrap.fill(restrict)
@@ -128,8 +125,7 @@ def get_global_useflags():
global_usedesc = {}
# Get global USE flag descriptions
try:
- path = os.path.join(gentoolkit.settings["PORTDIR"], 'profiles',
- 'use.desc')
+ path = os.path.join(settings["PORTDIR"], 'profiles', 'use.desc')
with open(path) as open_file:
for line in open_file:
if line.startswith('#'):
@@ -139,13 +135,16 @@ def get_global_useflags():
if len(fields) == 2:
global_usedesc[fields[0]] = fields[1].rstrip()
except IOError:
- pp.print_warn("Could not load USE flag descriptions from %s" %
- pp.path(path))
+ sys.stderr.write(
+ pp.warn(
+ "Could not load USE flag descriptions from %s" % pp.path(path)
+ )
+ )
del path, open_file
# Add USE_EXPANDED variables to usedesc hash -- Bug #238005
- for path in glob(os.path.join(gentoolkit.settings["PORTDIR"],
- 'profiles', 'desc', '*.desc')):
+ for path in glob(os.path.join(settings["PORTDIR"],
+ 'profiles', 'desc', '*.desc')):
try:
with open(path) as open_file:
for line in open_file:
@@ -157,49 +156,13 @@ def get_global_useflags():
(path.split("/")[-1][0:-5], fields[0])
global_usedesc[expanded_useflag] = fields[1]
except IOError:
- pp.print_warn("Could not load USE flag descriptions from %s" %
- path)
+ sys.stderr.write(
+ pp.warn("Could not load USE flag descriptions from %s" % path)
+ )
return global_usedesc
-def get_local_useflags(pkg):
- """Parse package-specific flag descriptions from a package's metadata.xml.
-
- @see: http://www.gentoo.org/proj/en/glep/glep-0056.html
- @type pkg: gentoolkit.package.Package
- @param pkg: the package to find useflags for
- @rtype: dict
- @return: {string: tuple}
- string = flag's name
- tuple = (description, restrictions)
- """
-
- result = {}
-
- metadata = os.path.join(pkg.get_package_path(), 'metadata.xml')
- try:
- xml_tree = ET.parse(metadata)
- except IOError:
- pp.print_error("Could not open %s" % metadata)
- return result
-
- for node in xml_tree.getiterator("flag"):
- name = node.get("name")
- restrict = node.get("restrict")
- # ElementTree handles nested element text in a funky way.
- # So we need to dump the raw XML and parse it manually.
- flagxml = ET.tostring(node)
- flagxml = re.sub("\s+", " ", flagxml)
- flagxml = re.sub("\n\t", "", flagxml)
- flagxml = re.sub("<(pkg|cat)>([^<]*)</(pkg|cat)>",
- pp.cpv("%s" % r"\2"), flagxml)
- flagtext = re.sub("<.*?>", "", flagxml)
- result[name] = (flagtext, restrict)
-
- return result
-
-
def get_matches(query):
"""Get packages matching query."""
@@ -208,24 +171,24 @@ def get_matches(query):
if None in matches:
matches = find_packages(query, include_masked=False)
if matches:
- matches = sorted(matches, compare_package_strings)[-1:]
+ matches.sort()
else:
matches = find_packages(query, include_masked=True)
if not matches:
raise errors.GentoolkitNoMatches(query)
-
+
return matches
def get_output_descriptions(pkg, global_usedesc):
"""Prepare descriptions and usage information for each USE flag."""
- local_usedesc = get_local_useflags(pkg)
+ local_usedesc = pkg.metadata.get_useflags()
iuse = pkg.get_env_var("IUSE")
if iuse:
- usevar = unique_array([x.lstrip('+-') for x in iuse.split()])
+ usevar = uniqify([x.lstrip('+-') for x in iuse.split()])
usevar.sort()
else:
usevar = []
@@ -233,23 +196,32 @@ def get_output_descriptions(pkg, global_usedesc):
if pkg.is_installed():
used_flags = pkg.get_use_flags().split()
else:
- used_flags = gentoolkit.settings["USE"].split()
+ used_flags = settings["USE"].split()
# store (inuse, inused, flag, desc, restrict)
output = []
for flag in usevar:
inuse = False
inused = False
+
+ local_use = None
+ for use in local_usedesc:
+ if use.name == flag:
+ local_use = use
+ break
+
try:
- desc = local_usedesc[flag][0]
- except KeyError:
+ desc = local_use.description
+ except AttributeError:
try:
desc = global_usedesc[flag]
except KeyError:
desc = ""
+
try:
- restrict = local_usedesc[flag][1]
- except KeyError:
+ restrict = local_use.restrict
+ restrict = restrict if restrict is not None else ""
+ except AttributeError:
restrict = ""
if flag in pkg.get_settings("USE").split():
@@ -258,12 +230,12 @@ def get_output_descriptions(pkg, global_usedesc):
inused = True
output.append((inuse, inused, flag, desc, restrict))
-
+
return output
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
for opt in opts:
@@ -274,13 +246,13 @@ def parse_module_options(module_opts):
QUERY_OPTS['allVersions'] = True
-def print_legend(query):
+def print_legend():
"""Print a legend to explain the output format."""
print "[ Legend : %s - flag is set in make.conf ]" % pp.emph("U")
print "[ : %s - package is installed with flag ]" % pp.emph("I")
print "[ Colors : %s, %s ]" % (
- pp.useflagon("set"), pp.useflagoff("unset"))
+ pp.useflag("set", enabled=True), pp.useflag("unset", enabled=False))
def main(input_args):
@@ -292,7 +264,7 @@ def main(input_args):
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -312,7 +284,7 @@ def main(input_args):
if not first_run:
print
- if Config['verbose']:
+ if CONFIG['verbose']:
print " * Searching for %s ..." % pp.pkgquery(query)
matches = get_matches(query)
@@ -323,15 +295,18 @@ def main(input_args):
output = get_output_descriptions(pkg, global_usedesc)
if output:
- if Config['verbose']:
- print_legend(query)
+ if CONFIG['verbose']:
+ print_legend()
print (" * Found these USE flags for %s:" %
- pp.cpv(pkg.cpv))
+ pp.cpv(str(pkg.cpv)))
print pp.emph(" U I")
display_useflags(output)
else:
- if Config['verbose']:
- pp.print_warn("No USE flags found for %s" %
- pp.cpv(pkg.cpv))
+ if CONFIG['verbose']:
+ sys.stderr.write(
+ pp.warn("No USE flags found for %s" % pp.cpv(pkg.cpv))
+ )
first_run = False
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/equery/which.py b/pym/gentoolkit/equery/which.py
index 828dae1..ada3fde 100644
--- a/pym/gentoolkit/equery/which.py
+++ b/pym/gentoolkit/equery/which.py
@@ -21,7 +21,7 @@ from getopt import gnu_getopt, GetoptError
import gentoolkit.pprinter as pp
from gentoolkit import errors
from gentoolkit.equery import format_options, mod_usage
-from gentoolkit.helpers2 import find_packages
+from gentoolkit.helpers import find_packages
# =======
# Globals
@@ -35,7 +35,7 @@ QUERY_OPTS = {"includeMasked": False}
def print_help(with_description=True):
"""Print description, usage and a detailed help message.
-
+
@type with_description: bool
@param with_description: if true, print module's __doc__ string
"""
@@ -53,7 +53,7 @@ def print_help(with_description=True):
def parse_module_options(module_opts):
- """Parse module options and update GLOBAL_OPTS"""
+ """Parse module options and update QUERY_OPTS"""
opts = (x[0] for x in module_opts)
for opt in opts:
@@ -73,7 +73,7 @@ def main(input_args):
try:
module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
except GetoptError, err:
- pp.print_error("Module %s" % err)
+ sys.stderr.write(pp.error("Module %s" % err))
print
print_help(with_description=False)
sys.exit(2)
@@ -93,6 +93,10 @@ def main(input_args):
if ebuild_path:
print os.path.normpath(ebuild_path)
else:
- pp.print_warn("No ebuilds to satisfy %s" % pkg.name)
+ sys.stderr.write(
+ pp.warn("No ebuilds to satisfy %s" % pkg.name)
+ )
else:
raise errors.GentoolkitNoMatches(query)
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/errors.py b/pym/gentoolkit/errors.py
index 9843b6b..988d91c 100644
--- a/pym/gentoolkit/errors.py
+++ b/pym/gentoolkit/errors.py
@@ -4,32 +4,25 @@
"""Exception classes for gentoolkit"""
-__all__ = [
- 'FatalError',
+__all__ = (
'GentoolkitException',
+ 'GentoolkitFatalError',
+ 'GentoolkitAmbiguousPackage',
'GentoolkitInvalidAtom',
'GentoolkitInvalidCategory',
- 'GentoolkitInvalidPackageName',
+ 'GentoolkitInvalidPackage',
'GentoolkitInvalidCPV',
'GentoolkitInvalidRegex',
'GentoolkitInvalidVersion',
'GentoolkitNoMatches'
-]
-
-# =======
-# Imports
-# =======
-
-import sys
-
-import gentoolkit.pprinter as pp
+)
# ==========
# Exceptions
# ==========
class GentoolkitException(Exception):
- """Base class for gentoolkit exceptions"""
+ """Base class for gentoolkit exceptions."""
def __init__(self):
pass
@@ -37,64 +30,85 @@ class GentoolkitException(Exception):
class GentoolkitFatalError(GentoolkitException):
"""A fatal error occurred. Usually used to catch Portage exceptions."""
def __init__(self, err):
- pp.print_error("Fatal error: %s" % err)
- sys.exit(2)
+ self.err = err
+
+ def __str__(self):
+ return "Fatal error: %s" % self.err
+
+
+class GentoolkitAmbiguousPackage(GentoolkitException):
+ """Got an ambiguous package name."""
+ def __init__(self, choices):
+ self.choices = choices
+
+ def __str__(self):
+ choices = '\n'.join(" %s" % x for x in self.choices)
+ return '\n'.join(("Ambiguous package name. Choose from:", choices))
class GentoolkitInvalidAtom(GentoolkitException):
- """Got a malformed package atom"""
+ """Got a malformed package atom."""
def __init__(self, atom):
- pp.print_error("Invalid atom: '%s'" % atom)
- sys.exit(2)
+ self.atom = atom
+
+ def __str__(self):
+ return "Invalid atom: '%s'" % self.atom
class GentoolkitInvalidCategory(GentoolkitException):
- """The category was not listed in portage.settings.categories"""
+ """The category was not listed in portage.settings.categories."""
def __init__(self, category):
- pp.print_error("Invalid category: '%s'" % category)
- if not category:
- pp.print_error("Try --category=cat1,cat2 with no spaces.")
- sys.exit(2)
+ self.category = category
+ def __str__(self):
+ return "Invalid category: '%s'" % self.category
-class GentoolkitInvalidPackageName(GentoolkitException):
- """Got an unknown package name"""
+
+class GentoolkitInvalidPackage(GentoolkitException):
+ """Got an unknown or invalid package."""
def __init__(self, package):
- pp.print_error("Invalid package name: '%s'" % package)
- sys.exit(2)
+ self.package = package
+
+ def __str__(self):
+ return "Invalid package: '%s'" % self.package
class GentoolkitInvalidCPV(GentoolkitException):
- """Got an unknown package name"""
+ """Got an invalid category/package-ver string."""
def __init__(self, cpv):
- pp.print_error("Invalid CPV: '%s'" % cpv)
- sys.exit(2)
+ self.cpv = cpv
+
+ def __str__(self):
+ return "Invalid CPV: '%s'" % self.cpv
class GentoolkitInvalidRegex(GentoolkitException):
- """The regex could not be compiled"""
+ """The regex could not be compiled."""
def __init__(self, regex):
- pp.print_error("Invalid regex: '%s'" % regex)
- sys.exit(2)
+ self.regex = regex
+
+ def __str__(self):
+ return "Invalid regex: '%s'" % self.regex
class GentoolkitInvalidVersion(GentoolkitException):
- """Got a malformed version"""
+ """Got a malformed version."""
def __init__(self, version):
- pp.print_error("Malformed version: '%s'" % version)
- sys.exit(2)
+ self.version = version
+
+ def __str__(self):
+ return "Malformed version: '%s'" % self.version
class GentoolkitNoMatches(GentoolkitException):
- """No packages were found matching the search query"""
- def __init__(self, query):
- pp.print_error("No packages matching '%s'" % query)
- sys.exit(2)
-
-
-# XXX: Deprecated
-class FatalError:
- def __init__(self, s):
- self._message = s
- def get_message(self):
- return self._message
+ """No packages were found matching the search query."""
+ def __init__(self, query, in_installed=False):
+ self.query = query
+ self.in_installed = in_installed
+
+ def __str__(self):
+ inst = 'installed ' if self.in_installed else ''
+ return "No %spackages matching '%s'" % (inst, self.query)
+
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/glsa/__init__.py b/pym/gentoolkit/glsa/__init__.py
index 2efb008..11b7dbe 100644
--- a/pym/gentoolkit/glsa/__init__.py
+++ b/pym/gentoolkit/glsa/__init__.py
@@ -33,7 +33,7 @@ except ImportError:
import portage
# Note: the space for rgt and rlt is important !!
-opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=",
+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
@@ -42,7 +42,7 @@ def center(text, width):
"""
Returns a string containing I{text} that is padded with spaces on both
sides. If C{len(text) >= width} I{text} is returned unchanged.
-
+
@type text: String
@param text: the text to be embedded
@type width: Integer
@@ -65,15 +65,15 @@ def center(text, width):
def wrap(text, width, caption=""):
"""
Wraps the given text at column I{width}, optionally indenting
- it so that no text is under I{caption}. It's possible to encode
+ it so that no text is under I{caption}. It's possible to encode
hard linebreaks in I{text} with L{NEWLINE_ESCAPE}.
-
+
@type text: String
@param text: the text to be wrapped
@type width: Integer
@param width: the column at which the text should be wrapped
@type caption: String
- @param caption: this string is inserted at the beginning of the
+ @param caption: this string is inserted at the beginning of the
return value and the paragraph is indented up to
C{len(caption)}.
@rtype: String
@@ -84,7 +84,7 @@ def wrap(text, width, caption=""):
text = text.replace(2*NEWLINE_ESCAPE, NEWLINE_ESCAPE+" "+NEWLINE_ESCAPE)
words = text.split()
indentLevel = len(caption)+1
-
+
for w in words:
if line[-1] == "\n":
rValue += line
@@ -132,13 +132,13 @@ def get_glsa_list(repository, myconfig):
Returns a list of all available GLSAs in the given repository
by comparing the filelist there with the pattern described in
the config.
-
+
@type repository: String
@param repository: The directory or an URL that contains GLSA files
(Note: not implemented yet)
@type myconfig: portage.config
@param myconfig: a GLSA aware config instance (see L{checkconfig})
-
+
@rtype: List of Strings
@return: a list of GLSA IDs in this repository
"""
@@ -151,7 +151,7 @@ def get_glsa_list(repository, myconfig):
dirlist = os.listdir(repository)
prefix = myconfig["GLSA_PREFIX"]
suffix = myconfig["GLSA_SUFFIX"]
-
+
for f in dirlist:
try:
if f[:len(prefix)] == prefix and f[-1*len(suffix):] == suffix:
@@ -163,7 +163,7 @@ def get_glsa_list(repository, myconfig):
def getListElements(listnode):
"""
Get all <li> elements for a given <ol> or <ul> node.
-
+
@type listnode: xml.dom.Node
@param listnode: <ul> or <ol> list to get the elements for
@rtype: List of Strings
@@ -184,7 +184,7 @@ def getText(node, format, textfd = None):
parameter the text might be formatted by adding/removing newlines,
tabs and spaces. This function is only useful for the GLSA DTD,
it's not applicable for other DTDs.
-
+
@type node: xml.dom.Node
@param node: the root node to start with the parsing
@type format: String
@@ -251,7 +251,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: xml.dom.Node
@param rootnode: the node to search for I{tagname}
@type tagname: String
@@ -267,9 +267,9 @@ def getMultiTagsText(rootnode, tagname, format):
def makeAtom(pkgname, versionNode):
"""
- creates from the given package name and information in the
+ creates from the given package name and information in the
I{versionNode} a (syntactical) valid portage atom.
-
+
@type pkgname: String
@param pkgname: the name of the package for this atom
@type versionNode: xml.dom.Node
@@ -292,9 +292,9 @@ def makeAtom(pkgname, versionNode):
def makeVersion(versionNode):
"""
- creates from the information in the I{versionNode} a
+ creates from the information in the I{versionNode} a
version string (format <op><version>).
-
+
@type versionNode: xml.dom.Node
@param versionNode: a <vulnerable> or <unaffected> Node that
contains the version information for this atom
@@ -314,17 +314,17 @@ def makeVersion(versionNode):
def match(atom, portdbname, match_type="default"):
"""
- wrapper that calls revisionMatch() or portage.dbapi.match() depending on
+ wrapper that calls revisionMatch() or portage.dbapi.match() depending on
the given atom.
-
+
@type atom: string
@param atom: a <~ or >~ atom or a normal portage atom that contains the atom to match against
@type portdb: portage.dbapi
@param portdb: one of the portage databases to use as information source
@type match_type: string
- @param match_type: if != "default" passed as first argument to dbapi.xmatch
+ @param match_type: if != "default" passed as first argument to dbapi.xmatch
to apply the wanted visibility filters
-
+
@rtype: list of strings
@return: a list with the matching versions
"""
@@ -341,15 +341,15 @@ def revisionMatch(revisionAtom, portdb, match_type="default"):
handler for the special >~, >=~, <=~ and <~ atoms that are supposed to behave
as > and < except that they are limited to the same version, the range only
applies to the revision part.
-
+
@type revisionAtom: string
@param revisionAtom: a <~ or >~ atom that contains the atom to match against
@type portdb: portage.dbapi
@param portdb: one of the portage databases to use as information source
@type match_type: string
- @param match_type: if != "default" passed as first argument to portdb.xmatch
+ @param match_type: if != "default" passed as first argument to portdb.xmatch
to apply the wanted visibility filters
-
+
@rtype: list of strings
@return: a list with the matching versions
"""
@@ -370,35 +370,34 @@ def revisionMatch(revisionAtom, portdb, match_type="default"):
if eval(r1+" "+revisionAtom[0:2]+" "+r2):
rValue.append(v)
return rValue
-
+
def getMinUpgrade(vulnerableList, unaffectedList, minimize=True):
"""
- Checks if the state of installed packages matches an atom in
- I{vulnerableList} and returns an update path.
-
- Return value is:
- * None if the system is not affected
- * a list of tuples (a,b) where
- a is a cpv describing an installed vulnerable atom
- b is a cpv describing an uninstalled unaffected atom
- in the same slot as a
- OR the empty string ("") which means no upgrade
- is possible
-
+ Checks if the systemstate is matching an atom in
+ I{vulnerableList} and returns string describing
+ the lowest version for the package that matches an atom in
+ I{unaffectedList} and is greater than the currently installed
+ version. It will return an empty list if the system is affected,
+ and no upgrade is possible or None if the system is not affected.
+ Both I{vulnerableList} and I{unaffectedList} should have the
+ same base package.
+
@type vulnerableList: List of Strings
@param vulnerableList: atoms matching vulnerable package versions
@type unaffectedList: List of Strings
@param unaffectedList: atoms matching unaffected package versions
@type minimize: Boolean
@param minimize: True for a least-change upgrade, False for emerge-like algorithm
-
- @rtype: List | None
- @return: None if unaffected or a list of (vuln, upgrade) atoms.
+
+ @rtype: String | None
+ @return: the lowest unaffected version that is greater than
+ the installed version.
"""
+ rValue = ""
v_installed = reduce(operator.add, [match(v, "vartree") for v in vulnerableList], [])
u_installed = reduce(operator.add, [match(u, "vartree") for u in unaffectedList], [])
-
+
# remove all unaffected atoms from vulnerable list
v_installed = list(set(v_installed).difference(set(u_installed)))
@@ -417,18 +416,13 @@ def getMinUpgrade(vulnerableList, unaffectedList, minimize=True):
for vuln in v_installed:
update = ""
- # find the best update path for the vuln atom
for c in avail_updates:
c_pv = portage.catpkgsplit(c)
i_pv = portage.catpkgsplit(vuln)
- if portage.pkgcmp(c_pv[1:], i_pv[1:]) <= 0:
- # c is less or equal than vuln
- continue
- if portage.db["/"]["porttree"].dbapi.aux_get(c, ["SLOT"]) != \
- portage.db["/"]["vartree"].dbapi.aux_get(vuln, ["SLOT"]):
- # upgrade to a different slot
- continue
- if update == "" or (minimize ^ (portage.pkgcmp(c_pv[1:], portage.catpkgsplit(update)[1:]) > 0)):
+ if portage.pkgcmp(c_pv[1:], i_pv[1:]) > 0 \
+ and (update == "" \
+ or (minimize ^ (portage.pkgcmp(c_pv[1:], portage.catpkgsplit(update)[1:]) > 0))) \
+ and portage.db["/"]["porttree"].dbapi.aux_get(c, ["SLOT"]) == portage.db["/"]["vartree"].dbapi.aux_get(vuln, ["SLOT"]):
update = c_pv[0]+"/"+c_pv[1]+"-"+c_pv[2]
if c_pv[3] != "r0": # we don't like -r0 for display
update += "-"+c_pv[3]
@@ -440,7 +434,7 @@ def format_date(datestr):
"""
Takes a date (announced, revised) date from a GLSA and formats
it as readable text (i.e. "January 1, 2008").
-
+
@type date: String
@param date: the date string to reformat
@rtype: String
@@ -450,16 +444,16 @@ def format_date(datestr):
splitdate = datestr.split("-", 2)
if len(splitdate) != 3:
return datestr
-
+
# This cannot raise an error as we use () instead of []
splitdate = (int(x) for x in splitdate)
-
+
from datetime import date
try:
d = date(*splitdate)
except ValueError:
return datestr
-
+
# TODO We could format to local date format '%x' here?
return d.strftime("%B %d, %Y")
@@ -470,7 +464,7 @@ class GlsaTypeException(Exception):
class GlsaFormatException(Exception):
pass
-
+
class GlsaArgumentException(Exception):
pass
@@ -482,9 +476,9 @@ class Glsa:
"""
def __init__(self, myid, myconfig):
"""
- Simple constructor to set the ID, store the config and gets the
+ Simple constructor to set the ID, store the config and gets the
XML data by calling C{self.read()}.
-
+
@type myid: String
@param myid: String describing the id for the GLSA object (standard
GLSAs have an ID of the form YYYYMM-nn) or an existing
@@ -506,7 +500,7 @@ class Glsa:
"""
Here we build the filename from the config and the ID and pass
it to urllib to fetch it from the filesystem or a remote server.
-
+
@rtype: None
@return: None
"""
@@ -523,10 +517,10 @@ class Glsa:
def parse(self, myfile):
"""
- This method parses the XML file and sets up the internal data
+ This method parses the XML file and sets up the internal data
structures by calling the different helper functions in this
module.
-
+
@type myfile: String
@param myfile: Filename to grab the XML data from
@rtype: None
@@ -549,7 +543,7 @@ class Glsa:
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>
@@ -560,15 +554,15 @@ class Glsa:
count = revisedEl.getAttribute("count")
elif (self.revised.find(":") >= 0):
(self.revised, count) = self.revised.split(":")
-
+
self.revised = format_date(self.revised)
-
+
try:
self.count = int(count)
except ValueError:
# TODO should this rais a GlsaFormatException?
self.count = 1
-
+
# now the optional and 0-n toplevel, #PCDATA tags and references
try:
self.access = getText(myroot.getElementsByTagName("access")[0], format="strip")
@@ -576,7 +570,7 @@ class Glsa:
self.access = ""
self.bugs = getMultiTagsText(myroot, "bug", format="strip")
self.references = getMultiTagsText(myroot.getElementsByTagName("references")[0], "uri", format="keep")
-
+
# and now the formatted text elements
self.description = getText(myroot.getElementsByTagName("description")[0], format="xml")
self.workaround = getText(myroot.getElementsByTagName("workaround")[0], format="xml")
@@ -586,7 +580,7 @@ class Glsa:
try:
self.background = getText(myroot.getElementsByTagName("background")[0], format="xml")
except IndexError:
- self.background = ""
+ self.background = ""
# finally the interesting tags (product, affected, package)
self.glsatype = myroot.getElementsByTagName("product")[0].getAttribute("type")
@@ -611,10 +605,10 @@ class Glsa:
def dump(self, outstream=sys.stdout, encoding="utf-8"):
"""
- Dumps a plaintext representation of this GLSA to I{outfile} or
+ Dumps a plaintext representation of this GLSA to I{outfile} or
B{stdout} if it is ommitted. You can specify an alternate
I{encoding} if needed (default is utf-8).
-
+
@type outstream: File
@param outfile: Stream that should be used for writing
(defaults to sys.stdout)
@@ -655,13 +649,13 @@ class Glsa:
myreferences = " ".join(r.replace(" ", SPACE_ESCAPE)+NEWLINE_ESCAPE for r in self.references)
outstream.write("\n"+wrap(myreferences, width, caption="References: "))
outstream.write("\n")
-
+
def isVulnerable(self):
"""
Tests if the system is affected by this GLSA by checking if any
vulnerable package versions are installed. Also checks for affected
architectures.
-
+
@rtype: Boolean
@returns: True if the system is affected, False if not
"""
@@ -674,12 +668,12 @@ class Glsa:
rValue = rValue \
or (None != getMinUpgrade([v,], path["unaff_atoms"]))
return rValue
-
+
def isInjected(self):
"""
Looks if the GLSA ID is in the GLSA checkfile to check if this
GLSA should be marked as applied.
-
+
@rtype: Boolean
@returns: True if the GLSA is in the inject file, False if not
"""
@@ -691,7 +685,7 @@ class Glsa:
def inject(self):
"""
Puts the ID of this GLSA into the GLSA checkfile, so it won't
- show up on future checks. Should be called after a GLSA is
+ show up on future checks. Should be called after a GLSA is
applied or on explicit user request.
@rtype: None
@@ -702,13 +696,13 @@ class Glsa:
checkfile.write(self.nr+"\n")
checkfile.close()
return None
-
+
def getMergeList(self, least_change=True):
"""
Returns the list of package-versions that have to be merged to
- apply this GLSA properly. The versions are as low as possible
+ apply this GLSA properly. The versions are as low as possible
while avoiding downgrades (see L{getMinUpgrade}).
-
+
@type least_change: Boolean
@param least_change: True if the smallest possible upgrade should be selected,
False for an emerge-like algorithm
diff --git a/pym/gentoolkit/helpers.py b/pym/gentoolkit/helpers.py
index 6d272d3..277e41f 100644
--- a/pym/gentoolkit/helpers.py
+++ b/pym/gentoolkit/helpers.py
@@ -1,163 +1,718 @@
-#!/usr/bin/python2
-#
-# Copyright(c) 2004, Karl Trygve Kalleberg <karltk@gentoo.org>
# Copyright(c) 2009, Gentoo Foundation
#
-# Licensed under the GNU General Public License, v2
+# Licensed under the GNU General Public License, v2 or higher
#
# $Header$
+"""Improved versions of the original helpers functions.
+
+As a convention, functions ending in '_packages' or '_match{es}' return
+Package objects, while functions ending in 'cpvs' return a sequence of strings.
+Functions starting with 'get_' return a set of packages by default and can be
+filtered, while functions starting with 'find_' return nothing unless the
+query matches one or more packages.
+"""
+
+# Move to Imports section after Python 2.6 is stable
+from __future__ import with_statement
+
+__all__ = (
+ 'ChangeLog',
+ 'FileOwner',
+ 'compare_package_strings',
+ 'do_lookup',
+ 'find_best_match',
+ 'find_installed_packages',
+ 'find_packages',
+ 'get_cpvs',
+ 'get_installed_cpvs',
+ 'get_uninstalled_cpvs',
+ 'uniqify',
+ 'uses_globbing',
+ 'split_cpv'
+)
+__docformat__ = 'epytext'
+
+# =======
+# Imports
+# =======
+
+import fnmatch
+import os
+import re
+from functools import partial
+from itertools import chain
+
import portage
-from gentoolkit import *
-from package import *
-from pprinter import print_warn
-try:
- from portage.util import unique_array
-except ImportError:
- from portage_util import unique_array
-
-def find_packages(search_key, masked=False):
- """Returns a list of Package objects that matched the search key."""
- try:
- if masked:
- t = portage.db["/"]["porttree"].dbapi.xmatch("match-all", search_key)
- t += portage.db["/"]["vartree"].dbapi.match(search_key)
+from portage.versions import catpkgsplit, pkgcmp
+
+from gentoolkit import pprinter as pp
+from gentoolkit import CONFIG
+from gentoolkit import errors
+from gentoolkit.atom import Atom
+from gentoolkit.cpv import CPV
+from gentoolkit.dbapi import PORTDB, VARDB
+from gentoolkit.versionmatch import VersionMatch
+# This has to be imported below to stop circular import.
+#from gentoolkit.package import Package
+
+# =======
+# Classes
+# =======
+
+class ChangeLog(object):
+ """Provides methods for working with a Gentoo ChangeLog file.
+
+ Example usage:
+ >>> from gentoolkit.helpers import ChangeLog
+ >>> portage = ChangeLog('/usr/portage/sys-apps/portage/ChangeLog')
+ >>> print portage.latest.strip()
+ *portage-2.2_rc50 (15 Nov 2009)
+
+ 15 Nov 2009; Zac Medico <zmedico@gentoo.org> +portage-2.2_rc50.ebuild:
+ 2.2_rc50 bump. This includes all fixes in 2.1.7.5.
+ >>> len(portage.full)
+ 75
+ >>> len(portage.entries_matching_range(
+ ... from_ver='2.2_rc40',
+ ... to_ver='2.2_rc50'))
+ 11
+
+ """
+ def __init__(self, changelog_path, invalid_entry_is_fatal=False):
+ if not (os.path.isfile(changelog_path) and
+ os.access(changelog_path, os.R_OK)):
+ raise errors.GentoolkitFatalError(
+ "%s does not exist or is unreadable" % pp.path(changelog_path)
+ )
+ self.changelog_path = changelog_path
+ self.invalid_entry_is_fatal = invalid_entry_is_fatal
+
+ # Process the ChangeLog:
+ self.entries = self._split_changelog()
+ self.indexed_entries = self._index_changelog()
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.changelog_path)
+
+ @property
+ def full(self):
+ """Return the output of L{self._split_changelog}."""
+ return self.entries
+
+ @property
+ def latest(self):
+ """Return the newest ChangeLog entry."""
+ return self.entries[0]
+
+ def entries_matching_atom(self, atom):
+ """Return entries whose header versions match atom's version.
+
+ @type atom: L{gentoolkit.atom.Atom} or str
+ @param atom: a atom to find matching entries against
+ @rtype: list
+ @return: entries matching atom
+ @raise errors.GentoolkitInvalidAtom: if atom is a string and malformed
+ """
+ result = []
+
+ if not isinstance(atom, Atom):
+ atom = Atom(atom)
+
+ for entry_set in self.indexed_entries:
+ i, entry = entry_set
+ # VersionMatch doesn't store .cp, so we'll force it to match here:
+ i.cpv.cp = atom.cpv.cp
+ if atom.intersects(i):
+ result.append(entry)
+
+ return result
+
+ def entries_matching_range(self, from_ver=None, to_ver=None):
+ """Return entries whose header versions are within a range of versions.
+
+ @type from_ver: str
+ @param from_ver: valid Gentoo version
+ @type to_ver: str
+ @param to_ver: valid Gentoo version
+ @rtype: list
+ @return: entries between from_ver and to_ver
+ @raise errors.GentoolkitFatalError: if neither vers are set
+ @raise errors.GentoolkitInvalidVersion: if either ver is invalid
+ """
+ result = []
+
+ # Make sure we have at least one version set
+ if not (from_ver or to_ver):
+ raise errors.GentoolkitFatalError(
+ "Need to specifiy 'from_ver' or 'to_ver'"
+ )
+
+ # Create a VersionMatch instance out of from_ver
+ from_restriction = None
+ if from_ver:
+ try:
+ from_ver_rev = CPV("null-%s" % from_ver)
+ except errors.GentoolkitInvalidCPV:
+ raise errors.GentoolkitInvalidVersion(from_ver)
+ from_restriction = VersionMatch(from_ver_rev, op='>=')
+
+ # Create a VersionMatch instance out of to_ver
+ to_restriction = None
+ if to_ver:
+ try:
+ to_ver_rev = CPV("null-%s" % to_ver)
+ except errors.GentoolkitInvalidCPV:
+ raise errors.GentoolkitInvalidVersion(to_ver)
+ to_restriction = VersionMatch(to_ver_rev, op='<=')
+
+ # Add entry to result if version ranges intersect it
+ for entry_set in self.indexed_entries:
+ i, entry = entry_set
+ if from_restriction and not from_restriction.match(i):
+ continue
+ if to_restriction and not to_restriction.match(i):
+ continue
+ result.append(entry)
+
+ return result
+
+ def _index_changelog(self):
+ """Use the output of L{self._split_changelog} to create an index list
+ of L{gentoolkit.versionmatch.VersionMatch} objects.
+
+ @rtype: list
+ @return: tuples containing a VersionMatch instance for the release
+ version of each entry header as the first item and the entire entry
+ as the second item
+ @raise ValueError: if self.invalid_entry_is_fatal is True and we hit an
+ invalid entry
+ """
+
+ result = []
+ for entry in self.entries:
+ # Extract the package name from the entry header, ex:
+ # *xterm-242 (07 Mar 2009) => xterm-242
+ pkg_name = entry.split(' ', 1)[0].lstrip('*')
+ if not pkg_name.strip():
+ continue
+ try:
+ entry_ver = CPV(pkg_name)
+ except errors.GentoolkitInvalidCPV:
+ if self.invalid_entry_is_fatal:
+ raise ValueError(entry_ver)
+ continue
+
+ result.append((VersionMatch(entry_ver, op='='), entry))
+
+ return result
+
+ def _split_changelog(self):
+ """Split the ChangeLog into individual entries.
+
+ @rtype: list
+ @return: individual ChangeLog entries
+ """
+
+ result = []
+ partial_entries = []
+ with open(self.changelog_path) as log:
+ for line in log:
+ if line.startswith('#'):
+ continue
+ elif line.startswith('*'):
+ # Append last entry to result...
+ entry = ''.join(partial_entries)
+ if entry and not entry.isspace():
+ result.append(entry)
+ # ... and start a new entry
+ partial_entries = [line]
+ else:
+ partial_entries.append(line)
+ else:
+ # Append the final entry
+ entry = ''.join(partial_entries)
+ result.append(entry)
+
+ return result
+
+
+class FileOwner(object):
+ """Creates a function for locating the owner of filename queries.
+
+ Example usage:
+ >>> from gentoolkit.helpers import FileOwner
+ >>> findowner = FileOwner()
+ >>> findowner(('/usr/bin/vim',))
+ [(<Package app-editors/vim-7.2.182>, '/usr/bin/vim')]
+ """
+ def __init__(self, is_regex=False, early_out=False, printer_fn=None):
+ """Instantiate function.
+
+ @type is_regex: bool
+ @param is_regex: funtion args are regular expressions
+ @type early_out: bool
+ @param early_out: return when first result is found (safe)
+ @type printer_fn: callable
+ @param printer_fn: If defined, will be passed useful information for
+ printing each result as it is found.
+ """
+ self.is_regex = is_regex
+ self.early_out = early_out
+ self.printer_fn = printer_fn
+
+ def __call__(self, queries):
+ """Run the function.
+
+ @type queries: iterable
+ @param queries: filepaths or filepath regexes
+ """
+ query_re_string = self._prepare_search_regex(queries)
+ try:
+ query_re = re.compile(query_re_string)
+ except (TypeError, re.error), err:
+ raise errors.GentoolkitInvalidRegex(err)
+
+ use_match = False
+ if ((self.is_regex or query_re_string.startswith('^\/'))
+ and '|' not in query_re_string ):
+ # If we were passed a regex or a single path starting with root,
+ # we can use re.match, else use re.search.
+ use_match = True
+
+ return self.find_owners(query_re, use_match=use_match)
+
+ def find_owners(self, query_re, use_match=False, pkgset=None):
+ """Find owners and feed data to supplied output function.
+
+ @type query_re: _sre.SRE_Pattern
+ @param query_re: file regex
+ @type use_match: bool
+ @param use_match: use re.match or re.search
+ @type pkgset: iterable or None
+ @param pkgset: list of packages to look through
+ """
+ # FIXME: Remove when lazyimport supports objects:
+ from gentoolkit.package import Package
+
+ if use_match:
+ query_fn = query_re.match
+ else:
+ query_fn = query_re.search
+
+ results = []
+ found_match = False
+ if pkgset is None:
+ pkgset = get_installed_cpvs()
+ for pkg in sorted([Package(x) for x in pkgset]):
+ files = pkg.get_contents()
+ for cfile in files:
+ match = query_fn(cfile)
+ if match:
+ results.append((pkg, cfile))
+ if self.printer_fn is not None:
+ self.printer_fn(pkg, cfile)
+ if self.early_out:
+ found_match = True
+ break
+ if found_match:
+ break
+ return results
+
+ @staticmethod
+ def extend_realpaths(paths):
+ """Extend a list of paths with the realpaths for any symlinks.
+
+ @type paths: list
+ @param paths: file path strs
+ @rtype: list
+ @return: the original list plus the realpaths for any symlinks
+ so long as the realpath doesn't already exist in the list
+ @raise AttributeError: if paths does not have attribute 'extend'
+ """
+
+ osp = os.path
+ paths.extend([osp.realpath(x) for x in paths
+ if osp.islink(x) and osp.realpath(x) not in paths])
+
+ return paths
+
+ def _prepare_search_regex(self, queries):
+ """Create a regex out of the queries"""
+
+ queries = list(queries)
+ if self.is_regex:
+ return '|'.join(queries)
else:
- t = portage.db["/"]["porttree"].dbapi.match(search_key)
- t += portage.db["/"]["vartree"].dbapi.match(search_key)
- # catch the "amgigous package" Exception
- except ValueError, e:
- if isinstance(e[0],list):
- t = []
- for cp in e[0]:
- if masked:
- t += portage.db["/"]["porttree"].dbapi.xmatch("match-all", cp)
- t += portage.db["/"]["vartree"].dbapi.match(cp)
+ result = []
+ # Trim trailing and multiple slashes from queries
+ slashes = re.compile('/+')
+ queries = self.extend_realpaths(queries)
+ for query in queries:
+ query = slashes.sub('/', query).rstrip('/')
+ if query.startswith('/'):
+ query = "^%s$" % re.escape(query)
else:
- t += portage.db["/"]["porttree"].dbapi.match(cp)
- t += portage.db["/"]["vartree"].dbapi.match(cp)
+ query = "/%s$" % re.escape(query)
+ result.append(query)
+ result = "|".join(result)
+ return result
+
+# =========
+# Functions
+# =========
+
+def compare_package_strings(pkg1, pkg2):
+ """Similar to the builtin cmp, but for package strings. Usually called
+ as: package_list.sort(compare_package_strings)
+
+ 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)
+ if pkg1[0] != pkg2[0]:
+ return cmp(pkg1[0], pkg2[0])
+ elif pkg1[1] != pkg2[1]:
+ return cmp(pkg1[1], pkg2[1])
+ else:
+ return pkgcmp(pkg1[1:], pkg2[1:])
+
+
+def do_lookup(query, query_opts):
+ """A high-level wrapper around gentoolkit package-finder functions.
+
+ @type query: str
+ @param query: pkg, cat/pkg, pkg-ver, cat/pkg-ver, atom, glob or regex
+ @type query_opts: dict
+ @param query_opts: user-configurable options from the calling module
+ Currently supported options are:
+
+ includeInstalled = bool
+ includePortTree = bool
+ includeOverlayTree = bool
+ isRegex = bool
+ printMatchInfo = bool # Print info about the search
+
+ @rtype: list
+ @return: Package objects matching query
+ """
+
+ 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(e)
- except portage_exception.InvalidAtom, e:
- print_warn("Invalid Atom: '%s'" % str(e))
+ 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:
+ raise errors.GentoolkitFatalError(
+ "Not searching in installed, Portage tree, or overlay. "
+ "Nothing to do."
+ )
+
+ 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, simple_package_finder, query_opts)
+ else:
+ matches = _do_complex_lookup(query, complex_package_finder, query_opts)
+
+ return matches
+
+
+def _do_complex_lookup(query, package_finder, query_opts):
+ """Find matches for a query which is a regex or includes globbing."""
+
+ # FIXME: Remove when lazyimport supports objects:
+ from gentoolkit.package import Package
+
+ result = []
+
+ if query_opts["printMatchInfo"] and not CONFIG["piping"]:
+ print_query_info(query, query_opts)
+
+ cat = split_cpv(query)[0]
+
+ pre_filter = []
+ # The "get_" functions can pre-filter against the whole package key,
+ # but since we allow globbing now, we run into issues like:
+ # >>> portage.dep.dep_getkey("sys-apps/portage-*")
+ # 'sys-apps/portage-'
+ # So the only way to guarantee we don't overrun the key is to
+ # prefilter by cat only.
+ if cat:
+ if query_opts["isRegex"]:
+ cat_re = cat
+ else:
+ cat_re = fnmatch.translate(cat)
+ # [::-1] reverses a sequence, so we're emulating an ".rreplace()"
+ # except we have to put our "new" string on backwards
+ cat_re = cat_re[::-1].replace('$', '*./', 1)[::-1]
+ predicate = lambda x: re.match(cat_re, x)
+ pre_filter = package_finder(predicate=predicate)
+
+ # Post-filter
+ if query_opts["isRegex"]:
+ predicate = lambda x: re.search(query, x)
+ else:
+ if cat:
+ query_re = fnmatch.translate(query)
+ else:
+ query_re = fnmatch.translate("*/%s" % query)
+ predicate = lambda x: re.search(query_re, x)
+ if pre_filter:
+ result = [x for x in pre_filter if predicate(x)]
+ else:
+ result = package_finder(predicate=predicate)
+
+ return [Package(x) for x in result]
+
+
+def _do_simple_lookup(query, package_finder, query_opts):
+ """Find matches for a query which is an atom or string."""
+
+ result = []
+
+ if query_opts["printMatchInfo"] and CONFIG['verbose']:
+ print_query_info(query, query_opts)
+
+ result = package_finder(query)
+ if not query_opts["includeInstalled"]:
+ result = [x for x in result if not x.is_installed()]
+
+ return result
+
+
+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
+ @raise portage.exception.InvalidAtom: if query is not valid input
+ """
+ # FIXME: Remove when lazyimport supports objects:
+ from gentoolkit.package import Package
+
+ try:
+ match = PORTDB.xmatch("bestmatch-visible", query)
+ except portage.exception.InvalidAtom, err:
+ raise errors.GentoolkitInvalidAtom(err)
+
+ return Package(match) if match else None
+
+
+def find_installed_packages(query):
+ """Return a list of Package objects that matched the search key."""
+ # FIXME: Remove when lazyimport supports objects:
+ from gentoolkit.package import Package
+
+ 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:
+ raise errors.GentoolkitInvalidAtom(err)
+
+ return [Package(x) for x in matches]
+
+
+def find_packages(query, include_masked=False):
+ """Returns a list of Package objects that matched the query.
+
+ @type query: str
+ @param query: can be of the form: pkg, pkg-ver, cat/pkg, cat/pkg-ver, atom
+ @type include_masked: bool
+ @param include_masked: include masked packages
+ @rtype: list
+ @return: matching Package objects
+ """
+ # FIXME: Remove when lazyimport supports objects:
+ from gentoolkit.package import Package
+
+ if not query:
return []
- # Make the list of packages unique
- t = unique_array(t)
- t.sort()
- return [Package(x) for x in t]
-def find_installed_packages(search_key, masked=False):
- """Returns a list of Package objects that matched the search key."""
try:
- t = portage.db["/"]["vartree"].dbapi.match(search_key)
- # catch the "amgigous package" Exception
- except ValueError, e:
- if isinstance(e[0],list):
- t = []
- for cp in e[0]:
- t += portage.db["/"]["vartree"].dbapi.match(cp)
+ if include_masked:
+ matches = PORTDB.xmatch("match-all", query)
else:
- raise ValueError(e)
- except portage_exception.InvalidAtom, e:
- print_warn("Invalid Atom: '%s'" % str(e))
- return []
- return [Package(x) for x in t]
-
-def find_best_match(search_key):
- """Returns a Package object for the best available candidate that
- matched the search key."""
- t = portage.db["/"]["porttree"].dep_bestmatch(search_key)
- if t:
- return Package(t)
- return None
-
-def find_system_packages(prefilter=None):
- """Returns a tuple of lists, first list is resolved system packages,
- second is a list of unresolved packages."""
- pkglist = settings.packages
- resolved = []
- unresolved = []
- for x in pkglist:
- cpv = x.strip()
- if len(cpv) and cpv[0] == "*":
- pkg = find_best_match(cpv)
- if pkg:
- resolved.append(pkg)
- else:
- unresolved.append(cpv)
- return (resolved, unresolved)
-
-def find_world_packages(prefilter=None):
- """Returns a tuple of lists, first list is resolved world packages,
- seond is unresolved package names."""
- f = open(portage.root+portage.WORLD_FILE)
- pkglist = f.readlines()
- resolved = []
- unresolved = []
- for x in pkglist:
- cpv = x.strip()
- if len(cpv) and cpv[0] != "#":
- pkg = find_best_match(cpv)
- if pkg:
- resolved.append(pkg)
- else:
- unresolved.append(cpv)
- return (resolved,unresolved)
-
-def find_all_installed_packages(prefilter=None):
- """Returns a list of all installed packages, after applying the prefilter
- function"""
- t = vartree.dbapi.cpv_all()
- if prefilter:
- t = filter(prefilter,t)
- return [Package(x) for x in t]
-
-def find_all_uninstalled_packages(prefilter=None):
- """Returns a list of all uninstalled packages, after applying the prefilter
- function"""
- alist = find_all_packages(prefilter)
- return [x for x in alist if not x.is_installed()]
-
-def find_all_packages(prefilter=None):
- """Returns a list of all known packages, installed or not, after applying
- the prefilter function"""
- t = porttree.dbapi.cp_all()
- t += vartree.dbapi.cp_all()
- if prefilter:
- t = filter(prefilter,t)
- t = unique_array(t)
- t2 = []
- for x in t:
- t2 += porttree.dbapi.cp_list(x)
- t2 += vartree.dbapi.cp_list(x)
- t2 = unique_array(t2)
- return [Package(x) for x in t2]
-
-def split_package_name(name):
- """Returns a list on the form [category, name, version, revision]. Revision will
- be 'r0' if none can be inferred. Category and version will be empty, if none can
- be inferred."""
- r = portage.catpkgsplit(name)
- if not r:
- r = name.split("/")
- if len(r) == 1:
- return ["", name, "", "r0"]
+ matches = PORTDB.match(query)
+ matches.extend(VARDB.match(query))
+ except portage.exception.InvalidAtom, err:
+ raise errors.GentoolkitInvalidAtom(str(err))
+
+ return [Package(x) for x in set(matches)]
+
+
+def get_cpvs(predicate=None, include_installed=True):
+ """Get all packages in the Portage tree and overlays. Optionally apply a
+ predicate.
+
+ Example usage:
+ >>> from gentoolkit.helpers import get_cpvs
+ >>> len(set(get_cpvs()))
+ 26065
+ >>> fn = lambda x: x.startswith('app-portage')
+ >>> len(get_cpvs(fn, include_installed=False))
+ 112
+
+ @type predicate: function
+ @param predicate: a function to filter the package list with
+ @type include_installed: bool
+ @param include_installed:
+ If True: Return the union of all_cpvs and all_installed_cpvs
+ If False: Return the difference of all_cpvs and all_installed_cpvs
+ @rtype: generator
+ @return: a generator that yields unsorted cat/pkg-ver strings from the
+ Portage tree
+ """
+
+ if predicate:
+ all_cps = iter(x for x in PORTDB.cp_all() if predicate(x))
+ else:
+ all_cps = PORTDB.cp_all()
+
+ all_cpvs = chain.from_iterable(PORTDB.cp_list(x) for x in all_cps)
+ all_installed_cpvs = get_installed_cpvs(predicate)
+
+ if include_installed:
+ for cpv in chain(all_cpvs, all_installed_cpvs):
+ yield cpv
+ else:
+ # Consume the smaller pkg set:
+ installed_cpvs = set(all_installed_cpvs)
+ for cpv in all_cpvs:
+ if cpv not in installed_cpvs:
+ yield cpv
+
+
+# pylint thinks this is a global variable
+# pylint: disable-msg=C0103
+get_uninstalled_cpvs = partial(get_cpvs, include_installed=False)
+
+
+def get_installed_cpvs(predicate=None):
+ """Get all installed packages. Optionally apply a predicate.
+
+ @type predicate: function
+ @param predicate: a function to filter the package list with
+ @rtype: generator
+ @return: a generator that yields unsorted installed cat/pkg-ver strings
+ from VARDB
+ """
+
+ if predicate:
+ installed_cps = iter(x for x in VARDB.cp_all() if predicate(x))
+ else:
+ installed_cps = VARDB.cp_all()
+
+ for cpv in chain.from_iterable(VARDB.cp_list(x) for x in installed_cps):
+ yield cpv
+
+
+def print_query_info(query, query_opts):
+ """Print info about the query to the screen."""
+
+ cat, pkg = split_cpv(query)[:2]
+ if cat and not query_opts["isRegex"]:
+ cat_str = "in %s " % pp.emph(cat.lstrip('><=~!'))
+ 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_cpv(query):
+ """Split a cpv into category, name, version and revision.
+
+ @type query: str
+ @param query: pkg, cat/pkg, pkg-ver, cat/pkg-ver, atom or regex
+ @rtype: tuple
+ @return: (category, pkg_name, version, revision)
+ Each tuple element is a string or empty string ("").
+ """
+
+ result = catpkgsplit(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:
- return r + ["", "r0"]
+ result = result + ['', '']
+
+ if len(result) != 4:
+ 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:
- r = list(r)
- if r[0] == 'null':
- r[0] = ''
- return r
+ result = list(set(seq))
+
+ return result
+
+
+def uses_globbing(query):
+ """Check the query to see if it is using globbing.
-# 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
+ @type query: str
+ @param query: user input package query
+ @rtype: bool
+ @return: True if query uses globbing, else False
+ """
-if __name__ == "__main__":
- print "This module is for import only"
+ if set('!*?[]').intersection(query):
+ # Is query an atom such as '=sys-apps/portage-2.2*'?
+ if query[0] != '=':
+ return True
+ return False
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/metadata.py b/pym/gentoolkit/metadata.py
new file mode 100644
index 0000000..9c65fd9
--- /dev/null
+++ b/pym/gentoolkit/metadata.py
@@ -0,0 +1,303 @@
+#!/usr/bin/python
+#
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2
+#
+# $Header$
+
+"""Provides an easy-to-use python interface to Gentoo's metadata.xml file.
+
+ Example usage:
+ >>> from gentoolkit.metadata import MetaData
+ >>> pkg_md = MetaData('/usr/portage/app-misc/gourmet/metadata.xml')
+ >>> pkg_md
+ <MetaData '/usr/portage/app-misc/gourmet/metadata.xml'>
+ >>> pkg_md.get_herds()
+ ['no-herd']
+ >>> for maint in pkg_md.get_maintainers():
+ ... print "{0} ({1})".format(maint.email, maint.name)
+ ...
+ nixphoeni@gentoo.org (Joe Sapp)
+ >>> for flag in pkg_md.get_useflags():
+ ... print flag.name, "->", flag.description
+ ...
+ rtf -> Enable export to RTF
+ gnome-print -> Enable printing support using gnome-print
+ >>> upstream = pkg_md.get_upstream()
+ >>> upstream
+ [<_Upstream {'docs': [], 'remoteid': [], 'maintainer':
+ [<_Maintainer 'Thomas_Hinkle@alumni.brown.edu'>], 'bugtracker': [],
+ 'changelog': []}>]
+ >>> upstream[0].maintainer[0].name
+ 'Thomas Mills Hinkle'
+"""
+
+# Move to Imports section after Python-2.6 is stable
+from __future__ import with_statement
+
+__all__ = ('MetaData',)
+__docformat__ = 'epytext'
+
+# =======
+# Imports
+# =======
+
+import re
+import os
+import xml.etree.cElementTree as etree
+
+from portage import settings
+
+# =======
+# Classes
+# =======
+
+class _Maintainer(object):
+ """An object for representing one maintainer.
+
+ @type email: str or None
+ @ivar email: Maintainer's email address. Used for both Gentoo and upstream.
+ @type name: str or None
+ @ivar name: Maintainer's name. Used for both Gentoo and upstream.
+ @type description: str or None
+ @ivar description: Description of what a maintainer does. Gentoo only.
+ @type restrict: str or None
+ @ivar restrict: e.g. &gt;=portage-2.2 means only maintains versions
+ of Portage greater than 2.2.
+ @type status: str or None
+ @ivar status: If set, either 'active' or 'inactive'. Upstream only.
+ """
+
+ def __init__(self, node):
+ self.email = None
+ self.name = None
+ self.description = None
+ self.restrict = node.get('restrict')
+ self.status = node.get('status')
+ maint_attrs = node.getchildren()
+ for attr in maint_attrs:
+ setattr(self, attr.tag, attr.text)
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.email)
+
+
+class _Useflag(object):
+ """An object for representing one USE flag.
+
+ @todo: Is there any way to have a keyword option to leave in
+ <pkg> and <cat> for later processing?
+ @type name: str or None
+ @ivar name: USE flag
+ @type restrict: str or None
+ @ivar restrict: e.g. &gt;=portage-2.2 means flag is only avaiable in
+ versions greater than 2.2
+ @type description: str
+ @ivar description: description of the USE flag
+ """
+
+ def __init__(self, node):
+ self.name = node.get('name')
+ self.restrict = node.get('restrict')
+ _desc = ''
+ if node.text:
+ _desc = node.text
+ for child in node.getchildren():
+ _desc += child.text if child.text else ''
+ _desc += child.tail if child.tail else ''
+ # This takes care of tabs and newlines left from the file
+ self.description = re.sub('\s+', ' ', _desc)
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.name)
+
+
+class _Upstream(object):
+ """An object for representing one package's upstream.
+
+ @type maintainers: list
+ @ivar maintainers: L{_Maintainer} objects for each upstream maintainer
+ @type changelogs: list
+ @ivar changelogs: URLs to upstream's ChangeLog file in str format
+ @type docs: list
+ @ivar docs: Sequence of tuples containing URLs to upstream documentation
+ in the first slot and 'lang' attribute in the second, e.g.,
+ [('http.../docs/en/tut.html', None), ('http.../doc/fr/tut.html', 'fr')]
+ @type bugtrackers: list
+ @ivar bugtrackers: URLs to upstream's bugtracker. May also contain an email
+ address if prepended with 'mailto:'
+ @type remoteids: list
+ @ivar remoteids: Sequence of tuples containing the project's hosting site
+ name in the first slot and the project's ID name or number for that
+ site in the second, e.g., [('sourceforge', 'systemrescuecd')]
+ """
+
+ def __init__(self, node):
+ self.node = node
+ self.maintainers = self.get_upstream_maintainers()
+ self.changelogs = self.get_upstream_changelogs()
+ self.docs = self.get_upstream_documentation()
+ self.bugtrackers = self.get_upstream_bugtrackers()
+ self.remoteids = self.get_upstream_remoteids()
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.__dict__)
+
+ def get_upstream_bugtrackers(self):
+ """Retrieve upstream bugtracker location from xml node."""
+ return [e.text for e in self.node.findall('bugs-to')]
+
+ def get_upstream_changelogs(self):
+ """Retrieve upstream changelog location from xml node."""
+ return [e.text for e in self.node.findall('changelog')]
+
+ def get_upstream_documentation(self):
+ """Retrieve upstream documentation location from xml node."""
+ result = []
+ for elem in self.node.findall('doc'):
+ lang = elem.get('lang')
+ result.append((elem.text, lang))
+ return result
+
+ def get_upstream_maintainers(self):
+ """Retrieve upstream maintainer information from xml node."""
+ return [_Maintainer(m) for m in self.node.findall('maintainer')]
+
+ def get_upstream_remoteids(self):
+ """Retrieve upstream remote ID from xml node."""
+ return [(e.text, e.get('type')) for e in self.node.findall('remote-id')]
+
+
+class MetaData(object):
+ """Access metadata.xml"""
+
+ def __init__(self, metadata_path):
+ """Parse a valid metadata.xml file.
+
+ @type metadata_path: str
+ @ivar metadata_path: path to a valid metadata.xml file
+ @raise IOError: if C{matadata_path} can not be read
+ """
+
+ self.metadata_path = metadata_path
+ self._xml_tree = etree.parse(metadata_path)
+
+ # Used for caching
+ self._herdstree = None
+ self._descriptions = None
+ self._maintainers = None
+ self._useflags = None
+ self._upstream = None
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.metadata_path)
+
+ def _get_herd_email(self, herd):
+ """Get a herd's email address.
+
+ @type herd: str
+ @param herd: herd whose email you want
+ @rtype: str or None
+ @return: email address or None if herd is not in herds.xml
+ @raise IOError: if $PORTDIR/metadata/herds.xml can not be read
+ """
+
+ if self._herdstree is None:
+ herds_path = os.path.join(settings['PORTDIR'], 'metadata/herds.xml')
+ self._herdstree = etree.parse(herds_path)
+
+ # Some special herds are not listed in herds.xml
+ if herd in ('no-herd', 'maintainer-wanted', 'maintainer-needed'):
+ return None
+
+ for node in self._herdstree.getiterator('herd'):
+ if node.findtext('name') == herd:
+ return node.findtext('email')
+
+ def get_herds(self, include_email=False):
+ """Return a list of text nodes for <herd>.
+
+ @type include_email: bool
+ @keyword include_email: if True, also look up the herd's email
+ @rtype: list
+ @return: if include_email is False, return a list of string;
+ if include_email is True, return a list of tuples containing:
+ [('herd1', 'herd1@gentoo.org'), ('no-herd', None);
+ """
+
+ result = []
+ for elem in self._xml_tree.findall('herd'):
+ if include_email:
+ herd_mail = self._get_herd_email(elem.text)
+ result.append((elem.text, herd_mail))
+ else:
+ result.append(elem.text)
+
+ return result
+
+ def get_descriptions(self):
+ """Return a list of text nodes for <longdescription>.
+
+ @rtype: list
+ @return: package description in string format
+ @todo: Support the C{lang} attribute
+ """
+
+ if self._descriptions is not None:
+ return self._descriptions
+
+ self._descriptions = [
+ e.text for e in self._xml_tree.findall("longdescription")
+ ]
+ return self._descriptions
+
+ def get_maintainers(self):
+ """Get maintainers' name, email and description.
+
+ @rtype: list
+ @return: a sequence of L{_Maintainer} objects in document order.
+ """
+
+ if self._maintainers is not None:
+ return self._maintainers
+
+ self._maintainers = []
+ for node in self._xml_tree.findall('maintainer'):
+ self._maintainers.append(_Maintainer(node))
+
+ return self._maintainers
+
+ def get_useflags(self):
+ """Get names and descriptions for USE flags defined in metadata.
+
+ @rtype: list
+ @return: a sequence of L{_Useflag} objects in document order.
+ """
+
+ if self._useflags is not None:
+ return self._useflags
+
+ self._useflags = []
+ for node in self._xml_tree.getiterator('flag'):
+ self._useflags.append(_Useflag(node))
+
+ return self._useflags
+
+ def get_upstream(self):
+ """Get upstream contact information.
+
+ @rtype: list
+ @return: a sequence of L{_Upstream} objects in document order.
+ """
+
+ if self._upstream is not None:
+ return self._upstream
+
+ self._upstream = []
+ for node in self._xml_tree.findall('upstream'):
+ self._upstream.append(_Upstream(node))
+
+ return self._upstream
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/package.py b/pym/gentoolkit/package.py
index 857470a..e348258 100644
--- a/pym/gentoolkit/package.py
+++ b/pym/gentoolkit/package.py
@@ -7,159 +7,198 @@
#
# $Header$
+"""Provides classes for accessing Portage db information for a given package."""
+
+__all__ = (
+ 'Package',
+ 'PackageFormatter'
+)
+
# =======
-# Imports
+# Imports
# =======
import os
import portage
-from portage.versions import catpkgsplit, vercmp
+from portage import settings
import gentoolkit.pprinter as pp
-from gentoolkit import settings, settingslock, PORTDB, VARDB
from gentoolkit import errors
-from gentoolkit.versionmatch import VersionMatch
+from gentoolkit.cpv import CPV
+from gentoolkit.dbapi import PORTDB, VARDB
+from gentoolkit.dependencies import Dependencies
+from gentoolkit.metadata import MetaData
# =======
# Classes
# =======
-class Package(object):
- """Package descriptor. Contains convenience functions for querying the
- state of a package, its contents, name manipulation, ebuild info and
- similar."""
-
- def __init__(self, arg):
-
- self._cpv = arg
- self.cpv = self._cpv
-
- if self.cpv[0] in ('<', '>'):
- if self.cpv[1] == '=':
- self.operator = self.cpv[:2]
- self.cpv = self.cpv[2:]
- else:
- self.operator = self.cpv[0]
- self.cpv = self.cpv[1:]
- elif self.cpv[0] == '=':
- if self.cpv[-1] == '*':
- self.operator = '=*'
- self.cpv = self.cpv[1:-1]
- else:
- self.cpv = self.cpv[1:]
- self.operator = '='
- elif self.cpv[0] == '~':
- self.operator = '~'
- self.cpv = self.cpv[1:]
+class Package(CPV):
+ """Provides methods for ascertaining the state of a given CPV."""
+
+ def __init__(self, cpv):
+ if isinstance(cpv, CPV):
+ self.cpv = cpv
else:
- self.operator = '='
- self._cpv = '=%s' % self._cpv
+ self.cpv = CPV(cpv)
+ del cpv
- if not portage.dep.isvalidatom(self._cpv):
- raise errors.GentoolkitInvalidCPV(self._cpv)
+ if not all(getattr(self.cpv, x) for x in ('category', 'version')):
+ # CPV allows some things that Package must not
+ raise errors.GentoolkitInvalidPackage(str(self.cpv))
- cpv_split = portage.catpkgsplit(self.cpv)
-
- try:
- self.key = "/".join(cpv_split[:2])
- except TypeError:
- # catpkgsplit returned None
- raise errors.GentoolkitInvalidCPV(self._cpv)
-
- cpv_split = list(cpv_split)
- if cpv_split[0] == 'null':
- cpv_split[0] = ''
- if cpv_split[3] == 'r0':
- cpv_split[3] = ''
- self.cpv_split = cpv_split
- self._scpv = self.cpv_split # XXX: namespace compatability 03/09
-
- self._db = None
- self._settings = settings
- self._settingslock = settingslock
- self._portdir_path = os.path.realpath(settings["PORTDIR"])
-
- self.category = self.cpv_split[0]
- self.name = self.cpv_split[1]
- self.version = self.cpv_split[2]
- self.revision = self.cpv_split[3]
- if not self.revision:
- self.fullversion = self.version
- else:
- self.fullversion = "%s-%s" % (self.version, self.revision)
+ # Set dynamically
+ self._package_path = None
+ self._dblink = None
+ self._metadata = None
+ self._deps = None
+ self._portdir_path = None
def __repr__(self):
- return "<%s %s @%#8x>" % (self.__class__.__name__, self._cpv, id(self))
+ return "<%s %r>" % (self.__class__.__name__, str(self.cpv))
def __eq__(self, other):
- return hash(self) == hash(other)
+ if not hasattr(other, 'cpv'):
+ return False
+ return self.cpv == other.cpv
def __ne__(self, other):
- return hash(self) != hash(other)
+ return not self == 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 self.category < other.category
- elif 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)
- result = cmp(portage.vercmp(self.fullversion, other.fullversion), 0)
- if result == -1:
- return True
- else:
- return False
+ return self.cpv < other.cpv
def __gt__(self, other):
- return not self.__lt__(other)
+ return self.cpv > other.cpv
def __hash__(self):
- return hash(self._cpv)
+ return hash(str(self.cpv))
def __contains__(self, key):
- return key in self._cpv
-
+ return key in str(self.cpv)
+
def __str__(self):
- return self._cpv
+ return str(self.cpv)
+
+ def _get_trees(self):
+ """Return dbapi objects for each repository that contains self."""
+
+ result = []
+ if self.is_installed():
+ result.append(VARDB)
+ if self.exists():
+ result.append(PORTDB)
+ if not result:
+ raise errors.GentoolkitFatalError("Could not find package tree")
+
+ return result
+
+ @property
+ def metadata(self):
+ """Instantiate a L{gentoolkit.metadata.MetaData} object here."""
+
+ if self._metadata is None:
+ metadata_path = os.path.join(
+ self.get_package_path(), 'metadata.xml'
+ )
+ self._metadata = MetaData(metadata_path)
+
+ return self._metadata
+
+ @property
+ def dblink(self):
+ """Instantiate a L{portage.dbapi.vartree.dblink} object here."""
+
+ if self._dblink is None:
+ self._dblink = portage.dblink(
+ self.cpv.category,
+ "%s-%s" % (self.cpv.name, self.cpv.fullversion),
+ settings["ROOT"],
+ settings
+ )
+
+ return self._dblink
+
+ @property
+ def deps(self):
+ """Instantiate a L{gentoolkit.dependencies.Dependencies} object here."""
+
+ if self._deps is None:
+ self._deps = Dependencies(self.cpv)
- def get_name(self):
- """Returns base name of package, no category nor version"""
- return self.name
+ return self._deps
- def get_version(self):
- """Returns version of package, with revision number"""
- return self.fullversion
+ def exists(self):
+ """Return True if package exists in the Portage tree, else False"""
- def get_category(self):
- """Returns category of package"""
- return self.category
+ return bool(PORTDB.cpv_exists(str(self.cpv)))
- def get_settings(self, key):
- """Returns the value of the given key for this package (useful
+ @staticmethod
+ def get_settings(key):
+ """Returns the value of the given key for this package (useful
for package.* files."""
+
+ if settings.locked:
+ settings.unlock()
try:
- self._settingslock.acquire()
- self._settings.setcpv(self.cpv)
- result = self._settings[key]
+ result = settings[key]
finally:
- self._settingslock.release()
+ settings.lock()
+ return result
+
+ def get_mask_status(self):
+ """Shortcut to L{portage.getmaskingstatus}.
+
+ @rtype: None or list
+ @return: a list containing none or some of:
+ 'profile'
+ 'package.mask'
+ license(s)
+ "kmask" keyword
+ 'missing keyword'
+ """
+
+ if settings.locked:
+ settings.unlock()
+ try:
+ result = portage.getmaskingstatus(str(self.cpv),
+ settings=settings,
+ portdb=PORTDB)
+ except KeyError:
+ # getmaskingstatus doesn't support packages without ebuilds in the
+ # Portage tree.
+ result = None
+
return result
- def get_cpv(self):
- """Returns full Category/Package-Version string"""
- return self.cpv
+ def get_mask_reason(self):
+ """Shortcut to L{portage.getmaskingreason}.
+
+ @rtype: None or tuple
+ @return: empty tuple if pkg not masked OR
+ ('mask reason', 'mask location')
+ """
+
+ try:
+ result = portage.getmaskingreason(str(self.cpv),
+ settings=settings,
+ PORTDB=PORTDB,
+ return_location=True)
+ if result is None:
+ result = tuple()
+ except KeyError:
+ # getmaskingstatus doesn't support packages without ebuilds in the
+ # Portage tree.
+ result = None
+
+ return result
def get_provide(self):
"""Return a list of provides, if any"""
+
if self.is_installed():
- result = VARDB.get_provide(self.cpv)
+ result = VARDB.get_provide(str(self.cpv))
else:
try:
result = [self.get_env_var('PROVIDE')]
@@ -167,289 +206,114 @@ class Package(object):
result = []
return result
- def get_dependants(self):
- """Retrieves a list of CPVs for all packages depending on this one"""
- raise NotImplementedError("Not implemented yet!")
+ def get_ebuild_path(self, in_vartree=False):
+ """Returns the complete path to the .ebuild file.
- def get_runtime_deps(self):
- """Returns a linearised list of first-level run time dependencies for
- this package, on the form [(comparator, [use flags], cpv), ...]
+ Example usage:
+ >>> pkg.get_ebuild_path()
+ '/usr/portage/sys-apps/portage/portage-2.1.6.13.ebuild'
+ >>> pkg.get_ebuild_path(in_vartree=True)
+ '/var/db/pkg/sys-apps/portage-2.1.6.13/portage-2.1.6.13.ebuild'
"""
- # Try to use the portage tree first, since emerge only uses the tree
- # when calculating dependencies
- try:
- rdepends = self.get_env_var("RDEPEND", PORTDB).split()
- except KeyError:
- 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
- for this package, on the form [(comparator, [use flags], cpv), ...]
- """
- # Try to use the portage tree first, since emerge only uses the tree
- # when calculating dependencies
- try:
- depends = self.get_env_var("DEPEND", PORTDB).split()
- except KeyError:
- depends = self.get_env_var("DEPEND", VARDB).split()
- return self._parse_deps(depends)[0]
+ if in_vartree:
+ return VARDB.findname(str(self.cpv))
+ return PORTDB.findname(str(self.cpv))
- def get_postmerge_deps(self):
- """Returns a linearised list of first-level post merge dependencies
- for this package, on the form [(comparator, [use flags], cpv), ...]
- """
- # Try to use the portage tree first, since emerge only uses the tree
- # when calculating dependencies
- try:
- postmerge_deps = self.get_env_var("PDEPEND", PORTDB).split()
- except KeyError:
- postmerge_deps = self.get_env_var("PDEPEND", VARDB).split()
- return self._parse_deps(postmerge_deps)[0]
+ def get_package_path(self):
+ """Return the path to where the ebuilds and other files reside."""
- def intersects(self, other):
- """Check if a passed in package atom "intersects" this atom.
+ if self._package_path is None:
+ path_split = self.get_ebuild_path().split(os.sep)
+ self._package_path = os.sep.join(path_split[:-1])
- Lifted from pkgcore.
+ return self._package_path
- Two atoms "intersect" if a package can be constructed that
- matches both:
- - if you query for just "dev-lang/python" it "intersects" both
- "dev-lang/python" and ">=dev-lang/python-2.4"
- - if you query for "=dev-lang/python-2.4" it "intersects"
- ">=dev-lang/python-2.4" and "dev-lang/python" but not
- "<dev-lang/python-2.3"
+ def get_repo_name(self):
+ """Using the package path, determine the repo name.
- @type other: L{gentoolkit.package.Package}
- @param other: other package to compare
- @see: pkgcore.ebuild.atom.py
+ @rtype: str
+ @return: /usr/<THIS>portage</THIS>/cat-egory/name/
"""
- # Our "key" (cat/pkg) must match exactly:
- if self.key != other.key:
- return False
- # If we are both "unbounded" in the same direction we intersect:
- if (('<' in self.operator and '<' in other.operator) or
- ('>' in self.operator and '>' in other.operator)):
- return True
-
- # If one of us is an exact match we intersect if the other matches it:
- if self.operator == '=':
- if other.operator == '=*':
- return self.fullversion.startswith(other.fullversion)
- return VersionMatch(other).match(self)
- if other.operator == '=':
- if self.operator == '=*':
- return other.fullversion.startswith(self.fullversion)
- return VersionMatch(self).match(other)
-
- # If we are both ~ matches we match if we are identical:
- if self.operator == other.operator == '~':
- return (self.version == other.version and
- self.revision == other.revision)
-
- # If we are both glob matches we match if one of us matches the other.
- if self.operator == other.operator == '=*':
- return (self.fullver.startswith(other.fullver) or
- other.fullver.startswith(self.fullver))
-
- # If one of us is a glob match and the other a ~ we match if the glob
- # matches the ~ (ignoring a revision on the glob):
- if self.operator == '=*' and other.operator == '~':
- return other.fullversion.startswith(self.version)
- if other.operator == '=*' and self.operator == '~':
- return self.fullversion.startswith(other.version)
-
- # If we get here at least one of us is a <, <=, > or >=:
- if self.operator in ('<', '<=', '>', '>='):
- ranged, other = self, other
- else:
- ranged, other = other, self
-
- if '<' in other.operator or '>' in other.operator:
- # We are both ranged, and in the opposite "direction" (or
- # we would have matched above). We intersect if we both
- # match the other's endpoint (just checking one endpoint
- # is not enough, it would give a false positive on <=2 vs >2)
- return (
- 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(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
- # that case other will match ranged. Be careful not to
- # give a false positive for ~2 vs <2 here:
- return ranged.operator in ('>', '>=') and VersionMatch(
- other.operator, other.version, other.revision).match(ranged)
-
- if other.operator == '=*':
- # a glob match definitely matches its own version, so if
- # ranged does too we're done:
- if VersionMatch(
- ranged.operator, ranged.version, ranged.revision).match(other):
- return True
- if '<' in ranged.operator:
- # If other.revision is not defined then other does not
- # match anything smaller than its own fullver:
- if not other.revision:
- return False
-
- # If other.revision is defined then we can always
- # construct a package smaller than other.fullver by
- # tagging e.g. an _alpha1 on.
- return ranged.fullversion.startswith(other.version)
- else:
- # Remaining cases where this intersects: there is a
- # package greater than ranged.fullver and
- # other.fullver that they both match.
- return ranged.fullversion.startswith(other.version)
-
- # Handled all possible ops.
- raise NotImplementedError(
- 'Someone added an operator without adding it to intersects')
-
-
- def _parse_deps(self,deps,curuse=[],level=0):
- # store (comparator, [use predicates], cpv)
- r = []
- comparators = ["~","<",">","=","<=",">="]
- end = len(deps)
- i = 0
- while i < end:
- tok = deps[i]
- if tok == ')':
- return r,i
- if tok[-1] == "?":
- tok = tok.replace("?","")
- sr,l = self._parse_deps(deps[i+2:],curuse=curuse+[tok],level=level+1)
- r += sr
- i += l + 3
- continue
- if tok == "||":
- sr,l = self._parse_deps(deps[i+2:],curuse,level=level+1)
- r += sr
- i += l + 3
- continue
- # conjunction, like in "|| ( ( foo bar ) baz )" => recurse
- if tok == "(":
- sr,l = self._parse_deps(deps[i+1:],curuse,level=level+1)
- r += sr
- i += l + 2
- continue
- # pkg block "!foo/bar" => ignore it
- if tok[0] == "!":
- i += 1
- continue
- # pick out comparator, if any
- cmp = ""
- for c in comparators:
- if tok.find(c) == 0:
- cmp = c
- tok = tok[len(cmp):]
- r.append((cmp,curuse,tok))
- i += 1
- return r,i
+ return self.get_package_path().split(os.sep)[-3]
- def is_installed(self):
- """Returns True if this package is installed (merged)"""
- return VARDB.cpv_exists(self.cpv)
+ def get_env_var(self, var, tree=None):
+ """Returns one of the predefined env vars DEPEND, SRC_URI, etc."""
- def is_overlay(self):
- """Returns True if the package is in an overlay."""
- ebuild, tree = portage.portdb.findname2(self.cpv)
- return tree != self._portdir_path
+ if tree is None:
+ tree = self._get_trees()[0]
+ try:
+ result = tree.aux_get(str(self.cpv), [var])
+ if len(result) != 1:
+ raise errors.GentoolkitFatalError
+ except (KeyError, errors.GentoolkitFatalError):
+ err = "aux_get returned unexpected results"
+ raise errors.GentoolkitFatalError(err)
+ return result[0]
- def is_masked(self):
- """Returns true if this package is masked against installation.
- Note: We blindly assume that the package actually exists on disk
- somewhere."""
- unmasked = portage.portdb.xmatch("match-visible", self.cpv)
- return self.cpv not in unmasked
+ def get_use_flags(self):
+ """Returns the USE flags active at time of installation."""
- def get_ebuild_path(self, in_vartree=False):
- """Returns the complete path to the .ebuild file"""
- if in_vartree:
- return VARDB.getebuildpath(self.cpv)
- return PORTDB.findname(self.cpv)
+ return self.dblink.getstring("USE")
- def get_package_path(self):
- """Returns the path to where the ChangeLog, Manifest, .ebuild files
- reside"""
- ebuild_path = self.get_ebuild_path()
- path_split = ebuild_path.split("/")
- if path_split:
- return os.sep.join(path_split[:-1])
+ def get_contents(self):
+ """Returns the parsed CONTENTS file.
- def get_env_var(self, var, tree=None):
- """Returns one of the predefined env vars DEPEND, RDEPEND,
- SRC_URI,...."""
- if tree == None:
- tree = VARDB
- if not self.is_installed():
- tree = PORTDB
- result = tree.aux_get(self.cpv, [var])
- if not result:
- raise errors.GentoolkitFatalError("Could not find the package tree")
- if len(result) != 1:
- raise errors.GentoolkitFatalError("Should only get one element!")
- return result[0]
+ @rtype: dict
+ @return: {'/full/path/to/obj': ['type', 'timestamp', 'md5sum'], ...}
+ """
- 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 self.dblink.getcontents()
- def get_contents(self):
- """Returns the full contents, as a dictionary, in the form
- ['/bin/foo' : [ 'obj', '1052505381', '45ca8b89751...' ], ... ]"""
- self._initdb()
- if self.is_installed():
- return self._db.getcontents()
- return {}
+ def get_size(self):
+ """Estimates the installed size of the contents of this package.
- 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)
+ @rtype: tuple
+ @return: (size, number of files in total, number of uncounted files)
"""
+
contents = self.get_contents()
- size = 0
- uncounted = 0
- files = 0
- for x in contents:
+ size = n_uncounted = n_files = 0
+ for cfile in contents:
try:
- size += os.lstat(x).st_size
- files += 1
+ size += os.lstat(cfile).st_size
+ n_files += 1
except OSError:
- uncounted += 1
- return (size, files, uncounted)
+ n_uncounted += 1
+ return (size, n_files, n_uncounted)
- def _initdb(self):
- """Internal helper function; loads package information from disk,
- when necessary.
- """
- if not self._db:
- self._db = portage.dblink(
- self.category,
- "%s-%s" % (self.name, self.fullversion),
- settings["ROOT"],
- settings
- )
+ def is_installed(self):
+ """Returns True if this package is installed (merged)"""
+
+ return VARDB.cpv_exists(str(self.cpv))
+
+ def is_overlay(self):
+ """Returns True if the package is in an overlay."""
+
+ ebuild, tree = PORTDB.findname2(str(self.cpv))
+ if not ebuild:
+ return None
+ if self._portdir_path is None:
+ self._portdir_path = os.path.realpath(settings["PORTDIR"])
+ return (tree and tree != self._portdir_path)
+
+ def is_masked(self):
+ """Returns true if this package is masked against installation.
+ Note: We blindly assume that the package actually exists on disk
+ somewhere."""
+
+ unmasked = PORTDB.xmatch("match-visible", str(self.cpv))
+ return str(self.cpv) not in unmasked
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.helpers import find_packages
>>> from gentoolkit.package import PackageFormatter
>>> pkgs = [PackageFormatter(x) for x in find_packages('gcc')]
>>> for pkg in pkgs:
@@ -457,49 +321,45 @@ class PackageFormatter(object):
... # 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.
+ @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
+ quiet output is desired. If C{do_format} is False, only the location
attribute will be created to save time.
"""
- def __init__(self, pkg, format=True):
- location = ''
- maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-']
-
+ def __init__(self, pkg, do_format=True):
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()
+ self.do_format = do_format
+ self.location = self.format_package_location() or ''
def __repr__(self):
return "<%s %s @%#8x>" % (self.__class__.__name__, self.pkg, id(self))
def __str__(self):
- if self.format:
+ if self.do_format:
+ maskmodes = [' ', ' ~', ' -', 'M ', 'M~', 'M-', 'XX']
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
+ 'mask': pp.maskflag(maskmodes[self.format_mask_status()[0]]),
+ 'package': pp.cpv(str(self.pkg.cpv)),
+ 'slot': self.pkg.get_env_var("SLOT")
}
else:
return self.pkg.cpv
- def get_package_location(self):
- """Get the physical location of a package on disk.
+ def format_package_location(self):
+ """Get the install status (in /var/db/?) and origin (from and overlay
+ and the Portage tree?).
@rtype: str
@return: one of:
+ 'I--' : Installed but ebuild doesn't exist on system anymore
'-P-' : Not installed and from the Portage tree
'--O' : Not installed and from an overlay
'IP-' : Installed and from the Portage tree
@@ -510,37 +370,47 @@ class PackageFormatter(object):
if self.pkg.is_installed():
result[0] = 'I'
- if self.pkg.is_overlay():
+
+ overlay = self.pkg.is_overlay()
+ if overlay is None:
+ pass
+ elif 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-"]
+ def format_mask_status(self):
+ """Get the mask status of a given package.
+
+ @rtype: tuple: (int, list)
+ @return: int = an index for this list:
+ [" ", " ~", " -", "M ", "M~", "M-", "XX"]
0 = not masked
1 = keyword masked
2 = arch masked
3 = hard masked
4 = hard and keyword masked,
5 = hard and arch masked
+ 6 = ebuild doesn't exist on system anymore
+
+ list = original output of portage.getmaskingstatus
"""
- 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
+ result = 0
+ masking_status = self.pkg.get_mask_status()
+ if masking_status is None:
+ return (6, [])
+
+ if ("~%s keyword" % self.pkg.get_settings("ARCH")) in masking_status:
+ result += 1
+ if "missing keyword" in masking_status:
+ result += 2
+ if set(('profile', 'package.mask')).intersection(masking_status):
+ result += 3
+
+ return (result, masking_status)
+
- return mask_status
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/pprinter.py b/pym/gentoolkit/pprinter.py
index ff92a26..db8a368 100644
--- a/pym/gentoolkit/pprinter.py
+++ b/pym/gentoolkit/pprinter.py
@@ -1,116 +1,131 @@
#!/usr/bin/python
#
# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org>
-# Copyright 2004 Gentoo Foundation
+# Copyright 2004-2009 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#
# $Header$
+"""Provides a consistent color scheme for Gentoolkit scripts."""
+
+__all__ = (
+ 'command',
+ 'cpv',
+ 'die',
+ 'emph',
+ 'error',
+ 'globaloption',
+ 'installedflag',
+ 'localoption',
+ 'maskflag',
+ 'number',
+ 'path',
+ 'path_symlink',
+ 'pkgquery',
+ 'productname',
+ 'regexpquery',
+ 'section',
+ 'slot',
+ 'subsection',
+ 'useflag',
+ 'warn'
+)
+
+# =======
+# Imports
+# =======
+
import sys
-import gentoolkit
-try:
- import portage.output as output
-except ImportError:
- import output
+import portage.output as output
+# =========
+# Functions
+# =========
-def print_error(s):
- """Prints an error string to stderr."""
- sys.stderr.write(output.red("!!! ") + s + "\n")
+# output creates color functions on the fly, which confuses pylint.
+# E1101: *%s %r has no %r member*
+# pylint: disable-msg=E1101
-def print_info(lv, s, line_break = True):
- """Prints an informational string to stdout."""
- if gentoolkit.Config["verbosityLevel"] >= lv:
- sys.stdout.write(s)
- if line_break:
- sys.stdout.write("\n")
+def command(string):
+ """Print a program command string."""
+ return output.green(string)
-def print_warn(s):
- """Print a warning string to stderr."""
- sys.stderr.write("!!! " + s + "\n")
-
-def die(err, s):
+def cpv(string):
+ """Print a category/package-<version> string."""
+ return output.green(string)
+
+def die(err, string):
"""Print an error string and die with an error code."""
- print_error(s)
+ sys.stderr.write(error(string))
sys.exit(err)
-# Colour settings
+def emph(string):
+ """Print a string as emphasized."""
+ return output.bold(string)
-def cpv(s):
- """Print a category/package-<version> string."""
- return output.green(s)
+def error(string):
+ """Prints an error string to stderr."""
+ return output.red("!!! ") + string + "\n"
-def slot(s):
- """Print a slot string"""
- return output.bold(s)
-
-def useflag(s):
- """Print a USE flag strign"""
- return output.blue(s)
-
-def useflagon(s):
- """Print an enabled USE flag string"""
- # FIXME: Collapse into useflag with parameter
- return output.red(s)
-
-def useflagoff(s):
- """Print a disabled USE flag string"""
- # FIXME: Collapse into useflag with parameter
- return output.blue(s)
-
-def maskflag(s):
- """Print a masking flag string"""
- return output.red(s)
+def globaloption(string):
+ """Print a global option string, i.e. the program global options."""
+ return output.yellow(string)
-def installedflag(s):
+def installedflag(string):
"""Print an installed flag string"""
- return output.bold(s)
-
-def number(s):
- """Print a number string"""
- return output.turquoise(s)
+ return output.bold(string)
-def pkgquery(s):
- """Print a package query string."""
- return output.bold(s)
+def localoption(string):
+ """Print a local option string, i.e. the program local options."""
+ return output.green(string)
-def regexpquery(s):
- """Print a regular expression string"""
- return output.bold(s)
+def maskflag(string):
+ """Print a masking flag string"""
+ return output.red(string)
+
+def number(string):
+ """Print a number string"""
+ return output.turquoise(string)
-def path(s):
+def path(string):
"""Print a file or directory path string"""
- return output.bold(s)
+ return output.bold(string)
-def path_symlink(s):
+def path_symlink(string):
"""Print a symlink string."""
- return output.turquoise(s)
+ return output.turquoise(string)
+
+def pkgquery(string):
+ """Print a package query string."""
+ return output.bold(string)
-def productname(s):
+def productname(string):
"""Print a product name string, i.e. the program name."""
- return output.turquoise(s)
-
-def globaloption(s):
- """Print a global option string, i.e. the program global options."""
- return output.yellow(s)
+ return output.turquoise(string)
-def localoption(s):
- """Print a local option string, i.e. the program local options."""
- return output.green(s)
+def regexpquery(string):
+ """Print a regular expression string"""
+ return output.bold(string)
-def command(s):
- """Print a program command string."""
- return output.green(s)
-
-def section(s):
+def section(string):
"""Print a string as a section header."""
- return output.turquoise(s)
+ return output.turquoise(string)
+
+def slot(string):
+ """Print a slot string"""
+ return output.bold(string)
-def subsection(s):
+def subsection(string):
"""Print a string as a subsection header."""
- return output.turquoise(s)
-
-def emph(s):
- """Print a string as emphasized."""
- return output.bold(s)
+ return output.turquoise(string)
+
+def useflag(string, enabled=False):
+ """Print a USE flag string"""
+ return output.red(string) if enabled else output.blue(string)
+
+def warn(string):
+ """Print a warning string to stderr."""
+ return "!!! " + string + "\n"
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/test/__init__.py b/pym/gentoolkit/test/__init__.py
new file mode 100644
index 0000000..94423e9
--- /dev/null
+++ b/pym/gentoolkit/test/__init__.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+# Copyright 2009 Gentoo Foundation
+#
+# Distributed under the terms of the GNU General Public License v2
+#
+# $Header$
diff --git a/pym/gentoolkit/test/equery/__init__.py b/pym/gentoolkit/test/equery/__init__.py
new file mode 100644
index 0000000..94423e9
--- /dev/null
+++ b/pym/gentoolkit/test/equery/__init__.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+# Copyright 2009 Gentoo Foundation
+#
+# Distributed under the terms of the GNU General Public License v2
+#
+# $Header$
diff --git a/pym/gentoolkit/test/equery/test_init.py b/pym/gentoolkit/test/equery/test_init.py
new file mode 100644
index 0000000..d135aa5
--- /dev/null
+++ b/pym/gentoolkit/test/equery/test_init.py
@@ -0,0 +1,52 @@
+import unittest
+from test import test_support
+
+from gentoolkit import equery
+
+class TestEqueryInit(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_expand_module_name(self):
+ # Test that module names are properly expanded
+ name_map = {
+ 'b': 'belongs',
+ 'c': 'changes',
+ 'k': 'check',
+ 'd': 'depends',
+ 'g': 'depgraph',
+ 'f': 'files',
+ 'h': 'hasuse',
+ 'l': 'list_',
+ 'm': 'meta',
+ 's': 'size',
+ 'u': 'uses',
+ 'w': 'which'
+ }
+ self.failUnlessEqual(equery.NAME_MAP, name_map)
+ for short_name, long_name in zip(name_map, name_map.values()):
+ self.failUnlessEqual(equery.expand_module_name(short_name),
+ long_name)
+ self.failUnlessEqual(equery.expand_module_name(long_name),
+ long_name)
+ unused_keys = set(map(chr, range(0, 256))).difference(name_map.keys())
+ for key in unused_keys:
+ self.failUnlessRaises(KeyError, equery.expand_module_name, key)
+
+ def test_format_timestamp(self):
+ # Test that a certain timetamp produces the correct formatted string
+ tstamp = 1257626685.6503389
+ tstr = '2009-11-07 15:44:45'
+ self.failUnlessEqual(equery.format_timestamp(tstamp), tstr)
+
+
+def test_main():
+ test_support.run_unittest(TestEqueryInit)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/pym/gentoolkit/test/test_helpers.py b/pym/gentoolkit/test/test_helpers.py
new file mode 100644
index 0000000..22509b7
--- /dev/null
+++ b/pym/gentoolkit/test/test_helpers.py
@@ -0,0 +1,107 @@
+import os
+import unittest
+import warnings
+from tempfile import NamedTemporaryFile
+from test import test_support
+
+from gentoolkit import helpers
+
+
+class TestFileOwner(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_extend_realpaths(self):
+ extend_realpaths = helpers.FileOwner.extend_realpaths
+
+ # Test that symlinks's realpaths are extended
+ f1 = NamedTemporaryFile(prefix='equeryunittest')
+ f2 = NamedTemporaryFile(prefix='equeryunittest')
+ f3 = NamedTemporaryFile(prefix='equeryunittest')
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ sym1 = os.tmpnam()
+ os.symlink(f1.name, sym1)
+ sym2 = os.tmpnam()
+ os.symlink(f3.name, sym2)
+ # We've created 3 files and 2 symlinks for testing. We're going to pass
+ # in only the first two files and both symlinks. sym1 points to f1.
+ # Since f1 is already in the list, sym1's realpath should not be added.
+ # sym2 points to f3, but f3's not in our list, so sym2's realpath
+ # should be added to the list.
+ p = [f1.name, f2.name, sym1, sym2]
+ p_xr = extend_realpaths(p)
+
+ self.failUnlessEqual(p_xr[0], f1.name)
+ self.failUnlessEqual(p_xr[1], f2.name)
+ self.failUnlessEqual(p_xr[2], sym1)
+ self.failUnlessEqual(p_xr[3], sym2)
+ self.failUnlessEqual(p_xr[4], f3.name)
+
+ # Clean up
+ os.unlink(sym1)
+ os.unlink(sym2)
+
+ # Make sure we raise an exception if we don't get acceptable input
+ self.failUnlessRaises(AttributeError, extend_realpaths, 'str')
+ self.failUnlessRaises(AttributeError, extend_realpaths, set())
+
+
+class TestGentoolkitHelpers2(unittest.TestCase):
+
+ def test_compare_package_strings(self):
+ # Test ordering of package strings, Portage has test for vercmp,
+ # so just do the rest
+ version_tests = [
+ # different categories
+ ('sys-apps/portage-2.1.6.8', 'sys-auth/pambase-20080318'),
+ # different package names
+ ('sys-apps/pkgcore-0.4.7.15-r1', 'sys-apps/portage-2.1.6.8'),
+ # different package versions
+ ('sys-apps/portage-2.1.6.8', 'sys-apps/portage-2.2_rc25')
+ ]
+ # Check less than
+ for vt in version_tests:
+ self.failUnless(
+ helpers.compare_package_strings(vt[0], vt[1]) == -1
+ )
+ # Check greater than
+ for vt in version_tests:
+ self.failUnless(
+ helpers.compare_package_strings(vt[1], vt[0]) == 1
+ )
+ # Check equal
+ vt = ('sys-auth/pambase-20080318', 'sys-auth/pambase-20080318')
+ self.failUnless(
+ helpers.compare_package_strings(vt[0], vt[1]) == 0
+ )
+
+ def test_uses_globbing(self):
+ globbing_tests = [
+ ('sys-apps/portage-2.1.6.13', False),
+ ('>=sys-apps/portage-2.1.6.13', False),
+ ('<=sys-apps/portage-2.1.6.13', False),
+ ('~sys-apps/portage-2.1.6.13', False),
+ ('=sys-apps/portage-2*', False),
+ ('sys-*/*-2.1.6.13', True),
+ ('sys-app?/portage-2.1.6.13', True),
+ ('sys-apps/[bp]ortage-2.1.6.13', True),
+ ('sys-apps/[!p]ortage*', True)
+ ]
+
+ for gt in globbing_tests:
+ self.failUnless(
+ helpers.uses_globbing(gt[0]) == gt[1]
+ )
+
+
+def test_main():
+ test_support.run_unittest(TestGentoolkitHelpers2)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/pym/gentoolkit/test/test_syntax.py b/pym/gentoolkit/test/test_syntax.py
new file mode 100644
index 0000000..5b00fc5
--- /dev/null
+++ b/pym/gentoolkit/test/test_syntax.py
@@ -0,0 +1,31 @@
+import os
+import os.path as osp
+import unittest
+import py_compile
+
+pym_dirs = os.walk(osp.dirname(osp.dirname(osp.dirname(__file__))))
+blacklist_dirs = frozenset(('.svn', 'tests'))
+
+class TestForSyntaxErrors(unittest.TestCase):
+
+ def test_compileability(self):
+ compileables = []
+ for thisdir, subdirs, files in pym_dirs:
+ if os.path.basename(thisdir) in blacklist_dirs:
+ continue
+ compileables.extend([
+ osp.join(thisdir, f)
+ for f in files
+ if osp.splitext(f)[1] == '.py'
+ ])
+
+ for c in compileables:
+ py_compile.compile(c, doraise=True)
+
+
+def test_main():
+ test_support.run_unittest(TestGentoolkitHelpers2)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/pym/gentoolkit/textwrap_.py b/pym/gentoolkit/textwrap_.py
index 6851402..845ae9d 100644
--- a/pym/gentoolkit/textwrap_.py
+++ b/pym/gentoolkit/textwrap_.py
@@ -1,5 +1,5 @@
"""This modification of textwrap allows it to wrap ANSI colorized text as if
-it weren't colorized. It also uses a much simpler word splitting regex to
+it weren't colorized. It also uses a much simpler word splitting regex to
prevent the splitting of ANSI colors as well as package names and versions."""
import re
@@ -13,81 +13,81 @@ class TextWrapper(textwrap.TextWrapper):
Split the text to wrap into indivisible chunks.
"""
- # Only split on whitespace to avoid mangling ANSI escape codes or
+ # Only split on whitespace to avoid mangling ANSI escape codes or
# package names.
wordsep_re = re.compile(r'(\s+)')
chunks = wordsep_re.split(text)
- chunks = filter(None, chunks)
+ chunks = [x for x in chunks if x is not None]
return chunks
- def _wrap_chunks(self, chunks):
- """_wrap_chunks(chunks : [string]) -> [string]
-
- Wrap a sequence of text chunks and return a list of lines of
- length 'self.width' or less. (If 'break_long_words' is false,
- some lines may be longer than this.) Chunks correspond roughly
- to words and the whitespace between them: each chunk is
- indivisible (modulo 'break_long_words'), but a line break can
- come between any two chunks. Chunks should not have internal
- whitespace; ie. a chunk is either all whitespace or a "word".
- Whitespace chunks will be removed from the beginning and end of
- lines, but apart from that whitespace is preserved.
- """
- lines = []
- if self.width <= 0:
- raise ValueError("invalid width %r (must be > 0)" % self.width)
-
- # Arrange in reverse order so items can be efficiently popped
- # from a stack of chunks.
- chunks.reverse()
+ def _wrap_chunks(self, chunks):
+ """_wrap_chunks(chunks : [string]) -> [string]
+
+ Wrap a sequence of text chunks and return a list of lines of
+ length 'self.width' or less. (If 'break_long_words' is false,
+ some lines may be longer than this.) Chunks correspond roughly
+ to words and the whitespace between them: each chunk is
+ indivisible (modulo 'break_long_words'), but a line break can
+ come between any two chunks. Chunks should not have internal
+ whitespace; ie. a chunk is either all whitespace or a "word".
+ Whitespace chunks will be removed from the beginning and end of
+ lines, but apart from that whitespace is preserved.
+ """
+ lines = []
+ if self.width <= 0:
+ raise ValueError("invalid width %r (must be > 0)" % self.width)
+
+ # Arrange in reverse order so items can be efficiently popped
+ # from a stack of chunks.
+ chunks.reverse()
# Regex to strip ANSI escape codes. It's only used for the
# length calculations of indent and each chuck.
ansi_re = re.compile('\x1b\[[0-9;]*m')
- while chunks:
+ while chunks:
- # Start the list of chunks that will make up the current line.
- # cur_len is just the length of all the chunks in cur_line.
- cur_line = []
- cur_len = 0
+ # Start the list of chunks that will make up the current line.
+ # cur_len is just the length of all the chunks in cur_line.
+ cur_line = []
+ cur_len = 0
- # Figure out which static string will prefix this line.
- if lines:
- indent = self.subsequent_indent
- else:
- indent = self.initial_indent
+ # Figure out which static string will prefix this line.
+ if lines:
+ indent = self.subsequent_indent
+ else:
+ indent = self.initial_indent
# Maximum width for this line. Ingore ANSI escape codes.
width = self.width - len(re.sub(ansi_re, '', indent))
- # First chunk on line is whitespace -- drop it, unless this
- # is the very beginning of the text (ie. no lines started yet).
- if chunks[-1].strip() == '' and lines:
- del chunks[-1]
+ # First chunk on line is whitespace -- drop it, unless this
+ # is the very beginning of the text (ie. no lines started yet).
+ if chunks[-1].strip() == '' and lines:
+ del chunks[-1]
- while chunks:
+ while chunks:
# Ignore ANSI escape codes.
- l = len(re.sub(ansi_re, '', chunks[-1]))
+ chunk_len = len(re.sub(ansi_re, '', chunks[-1]))
- # Can at least squeeze this chunk onto the current line.
- if cur_len + l <= width:
- cur_line.append(chunks.pop())
- cur_len += l
+ # Can at least squeeze this chunk onto the current line.
+ if cur_len + chunk_len <= width:
+ cur_line.append(chunks.pop())
+ cur_len += chunk_len
- # Nope, this line is full.
- else:
- break
+ # Nope, this line is full.
+ else:
+ break
- # The current line is full, and the next chunk is too big to
- # fit on *any* line (not just this one).
+ # The current line is full, and the next chunk is too big to
+ # fit on *any* line (not just this one).
# Ignore ANSI escape codes.
- if chunks and len(re.sub(ansi_re, '', chunks[-1])) > width:
- self._handle_long_word(chunks, cur_line, cur_len, width)
+ if chunks and len(re.sub(ansi_re, '', chunks[-1])) > width:
+ self._handle_long_word(chunks, cur_line, cur_len, width)
- # If the last chunk on this line is all whitespace, drop it.
- if cur_line and cur_line[-1].strip() == '':
- del cur_line[-1]
+ # If the last chunk on this line is all whitespace, drop it.
+ if cur_line and cur_line[-1].strip() == '':
+ del cur_line[-1]
# Convert current line back to a string and store it in list
# of all lines (return value).
@@ -95,3 +95,5 @@ class TextWrapper(textwrap.TextWrapper):
lines.append(indent + ''.join(cur_line))
return lines
+
+# vim: set ts=4 sw=4 tw=79:
diff --git a/pym/gentoolkit/versionmatch.py b/pym/gentoolkit/versionmatch.py
index 4cafa00..12f6732 100644
--- a/pym/gentoolkit/versionmatch.py
+++ b/pym/gentoolkit/versionmatch.py
@@ -1,6 +1,6 @@
#! /usr/bin/python
#
-# Copyright(c) 2009, Gentoo Foundation
+# Copyright(c) 2009 Gentoo Foundation
# Licensed under the GNU General Public License, v2
#
# Copyright: 2005-2007 Brian Harring <ferringb@gmail.com>
@@ -8,52 +8,7 @@
#
# $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.
-"""
+"""Gentoo version comparison object from pkgcore.ebuild.atom_restricts."""
# =======
# Imports
@@ -61,15 +16,15 @@ True
from portage.versions import vercmp
-import gentoolkit
from gentoolkit import errors
+from gentoolkit.cpv import CPV
# =======
# Classes
# =======
class VersionMatch(object):
- """Gentoo package version comparison object from pkgcore.ebuild.atom_restricts.
+ """Gentoo 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.
@@ -78,41 +33,23 @@ class VersionMatch(object):
(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.
+ def __init__(self, cpv, op='='):
+ """Initialize a VersionMatch instance.
- 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
+ @type cpv: L{gentoolkit.cpv.CPV}
+ @param cpv: cpv object
+ @type op: str
+ @keyword op: operator
"""
- 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 not isinstance(cpv, CPV):
+ raise ValueError("cpv must be a gentoolkit.cpv.CPV instance")
+ self.cpv = cpv
+ self.operator = op
+ self.version = cpv.version
+ self.revision = cpv.revision
+ self.fullversion = cpv.fullversion
if self.operator != "~" and self.operator not in self._convert_int2op:
raise errors.GentoolkitInvalidVersion(
@@ -121,16 +58,15 @@ class VersionMatch(object):
if self.operator == "~":
if not self.version:
raise errors.GentoolkitInvalidVersion(
- "for ~ op, version must be specified")
+ "for ~ op, ver 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.
+ def match(self, other):
+ """See whether a passed in VersionMatch or CPV instance matches self.
Example usage:
>>> from gentoolkit.versionmatch import VersionMatch
@@ -138,37 +74,30 @@ class VersionMatch(object):
... 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
+ @type other: gentoolkit.versionmatch.VersionMatch OR
+ gentoolkit.cpv.CPV
+ @param other: version to compare with self's version
@rtype: bool
"""
if self.droprevision:
- ver1, ver2 = self.version, pkginst.version
+ ver1, ver2 = self.version, other.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 =="
+ ver1, ver2 = self.fullversion, other.fullversion
return vercmp(ver2, ver1) in self.values
def __str__(self):
operator = self._convert_op2int[self.values]
- if self.droprevision or not self.revision:
+ if self.droprevision or not self.cpv.revision:
return "ver %s %s" % (operator, self.version)
- return "ver-rev %s %s-%s" % (operator, self.version, self.revision)
+ 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))
+ return "<%s %r>" % (self.__class__.__name__, str(self))
@staticmethod
def _convert_ops(inst):
@@ -188,6 +117,15 @@ class VersionMatch(object):
return False
+ def __ne__(self, other):
+ return not self == other
+
def __hash__(self):
- return hash((self.droprevision, self.version, self.revision,
- self.values))
+ return hash((
+ self.droprevision,
+ self.version,
+ self.revision,
+ self.values
+ ))
+
+# vim: set ts=4 sw=4 tw=79: