Added some code to peer into a data structure in Maemian/Schedule.pm. Also added the
[maemian] / nokia-lintian / depcheck / version.py
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
5 # as dpkg.
6
7 # Copyright (C) 1998 Richard Braakman
8 #
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
12 # later version.
13 #
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.
18 #
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,
23 # MA 02110-1301, USA.
24
25 import string
26 import re
27
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.
30
31 # python can be just as incomprehensible as perl!
32 version_format = re.compile(r"^(\d+:)?([\w][\w+.:\-]*?)(-[\w+.]+)?$")
33
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.
42
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*)")
47
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]*)")
52
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.
56 def alphcmp(x, y):
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()
62         if xalph == yalph:
63             if xnonalph == ynonalph:
64                 x = x[len(xalph) + len(xnonalph):]
65                 y = y[len(yalph) + len(ynonalph):]
66             else:
67                 return cmp(xnonalph, ynonalph)
68         else:
69             common = min(len(xalph), len(yalph))
70             if xalph[:common] == yalph[:common]:
71                 if len(xalph) == common:
72                     if xnonalph == '':
73                         return -1  # y is the longer string
74                     else:
75                         return 1   # xnonalph will sort newer than yalph's tail
76                 else:
77                     if ynonalph == '':
78                         return 1   # x is the longer string
79                     else:
80                         return -1  # ynonalph will sort newer than xalph's tail
81             else:
82                 return cmp(xalph[:common], yalph[:common])
83
84     # One of the strings is exhausted.  The longer string counts as newer.
85     return cmp(len(x), len(y))
86             
87
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.
91 def versioncmp(x, y):
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:
98             if xdigit == ydigit:
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.)
102             elif xdigit == '':
103                 return cmp(0, string.atoi(ydigit))
104             elif ydigit == '':
105                 return cmp(string.atoi(xdigit), 0)
106             else:
107                 return cmp(string.atoi(xdigit), string.atoi(ydigit))
108         else:
109             return alphcmp(xnondigit, ynondigit)
110
111     # One of the strings is exhausted.  The longer string counts as newer.
112     return cmp(len(x), len(y))
113
114 compare_cache = {}
115
116 def cache_versioncmp(x, y):
117     if compare_cache.has_key((x, y)):
118         return compare_cache[(x, y)]
119     c = versioncmp(x, y)
120     compare_cache[(x, y)] = c
121     compare_cache[(y, x)] = -c
122     return c
123         
124 # A version is an immutable object.  It is created with Version(string)
125 # and is not changed thereafter.  This is not enforced.
126 class Version:
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
130     # a string).
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)
136         if not match:
137             raise ValueError, version
138         (epoch, upstream, debian) = match.group(1, 2, 3)
139         if epoch:
140             # slice off the colon
141             self.epoch = string.atoi(epoch[:-1])
142         else:
143             self.epoch = 0
144         self.upstream = upstream
145         if debian:
146             # slice off the leading hyphen
147             self.debian = debian[1:]
148         else:
149             self.debian = None
150
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:
160                     return 0
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:
164                     return 1
165                 elif not self.debian and other.debian:
166                     return -1
167                 else:
168                     return cache_versioncmp(self.debian, other.debian)
169             else:
170                 return cache_versioncmp(self.upstream, other.upstream)
171         else:
172             return cmp(self.epoch, other.epoch)
173
174     # Return a good hash value when this object is used as a key
175     # in a dictionary.  Only immutable objects should define a
176     # hash function.
177     def __hash__(self):
178         return hash(self.epoch) ^ hash(self.upstream) ^ hash(self.debian)
179
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.  
183     def __str__(self):
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:
188             if self.debian:
189                 return '%d:%s-%s' % (self.epoch, self.upstream, self.debian)
190             else:
191                 return '%d:%s' % (self.epoch, self.upstream)
192         elif self.debian:
193             return self.upstream + '-' + self.debian
194         else:
195             return self.upstream
196
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.
200     def __repr__(self):
201         return 'Version(' + `str(self)` + ')'
202         
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
208
209 version_cache = {}
210
211 def make(version):
212     if version_cache.has_key(version):
213         return version_cache[version]
214     else:
215         v = Version(version)
216         version_cache[version] = v
217         return v