1 |
Thanks for the comments. |
2 |
|
3 |
Version 2, updates: |
4 |
* Use "import sys" instead of "from sys import hexversion" |
5 |
* Use super() to call functions from parent class. |
6 |
* Fix initialization, used to pass m.groups() to parent object which is a |
7 |
leftover from older versions where I used to subclass list instead of str. |
8 |
Removed __str__ functions and __str which are useless as well. |
9 |
* Added readonly attributes cvs, main_version and revision to version, pv and |
10 |
cpv, package to pv, cpv and category to cpv. I think this is a better way than |
11 |
making __parts available so it's guaranteed that there'll be no unwanted change |
12 |
in internal state. |
13 |
* version uses __init__ now. pv and cpv still use __new__ because to fit code |
14 |
easily they return None when the argument isn't a valid PV or CPV instead of |
15 |
raising TypeError. |
16 |
|
17 |
Please comment. |
18 |
|
19 |
--- |
20 |
pym/portage/versions.py | 332 +++++++++++++++++++++++++++++++++++++++++----- |
21 |
1 files changed, 296 insertions(+), 36 deletions(-) |
22 |
|
23 |
diff --git a/pym/portage/versions.py b/pym/portage/versions.py |
24 |
index 261fa9d..507d7a1 100644 |
25 |
--- a/pym/portage/versions.py |
26 |
+++ b/pym/portage/versions.py |
27 |
@@ -4,6 +4,7 @@ |
28 |
# $Id$ |
29 |
|
30 |
import re |
31 |
+import warnings |
32 |
|
33 |
ver_regexp = re.compile("^(cvs\\.)?(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$") |
34 |
suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$") |
35 |
@@ -12,6 +13,245 @@ endversion_keys = ["pre", "p", "alpha", "beta", "rc"] |
36 |
|
37 |
from portage.exception import InvalidData |
38 |
|
39 |
+# builtin all() is new in Python-2.5 |
40 |
+# TODO Move compatibility stuff to a new module portage.compat |
41 |
+# and import from it like from portage.compat import all |
42 |
+import sys |
43 |
+if sys.hexversion < 0x02050000: |
44 |
+ def all(iterable): |
45 |
+ for i in iterable: |
46 |
+ if not bool(i): |
47 |
+ return False |
48 |
+ return True |
49 |
+ |
50 |
+def needs_version(func): |
51 |
+ """Decorator for functions that require non-keyword arguments of type version.""" |
52 |
+ def func_proxy(*args, **kwargs): |
53 |
+ if not all([isinstance(arg, version) for arg in args]): |
54 |
+ raise TypeError("Not all non-keyword arguments are of type version") |
55 |
+ return func(*args, **kwargs) |
56 |
+ func_proxy.__doc__ = func.__doc__ |
57 |
+ return func_proxy |
58 |
+ |
59 |
+def needs_pv(func): |
60 |
+ """Decorator for functions that require non-keyword arguments of type pv.""" |
61 |
+ def func_proxy(*args, **kwargs): |
62 |
+ if not all([isinstance(arg, pv) for arg in args]): |
63 |
+ raise TypeError("Not all non-keyword arguments are of type pv") |
64 |
+ return func(*args, **kwargs) |
65 |
+ func_proxy.__doc__ = func.__doc__ |
66 |
+ return func_proxy |
67 |
+ |
68 |
+def needs_cpv(func): |
69 |
+ """Decorator for functions that require non-keyword arguments of type cpv.""" |
70 |
+ def func_proxy(*args, **kwargs): |
71 |
+ if not all([isinstance(arg, cpv) for arg in args]): |
72 |
+ raise TypeError("Not all non-keyword arguments are of type cpv") |
73 |
+ return func(*args, **kwargs) |
74 |
+ func_proxy.__doc__ = func.__doc__ |
75 |
+ return func_proxy |
76 |
+ |
77 |
+class version(str): |
78 |
+ """Represents a package version""" |
79 |
+ |
80 |
+ __hash = None |
81 |
+ __parts = () |
82 |
+ |
83 |
+ def __init__(self, value): |
84 |
+ m = ver_regexp.match(value) |
85 |
+ if m is None: |
86 |
+ raise TypeError("Syntax error in version: %s" % value) |
87 |
+ else: |
88 |
+ super(version, self).__init__(value) |
89 |
+ self.__hash = hash(m.groups()) + hash(value) |
90 |
+ self.__parts = m.groups() |
91 |
+ |
92 |
+ def __repr__(self): |
93 |
+ return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
94 |
+ id(self), self) |
95 |
+ |
96 |
+ def __hash__(self): |
97 |
+ return self.__hash |
98 |
+ |
99 |
+ def __getitem__(self, i): |
100 |
+ return self.__parts[i] |
101 |
+ |
102 |
+ def __getslice__(self, i, j): |
103 |
+ return self.__parts[i:j] |
104 |
+ |
105 |
+ def __len__(self): |
106 |
+ return len(self.__parts) |
107 |
+ |
108 |
+ @needs_version |
109 |
+ def __cmp__(self, y): |
110 |
+ return vercmp(self, y) |
111 |
+ |
112 |
+ @needs_version |
113 |
+ def __eq__(self, y): |
114 |
+ return vercmp(self, y) == 0 |
115 |
+ |
116 |
+ @needs_version |
117 |
+ def __ne__(self, y): |
118 |
+ return vercmp(self, y) != 0 |
119 |
+ |
120 |
+ @needs_version |
121 |
+ def __lt__(self, y): |
122 |
+ return vercmp(self, y) < 0 |
123 |
+ |
124 |
+ @needs_version |
125 |
+ def __le__(self, y): |
126 |
+ return vercmp(self, y) <= 0 |
127 |
+ |
128 |
+ @needs_version |
129 |
+ def __gt__(self, y): |
130 |
+ return vercmp(self, y) > 0 |
131 |
+ |
132 |
+ @needs_version |
133 |
+ def __ge__(self, y): |
134 |
+ return vercmp(self, y) >= 0 |
135 |
+ |
136 |
+ @property |
137 |
+ def cvs(self): |
138 |
+ return self.__parts[0] |
139 |
+ |
140 |
+ @property |
141 |
+ def main_version(self): |
142 |
+ mv = self.__parts[1:3] |
143 |
+ mv += self.__parts[4:6] |
144 |
+ return "".join(mv) |
145 |
+ |
146 |
+ @property |
147 |
+ def revision(self): |
148 |
+ return self.__parts[8] |
149 |
+ |
150 |
+class pv(str): |
151 |
+ """Represents a pv""" |
152 |
+ |
153 |
+ __hash = None |
154 |
+ __parts = () |
155 |
+ |
156 |
+ def __new__(cls, value): |
157 |
+ parts = pkgsplit(value) |
158 |
+ if parts is None: |
159 |
+ # Ideally a TypeError should be raised here. |
160 |
+ # But to fit code using this easily, fail silently. |
161 |
+ return None |
162 |
+ else: |
163 |
+ new_pv = super(pv, cls).__new__(cls, value) |
164 |
+ new_pv.__hash = hash(parts) + hash(value) |
165 |
+ new_pv.__parts = (parts[0], version("-".join(parts[1:]))) |
166 |
+ |
167 |
+ return new_pv |
168 |
+ |
169 |
+ def __repr__(self): |
170 |
+ return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
171 |
+ id(self), self) |
172 |
+ |
173 |
+ def __hash__(self): |
174 |
+ return self.__hash |
175 |
+ |
176 |
+ def __getitem__(self, i): |
177 |
+ return self.__parts[i] |
178 |
+ |
179 |
+ def __getslice__(self, i, j): |
180 |
+ return self.__parts[i:j] |
181 |
+ |
182 |
+ def __len__(self): |
183 |
+ return len(self.__parts) |
184 |
+ |
185 |
+ @needs_pv |
186 |
+ def __cmp__(self, y): |
187 |
+ if self.__parts[0] != y.__parts[0]: |
188 |
+ return None |
189 |
+ else: |
190 |
+ return cmp(self[1], y[1]) |
191 |
+ |
192 |
+ @property |
193 |
+ def package(self): |
194 |
+ return self.__parts[0] |
195 |
+ |
196 |
+ @property |
197 |
+ def version(self): |
198 |
+ return self.__parts[1] |
199 |
+ |
200 |
+ @property |
201 |
+ def cvs(self): |
202 |
+ return self.__parts[1].cvs |
203 |
+ |
204 |
+ @property |
205 |
+ def main_version(self): |
206 |
+ return self.__parts[1].main_version |
207 |
+ |
208 |
+ @property |
209 |
+ def revision(self): |
210 |
+ return self.__parts[1].revision |
211 |
+ |
212 |
+class cpv(str): |
213 |
+ """Represents a cpv""" |
214 |
+ |
215 |
+ __hash = None |
216 |
+ __parts = () |
217 |
+ |
218 |
+ def __new__(cls, value): |
219 |
+ parts = catpkgsplit(value) |
220 |
+ if parts is None: |
221 |
+ # Ideally a TypeError should be raised here. |
222 |
+ # But to fit code using this easily, fail silently. |
223 |
+ return None |
224 |
+ else: |
225 |
+ new_cpv = super(cpv, cls).__new__(cls, value) |
226 |
+ new_cpv.__hash = hash(parts) + hash(value) |
227 |
+ new_cpv.__parts = (parts[0], pv("-".join(parts[1:]))) |
228 |
+ |
229 |
+ return new_cpv |
230 |
+ |
231 |
+ def __repr__(self): |
232 |
+ return "<%s object at 0x%x: %s>" % (self.__class__.__name__, |
233 |
+ id(self), self) |
234 |
+ |
235 |
+ def __hash__(self): |
236 |
+ return self.__hash |
237 |
+ |
238 |
+ def __getitem__(self, i): |
239 |
+ return self.__parts[i] |
240 |
+ |
241 |
+ def __getslice__(self, i, j): |
242 |
+ return self.__parts[i:j] |
243 |
+ |
244 |
+ def __len__(self): |
245 |
+ return len(self.__parts) |
246 |
+ |
247 |
+ @needs_cpv |
248 |
+ def __cmp__(self, y): |
249 |
+ if self[0] != y[0]: |
250 |
+ return None |
251 |
+ else: |
252 |
+ return cmp(self[1], y[1]) |
253 |
+ |
254 |
+ @property |
255 |
+ def category(self): |
256 |
+ return self.__parts[0] |
257 |
+ |
258 |
+ @property |
259 |
+ def package(self): |
260 |
+ return self.__parts[1] |
261 |
+ |
262 |
+ @property |
263 |
+ def version(self): |
264 |
+ return self.__parts[1].version |
265 |
+ |
266 |
+ @property |
267 |
+ def cvs(self): |
268 |
+ return self.__parts[1].cvs |
269 |
+ |
270 |
+ @property |
271 |
+ def main_version(self): |
272 |
+ return self.__parts[1].main_version |
273 |
+ |
274 |
+ @property |
275 |
+ def revision(self): |
276 |
+ return self.__parts[1].revision |
277 |
+ |
278 |
def ververify(myver, silent=1): |
279 |
if ver_regexp.match(myver): |
280 |
return 1 |
281 |
@@ -45,43 +285,63 @@ def vercmp(ver1, ver2, silent=1): |
282 |
4. None if ver1 or ver2 are invalid (see ver_regexp in portage.versions.py) |
283 |
""" |
284 |
|
285 |
- if ver1 == ver2: |
286 |
- return 0 |
287 |
- mykey=ver1+":"+ver2 |
288 |
- try: |
289 |
- return vercmp_cache[mykey] |
290 |
- except KeyError: |
291 |
- pass |
292 |
- match1 = ver_regexp.match(ver1) |
293 |
- match2 = ver_regexp.match(ver2) |
294 |
- |
295 |
- # checking that the versions are valid |
296 |
- if not match1 or not match1.groups(): |
297 |
- if not silent: |
298 |
- print "!!! syntax error in version: %s" % ver1 |
299 |
- return None |
300 |
- if not match2 or not match2.groups(): |
301 |
- if not silent: |
302 |
- print "!!! syntax error in version: %s" % ver2 |
303 |
- return None |
304 |
+ if isinstance(ver1, version) and isinstance(ver2, version): |
305 |
+ if ver1._str == ver2._str: |
306 |
+ return 0 |
307 |
+ mykey = ver1._str+":"+ver2._str |
308 |
+ if mykey in vercmp_cache: |
309 |
+ return vercmp_cache[mykey] |
310 |
+ |
311 |
+ group1 = ver1[:] |
312 |
+ group2 = ver2[:] |
313 |
+ elif isinstance(ver1, str) and isinstance(ver2, str): |
314 |
+ ## Backwards compatibility |
315 |
+ msg = "vercmp(str,str) is deprecated use portage.version object instead" |
316 |
+ warnings.warn(msg, DeprecationWarning) |
317 |
+ |
318 |
+ if ver1 == ver2: |
319 |
+ return 0 |
320 |
+ mykey=ver1+":"+ver2 |
321 |
+ try: |
322 |
+ return vercmp_cache[mykey] |
323 |
+ except KeyError: |
324 |
+ pass |
325 |
+ match1 = ver_regexp.match(ver1) |
326 |
+ match2 = ver_regexp.match(ver2) |
327 |
+ |
328 |
+ # checking that the versions are valid |
329 |
+ if not match1 or not match1.groups(): |
330 |
+ if not silent: |
331 |
+ print "!!! syntax error in version: %s" % ver1 |
332 |
+ return None |
333 |
+ if not match2 or not match2.groups(): |
334 |
+ if not silent: |
335 |
+ print "!!! syntax error in version: %s" % ver2 |
336 |
+ return None |
337 |
+ |
338 |
+ group1 = match1.groups() |
339 |
+ group2 = match2.groups() |
340 |
+ else: |
341 |
+ raise TypeError( |
342 |
+ "Arguments aren't of type str,str or version,version") |
343 |
|
344 |
# shortcut for cvs ebuilds (new style) |
345 |
- if match1.group(1) and not match2.group(1): |
346 |
+ if group1[0] and not group2[0]: |
347 |
vercmp_cache[mykey] = 1 |
348 |
return 1 |
349 |
- elif match2.group(1) and not match1.group(1): |
350 |
+ elif group2[0] and not group1[0]: |
351 |
vercmp_cache[mykey] = -1 |
352 |
return -1 |
353 |
|
354 |
# building lists of the version parts before the suffix |
355 |
# first part is simple |
356 |
- list1 = [int(match1.group(2))] |
357 |
- list2 = [int(match2.group(2))] |
358 |
+ list1 = [int(group1[1])] |
359 |
+ list2 = [int(group2[1])] |
360 |
|
361 |
# this part would greatly benefit from a fixed-length version pattern |
362 |
- if len(match1.group(3)) or len(match2.group(3)): |
363 |
- vlist1 = match1.group(3)[1:].split(".") |
364 |
- vlist2 = match2.group(3)[1:].split(".") |
365 |
+ if len(group1[2]) or len(group2[2]): |
366 |
+ vlist1 = group1[2][1:].split(".") |
367 |
+ vlist2 = group2[2][1:].split(".") |
368 |
for i in range(0, max(len(vlist1), len(vlist2))): |
369 |
# Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it |
370 |
# would be ambiguous if two versions that aren't literally equal |
371 |
@@ -111,10 +371,10 @@ def vercmp(ver1, ver2, silent=1): |
372 |
list2.append(int(vlist2[i].ljust(max_len, "0"))) |
373 |
|
374 |
# and now the final letter |
375 |
- if len(match1.group(5)): |
376 |
- list1.append(ord(match1.group(5))) |
377 |
- if len(match2.group(5)): |
378 |
- list2.append(ord(match2.group(5))) |
379 |
+ if len(group1[4]): |
380 |
+ list1.append(ord(group1[4])) |
381 |
+ if len(group2[4]): |
382 |
+ list2.append(ord(group2[4])) |
383 |
|
384 |
for i in range(0, max(len(list1), len(list2))): |
385 |
if len(list1) <= i: |
386 |
@@ -128,8 +388,8 @@ def vercmp(ver1, ver2, silent=1): |
387 |
return list1[i] - list2[i] |
388 |
|
389 |
# main version is equal, so now compare the _suffix part |
390 |
- list1 = match1.group(6).split("_")[1:] |
391 |
- list2 = match2.group(6).split("_")[1:] |
392 |
+ list1 = group1[5].split("_")[1:] |
393 |
+ list2 = group2[5].split("_")[1:] |
394 |
|
395 |
for i in range(0, max(len(list1), len(list2))): |
396 |
# Implicit _p0 is given a value of -1, so that 1 < 1_p0 |
397 |
@@ -154,12 +414,12 @@ def vercmp(ver1, ver2, silent=1): |
398 |
return r1 - r2 |
399 |
|
400 |
# the suffix part is equal to, so finally check the revision |
401 |
- if match1.group(10): |
402 |
- r1 = int(match1.group(10)) |
403 |
+ if group1[9]: |
404 |
+ r1 = int(group1[9]) |
405 |
else: |
406 |
r1 = 0 |
407 |
- if match2.group(10): |
408 |
- r2 = int(match2.group(10)) |
409 |
+ if group2[9]: |
410 |
+ r2 = int(group2[9]) |
411 |
else: |
412 |
r2 = 0 |
413 |
vercmp_cache[mykey] = r1 - r2 |
414 |
-- |
415 |
Regards, |
416 |
Ali Polatel |