1 # Copyright(c) 2005 Jason Stubbs <jstubbs@gentoo.org>
2 # Copyright(c) 2005-2006 Brian Harring <ferringb@gmail.com>
3 # Copyright(c) 2009-2010 Gentoo Foundation
5 # Licensed under the GNU General Public License, v2
9 """Provides attributes and methods for a category/package-version string."""
23 from portage.versions import catpkgsplit, vercmp, pkgcmp
25 from gentoolkit import errors
31 isvalid_version_re = re.compile("^(?:cvs\\.)?(?:\\d+)(?:\\.\\d+)*[a-z]?"
32 "(?:_(p(?:re)?|beta|alpha|rc)\\d*)*$")
33 isvalid_cat_re = re.compile("^(?:[a-zA-Z0-9][-a-zA-Z0-9+._]*(?:/(?!$))?)+$")
34 _pkg_re = re.compile("^[a-zA-Z0-9+._]+$")
35 # Prefix specific revision is of the form -r0<digit>+.<digit>+
36 isvalid_rev_re = re.compile(r'(\d+|0\d+\.\d+)')
43 """Provides methods on a category/package-version string.
45 Will also correctly split just a package or package-version string.
48 >>> from gentoolkit.cpv import CPV
49 >>> cpv = CPV('sys-apps/portage-2.2-r1')
50 >>> cpv.category, cpv.name, cpv.fullversion
51 ('sys-apps', 'portage', '2.2-r1')
53 'sys-apps/portage-2.2-r1'
54 >>> # An 'rc' (release candidate) version is less than non 'rc' version:
55 ... CPV('sys-apps/portage-2') > CPV('sys-apps/portage-2_rc10')
59 def __init__(self, cpv, validate=False):
66 self._fullversion = None
68 self.validate = validate
69 if validate and not self.name:
70 raise errors.GentoolkitInvalidCPV(cpv)
74 if self._category is None:
75 self._set_cpv_chunks()
80 if self._name is None:
81 self._set_cpv_chunks()
86 if self._version is None:
87 self._set_cpv_chunks()
92 if self._revision is None:
93 self._set_cpv_chunks()
99 sep = '/' if self.category else ''
100 self._cp = sep.join((self.category, self.name))
104 def fullversion(self):
105 if self._fullversion is None:
106 sep = '-' if self.revision else ''
107 self._fullversion = sep.join((self.version, self.revision))
108 return self._fullversion
110 def _set_cpv_chunks(self):
111 chunks = split_cpv(self.cpv, validate=self.validate)
112 self._category = chunks[0]
113 self._name = chunks[1]
114 self._version = chunks[2]
115 self._revision = chunks[3]
117 def __eq__(self, other):
118 if not isinstance(other, self.__class__):
120 return self.cpv == other.cpv
123 return hash(self.cpv)
125 def __ne__(self, other):
126 return not self == other
128 def __lt__(self, other):
129 if not isinstance(other, self.__class__):
130 raise TypeError("other isn't of %s type, is %s" % (
131 self.__class__, other.__class__)
134 if self.category != other.category:
135 return self.category < other.category
136 elif self.name != other.name:
137 return self.name < other.name
139 # FIXME: this cmp() hack is for vercmp not using -1,0,1
140 # See bug 266493; this was fixed in portage-2.2_rc31
141 #return vercmp(self.fullversion, other.fullversion)
142 return vercmp(self.fullversion, other.fullversion) < 0
144 def __gt__(self, other):
145 if not isinstance(other, self.__class__):
146 raise TypeError("other isn't of %s type, is %s" % (
147 self.__class__, other.__class__)
149 return not self <= other
151 def __le__(self, other):
152 if not isinstance(other, self.__class__):
153 raise TypeError("other isn't of %s type, is %s" % (
154 self.__class__, other.__class__)
156 return self < other or self == other
158 def __ge__(self, other):
159 if not isinstance(other, self.__class__):
160 raise TypeError("other isn't of %s type, is %s" % (
161 self.__class__, other.__class__)
163 return self > other or self == other
166 return "<%s %r>" % (self.__class__.__name__, str(self))
176 def compare_strs(pkg1, pkg2):
177 """Similar to the builtin cmp, but for package strings. Usually called
178 as: package_list.sort(cpv.compare_strs)
180 An alternative is to use the CPV descriptor from gentoolkit.cpv:
181 >>> cpvs = sorted(CPV(x) for x in package_list)
186 pkg1 = catpkgsplit(pkg1)
187 pkg2 = catpkgsplit(pkg2)
188 if pkg1[0] != pkg2[0]:
189 return -1 if pkg1[0] < pkg2[0] else 1
190 elif pkg1[1] != pkg2[1]:
191 return -1 if pkg1[1] < pkg2[1] else 1
193 return pkgcmp(pkg1[1:], pkg2[1:])
196 def split_cpv(cpv, validate=True):
197 """Split a cpv into category, name, version and revision.
199 Modified from pkgcore.ebuild.cpv
202 @param cpv: pkg, cat/pkg, pkg-ver, cat/pkg-ver
204 @return: (category, pkg_name, version, revision)
205 Each tuple element is a string or empty string ("").
208 category = name = version = revision = ''
211 category, pkgver = cpv.rsplit("/", 1)
214 if validate and category and not isvalid_cat_re.match(category):
215 raise errors.GentoolkitInvalidCPV(cpv)
216 pkg_chunks = pkgver.split("-")
217 lpkg_chunks = len(pkg_chunks)
219 return (category, pkg_chunks[0], version, revision)
220 if isvalid_rev(pkg_chunks[-1]):
222 # needs at least ('pkg', 'ver', 'rev')
223 raise errors.GentoolkitInvalidCPV(cpv)
224 rev = pkg_chunks.pop(-1)
228 if isvalid_version_re.match(pkg_chunks[-1]):
229 version = pkg_chunks.pop(-1)
231 if not isvalid_pkg_name(pkg_chunks):
232 raise errors.GentoolkitInvalidCPV(cpv)
233 name = '-'.join(pkg_chunks)
235 return (category, name, version, revision)
238 def isvalid_pkg_name(chunks):
240 # this means a leading -
243 if not all(not s or mf(s) for s in chunks):
245 if chunks[-1].isdigit() or not chunks[-1]:
252 return s and s[0] == 'r' and isvalid_rev_re.match(s[1:])
254 # vim: set ts=4 sw=4 tw=79: