Bug#562257: [PATCH] debian_support: Add a native Python Version class

John Wright john.wright at hp.com
Thu Mar 11 01:36:19 UTC 2010


Based on the DpkgVersion class by Raphael Hertzog in
svn://svn.debian.org/qa/trunk/pts/www/bin/common.py r2361

Closes: #562257, #573009
---
 debian_bundle/debian_support.py |  176 ++++++++++++++++++++++++++++++++-------
 1 files changed, 146 insertions(+), 30 deletions(-)

diff --git a/debian_bundle/debian_support.py b/debian_bundle/debian_support.py
index 6543206..e13a251 100644
--- a/debian_bundle/debian_support.py
+++ b/debian_bundle/debian_support.py
@@ -23,8 +23,13 @@ import hashlib
 import types
 
 from deprecation import function_deprecated_by
-import apt_pkg
-apt_pkg.init()
+
+try:
+    import apt_pkg
+    apt_pkg.init()
+    __have_apt_pkg = True
+except ImportError:
+    __have_apt_pkg = False
 
 class ParseError(Exception):
     """An exception which is used to signal a parse failure.
@@ -58,24 +63,24 @@ class ParseError(Exception):
 
     printOut = function_deprecated_by(print_out)
 
-class Version:
-    """Version class which uses the original APT comparison algorithm."""
+class AptPkgVersion(object):
+    """Represents a Debian package version, using apt_pkg.VersionCompare"""
 
     def __init__(self, version):
         """Creates a new Version object."""
-        t = type(version)
-        if t == types.UnicodeType:
-            version = version.encode('UTF-8')
+        if not isinstance(version, (str, unicode)):
+            raise ValueError, "version must be a string or unicode object"
+
+        if isinstance(version, unicode):
+            self.__as_string = version.encode("utf-8")
         else:
-            assert t == types.StringType, `version`
-        assert version <> ""
-        self.__asString = version
+            self.__as_string = version
 
     def __str__(self):
-        return self.__asString
+        return self.__as_string
 
     def __repr__(self):
-        return 'Version(%s)' % `self.__asString`
+        return "%s('%s')" % (self.__class__.__name__, self)
 
     def __cmp__(self, other):
         return apt_pkg.VersionCompare(str(self), str(other))
@@ -83,8 +88,118 @@ class Version:
     def __hash__(self):
         return hash(str(self))
 
+# NativeVersion based on the DpkgVersion class by Raphael Hertzog in
+# svn://svn.debian.org/qa/trunk/pts/www/bin/common.py r2361
+class NativeVersion(object):
+    """Represents a Debian package version, with native Python comparison"""
+
+    re_all_digits_or_not = re.compile("\d+|\D+")
+    re_digits = re.compile("\d+")
+    re_digit = re.compile("\d")
+    re_alpha = re.compile("[A-Za-z]")
+    re_epoch_and_version = re.compile("(\d*):(.+)")
+    re_version_and_revision = re.compile("(.+)-(.*)")
+
+    def __init__(self, version):
+        result = self.re_epoch_and_version.match(version)
+        if result is not None:
+            self.epoch = int(result.group(1))
+            version = result.group(2)
+            self.no_epoch = False
+        else:
+            self.epoch = 0
+            self.no_epoch = True
+        result = self.re_version_and_revision.match(version)
+        if result is not None:
+            self.version = result.group(1)
+            self.revision = result.group(2)
+            self.no_revision = False
+        else:
+            self.version = version
+            self.revision = "0"
+            self.no_revision = True
 
-version_compare = apt_pkg.VersionCompare
+    def __str__(self):
+        version = ""
+        if not self.no_epoch:
+            version += self.epoch + ":"
+        version += self.version
+        if not self.no_revision:
+            version += "-" + self.revision
+        return version
+
+    def __repr__(self):
+        return "%s('%s')" % (self.__class__.__name__, self)
+
+    def __cmp__(self, other):
+        res = cmp(self.epoch, other.epoch)
+        if res != 0:
+            return res
+        res = self._version_cmp_part(self.version, other.version)
+        if res != 0:
+            return res
+        return self._version_cmp_part(self.revision, other.revision)
+
+    @classmethod
+    def _order(cls, x):
+        """Return an integer value for character x"""
+        if x == '~':
+            return -1
+        elif cls.re_digit.match(x):
+            return int(x) + 1
+        elif cls.re_alpha.match(x):
+            return ord(x)
+        else:
+            return ord(x) + 256
+
+    @classmethod
+    def _version_cmp_string(cls, va, vb):
+        la = [cls._order(x) for x in va]
+        lb = [cls._order(x) for x in vb]
+        while la or lb:
+            a = 0
+            b = 0
+            if la:
+                a = la.pop(0)
+            if lb:
+                b = lb.pop(0)
+            res = cmp(a, b)
+            if res != 0:
+                return res
+        return 0
+
+    @classmethod
+    def _version_cmp_part(cls, va, vb):
+        la = cls.re_all_digits_or_not.findall(va)
+        lb = cls.re_all_digits_or_not.findall(vb)
+        while la or lb:
+            a = "0"
+            b = "0"
+            if la:
+                a = la.pop(0)
+            if lb:
+                b = lb.pop(0)
+            if cls.re_digits.match(a) and cls.re_digits.match(b):
+                a = int(a)
+                b = int(b)
+                res = cmp(a, b)
+                if res != 0:
+                    return res
+            else:
+                res = cls._version_cmp_string(a, b)
+                if res != 0:
+                    return res
+        return 0
+
+if __have_apt_pkg:
+    class Version(AptPkgVersion):
+        pass
+else:
+    class Version(NativeVersion):
+        pass
+
+def version_compare(a, b):
+    return cmp(Version(a), Version(b))
 
 class PackageFile:
     """A Debian package file.
