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