]> git.saurik.com Git - wxWidgets.git/blob - wxPython/distutils/version.py
fixed extraneous scrolling when scrollbars are added/removed (patch 788026; bug 746618)
[wxWidgets.git] / wxPython / distutils / version.py
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