Thanks for the comments.

Version 2, updates:
* Use "import sys" instead of "from sys import hexversion"
* Use super() to call functions from parent class.
* Fix initialization, used to pass m.groups() to parent object which is a
leftover from older versions where I used to subclass list instead of str.
Removed __str__ functions and __str which are useless as well.
* Added readonly attributes cvs, main_version and revision to version, pv and
cpv, package to pv, cpv and category to cpv. I think this is a better way than
making __parts available so it's guaranteed that there'll be no unwanted change
in internal state.
* version uses __init__ now. pv and cpv still use __new__ because to fit code
easily they return None when the argument isn't a valid PV or CPV instead of
raising TypeError.

Please comment.

---
 pym/portage/versions.py |  332 +++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 296 insertions(+), 36 deletions(-)

diff --git a/pym/portage/versions.py b/pym/portage/versions.py
index 261fa9d..507d7a1 100644
--- a/pym/portage/versions.py
+++ b/pym/portage/versions.py
@@ -4,6 +4,7 @@
 # $Id$
 
 import re
+import warnings
 
 ver_regexp = 
re.compile("^(cvs\\.)?(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$")
 suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$")
@@ -12,6 +13,245 @@ endversion_keys = ["pre", "p", "alpha", "beta", "rc"]
 
 from portage.exception import InvalidData
 
