Make pkgsplit() accept a cpv again, since accepting a pv only will probably
[portage.git] / pym / portage / versions.py
1 # versions.py -- core Portage functionality
2 # Copyright 1998-2006 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 # $Id$
5
6 import re
7
8
9 # \w is [a-zA-Z0-9_]
10
11 # 2.1.1 A category name may contain any of the characters [A-Za-z0-9+_.-].
12 # It must not begin with a hyphen or a dot.
13 _cat = r'[\w+][\w+.-]*'
14
15 # 2.1.2 A package name may contain any of the characters [A-Za-z0-9+_-].
16 # It must not begin with a hyphen,
17 # and must not end in a hyphen followed by one or more digits.
18 _pkg = r'[\w+][\w+-]*?'
19
20 _v = r'(cvs\.)?(\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)'
21 _rev = r'\d+'
22 _vr = _v + '(-r(' + _rev + '))?'
23
24 _cp = '(' + _cat + '/' + _pkg + '(-' + _vr + ')?)'
25 _cpv = '(' + _cp + '-' + _vr + ')'
26 _pv = '(?P<pn>' + _pkg + '(?P<pn_inval>-' + _vr + ')?)' + '-(?P<ver>' + _v + ')(-r(?P<rev>' + _rev + '))?'
27
28 ver_regexp = re.compile("^" + _vr + "$")
29 suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$")
30 suffix_value = {"pre": -2, "p": 0, "alpha": -4, "beta": -3, "rc": -1}
31 endversion_keys = ["pre", "p", "alpha", "beta", "rc"]
32
33 from portage.exception import InvalidData
34 from portage.localization import _
35
36 def ververify(myver, silent=1):
37         if ver_regexp.match(myver):
38                 return 1
39         else:
40                 if not silent:
41                         print(_("!!! syntax error in version: %s") % myver)
42                 return 0
43
44 vercmp_cache = {}
45 def vercmp(ver1, ver2, silent=1):
46         """
47         Compare two versions
48         Example usage:
49                 >>> from portage.versions import vercmp
50                 >>> vercmp('1.0-r1','1.2-r3')
51                 negative number
52                 >>> vercmp('1.3','1.2-r3')
53                 positive number
54                 >>> vercmp('1.0_p3','1.0_p3')
55                 0
56         
57         @param pkg1: version to compare with (see ver_regexp in portage.versions.py)
58         @type pkg1: string (example: "2.1.2-r3")
59         @param pkg2: version to compare againts (see ver_regexp in portage.versions.py)
60         @type pkg2: string (example: "2.1.2_rc5")
61         @rtype: None or float
62         @return:
63         1. positive if ver1 is greater than ver2
64         2. negative if ver1 is less than ver2 
65         3. 0 if ver1 equals ver2
66         4. None if ver1 or ver2 are invalid (see ver_regexp in portage.versions.py)
67         """
68
69         if ver1 == ver2:
70                 return 0
71         mykey=ver1+":"+ver2
72         try:
73                 return vercmp_cache[mykey]
74         except KeyError:
75                 pass
76         match1 = ver_regexp.match(ver1)
77         match2 = ver_regexp.match(ver2)
78         
79         # checking that the versions are valid
80         if not match1 or not match1.groups():
81                 if not silent:
82                         print(_("!!! syntax error in version: %s") % ver1)
83                 return None
84         if not match2 or not match2.groups():
85                 if not silent:
86                         print(_("!!! syntax error in version: %s") % ver2)
87                 return None
88
89         # shortcut for cvs ebuilds (new style)
90         if match1.group(1) and not match2.group(1):
91                 vercmp_cache[mykey] = 1
92                 return 1
93         elif match2.group(1) and not match1.group(1):
94                 vercmp_cache[mykey] = -1
95                 return -1
96         
97         # building lists of the version parts before the suffix
98         # first part is simple
99         list1 = [int(match1.group(2))]
100         list2 = [int(match2.group(2))]
101
102         # this part would greatly benefit from a fixed-length version pattern
103         if len(match1.group(3)) or len(match2.group(3)):
104                 vlist1 = match1.group(3)[1:].split(".")
105                 vlist2 = match2.group(3)[1:].split(".")
106                 for i in range(0, max(len(vlist1), len(vlist2))):
107                         # Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it
108                         # would be ambiguous if two versions that aren't literally equal
109                         # are given the same value (in sorting, for example).
110                         if len(vlist1) <= i or len(vlist1[i]) == 0:
111                                 list1.append(-1)
112                                 list2.append(int(vlist2[i]))
113                         elif len(vlist2) <= i or len(vlist2[i]) == 0:
114                                 list1.append(int(vlist1[i]))
115                                 list2.append(-1)
116                         # Let's make life easy and use integers unless we're forced to use floats
117                         elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"):
118                                 list1.append(int(vlist1[i]))
119                                 list2.append(int(vlist2[i]))
120                         # now we have to use floats so 1.02 compares correctly against 1.1
121                         else:
122                                 # list1.append(float("0."+vlist1[i]))
123                                 # list2.append(float("0."+vlist2[i]))
124                                 # Since python floats have limited range, we multiply both
125                                 # floating point representations by a constant so that they are
126                                 # transformed into whole numbers. This allows the practically
127                                 # infinite range of a python int to be exploited. The
128                                 # multiplication is done by padding both literal strings with
129                                 # zeros as necessary to ensure equal length.
130                                 max_len = max(len(vlist1[i]), len(vlist2[i]))
131                                 list1.append(int(vlist1[i].ljust(max_len, "0")))
132                                 list2.append(int(vlist2[i].ljust(max_len, "0")))
133
134         # and now the final letter
135         if len(match1.group(5)):
136                 list1.append(ord(match1.group(5)))
137         if len(match2.group(5)):
138                 list2.append(ord(match2.group(5)))
139
140         for i in range(0, max(len(list1), len(list2))):
141                 if len(list1) <= i:
142                         vercmp_cache[mykey] = -1
143                         return -1
144                 elif len(list2) <= i:
145                         vercmp_cache[mykey] = 1
146                         return 1
147                 elif list1[i] != list2[i]:
148                         a = list1[i]
149                         b = list2[i]
150                         rval = (a > b) - (a < b)
151                         vercmp_cache[mykey] = rval
152                         return rval
153
154         # main version is equal, so now compare the _suffix part
155         list1 = match1.group(6).split("_")[1:]
156         list2 = match2.group(6).split("_")[1:]
157         
158         for i in range(0, max(len(list1), len(list2))):
159                 # Implicit _p0 is given a value of -1, so that 1 < 1_p0
160                 if len(list1) <= i:
161                         s1 = ("p","-1")
162                 else:
163                         s1 = suffix_regexp.match(list1[i]).groups()
164                 if len(list2) <= i:
165                         s2 = ("p","-1")
166                 else:
167                         s2 = suffix_regexp.match(list2[i]).groups()
168                 if s1[0] != s2[0]:
169                         a = suffix_value[s1[0]]
170                         b = suffix_value[s2[0]]
171                         rval = (a > b) - (a < b)
172                         vercmp_cache[mykey] = rval
173                         return rval
174                 if s1[1] != s2[1]:
175                         # it's possible that the s(1|2)[1] == ''
176                         # in such a case, fudge it.
177                         try:
178                                 r1 = int(s1[1])
179                         except ValueError:
180                                 r1 = 0
181                         try:
182                                 r2 = int(s2[1])
183                         except ValueError:
184                                 r2 = 0
185                         rval = (r1 > r2) - (r1 < r2)
186                         if rval:
187                                 vercmp_cache[mykey] = rval
188                                 return rval
189
190         # the suffix part is equal to, so finally check the revision
191         if match1.group(10):
192                 r1 = int(match1.group(10))
193         else:
194                 r1 = 0
195         if match2.group(10):
196                 r2 = int(match2.group(10))
197         else:
198                 r2 = 0
199         rval = (r1 > r2) - (r1 < r2)
200         vercmp_cache[mykey] = rval
201         return rval
202         
203 def pkgcmp(pkg1, pkg2):
204         """
205         Compare 2 package versions created in pkgsplit format.
206
207         Example usage:
208                 >>> from portage.versions import *
209                 >>> pkgcmp(pkgsplit('test-1.0-r1'),pkgsplit('test-1.2-r3'))
210                 -1
211                 >>> pkgcmp(pkgsplit('test-1.3'),pkgsplit('test-1.2-r3'))
212                 1
213
214         @param pkg1: package to compare with
215         @type pkg1: list (example: ['test', '1.0', 'r1'])
216         @param pkg2: package to compare againts
217         @type pkg2: list (example: ['test', '1.0', 'r1'])
218         @rtype: None or integer
219         @return: 
220                 1. None if package names are not the same
221                 2. 1 if pkg1 is greater than pkg2
222                 3. -1 if pkg1 is less than pkg2 
223                 4. 0 if pkg1 equals pkg2
224         """
225         if pkg1[0] != pkg2[0]:
226                 return None
227         return vercmp("-".join(pkg1[1:]), "-".join(pkg2[1:]))
228
229 _pv_re = re.compile('^' + _pv + '$', re.VERBOSE)
230
231 def _pkgsplit(mypkg):
232
233         m = _pv_re.match(mypkg)
234         if m is None:
235                 return None
236
237         if m.group('pn_inval') is not None:
238                 # package name appears to have a version-like suffix
239                 return None
240
241         rev = m.group('rev')
242         if rev is None:
243                 rev = '0'
244         rev = 'r' + rev
245
246         return  (m.group('pn'), m.group('ver'), rev) 
247
248 catcache={}
249 def catpkgsplit(mydata,silent=1):
250         """
251         Takes a Category/Package-Version-Rev and returns a list of each.
252         
253         @param mydata: Data to split
254         @type mydata: string 
255         @param silent: suppress error messages
256         @type silent: Boolean (integer)
257         @rype: list
258         @return:
259         1.  If each exists, it returns [cat, pkgname, version, rev]
260         2.  If cat is not specificed in mydata, cat will be "null"
261         3.  if rev does not exist it will be '-r0'
262         """
263
264         try:
265                 return catcache[mydata]
266         except KeyError:
267                 pass
268         mysplit = mydata.split('/', 1)
269         p_split=None
270         if len(mysplit)==1:
271                 cat = "null"
272                 p_split = _pkgsplit(mydata)
273         elif len(mysplit)==2:
274                 cat = mysplit[0]
275                 p_split = _pkgsplit(mysplit[1])
276         if not p_split:
277                 catcache[mydata]=None
278                 return None
279         retval = (cat, p_split[0], p_split[1], p_split[2])
280         catcache[mydata]=retval
281         return retval
282
283 def pkgsplit(mypkg, silent=1):
284         """
285         @param mypkg: either a pv or cpv
286         @return:
287         1. None if input is invalid.
288         2. (pn, ver, rev) if input is pv
289         3. (cp, ver, rev) if input is a cpv
290         """
291         catpsplit = catpkgsplit(mypkg)
292         if catpsplit is None:
293                 return None
294         cat, pn, ver, rev = catpsplit
295         if cat == 'null':
296                 return (pn, ver, rev)
297         else:
298                 return (cat + '/' + pn, ver, rev)
299
300 def catsplit(mydep):
301         return mydep.split("/", 1)
302
303 def best(mymatches):
304         """Accepts None arguments; assumes matches are valid."""
305         if not mymatches:
306                 return ""
307         if len(mymatches) == 1:
308                 return mymatches[0]
309         bestmatch = mymatches[0]
310         p2 = catpkgsplit(bestmatch)[1:]
311         for x in mymatches[1:]:
312                 p1 = catpkgsplit(x)[1:]
313                 if pkgcmp(p1, p2) > 0:
314                         bestmatch = x
315                         p2 = catpkgsplit(bestmatch)[1:]
316         return bestmatch