blob: 9d3d172429580e0925ce8c9e0e164aa914ed6d8d [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#
7# written by Greg Ward, 1998/12/17
8#
9# $Id$
10#
11
12"""Provides classes to represent module version numbers (one class for
13each style of version numbering). There are currently two such classes
14implemented: StrictVersion and LooseVersion.
15
16Every version number class implements the following interface:
17 * the 'parse' method takes a string and parses it to some internal
18 representation; if the string is an invalid version number,
19 'parse' raises a ValueError exception
20 * the class constructor takes an optional string argument which,
21 if supplied, is passed to 'parse'
22 * __str__ reconstructs the string that was passed to 'parse' (or
23 an equivalent string -- ie. one that will generate an equivalent
24 version number instance)
25 * __repr__ generates Python code to recreate the version number instance
26 * __cmp__ compares the current instance with either another instance
27 of the same class or a string (which will be parsed to an instance
28 of the same class, thus must follow the same rules)
29"""
30
31import string, re
32from types import StringType
33
34class Version:
35 """Abstract base class for version numbering classes. Just provides
36 constructor (__init__) and reproducer (__repr__), because those
37 seem to be the same for all version numbering classes.
38 """
39
40 def __init__ (self, vstring=None):
41 if vstring:
Greg Wardbe86bde2000-09-26 01:56:15 +000042 self.parse(vstring)
Greg Wardee789b91998-12-18 22:00:30 +000043
44 def __repr__ (self):
Greg Wardbe86bde2000-09-26 01:56:15 +000045 return "%s ('%s')" % (self.__class__.__name__, str(self))
Greg Wardee789b91998-12-18 22:00:30 +000046
47
48# Interface for version-number classes -- must be implemented
49# by the following classes (the concrete ones -- Version should
50# be treated as an abstract class).
51# __init__ (string) - create and take same action as 'parse'
52# (string parameter is optional)
53# parse (string) - convert a string representation to whatever
54# internal representation is appropriate for
55# this style of version numbering
56# __str__ (self) - convert back to a string; should be very similar
57# (if not identical to) the string supplied to parse
58# __repr__ (self) - generate Python code to recreate
59# the instance
60# __cmp__ (self, other) - compare two version numbers ('other' may
61# be an unparsed version string, or another
62# instance of your version class)
63
64
65class StrictVersion (Version):
66
67 """Version numbering for anal retentives and software idealists.
68 Implements the standard interface for version number classes as
69 described above. A version number consists of two or three
70 dot-separated numeric components, with an optional "pre-release" tag
71 on the end. The pre-release tag consists of the letter 'a' or 'b'
72 followed by a number. If the numeric components of two version
73 numbers are equal, then one with a pre-release tag will always
74 be deemed earlier (lesser) than one without.
75
76 The following are valid version numbers (shown in the order that
77 would be obtained by sorting according to the supplied cmp function):
78
79 0.4 0.4.0 (these two are equivalent)
80 0.4.1
81 0.5a1
82 0.5b3
83 0.5
84 0.9.6
85 1.0
86 1.0.4a3
87 1.0.4b1
88 1.0.4
89
90 The following are examples of invalid version numbers:
91
92 1
93 2.7.2.2
94 1.3.a4
95 1.3pl1
96 1.3c4
97
98 The rationale for this version numbering system will be explained
99 in the distutils documentation.
100 """
101
Greg Wardbe86bde2000-09-26 01:56:15 +0000102 version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
103 re.VERBOSE)
Greg Wardee789b91998-12-18 22:00:30 +0000104
105
106 def parse (self, vstring):
Greg Wardbe86bde2000-09-26 01:56:15 +0000107 match = self.version_re.match(vstring)
Greg Wardee789b91998-12-18 22:00:30 +0000108 if not match:
109 raise ValueError, "invalid version number '%s'" % vstring
110
111 (major, minor, patch, prerelease, prerelease_num) = \
Greg Wardbe86bde2000-09-26 01:56:15 +0000112 match.group(1, 2, 4, 5, 6)
Greg Wardee789b91998-12-18 22:00:30 +0000113
114 if patch:
Greg Wardbe86bde2000-09-26 01:56:15 +0000115 self.version = tuple(map(string.atoi, [major, minor, patch]))
Greg Wardee789b91998-12-18 22:00:30 +0000116 else:
Greg Wardbe86bde2000-09-26 01:56:15 +0000117 self.version = tuple(map(string.atoi, [major, minor]) + [0])
Greg Wardee789b91998-12-18 22:00:30 +0000118
119 if prerelease:
Greg Wardbe86bde2000-09-26 01:56:15 +0000120 self.prerelease = (prerelease[0], string.atoi(prerelease_num))
Greg Wardee789b91998-12-18 22:00:30 +0000121 else:
122 self.prerelease = None
123
124
125 def __str__ (self):
126
127 if self.version[2] == 0:
Greg Wardbe86bde2000-09-26 01:56:15 +0000128 vstring = string.join(map(str, self.version[0:2]), '.')
Greg Wardee789b91998-12-18 22:00:30 +0000129 else:
Greg Wardbe86bde2000-09-26 01:56:15 +0000130 vstring = string.join(map(str, self.version), '.')
Greg Wardee789b91998-12-18 22:00:30 +0000131
132 if self.prerelease:
Greg Wardbe86bde2000-09-26 01:56:15 +0000133 vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
Greg Wardee789b91998-12-18 22:00:30 +0000134
135 return vstring
136
137
138 def __cmp__ (self, other):
Greg Wardbe86bde2000-09-26 01:56:15 +0000139 if isinstance(other, StringType):
140 other = StrictVersion(other)
Greg Wardee789b91998-12-18 22:00:30 +0000141
Greg Wardbe86bde2000-09-26 01:56:15 +0000142 compare = cmp(self.version, other.version)
Greg Wardee789b91998-12-18 22:00:30 +0000143 if (compare == 0): # have to compare prerelease
144
145 # case 1: neither has prerelease; they're equal
146 # case 2: self has prerelease, other doesn't; other is greater
147 # case 3: self doesn't have prerelease, other does: self is greater
148 # case 4: both have prerelease: must compare them!
149
150 if (not self.prerelease and not other.prerelease):
151 return 0
152 elif (self.prerelease and not other.prerelease):
153 return -1
154 elif (not self.prerelease and other.prerelease):
155 return 1
156 elif (self.prerelease and other.prerelease):
Greg Wardbe86bde2000-09-26 01:56:15 +0000157 return cmp(self.prerelease, other.prerelease)
Greg Wardee789b91998-12-18 22:00:30 +0000158
159 else: # numeric versions don't match --
160 return compare # prerelease stuff doesn't matter
161
162
163# end class StrictVersion
164
165
166# The rules according to Greg Stein:
167# 1) a version number has 1 or more numbers separate by a period or by
168# sequences of letters. If only periods, then these are compared
169# left-to-right to determine an ordering.
170# 2) sequences of letters are part of the tuple for comparison and are
171# compared lexicographically
172# 3) recognize the numeric components may have leading zeroes
173#
174# The LooseVersion class below implements these rules: a version number
175# string is split up into a tuple of integer and string components, and
176# comparison is a simple tuple comparison. This means that version
177# numbers behave in a predictable and obvious way, but a way that might
178# not necessarily be how people *want* version numbers to behave. There
179# wouldn't be a problem if people could stick to purely numeric version
180# numbers: just split on period and compare the numbers as tuples.
181# However, people insist on putting letters into their version numbers;
182# the most common purpose seems to be:
183# - indicating a "pre-release" version
184# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
185# - indicating a post-release patch ('p', 'pl', 'patch')
186# but of course this can't cover all version number schemes, and there's
187# no way to know what a programmer means without asking him.
188#
189# The problem is what to do with letters (and other non-numeric
190# characters) in a version number. The current implementation does the
191# obvious and predictable thing: keep them as strings and compare
192# lexically within a tuple comparison. This has the desired effect if
193# an appended letter sequence implies something "post-release":
194# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
195#
196# However, if letters in a version number imply a pre-release version,
197# the "obvious" thing isn't correct. Eg. you would expect that
198# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
199# implemented here, this just isn't so.
200#
201# Two possible solutions come to mind. The first is to tie the
202# comparison algorithm to a particular set of semantic rules, as has
203# been done in the StrictVersion class above. This works great as long
204# as everyone can go along with bondage and discipline. Hopefully a
205# (large) subset of Python module programmers will agree that the
206# particular flavour of bondage and discipline provided by StrictVersion
207# provides enough benefit to be worth using, and will submit their
208# version numbering scheme to its domination. The free-thinking
209# anarchists in the lot will never give in, though, and something needs
Jeremy Hyltona05e2932000-06-28 14:48:01 +0000210# to be done to accommodate them.
Greg Wardee789b91998-12-18 22:00:30 +0000211#
212# Perhaps a "moderately strict" version class could be implemented that
213# lets almost anything slide (syntactically), and makes some heuristic
214# assumptions about non-digits in version number strings. This could
215# sink into special-case-hell, though; if I was as talented and
216# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
217# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
218# just as happy dealing with things like "2g6" and "1.13++". I don't
219# think I'm smart enough to do it right though.
220#
221# In any case, I've coded the test suite for this module (see
222# ../test/test_version.py) specifically to fail on things like comparing
223# "1.2a2" and "1.2". That's not because the *code* is doing anything
224# wrong, it's because the simple, obvious design doesn't match my
225# complicated, hairy expectations for real-world version numbers. It
226# would be a snap to fix the test suite to say, "Yep, LooseVersion does
227# the Right Thing" (ie. the code matches the conception). But I'd rather
228# have a conception that matches common notions about version numbers.
229
230class LooseVersion (Version):
231
232 """Version numbering for anarchists and software realists.
233 Implements the standard interface for version number classes as
234 described above. A version number consists of a series of numbers,
235 separated by either periods or strings of letters. When comparing
236 version numbers, the numeric components will be compared
237 numerically, and the alphabetic components lexically. The following
238 are all valid version numbers, in no particular order:
239
240 1.5.1
241 1.5.2b2
242 161
243 3.10a
244 8.02
245 3.4j
246 1996.07.12
247 3.2.pl0
248 3.1.1.6
249 2g6
250 11g
251 0.960923
252 2.2beta29
253 1.13++
254 5.5.kw
255 2.0b1pl0
256
257 In fact, there is no such thing as an invalid version number under
258 this scheme; the rules for comparison are simple and predictable,
259 but may not always give the results you want (for some definition
260 of "want").
261 """
262
263 component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
264
265 def __init__ (self, vstring=None):
266 if vstring:
Greg Wardbe86bde2000-09-26 01:56:15 +0000267 self.parse(vstring)
Greg Wardee789b91998-12-18 22:00:30 +0000268
269
270 def parse (self, vstring):
271 # I've given up on thinking I can reconstruct the version string
272 # from the parsed tuple -- so I just store the string here for
273 # use by __str__
274 self.vstring = vstring
Greg Wardbe86bde2000-09-26 01:56:15 +0000275 components = filter(lambda x: x and x != '.',
276 self.component_re.split(vstring))
277 for i in range(len(components)):
Greg Wardee789b91998-12-18 22:00:30 +0000278 try:
Greg Wardbe86bde2000-09-26 01:56:15 +0000279 components[i] = int(components[i])
Greg Wardee789b91998-12-18 22:00:30 +0000280 except ValueError:
281 pass
282
283 self.version = components
284
285
286 def __str__ (self):
287 return self.vstring
288
289
290 def __repr__ (self):
Greg Wardbe86bde2000-09-26 01:56:15 +0000291 return "LooseVersion ('%s')" % str(self)
Greg Wardee789b91998-12-18 22:00:30 +0000292
293
294 def __cmp__ (self, other):
Greg Wardbe86bde2000-09-26 01:56:15 +0000295 if isinstance(other, StringType):
296 other = LooseVersion(other)
Greg Wardee789b91998-12-18 22:00:30 +0000297
Greg Wardbe86bde2000-09-26 01:56:15 +0000298 return cmp(self.version, other.version)
Greg Wardee789b91998-12-18 22:00:30 +0000299
300
301# end class LooseVersion