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