+# builtin all() is new in Python-2.5
+# TODO Move compatibility stuff to a new module portage.compat
+# and import from it like from portage.compat import all
+import sys
+if sys.hexversion < 0x02050000:
+       def all(iterable):
+               for i in iterable:
+                       if not bool(i):
+                               return False
+               return True
+
+def needs_version(func):
+       """Decorator for functions that require non-keyword arguments of type 
version."""
+       def func_proxy(*args, **kwargs):
+               if not all([isinstance(arg, version) for arg in args]):
+                       raise TypeError("Not all non-keyword arguments are of 
type version")
+               return func(*args, **kwargs)
+       func_proxy.__doc__ = func.__doc__
+       return func_proxy
+
+def needs_pv(func):
+       """Decorator for functions that require non-keyword arguments of type 
pv."""
+       def func_proxy(*args, **kwargs):
+               if not all([isinstance(arg, pv) for arg in args]):
+                       raise TypeError("Not all non-keyword arguments are of 
type pv")
+               return func(*args, **kwargs)
+       func_proxy.__doc__ = func.__doc__
+       return func_proxy
+
+def needs_cpv(func):
+       """Decorator for functions that require non-keyword arguments of type 
cpv."""
+       def func_proxy(*args, **kwargs):
+               if not all([isinstance(arg, cpv) for arg in args]):
+                       raise TypeError("Not all non-keyword arguments are of 
type cpv")
+               return func(*args, **kwargs)
+       func_proxy.__doc__ = func.__doc__
+       return func_proxy
+
+class version(str):
+       """Represents a package version"""
+
+       __hash = None
+       __parts = ()
+
+       def __init__(self, value):
+               m = ver_regexp.match(value)
+               if m is None:
+                       raise TypeError("Syntax error in version: %s" % value)
+               else:
+                       super(version, self).__init__(value)
+                       self.__hash = hash(m.groups()) + hash(value)
+                       self.__parts = m.groups()
+
+       def __repr__(self):
+               return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+                                               id(self), self)
+
+       def __hash__(self):
+               return self.__hash
+
+       def __getitem__(self, i):
+               return self.__parts[i]
+
+       def __getslice__(self, i, j):
+               return self.__parts[i:j]
+
+       def __len__(self):
+               return len(self.__parts)
+
+       @needs_version
+       def __cmp__(self, y):
+               return vercmp(self, y)
+
+       @needs_version
+       def __eq__(self, y):
+               return vercmp(self, y) == 0
+
+       @needs_version
+       def __ne__(self, y):
+               return vercmp(self, y) != 0
+
+       @needs_version
+       def __lt__(self, y):
+               return vercmp(self, y) < 0
+
+       @needs_version
+       def __le__(self, y):
+               return vercmp(self, y) <= 0
+
+       @needs_version
+       def __gt__(self, y):
+               return vercmp(self, y) > 0
+
+       @needs_version
+       def __ge__(self, y):
+               return vercmp(self, y) >= 0
+
+       @property
+       def cvs(self):
+               return self.__parts[0]
+
+       @property
+       def main_version(self):
+               mv = self.__parts[1:3]
+               mv += self.__parts[4:6]
+               return "".join(mv)
+
+       @property
+       def revision(self):
+               return self.__parts[8]
+
+class pv(str):
+       """Represents a pv"""
+
+       __hash = None
+       __parts = ()
+
+       def __new__(cls, value):
+               parts = pkgsplit(value)
+               if parts is None:
+                       # Ideally a TypeError should be raised here.
+                       # But to fit code using this easily, fail silently.
+                       return None
+               else:
+                       new_pv = super(pv, cls).__new__(cls, value)
+                       new_pv.__hash = hash(parts) + hash(value)
+                       new_pv.__parts = (parts[0], 
version("-".join(parts[1:])))
+
+                       return new_pv
+
+       def __repr__(self):
+               return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+                                               id(self), self)
+
+       def __hash__(self):
+               return self.__hash
+
+       def __getitem__(self, i):
+               return self.__parts[i]
+
+       def __getslice__(self, i, j):
+               return self.__parts[i:j]
+
+       def __len__(self):
+               return len(self.__parts)
+
+       @needs_pv
+       def __cmp__(self, y):
+               if self.__parts[0] != y.__parts[0]:
+                       return None
+               else:
+                       return cmp(self[1], y[1])
+
+       @property
+       def package(self):
+               return self.__parts[0]
+
+       @property
+       def version(self):
+               return self.__parts[1]
+
+       @property
+       def cvs(self):
+               return self.__parts[1].cvs
+
+       @property
+       def main_version(self):
+               return self.__parts[1].main_version
+
+       @property
+       def revision(self):
+               return self.__parts[1].revision
+
+class cpv(str):
+       """Represents a cpv"""
+
+       __hash = None
+       __parts = ()
+
+       def __new__(cls, value):
+               parts = catpkgsplit(value)
+               if parts is None:
+                       # Ideally a TypeError should be raised here.
+                       # But to fit code using this easily, fail silently.
+                               return None
+               else:
+                       new_cpv = super(cpv, cls).__new__(cls, value)
+                       new_cpv.__hash = hash(parts) + hash(value)
+                       new_cpv.__parts = (parts[0], pv("-".join(parts[1:])))
+
+                       return new_cpv
+
+       def __repr__(self):
+               return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+                                               id(self), self)
+
+       def __hash__(self):
+               return self.__hash
+
+       def __getitem__(self, i):
+               return self.__parts[i]
+
+       def __getslice__(self, i, j):
+               return self.__parts[i:j]
+
+       def __len__(self):
+               return len(self.__parts)
+
+       @needs_cpv
+       def __cmp__(self, y):
+               if self[0] != y[0]:
+                       return None
+               else:
+                       return cmp(self[1], y[1])
+
+       @property
+       def category(self):
+               return self.__parts[0]
+
+       @property
+       def package(self):
+               return self.__parts[1]
+
+       @property
+       def version(self):
+               return self.__parts[1].version
+
+       @property
+       def cvs(self):
+               return self.__parts[1].cvs
+
+       @property
+       def main_version(self):
+               return self.__parts[1].main_version
+
+       @property
+       def revision(self):
+               return self.__parts[1].revision
+
 def ververify(myver, silent=1):
        if ver_regexp.match(myver):
                return 1
