public inbox for gentoo-portage-dev@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings.
@ 2008-07-18  9:41 Ali Polatel
  2008-07-18 11:29 ` René 'Necoro' Neumann
  2008-07-24  0:12 ` [gentoo-portage-dev] [RFC/PATCH v2] " Ali Polatel
  0 siblings, 2 replies; 7+ messages in thread
From: Ali Polatel @ 2008-07-18  9:41 UTC (permalink / raw
  To: gentoo-portage-dev

Hi,
Attached patch adds objects cpv, pv and version to portage.versions. This is
meant as a thin layer over functions vercmp(), pkgcmp(), pkgsplit() and
catpkgsplit().
Using these objects instead of the mentioned functions allows us to write
cleaner code and remove deprecated stuff like:
    list.sort(pkgcmp)
which won't exist in py3k.

Please comment.

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

diff --git a/pym/portage/versions.py b/pym/portage/versions.py
index 261fa9d..20b0d4f 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,205 @@ 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
+from sys import hexversion
+if hexversion < 0x02050000:
+    def all(iterable):
+        for i in iterable:
+            if not bool(i):
+                return False
+        return True
+del hexversion
+
+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 = ()
+	__str = ""
+
+	def __new__(cls, value):
+		m = ver_regexp.match(value)
+		if m is None:
+			raise TypeError("Syntax error in version: %s" % value)
+		else:
+			new_ver = str.__new__(cls, m.groups())
+			new_ver.__hash = hash(m.groups()) + hash(value)
+			new_ver.__parts = m.groups()
+			new_ver.__str = value
+
+			return new_ver
+
+	def __str__(self):
+		return self.__str
+
+	def __repr__(self):
+		return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+				id(self), self.__str)
+
+	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
+
+class pv(str):
+	"""Represents a pv"""
+
+	__hash = None
+	__parts = ()
+	__str = ""
+
+	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 = str.__new__(cls, parts)
+			new_pv.__hash = hash(parts) + hash(value)
+			new_pv.__parts = (parts[0], version("-".join(parts[1:])))
+			new_pv.__str = value
+
+			return new_pv
+
+	def __str__(self):
+		return self.__str
+
+	def __repr__(self):
+		return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+				id(self), self.__str)
+
+	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])
+
+class cpv(str):
+	"""Represents a cpv"""
+
+	__hash = None
+	__parts = ()
+	__str = ""
+
+	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 = str.__new__(cls, parts)
+			new_cpv.__hash = hash(parts) + hash(value)
+			new_cpv.__parts = (parts[0], pv("-".join(parts[1:])))
+			new_cpv.__str = value
+
+			return new_cpv
+
+	def __str__(self):
+		return self.__str
+
+	def __repr__(self):
+		return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
+				id(self), self.__str)
+
+	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])
+
 def ververify(myver, silent=1):
 	if ver_regexp.match(myver):
 		return 1
@@ -45,43 +245,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 +331,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 +348,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 +374,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
-- 
1.5.6.2

