Author: fw Date: 2011-04-24 14:06:14 +0000 (Sun, 24 Apr 2011) New Revision: 16575 Modified: lib/python/debian_support.py Log: debian_support.inspect_deb(): attempt add a .deb parser Also adds debian_support.BinaryPackage class. Modified: lib/python/debian_support.py ==================================================================--- lib/python/debian_support.py 2011-04-23 17:52:11 UTC (rev 16574) +++ lib/python/debian_support.py 2011-04-24 14:06:14 UTC (rev 16575) @@ -20,6 +20,9 @@ import types import os import re +import subprocess +import tempfile +from cStringIO import StringIO try: from hashlib import sha1 @@ -415,6 +418,117 @@ l.sort() return l +class BinaryPackage(object): + __slots__ = ("name", "version", "arch", "source", "source_version") + re_source = re.compile\ + (r''^([a-zA-Z0-9.+-]+)(?:\s+\(([a-zA-Z0-9.+:~-]+)\))?$'') + + def __init__(self, data=None): + if data is not None: + self.loadtuple(data) + + def loadentry(self, lines): + """Loads an entry from the Packages file. + + LINES is a sequence of string pairs (KEY, VALUE). + """ + pkg_name = None + pkg_version = None + pkg_arch = None + pkg_source = None + pkg_source_version = None + for (name, contents) in lines: + name = name.lower() + if name == "package": + pkg_name = contents + elif name == "version": + pkg_version = contents + elif name == "source": + match = self.re_source.match(contents) + if match is None: + raise SyntaxError((''package %s references '' + + ''invalid source package %s'') % + (pkg_name, `contents`)) + (pkg_source, pkg_source_version) = match.groups() + elif name == "architecture": + pkg_arch = contents + if pkg_name is None: + raise SyntaxError\ + ("package record does not contain package name") + if pkg_version is None: + raise SyntaxError\ + ("package record for %s does not contain version" + % pkg_name) + if pkg_arch is None: + raise SyntaxError\ + ("package record for %s lacks Architecture: field" + % pkg_name) + if pkg_source is None: + pkg_source = pkg_name + if pkg_source_version is None: + pkg_source_version = pkg_version + self.loadtuple((pkg_name, pkg_version, pkg_arch, + pkg_source, pkg_source_version)) + + def loadtuple(self, data): + self.name, self.version, self.arch, self.source, self.source_version =\ + data + + def astuple(self): + return (self.name, self.version, self.arch, + self.source, self.source_version) + + def __repr__(self): + return "BinaryPackage(" + repr(self.astuple()) + ")" + +def inspect_deb(path): + "Extracts meta-data from a Debian package file (.deb)." + devnull = file("/dev/null", "r") + tempout = tempfile.TemporaryFile() + temperr = tempfile.TemporaryFile() + dpkg = subprocess.Popen(("dpkg", "-I", "--", path), + stdin=devnull, + stdout=tempout, + stderr=temperr) + dpkg.wait() + temperr.seek(0) + temperr = temperr.read() + if temperr: + raise IOError("unexpected dpkg output for " + repr(path) + ": " + + repr(temperr)) + if dpkg.returncode <> 0: + raise IOError("unexpected dpkg status for " + repr(path) + ": " + + repr(dpkg.returncode)) + tempout.seek(0) + lines = list(tempout.readlines()) + if not lines: + raise IOError("empty dpkg output for " + repr(path)) + if lines[0] <> " new debian package, version 2.0.\n": + raise IOError("unexpected dpkg format for " +repr(path) + ":" + + repr(lines[0])) + while lines and not lines[0].startswith(" Package: "): + del lines[0] + if not lines: + raise IOError("no Package: line in dpkg output for " +repr(path)) + stripped = StringIO() + for line in lines: + if line.startswith(" "): + stripped.write(line[1:]) + else: + break + stripped.seek(0) + pkgfile = PackageFile(path, stripped) + result = None + for pkg in pkgfile: + if result is None: + result = BinaryPackage() + result.loadentry(pkg) + else: + raise IOError("multiple package entries for " + repr(path)) + if result is None: + raise IOError("no package entries for " + repr(path)) + return result + def test(): # Version assert Version(''0'') < Version(''a'')