@@ -45,43 +285,63 @@ def vercmp(ver1, ver2, silent=1):
        4. None if ver1 or ver2 are invalid (see ver_regexp in 
portage.versions.py)
        """
 
-       if ver1 == ver2:
-               return 0
-       mykey=ver1+":"+ver2
-       try:
-               return vercmp_cache[mykey]
-       except KeyError:
-               pass
-       match1 = ver_regexp.match(ver1)
-       match2 = ver_regexp.match(ver2)
-       
-       # checking that the versions are valid
-       if not match1 or not match1.groups():
-               if not silent:
-                       print "!!! syntax error in version: %s" % ver1
-               return None
-       if not match2 or not match2.groups():
-               if not silent:
-                       print "!!! syntax error in version: %s" % ver2
-               return None
+       if isinstance(ver1, version) and isinstance(ver2, version):
+               if ver1._str == ver2._str:
+                       return 0
+               mykey = ver1._str+":"+ver2._str
+               if mykey in vercmp_cache:
+                       return vercmp_cache[mykey]
+
+               group1 = ver1[:]
+               group2 = ver2[:]
+       elif isinstance(ver1, str) and isinstance(ver2, str):
+               ## Backwards compatibility
+               msg = "vercmp(str,str) is deprecated use portage.version object 
instead"
+               warnings.warn(msg, DeprecationWarning)
+
+               if ver1 == ver2:
+                       return 0
+               mykey=ver1+":"+ver2
+               try:
+                       return vercmp_cache[mykey]
+               except KeyError:
+                       pass
+               match1 = ver_regexp.match(ver1)
+               match2 = ver_regexp.match(ver2)
+
+               # checking that the versions are valid
+               if not match1 or not match1.groups():
+                       if not silent:
+                               print "!!! syntax error in version: %s" % ver1
+                       return None
+               if not match2 or not match2.groups():
+                       if not silent:
+                               print "!!! syntax error in version: %s" % ver2
+                       return None
+
+               group1 = match1.groups()
+               group2 = match2.groups()
+       else:
+               raise TypeError(
+                       "Arguments aren't of type str,str or version,version")
 
        # shortcut for cvs ebuilds (new style)
-       if match1.group(1) and not match2.group(1):
+       if group1[0] and not group2[0]:
                vercmp_cache[mykey] = 1
                return 1
-       elif match2.group(1) and not match1.group(1):
+       elif group2[0] and not group1[0]:
                vercmp_cache[mykey] = -1
                return -1
        
        # building lists of the version parts before the suffix
        # first part is simple
-       list1 = [int(match1.group(2))]
-       list2 = [int(match2.group(2))]
+       list1 = [int(group1[1])]
+       list2 = [int(group2[1])]
 
        # this part would greatly benefit from a fixed-length version pattern
-       if len(match1.group(3)) or len(match2.group(3)):
-               vlist1 = match1.group(3)[1:].split(".")
-               vlist2 = match2.group(3)[1:].split(".")
+       if len(group1[2]) or len(group2[2]):
+               vlist1 = group1[2][1:].split(".")
+               vlist2 = group2[2][1:].split(".")
                for i in range(0, max(len(vlist1), len(vlist2))):
                        # Implcit .0 is given a value of -1, so that 1.0.0 > 
1.0, since it
                        # would be ambiguous if two versions that aren't 
literally equal
@@ -111,10 +371,10 @@ def vercmp(ver1, ver2, silent=1):
                                list2.append(int(vlist2[i].ljust(max_len, "0")))
 
        # and now the final letter
-       if len(match1.group(5)):
-               list1.append(ord(match1.group(5)))
-       if len(match2.group(5)):
-               list2.append(ord(match2.group(5)))
+       if len(group1[4]):
+               list1.append(ord(group1[4]))
+       if len(group2[4]):
+               list2.append(ord(group2[4]))
 
        for i in range(0, max(len(list1), len(list2))):
                if len(list1) <= i:
@@ -128,8 +388,8 @@ def vercmp(ver1, ver2, silent=1):
                        return list1[i] - list2[i]
        
        # main version is equal, so now compare the _suffix part
-       list1 = match1.group(6).split("_")[1:]
-       list2 = match2.group(6).split("_")[1:]
+       list1 = group1[5].split("_")[1:]
+       list2 = group2[5].split("_")[1:]
        
        for i in range(0, max(len(list1), len(list2))):
                # Implicit _p0 is given a value of -1, so that 1 < 1_p0
@@ -154,12 +414,12 @@ def vercmp(ver1, ver2, silent=1):
                                return r1 - r2
        
        # the suffix part is equal to, so finally check the revision
-       if match1.group(10):
-               r1 = int(match1.group(10))
+       if group1[9]:
+               r1 = int(group1[9])
        else:
                r1 = 0
-       if match2.group(10):
-               r2 = int(match2.group(10))
+       if group2[9]:
+               r2 = int(group2[9])
        else:
                r2 = 0
        vercmp_cache[mykey] = r1 - r2
-- 
Regards,
Ali Polatel


Reply via email to