-- 
gentoo-portage-dev@lists.gentoo.org mailing list



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-18  9:41 [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings Ali Polatel
@ 2008-07-18 11:29 ` René 'Necoro' Neumann
  2008-07-18 13:56   ` [gentoo-portage-dev] " Ali Polatel
  2008-07-24  0:12 ` [gentoo-portage-dev] [RFC/PATCH v2] " Ali Polatel
  1 sibling, 1 reply; 7+ messages in thread
From: René 'Necoro' Neumann @ 2008-07-18 11:29 UTC (permalink / raw
  To: gentoo-portage-dev

On Fri, 18 Jul 2008 12:41:52 +0300, Ali Polatel <hawking@gentoo.org> wrote:
> Hi,
> Attached patch adds objects cpv, pv and version to portage.versions. This
> is
> meant as a thin layer over functions vercmp(), pkgcmp(), pkgsplit() and
> catpkgsplit().
> Using these objects instead of the mentioned functions allows us to write
> cleaner code and remove deprecated stuff like:
>     list.sort(pkgcmp)
> which won't exist in py3k.
> 
> Please comment.

Is there a reason, why you are using "__new__" instead of "__init__"?

Regards,
René

-- 
gentoo-portage-dev@lists.gentoo.org mailing list



^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gentoo-portage-dev] Re: [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-18 11:29 ` René 'Necoro' Neumann
@ 2008-07-18 13:56   ` Ali Polatel
  2008-07-18 22:59     ` Brian Harring
  0 siblings, 1 reply; 7+ messages in thread
From: Ali Polatel @ 2008-07-18 13:56 UTC (permalink / raw
  To: gentoo-portage-dev

[-- Attachment #1: Type: text/plain, Size: 931 bytes --]

René 'Necoro' Neumann yazmış:
> On Fri, 18 Jul 2008 12:41:52 +0300, Ali Polatel <hawking@gentoo.org> wrote:
> > Hi,
> > Attached patch adds objects cpv, pv and version to portage.versions. This
> > is
> > meant as a thin layer over functions vercmp(), pkgcmp(), pkgsplit() and
> > catpkgsplit().
> > Using these objects instead of the mentioned functions allows us to write
> > cleaner code and remove deprecated stuff like:
> >     list.sort(pkgcmp)
> > which won't exist in py3k.
> > 
> > Please comment.
> 
> Is there a reason, why you are using "__new__" instead of "__init__"?

__new__ is about object creation and the __new__ methods of these
objects create the objects from raw strings.

__init__ is for initializing and customizing objects and that method
is for application writers who want to customize these objects to suit
their needs.

> 
> Regards,
> René
> 
 
-- 
Regards,
Ali Polatel

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [gentoo-portage-dev] Re: [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-18 13:56   ` [gentoo-portage-dev] " Ali Polatel
@ 2008-07-18 22:59     ` Brian Harring
  0 siblings, 0 replies; 7+ messages in thread
From: Brian Harring @ 2008-07-18 22:59 UTC (permalink / raw
  To: gentoo-portage-dev

On Fri, Jul 18, 2008 at 04:56:54PM +0300, Ali Polatel wrote:
> René 'Necoro' Neumann yazmış:
> > On Fri, 18 Jul 2008 12:41:52 +0300, Ali Polatel <hawking@gentoo.org> wrote:
> > > Hi,
> > > Attached patch adds objects cpv, pv and version to portage.versions. This
> > > is
> > > meant as a thin layer over functions vercmp(), pkgcmp(), pkgsplit() and
> > > catpkgsplit().
> > > Using these objects instead of the mentioned functions allows us to write
> > > cleaner code and remove deprecated stuff like:
> > >     list.sort(pkgcmp)
> > > which won't exist in py3k.
> > > 
> > > Please comment.
> > 
> > Is there a reason, why you are using "__new__" instead of "__init__"?
> 
> __new__ is about object creation and the __new__ methods of these
> objects create the objects from raw strings.
> 
> __init__ is for initializing and customizing objects and that method
> is for application writers who want to customize these objects to suit
> their needs.

as you said, __new__ is about object creation- literally, allocation.  
You're using it for initialization however.

Re: customization, customization isn't particularly possible anyways 
with your extreme usage of __ to hide variables; rather unpythonic.
~brian
--
gentoo-portage-dev@lists.gentoo.org mailing list



^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-18  9:41 [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings Ali Polatel
  2008-07-18 11:29 ` René 'Necoro' Neumann
@ 2008-07-24  0:12 ` Ali Polatel
  2008-07-24 11:11   ` René 'Necoro' Neumann
  2008-07-27  3:00   ` Zac Medico
  1 sibling, 2 replies; 7+ messages in thread
From: Ali Polatel @ 2008-07-24  0:12 UTC (permalink / raw
  To: gentoo-portage-dev

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




^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-24  0:12 ` [gentoo-portage-dev] [RFC/PATCH v2] " Ali Polatel
@ 2008-07-24 11:11   ` René 'Necoro' Neumann
  2008-07-27  3:00   ` Zac Medico
  1 sibling, 0 replies; 7+ messages in thread
From: René 'Necoro' Neumann @ 2008-07-24 11:11 UTC (permalink / raw
  To: gentoo-portage-dev


For the decorators: I'm not sure if this is overkill: But in Python2.5
there is the "functools.wraps"-decorator, that takes care that the wrapped
function looks the same like the original one :)...

This would look like:

def decorator (f):
    @wraps(f)
    def func_proxy(...):
        # something
    return func_proxy

And when we are not using 2.5, your current approach could be used (though
enhanced a bit to copy more data of the wrapped func):

def wraps (f):
    """Emulate functools.wraps for <python-2.5"""
    def w_deco(func):
        func.__doc__ = f.__doc__
        func.__dict__ = f.__dict__
        func.__name__ = f.__name__
        return func
    return w_deco

On Thu, 24 Jul 2008 03:12:50 +0300, Ali Polatel <hawking@gentoo.org> wrote:
> 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




^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings.
  2008-07-24  0:12 ` [gentoo-portage-dev] [RFC/PATCH v2] " Ali Polatel
  2008-07-24 11:11   ` René 'Necoro' Neumann
@ 2008-07-27  3:00   ` Zac Medico
  1 sibling, 0 replies; 7+ messages in thread
From: Zac Medico @ 2008-07-27  3:00 UTC (permalink / raw
  To: gentoo-portage-dev

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Ali Polatel wrote:
> Thanks for the comments.

I haven't been able to finish reviewing your patch yet but I just
want you to know that it's still in my queue.

Zac
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.9 (GNU/Linux)

iEYEARECAAYFAkiL5MwACgkQ/ejvha5XGaMSzACgyuAEQHMNFOjiubmTQhyuZzFa
EusAoM97a0BCfByh/gEU6Sj6X6kx9MCt
=vJa6
-----END PGP SIGNATURE-----



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2008-07-27  3:00 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-07-18  9:41 [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings Ali Polatel
2008-07-18 11:29 ` René 'Necoro' Neumann
2008-07-18 13:56   ` [gentoo-portage-dev] " Ali Polatel
2008-07-18 22:59     ` Brian Harring
2008-07-24  0:12 ` [gentoo-portage-dev] [RFC/PATCH v2] " Ali Polatel
2008-07-24 11:11   ` René 'Necoro' Neumann
2008-07-27  3:00   ` Zac Medico

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox