Home | History | Annotate | Download | only in distutils
      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