]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/distutils/version.py
   4 # Implements multiple version numbering conventions for the 
   5 # Python Module Distribution Utilities. 
  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. 
  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) 
  30 from types 
import StringType
 
  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. 
  38     def __init__ (self
, vstring
=None): 
  43         return "%s ('%s')" % (self
.__class
__.__name
__, str(self
)) 
  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 
  58 #    __cmp__ (self, other) - compare two version numbers ('other' may 
  59 #                        be an unparsed version string, or another 
  60 #                        instance of your version class) 
  63 class StrictVersion (Version
): 
  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. 
  74     The following are valid version numbers (shown in the order that 
  75     would be obtained by sorting according to the supplied cmp function): 
  77         0.4       0.4.0  (these two are equivalent) 
  88     The following are examples of invalid version numbers: 
  96     The rationale for this version numbering system will be explained 
  97     in the distutils documentation. 
 100     version_re 
= re
.compile(r
'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', 
 104     def parse (self
, vstring
): 
 105         match 
= self
.version_re
.match(vstring
) 
 107             raise ValueError, "invalid version number '%s'" % vstring
 
 109         (major
, minor
, patch
, prerelease
, prerelease_num
) = \
 
 110             match
.group(1, 2, 4, 5, 6) 
 113             self
.version 
= tuple(map(string
.atoi
, [major
, minor
, patch
])) 
 115             self
.version 
= tuple(map(string
.atoi
, [major
, minor
]) + [0]) 
 118             self
.prerelease 
= (prerelease
[0], string
.atoi(prerelease_num
)) 
 120             self
.prerelease 
= None 
 125         if self
.version
[2] == 0: 
 126             vstring 
= string
.join(map(str, self
.version
[0:2]), '.') 
 128             vstring 
= string
.join(map(str, self
.version
), '.') 
 131             vstring 
= vstring 
+ self
.prerelease
[0] + str(self
.prerelease
[1]) 
 136     def __cmp__ (self
, other
): 
 137         if isinstance(other
, StringType
): 
 138             other 
= StrictVersion(other
) 
 140         compare 
= cmp(self
.version
, other
.version
) 
 141         if (compare 
== 0):              # have to compare prerelease 
 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! 
 148             if (not self
.prerelease 
and not other
.prerelease
): 
 150             elif (self
.prerelease 
and not other
.prerelease
): 
 152             elif (not self
.prerelease 
and other
.prerelease
): 
 154             elif (self
.prerelease 
and other
.prerelease
): 
 155                 return cmp(self
.prerelease
, other
.prerelease
) 
 157         else:                           # numeric versions don't match -- 
 158             return compare              
# prerelease stuff doesn't matter 
 161 # end class StrictVersion 
 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 
 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. 
 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". 
 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. 
 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. 
 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. 
 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. 
 228 class LooseVersion (Version
): 
 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: 
 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 
 261     component_re 
= re
.compile(r
'(\d+ | [a-z]+ | \.)', re
.VERBOSE
) 
 263     def __init__ (self
, vstring
=None): 
 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 
 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
)): 
 277                 components
[i
] = int(components
[i
]) 
 281         self
.version 
= components
 
 289         return "LooseVersion ('%s')" % str(self
) 
 292     def __cmp__ (self
, other
): 
 293         if isinstance(other
, StringType
): 
 294             other 
= LooseVersion(other
) 
 296         return cmp(self
.version
, other
.version
) 
 299 # end class LooseVersion