• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
11each style of version numbering).  There are currently two such classes
12implemented: StrictVersion and LooseVersion.
13
14Every 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
29import string, re
30from types import StringType
31
32class 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
63class 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
228class 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