blob: 2cd36365e75797f350a997ffdca469c4056f3391 [file] [log] [blame]
Greg Wardee789b91998-12-18 22:00:30 +00001#
2# distutils/version.py
3#
4# Implements multiple version numbering conventions for the
5# Python Module Distribution Utilities.
6#
Greg Wardee789b91998-12-18 22:00:30 +00007# $Id$
8#
9
10"""Provides classes to represent module version numbers (one class for
11each style of version numbering). There are currently two such classes
12implemented: StrictVersion and LooseVersion.
13
14Every version number class implements the following interface:
15 * the 'parse' method takes a string and parses it to some internal
16 representation; if the string is an invalid version number,
17 'parse' raises a ValueError exception
18 * the class constructor takes an optional string argument which,
19 if supplied, is passed to 'parse'
20 * __str__ reconstructs the string that was passed to 'parse' (or
21 an equivalent string -- ie. one that will generate an equivalent
22 version number instance)
23 * __repr__ generates Python code to recreate the version number instance
24 * __cmp__ compares the current instance with either another instance
25 of the same class or a string (which will be parsed to an instance
26 of the same class, thus must follow the same rules)
27"""
28
29import string, re
30from types import StringType
31
32class Version:
33 """Abstract base class for version numbering classes. Just provides
34 constructor (__init__) and reproducer (__repr__), because those
Guido van Rossum47b9ff62006-08-24 00:41:19 +000035 seem to be the same for all version numbering classes; and route
36 rich comparisons to __cmp__.
Greg Wardee789b91998-12-18 22:00:30 +000037 """
38
39 def __init__ (self, vstring=None):
40 if vstring:
Greg Wardbe86bde2000-09-26 01:56:15 +000041 self.parse(vstring)
Greg Wardee789b91998-12-18 22:00:30 +000042
43 def __repr__ (self):
Greg Wardbe86bde2000-09-26 01:56:15 +000044 return "%s ('%s')" % (self.__class__.__name__, str(self))
Greg Wardee789b91998-12-18 22:00:30 +000045
Guido van Rossum47b9ff62006-08-24 00:41:19 +000046 def __eq__(self, other):
47 c = self.__cmp__(other)
48 if c is NotImplemented:
49 return c
50 return c == 0
51
52 def __ne__(self, other):
53 c = self.__cmp__(other)
54 if c is NotImplemented:
55 return c
56 return c != 0
57
58 def __lt__(self, other):
59 c = self.__cmp__(other)
60 if c is NotImplemented:
61 return c
62 return c < 0
63
64 def __le__(self, other):
65 c = self.__cmp__(other)
66 if c is NotImplemented:
67 return c
68 return c <= 0
69
70 def __gt__(self, other):
71 c = self.__cmp__(other)
72 if c is NotImplemented:
73 return c
74 return c > 0
75
76 def __ge__(self, other):
77 c = self.__cmp__(other)
78 if c is NotImplemented:
79 return c
80 return c >= 0
81
Greg Wardee789b91998-12-18 22:00:30 +000082
83# Interface for version-number classes -- must be implemented
84# by the following classes (the concrete ones -- Version should
85# be treated as an abstract class).
86# __init__ (string) - create and take same action as 'parse'
87# (string parameter is optional)
88# parse (string) - convert a string representation to whatever
89# internal representation is appropriate for
90# this style of version numbering
91# __str__ (self) - convert back to a string; should be very similar
92# (if not identical to) the string supplied to parse
93# __repr__ (self) - generate Python code to recreate
94# the instance
95# __cmp__ (self, other) - compare two version numbers ('other' may
96# be an unparsed version string, or another
97# instance of your version class)
98
99
100class StrictVersion (Version):
101
102 """Version numbering for anal retentives and software idealists.
103 Implements the standard interface for version number classes as
104 described above. A version number consists of two or three
105 dot-separated numeric components, with an optional "pre-release" tag
106 on the end. The pre-release tag consists of the letter 'a' or 'b'
107 followed by a number. If the numeric components of two version
108 numbers are equal, then one with a pre-release tag will always
109 be deemed earlier (lesser) than one without.
110
111 The following are valid version numbers (shown in the order that
112 would be obtained by sorting according to the supplied cmp function):
113
114 0.4 0.4.0 (these two are equivalent)
115 0.4.1
116 0.5a1
117 0.5b3
118 0.5
119 0.9.6
120 1.0
121 1.0.4a3
122 1.0.4b1
123 1.0.4
124
125 The following are examples of invalid version numbers:
126
127 1
128 2.7.2.2
129 1.3.a4
130 1.3pl1
131 1.3c4
132
133 The rationale for this version numbering system will be explained
134 in the distutils documentation.
135 """
Fred Drakeb94b8492001-12-06 20:51:35 +0000136
Greg Wardbe86bde2000-09-26 01:56:15 +0000137 version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
138 re.VERBOSE)
Greg Wardee789b91998-12-18 22:00:30 +0000139
140
141 def parse (self, vstring):
Greg Wardbe86bde2000-09-26 01:56:15 +0000142 match = self.version_re.match(vstring)
Greg Wardee789b91998-12-18 22:00:30 +0000143 if not match:
144 raise ValueError, "invalid version number '%s'" % vstring
145
146 (major, minor, patch, prerelease, prerelease_num) = \
Greg Wardbe86bde2000-09-26 01:56:15 +0000147 match.group(1, 2, 4, 5, 6)
Greg Wardee789b91998-12-18 22:00:30 +0000148
149 if patch:
Andrew M. Kuchlingac20f772001-03-22 03:48:31 +0000150 self.version = tuple(map(string.atoi, [major, minor, patch]))
Greg Wardee789b91998-12-18 22:00:30 +0000151 else:
Andrew M. Kuchlingac20f772001-03-22 03:48:31 +0000152 self.version = tuple(map(string.atoi, [major, minor]) + [0])
Greg Wardee789b91998-12-18 22:00:30 +0000153
154 if prerelease:
Andrew M. Kuchlingac20f772001-03-22 03:48:31 +0000155 self.prerelease = (prerelease[0], string.atoi(prerelease_num))
Greg Wardee789b91998-12-18 22:00:30 +0000156 else:
157 self.prerelease = None
158
159
160 def __str__ (self):
Fred Drakeb94b8492001-12-06 20:51:35 +0000161
Greg Wardee789b91998-12-18 22:00:30 +0000162 if self.version[2] == 0:
Andrew M. Kuchlingac20f772001-03-22 03:48:31 +0000163 vstring = string.join(map(str, self.version[0:2]), '.')
Greg Wardee789b91998-12-18 22:00:30 +0000164 else:
Andrew M. Kuchlingac20f772001-03-22 03:48:31 +0000165 vstring = string.join(map(str, self.version), '.')
Greg Wardee789b91998-12-18 22:00:30 +0000166
167 if self.prerelease:
Greg Wardbe86bde2000-09-26 01:56:15 +0000168 vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
Greg Wardee789b91998-12-18 22:00:30 +0000169
170 return vstring
Fred Drakeb94b8492001-12-06 20:51:35 +0000171
Greg Wardee789b91998-12-18 22:00:30 +0000172
173 def __cmp__ (self, other):
Greg Wardbe86bde2000-09-26 01:56:15 +0000174 if isinstance(other, StringType):
175 other = StrictVersion(other)
Greg Wardee789b91998-12-18 22:00:30 +0000176
Greg Wardbe86bde2000-09-26 01:56:15 +0000177 compare = cmp(self.version, other.version)
Greg Wardee789b91998-12-18 22:00:30 +0000178 if (compare == 0): # have to compare prerelease
179
180 # case 1: neither has prerelease; they're equal
181 # case 2: self has prerelease, other doesn't; other is greater
182 # case 3: self doesn't have prerelease, other does: self is greater
183 # case 4: both have prerelease: must compare them!
184
185 if (not self.prerelease and not other.prerelease):
186 return 0
187 elif (self.prerelease and not other.prerelease):
188 return -1
189 elif (not self.prerelease and other.prerelease):
190 return 1
191 elif (self.prerelease and other.prerelease):
Greg Wardbe86bde2000-09-26 01:56:15 +0000192 return cmp(self.prerelease, other.prerelease)
Greg Wardee789b91998-12-18 22:00:30 +0000193
194 else: # numeric versions don't match --
195 return compare # prerelease stuff doesn't matter
196
197
198# end class StrictVersion
199
200
201# The rules according to Greg Stein:
202# 1) a version number has 1 or more numbers separate by a period or by
203# sequences of letters. If only periods, then these are compared
204# left-to-right to determine an ordering.
205# 2) sequences of letters are part of the tuple for comparison and are
206# compared lexicographically
207# 3) recognize the numeric components may have leading zeroes
Fred Drakeb94b8492001-12-06 20:51:35 +0000208#
Greg Wardee789b91998-12-18 22:00:30 +0000209# The LooseVersion class below implements these rules: a version number
210# string is split up into a tuple of integer and string components, and
211# comparison is a simple tuple comparison. This means that version
212# numbers behave in a predictable and obvious way, but a way that might
213# not necessarily be how people *want* version numbers to behave. There
214# wouldn't be a problem if people could stick to purely numeric version
215# numbers: just split on period and compare the numbers as tuples.
216# However, people insist on putting letters into their version numbers;
217# the most common purpose seems to be:
218# - indicating a "pre-release" version
219# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
220# - indicating a post-release patch ('p', 'pl', 'patch')
221# but of course this can't cover all version number schemes, and there's
222# no way to know what a programmer means without asking him.
Fred Drakeb94b8492001-12-06 20:51:35 +0000223#
Greg Wardee789b91998-12-18 22:00:30 +0000224# The problem is what to do with letters (and other non-numeric
225# characters) in a version number. The current implementation does the
226# obvious and predictable thing: keep them as strings and compare
227# lexically within a tuple comparison. This has the desired effect if
228# an appended letter sequence implies something "post-release":
229# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
230#
231# However, if letters in a version number imply a pre-release version,
232# the "obvious" thing isn't correct. Eg. you would expect that
233# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
234# implemented here, this just isn't so.
235#
236# Two possible solutions come to mind. The first is to tie the
237# comparison algorithm to a particular set of semantic rules, as has
238# been done in the StrictVersion class above. This works great as long
239# as everyone can go along with bondage and discipline. Hopefully a
240# (large) subset of Python module programmers will agree that the
241# particular flavour of bondage and discipline provided by StrictVersion
242# provides enough benefit to be worth using, and will submit their
243# version numbering scheme to its domination. The free-thinking
244# anarchists in the lot will never give in, though, and something needs
Jeremy Hyltona05e2932000-06-28 14:48:01 +0000245# to be done to accommodate them.
Fred Drakeb94b8492001-12-06 20:51:35 +0000246#
Greg Wardee789b91998-12-18 22:00:30 +0000247# Perhaps a "moderately strict" version class could be implemented that
248# lets almost anything slide (syntactically), and makes some heuristic
249# assumptions about non-digits in version number strings. This could
250# sink into special-case-hell, though; if I was as talented and
251# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
252# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
253# just as happy dealing with things like "2g6" and "1.13++". I don't
254# think I'm smart enough to do it right though.
Fred Drakeb94b8492001-12-06 20:51:35 +0000255#
Greg Wardee789b91998-12-18 22:00:30 +0000256# In any case, I've coded the test suite for this module (see
257# ../test/test_version.py) specifically to fail on things like comparing
258# "1.2a2" and "1.2". That's not because the *code* is doing anything
259# wrong, it's because the simple, obvious design doesn't match my
260# complicated, hairy expectations for real-world version numbers. It
261# would be a snap to fix the test suite to say, "Yep, LooseVersion does
262# the Right Thing" (ie. the code matches the conception). But I'd rather
263# have a conception that matches common notions about version numbers.
264
265class LooseVersion (Version):
266
267 """Version numbering for anarchists and software realists.
268 Implements the standard interface for version number classes as
269 described above. A version number consists of a series of numbers,
270 separated by either periods or strings of letters. When comparing
271 version numbers, the numeric components will be compared
272 numerically, and the alphabetic components lexically. The following
273 are all valid version numbers, in no particular order:
274
275 1.5.1
276 1.5.2b2
277 161
278 3.10a
279 8.02
280 3.4j
281 1996.07.12
282 3.2.pl0
283 3.1.1.6
284 2g6
285 11g
286 0.960923
287 2.2beta29
288 1.13++
289 5.5.kw
290 2.0b1pl0
291
292 In fact, there is no such thing as an invalid version number under
293 this scheme; the rules for comparison are simple and predictable,
294 but may not always give the results you want (for some definition
295 of "want").
296 """
297
298 component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
299
300 def __init__ (self, vstring=None):
301 if vstring:
Greg Wardbe86bde2000-09-26 01:56:15 +0000302 self.parse(vstring)
Greg Wardee789b91998-12-18 22:00:30 +0000303
304
305 def parse (self, vstring):
306 # I've given up on thinking I can reconstruct the version string
307 # from the parsed tuple -- so I just store the string here for
308 # use by __str__
309 self.vstring = vstring
Greg Wardbe86bde2000-09-26 01:56:15 +0000310 components = filter(lambda x: x and x != '.',
311 self.component_re.split(vstring))
312 for i in range(len(components)):
Greg Wardee789b91998-12-18 22:00:30 +0000313 try:
Greg Wardbe86bde2000-09-26 01:56:15 +0000314 components[i] = int(components[i])
Greg Wardee789b91998-12-18 22:00:30 +0000315 except ValueError:
316 pass
317
318 self.version = components
319
320
321 def __str__ (self):
322 return self.vstring
323
324
325 def __repr__ (self):
Greg Wardbe86bde2000-09-26 01:56:15 +0000326 return "LooseVersion ('%s')" % str(self)
Greg Wardee789b91998-12-18 22:00:30 +0000327
328
329 def __cmp__ (self, other):
Greg Wardbe86bde2000-09-26 01:56:15 +0000330 if isinstance(other, StringType):
331 other = LooseVersion(other)
Greg Wardee789b91998-12-18 22:00:30 +0000332
Greg Wardbe86bde2000-09-26 01:56:15 +0000333 return cmp(self.version, other.version)
Fred Drakeb94b8492001-12-06 20:51:35 +0000334
Greg Wardee789b91998-12-18 22:00:30 +0000335
336# end class LooseVersion