[python-debian/master] structured access to inter-package relationships
Stefano Zacchiroli
zack at upsilon.cc
Thu Jun 19 13:30:16 UTC 2008
First implementation of new accessors for inter-package relationships
(e.g. Depends, Suggests, Build-depends, ...). Added a mixin which adds
corresponding methods (.depends(), .suggests(), ...) on the fly. Mixed
in with the usual suspects: deb822.Sources, deb822.Packages
---
debian_bundle/deb822.py | 155 ++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 153 insertions(+), 2 deletions(-)
diff --git a/debian_bundle/deb822.py b/debian_bundle/deb822.py
index f824d67..f3147de 100644
--- a/debian_bundle/deb822.py
+++ b/debian_bundle/deb822.py
@@ -28,7 +28,9 @@ try:
except ImportError:
_have_apt_pkg = False
+import new
import re
+import sys
import StringIO
import UserDict
@@ -390,6 +392,135 @@ class Deb822(Deb822Dict):
###
+class PkgRel(object):
+ """Package relationships
+
+ Structured representation of the relationships of a package to another,
+ i.e. of what can appear in a Deb882 field like Depends, Recommends,
+ Suggests, ... (see Debian Policy 7.1).
+ """
+
+ def __init__(self, rels=[]):
+ """Argument is a list of dependencies logically AND-ed together.
+
+ In Deb822 syntax that would be the top-level comma-separated list.
+ """
+ self.__rels = rels
+
+ # XXX *NOT* a real dependency parser, and that is not even a goal here, we
+ # just parse as much as we need to split the various parts composing a
+ # dependency, checking their correctness wrt policy is out of scope
+ __dep_RE = re.compile( \
+ r'^\s*(?P<name>[a-z0-9.+\-]{2,})(\s*\(\s*(?P<relop>[>=<]+)\s*(?P<version>[0-9a-zA-Z:\-+~.]+)\s*\))?(\s*\[(?P<archs>[\s!\w]+)\])?\s*$')
+ __comma_sep_RE = re.compile(r'\s*,\s*')
+ __pipe_sep_RE = re.compile(r'\s*\|\s*')
+ __blank_sep_RE = re.compile(r'\s*')
+
+ @classmethod
+ def parse_rels(cls, raw):
+ """Parse a package relationship string (i.e. the value of a field like
+ Depends, Recommends, Build-Depends ...)
+
+ For a description of return value format see the rels property
+ """
+ def parse_archs(raw):
+ # assumption: no space beween '!' and architecture name
+ archs = []
+ for arch in cls.__blank_sep_RE.split(raw.strip()):
+ if len(arch) and arch[0] == '!':
+ archs.append((False, arch[1:]))
+ else:
+ archs.append((True, arch))
+ return archs
+
+ def parse_rel(raw):
+ match = cls.__dep_RE.match(raw)
+ if match:
+ parts = match.groupdict()
+ d = { 'name': parts['name'] }
+ if not (parts['relop'] is None or parts['version'] is None):
+ d['version'] = (parts['relop'], parts['version'])
+ if not (parts['archs'] is None):
+ d['arch'] = parse_archs(parts['archs'])
+ return d
+ else:
+ print >> sys.stderr, \
+ 'deb822.py: WARNING: cannot parse package' \
+ ' relationship "%s", returning it raw' % raw
+ return { 'name': raw }
+
+ tl_deps = cls.__comma_sep_RE.split(raw.strip()) # top-level deps
+ cnf = map(cls.__pipe_sep_RE.split, tl_deps)
+ return map(lambda or_deps: map(parse_rel, or_deps), cnf)
+
+ @property
+ def rels(self):
+ """Get raw package relationships
+
+ Relationships are returned as lists of lists of dictionaries (see below
+ for some examples).
+
+ The semantics is as follows:
+ - the top-level lists corresponds to the comma-separated list of
+ Deb822, their components form a conjuction, i.e. they have to be
+ AND-ed together
+ - the inner lists corresponds to the pipe-separated list of Deb822,
+ their components form a disjunction, i.e. they have to be OR-ed
+ together
+ - member of the inner lists are dictionaries with the following keys:
+ - name: package (or virtual package) name
+ - version: a pair <operator, version>. operator is one of "<<",
+ "<=", "=", ">=", ">>"; version is the given version as
+ a string.
+ This key is present only if the relationship is
+ versioned
+ - arch: a list of pairs <polarity, architecture>; polarity is a
+ boolean (false if the architecture is negated with "!",
+ true otherwise), architecture the Debian archtiecture
+ name as a string.
+ This key is present only if the relationship is
+ architecture specific
+
+ Examples:
+
+ "emacs | emacsen, make, debianutils (>= 1.7)" becomes
+ [ [ {'name': 'emacs'}, {'name': 'emacsen'} ],
+ [ {'name': 'make'} ],
+ [ {'name': 'debianutils', 'version': ('>=', '1.7')} ] ]
+
+ "tcl8.4-dev, procps [!hurd-i386]" becomes
+ [ [ {'name': 'tcl8.4-dev'} ],
+ [ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
+ """
+ return self.__rels
+
+class _PkgRelMixin(object):
+ """Package relationship mixin
+
+ Inheriting from this mixin you can extend a Deb882 object with attributes
+ letting you access inter-package relationship in a structured way, rather
+ than as strings. For example, while you can usually use pkg['depends'] to
+ obtain the Depends string of package pkg, mixing in with this class you
+ gain pkg.depends to access Depends as a Pkgrel instance
+
+ To use, subclass _PkgRelMixin from a class with a _relationship_fields
+ attribute. It should be a list of field names for which structured access
+ is desired; for each of them a method wild be added to the inherited class.
+ The method name will be the lowercase version of field name. The method
+ would return relationships in the same format of the PkgRel' rels property.
+
+ See Packages and Sources as examples.
+ """
+
+ def __init__(self, *args, **kwargs):
+ for name in self._relationship_fields:
+ name = name.lower()
+ if self.has_key(name):
+ method = new.instancemethod(
+ lambda self, name=name: PkgRel.parse_rels(self[name]),
+ self, _PkgRelMixin)
+ setattr(_PkgRelMixin, name, method)
+
class _multivalued(Deb822):
"""A class with (R/W) support for multivalued fields.
@@ -573,8 +704,28 @@ class Release(_multivalued):
lengths = [len(str(item['size'])) for item in self[key]]
return max(lengths)
-Sources = Dsc
-Packages = Deb822
+
+class Sources(Dsc, _PkgRelMixin):
+ """Represent an APT source package list"""
+
+ _relationship_fields = [ 'build-depends', 'build-depends-indep',
+ 'build-conflicts', 'build-conflicts-indep' ]
+
+ def __init__(self, *args, **kwargs):
+ Dsc.__init__(self, *args, **kwargs)
+ _PkgRelMixin.__init__(self, *args, **kwargs)
+
+
+class Packages(Deb822, _PkgRelMixin):
+ """Represent an APT binary package list"""
+
+ _relationship_fields = [ 'depends', 'pre-depends', 'recommends',
+ 'suggests', 'breaks', 'conflicts', 'provides', 'replaces',
+ 'enhances' ]
+
+ def __init__(self, *args, **kwargs):
+ Deb822.__init__(self, *args, **kwargs)
+ _PkgRelMixin.__init__(self, *args, **kwargs)
###
--
1.5.4.2
More information about the pkg-python-debian-commits
mailing list