Gentoo Archives: gentoo-portage-dev

From: Ali Polatel <hawking@g.o>
To: gentoo-portage-dev@l.g.o
Subject: [gentoo-portage-dev] [RFC/PATCH v2] New objects cpv, pv and version to be used instead of raw strings.
Date: Thu, 24 Jul 2008 00:12:58
Message-Id: 1216858370-27075-1-git-send-email-hawking@gentoo.org
In Reply to: [gentoo-portage-dev] [RFC/PATCH] New objects cpv, pv and version to be used instead of raw strings. by Ali Polatel
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

Replies