1# distutils/version.py
2#
3# Vendored from:
4# https://github.com/python/cpython/blob/6fea61a9e02260648fbec204e9caac6d5176cc7b/Lib/distutils/version.py
5
6"""Provides classes to represent module version numbers (one class for
7each style of version numbering). There are currently two such classes
8implemented: StrictVersion and LooseVersion.
9
10Every version number class implements the following interface:
11 * the 'parse' method takes a string and parses it to some internal
12 representation; if the string is an invalid version number,
13 'parse' raises a ValueError exception
14 * the class constructor takes an optional string argument which,
15 if supplied, is passed to 'parse'
16 * __str__ reconstructs the string that was passed to 'parse' (or
17 an equivalent string -- ie. one that will generate an equivalent
18 version number instance)
19 * __repr__ generates Python code to recreate the version number instance
20 * _cmp compares the current instance with either another instance
21 of the same class or a string (which will be parsed to an instance
22 of the same class, thus must follow the same rules)
23"""
24
25import re
26
27
28class Version:
29 """Abstract base class for version numbering classes. Just provides
30 constructor (__init__) and reproducer (__repr__), because those
31 seem to be the same for all version numbering classes; and route
32 rich comparisons to _cmp.
33 """
34
35 def __init__(self, vstring=None):
36 if vstring:
37 self.parse(vstring)
38
39 def __repr__(self):
40 return "%s ('%s')" % (self.__class__.__name__, str(self))
41
42 def __eq__(self, other):
43 c = self._cmp(other)
44 if c is NotImplemented:
45 return c
46 return c == 0
47
48 def __lt__(self, other):
49 c = self._cmp(other)
50 if c is NotImplemented:
51 return c
52 return c < 0
53
54 def __le__(self, other):
55 c = self._cmp(other)
56 if c is NotImplemented:
57 return c
58 return c <= 0
59
60 def __gt__(self, other):
61 c = self._cmp(other)
62 if c is NotImplemented:
63 return c
64 return c > 0
65
66 def __ge__(self, other):
67 c = self._cmp(other)
68 if c is NotImplemented:
69 return c
70 return c >= 0
71
72
73# Interface for version-number classes -- must be implemented
74# by the following classes (the concrete ones -- Version should
75# be treated as an abstract class).
76# __init__ (string) - create and take same action as 'parse'
77# (string parameter is optional)
78# parse (string) - convert a string representation to whatever
79# internal representation is appropriate for
80# this style of version numbering
81# __str__ (self) - convert back to a string; should be very similar
82# (if not identical to) the string supplied to parse
83# __repr__ (self) - generate Python code to recreate
84# the instance
85# _cmp (self, other) - compare two version numbers ('other' may
86# be an unparsed version string, or another
87# instance of your version class)
88
89# The rules according to Greg Stein:
90# 1) a version number has 1 or more numbers separated by a period or by
91# sequences of letters. If only periods, then these are compared
92# left-to-right to determine an ordering.
93# 2) sequences of letters are part of the tuple for comparison and are
94# compared lexicographically
95# 3) recognize the numeric components may have leading zeroes
96#
97# The LooseVersion class below implements these rules: a version number
98# string is split up into a tuple of integer and string components, and
99# comparison is a simple tuple comparison. This means that version
100# numbers behave in a predictable and obvious way, but a way that might
101# not necessarily be how people *want* version numbers to behave. There
102# wouldn't be a problem if people could stick to purely numeric version
103# numbers: just split on period and compare the numbers as tuples.
104# However, people insist on putting letters into their version numbers;
105# the most common purpose seems to be:
106# - indicating a "pre-release" version
107# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
108# - indicating a post-release patch ('p', 'pl', 'patch')
109# but of course this can't cover all version number schemes, and there's
110# no way to know what a programmer means without asking him.
111#
112# The problem is what to do with letters (and other non-numeric
113# characters) in a version number. The current implementation does the
114# obvious and predictable thing: keep them as strings and compare
115# lexically within a tuple comparison. This has the desired effect if
116# an appended letter sequence implies something "post-release":
117# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
118#
119# However, if letters in a version number imply a pre-release version,
120# the "obvious" thing isn't correct. Eg. you would expect that
121# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
122# implemented here, this just isn't so.
123#
124# Two possible solutions come to mind. The first is to tie the
125# comparison algorithm to a particular set of semantic rules, as has
126# been done in the StrictVersion class above. This works great as long
127# as everyone can go along with bondage and discipline. Hopefully a
128# (large) subset of Python module programmers will agree that the
129# particular flavour of bondage and discipline provided by StrictVersion
130# provides enough benefit to be worth using, and will submit their
131# version numbering scheme to its domination. The free-thinking
132# anarchists in the lot will never give in, though, and something needs
133# to be done to accommodate them.
134#
135# Perhaps a "moderately strict" version class could be implemented that
136# lets almost anything slide (syntactically), and makes some heuristic
137# assumptions about non-digits in version number strings. This could
138# sink into special-case-hell, though; if I was as talented and
139# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
140# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
141# just as happy dealing with things like "2g6" and "1.13++". I don't
142# think I'm smart enough to do it right though.
143#
144# In any case, I've coded the test suite for this module (see
145# ../test/test_version.py) specifically to fail on things like comparing
146# "1.2a2" and "1.2". That's not because the *code* is doing anything
147# wrong, it's because the simple, obvious design doesn't match my
148# complicated, hairy expectations for real-world version numbers. It
149# would be a snap to fix the test suite to say, "Yep, LooseVersion does
150# the Right Thing" (ie. the code matches the conception). But I'd rather
151# have a conception that matches common notions about version numbers.
152
153class LooseVersion (Version):
154
155 """Version numbering for anarchists and software realists.
156 Implements the standard interface for version number classes as
157 described above. A version number consists of a series of numbers,
158 separated by either periods or strings of letters. When comparing
159 version numbers, the numeric components will be compared
160 numerically, and the alphabetic components lexically. The following
161 are all valid version numbers, in no particular order:
162
163 1.5.1
164 1.5.2b2
165 161
166 3.10a
167 8.02
168 3.4j
169 1996.07.12
170 3.2.pl0
171 3.1.1.6
172 2g6
173 11g
174 0.960923
175 2.2beta29
176 1.13++
177 5.5.kw
178 2.0b1pl0
179
180 In fact, there is no such thing as an invalid version number under
181 this scheme; the rules for comparison are simple and predictable,
182 but may not always give the results you want (for some definition
183 of "want").
184 """
185
186 component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
187
188 def __init__(self, vstring=None):
189 if vstring:
190 self.parse(vstring)
191
192 def parse(self, vstring):
193 # I've given up on thinking I can reconstruct the version string
194 # from the parsed tuple -- so I just store the string here for
195 # use by __str__
196 self.vstring = vstring
197 components = [x for x in self.component_re.split(vstring)
198 if x and x != '.']
199 for i, obj in enumerate(components):
200 try:
201 components[i] = int(obj)
202 except ValueError:
203 pass
204
205 self.version = components
206
207 def __str__(self):
208 return self.vstring
209
210 def __repr__(self):
211 return "LooseVersion ('%s')" % str(self)
212
213 def _cmp(self, other):
214 if isinstance(other, str):
215 other = LooseVersion(other)
216 elif not isinstance(other, LooseVersion):
217 return NotImplemented
218
219 if self.version == other.version:
220 return 0
221 if self.version < other.version:
222 return -1
223 if self.version > other.version:
224 return 1
225
226
227# end class LooseVersion