@@ -423,23 +538,24 @@ mergeAsSets = function_deprecated_by(merge_as_sets)
 
 def test():
     # Version
-    assert Version('0') < Version('a')
-    assert Version('1.0') < Version('1.1')
-    assert Version('1.2') < Version('1.11')
-    assert Version('1.0-0.1') < Version('1.1')
-    assert Version('1.0-0.1') < Version('1.0-1')
-    assert Version('1.0-0.1') == Version('1.0-0.1')
-    assert Version('1.0-0.1') < Version('1.0-1')
-    assert Version('1.0final-5sarge1') > Version('1.0final-5') \
-           > Version('1.0a7-2')
-    assert Version('0.9.2-5') < Version('0.9.2+cvs.1.0.dev.2004.07.28-1.5')
-    assert Version('1:500') < Version('1:5000')
-    assert Version('100:500') > Version('11:5000')
-    assert Version('1.0.4-2') > Version('1.0pre7-2')
-    assert Version('1.5~rc1') < Version('1.5')
-    assert Version('1.5~rc1') < Version('1.5+b1')
-    assert Version('1.5~rc1') < Version('1.5~rc2')
-    assert Version('1.5~rc1') > Version('1.5~dev0')
+    for cls in (AptPkgVersion, NativeVersion):
+        assert cls('0') < cls('a')
+        assert cls('1.0') < cls('1.1')
+        assert cls('1.2') < cls('1.11')
+        assert cls('1.0-0.1') < cls('1.1')
+        assert cls('1.0-0.1') < cls('1.0-1')
+        assert cls('1.0-0.1') == cls('1.0-0.1')
+        assert cls('1.0-0.1') < cls('1.0-1')
+        assert cls('1.0final-5sarge1') > cls('1.0final-5') \
+               > cls('1.0a7-2')
+        assert cls('0.9.2-5') < cls('0.9.2+cvs.1.0.dev.2004.07.28-1.5')
+        assert cls('1:500') < cls('1:5000')
+        assert cls('100:500') > cls('11:5000')
+        assert cls('1.0.4-2') > cls('1.0pre7-2')
+        assert cls('1.5~rc1') < cls('1.5')
+        assert cls('1.5~rc1') < cls('1.5+b1')
+        assert cls('1.5~rc1') < cls('1.5~rc2')
+        assert cls('1.5~rc1') > cls('1.5~dev0')
 
     # Release
     assert intern_release('sarge') < intern_release('etch')
-- 
1.6.6






More information about the pkg-python-debian-maint mailing list