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 separated 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 300