1 # This module defines the immutable Version type.
2 # Initialize it with Version(string). It defines attributes
3 # epoch, upstream, and debian.
4 # Comparison operations are defined on Versions and follow the same rules
7 # Copyright (C) 1998 Richard Braakman
9 # This program is free software. It is distributed under the terms of
10 # the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, you can find it on the World Wide
21 # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
22 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
28 # TODO: Is the regexp very slow? Find out. It could be simplified
29 # at the cost of some extra string slicing by the caller.
31 # python can be just as incomprehensible as perl!
32 version_format = re.compile(r"^(\d+:)?([\w][\w+.:\-]*?)(-[\w+.]+)?$")
34 # The regexp above breaks down into three parts:
35 # (\d+:)? Optional epoch
36 # ([\w][\w+.:\-]*?) Upstream version number
37 # (-[\w+.]+)? Optional Debian revision
38 # The *? notation at the end of the upstream version number means the
39 # regexp engine should attempt a minimum-length match, rather than
40 # maximum-length. This prevents the upstream-version pattern from
41 # gobbling up the Debian revision.
43 # A non-numeric sequence followed by a numeric sequence.
44 # The comparison code uses it to consume the version string according
45 # to the algorithm in the Policy manual, two steps at a time.
46 compare_format = re.compile(r"([^\d]*)(\d*)")
48 # An alphabetic sequence followed by a non-alphabetic sequence.
49 # This way, the non-numeric parts are divided into parts just
50 # like the entire version string is.
51 alph_compare_format = re.compile(r"([a-zA-Z]*)([^a-zA-Z]*)")
53 # Compare non-numeric parts of version strings x and y.
54 # It differs from the normal cmp, because non-alphabetic characters
55 # must sort newer than alphabetic ones.
57 while len(x) > 0 and len(y) > 0:
58 # The match is guaranteed not to fail, because the regexp can match
59 # a zero-length string.
60 (xalph, xnonalph) = alph_compare_format.match(x).groups()
61 (yalph, ynonalph) = alph_compare_format.match(y).groups()
63 if xnonalph == ynonalph:
64 x = x[len(xalph) + len(xnonalph):]
65 y = y[len(yalph) + len(ynonalph):]
67 return cmp(xnonalph, ynonalph)
69 common = min(len(xalph), len(yalph))
70 if xalph[:common] == yalph[:common]:
71 if len(xalph) == common:
73 return -1 # y is the longer string
75 return 1 # xnonalph will sort newer than yalph's tail
78 return 1 # x is the longer string
80 return -1 # ynonalph will sort newer than xalph's tail
82 return cmp(xalph[:common], yalph[:common])
84 # One of the strings is exhausted. The longer string counts as newer.
85 return cmp(len(x), len(y))
88 # Compare the version strings x and y. Return positive if x is
89 # newer than y, and negative if x is older than y. The caller
90 # guarantees that they are not equal.
92 while len(x) > 0 and len(y) > 0:
93 # The match is guaranteed not to fail, because the regexp can match
94 # a zero-length string.
95 (xnondigit, xdigit) = compare_format.match(x).groups()
96 (ynondigit, ydigit) = compare_format.match(y).groups()
97 if xnondigit == ynondigit:
99 x = x[len(xnondigit) + len(xdigit):]
100 y = y[len(ynondigit) + len(ydigit):]
101 # Count an empty digit string as zero. (i.e. 1.1 versus 1.)
103 return cmp(0, string.atoi(ydigit))
105 return cmp(string.atoi(xdigit), 0)
107 return cmp(string.atoi(xdigit), string.atoi(ydigit))
109 return alphcmp(xnondigit, ynondigit)
111 # One of the strings is exhausted. The longer string counts as newer.
112 return cmp(len(x), len(y))
116 def cache_versioncmp(x, y):
117 if compare_cache.has_key((x, y)):
118 return compare_cache[(x, y)]
120 compare_cache[(x, y)] = c
121 compare_cache[(y, x)] = -c
124 # A version is an immutable object. It is created with Version(string)
125 # and is not changed thereafter. This is not enforced.
127 # A Version object is defined by an epoch (stored in self.epoch
128 # as an integer), an upstream version (stored in self.upstream as
129 # a string), and a debian revision (stroed in self.debian as
131 # self.debian may be None to indicate that the version number does
132 # not have a Debian revision.
133 def __init__(self, version):
134 # See Policy manual, chapter 4.
135 match = version_format.match(version)
137 raise ValueError, version
138 (epoch, upstream, debian) = match.group(1, 2, 3)
140 # slice off the colon
141 self.epoch = string.atoi(epoch[:-1])
144 self.upstream = upstream
146 # slice off the leading hyphen
147 self.debian = debian[1:]
151 # This function compares two versions. We use the earlier/later
152 # relationship defined in the policy manual as our ordering
153 # relationship. Thus, the function should return a negative
154 # number if self is earlier than other, zero if they are equal,
155 # and a positive number if self is later than other.
156 def __cmp__(self, other):
157 if self.epoch == other.epoch:
158 if self.upstream == other.upstream:
159 if self.debian == other.debian:
161 # "The absence of a <debian_revision> compares earlier than
162 # the presence of one". (Policy manual chapter 4)
163 elif self.debian and not other.debian:
165 elif not self.debian and other.debian:
168 return cache_versioncmp(self.debian, other.debian)
170 return cache_versioncmp(self.upstream, other.upstream)
172 return cmp(self.epoch, other.epoch)
174 # Return a good hash value when this object is used as a key
175 # in a dictionary. Only immutable objects should define a
178 return hash(self.epoch) ^ hash(self.upstream) ^ hash(self.debian)
180 # This should return a string representation of this object.
181 # We represent a version string by recombining the epoch,
182 # upstream version, and debian revision.
184 # We normally leave out an epoch of 0, but we do add it if
185 # the upstream version contains a colon (:), in order to
186 # keep it a valid version string.
187 if self.epoch != 0 or string.find(self.upstream, ':') >= 0:
189 return '%d:%s-%s' % (self.epoch, self.upstream, self.debian)
191 return '%d:%s' % (self.epoch, self.upstream)
193 return self.upstream + '-' + self.debian
197 # This should return a string that is a valid Python expression
198 # for recreating this value. It is the "official" representation.
199 # Useful for debugging.
201 return 'Version(' + `str(self)` + ')'
203 # Cheap comparison, when only equality is asked for.
204 def equals(self, other):
205 return self.upstream == other.upstream and \
206 self.debian == other.debian and \
207 self.epoch == other.epoch
212 if version_cache.has_key(version):
213 return version_cache[version]
216 version_cache[version] = v