1
2
3
4 """Parse and compare Debian version strings.
5
6 This module contains a class designed to sit in your Python code pretty
7 naturally and represent a Debian version string. It implements various
8 special methods to make dealing with them sweet.
9 """
10
11 __author__ = "Scott James Remnant <scott@netsplit.com>"
12
13
14 import re
15 from linda.debug import dprint
16
17
18 valid_epoch = re.compile(r'^[0-9]+$')
19 valid_upstream = re.compile(r'^[0-9][A-Za-z0-9+:.~-]*$')
20 valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$')
21
22
23 cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:"
24
25
31
33 """
34 Debian version number.
35
36 This class is designed to be reasonably transparent and allow you to write code like:
37
38 >>> s.version >= '1.100-1'
39
40 The comparison will be done according to Debian rules, so '1.2' will compare lower.
41
42 Properties:
43 - epoch: Epoch
44 - upstream: Upstream version
45 - revision: Debian/local revision
46 """
47
49 """Parse a string or number into the three components."""
50 self.epoch = 0
51 self.upstream = None
52 self.revision = None
53
54 ver = str(ver)
55 if not len(ver):
56 raise BadInputError, "Input cannot be empty"
57
58
59 idx = ver.find(":")
60 if idx != -1:
61 self.epoch = ver[:idx]
62 if not len(self.epoch):
63 raise BadEpochError, "Epoch cannot be empty"
64 if not valid_epoch.search(self.epoch):
65 raise BadEpochError, "Bad epoch format"
66 ver = ver[idx+1:]
67
68
69 idx = ver.rfind("-")
70 if idx != -1:
71 self.revision = ver[idx+1:]
72 if not len(self.revision):
73 raise BadRevisionError, "Revision cannot be empty"
74 if not valid_revision.search(self.revision):
75 raise BadRevisionError, "Bad revision format"
76 ver = ver[:idx]
77
78
79 self.upstream = ver
80 if not len(self.upstream):
81 raise BadUpstreamError, "Upstream version cannot be empty"
82
83 dprint(_("Verifying upstream version: %s") % self.upstream)
84 if not valid_upstream.search(self.upstream):
85 raise BadUpstreamError, "Bad upstream version format: %s" % \
86 self.upstream
87
88 self.epoch = int(self.epoch)
89
91 """Return the version without the epoch."""
92 str = self.upstream
93 if self.revision is not None:
94 str += "-%s" % (self.revision,)
95 return str
96
97 without_epoch = property(getWithoutEpoch)
98
100 """Return the class as a string for printing."""
101 str = ""
102 if self.epoch > 0:
103 str += "%d:" % (self.epoch,)
104 str += self.upstream
105 if self.revision is not None:
106 str += "-%s" % (self.revision,)
107 return str
108
110 """Return a debugging representation of the object."""
111 return "<%s epoch: %d, upstream: %r, revision: %r>" \
112 % (self.__class__.__name__, self.epoch,
113 self.upstream, self.revision)
114
116 """Compare two Version classes."""
117 other = DpkgVersion(other)
118
119 result = cmp(self.epoch, other.epoch)
120 if result != 0: return result
121
122 result = deb_cmp(self.upstream, other.upstream)
123 if result != 0: return result
124
125 result = deb_cmp(self.revision or "", other.revision or "")
126 if result != 0: return result
127
128 return 0
129
131 native = False
132 if not self.revision:
133 native = True
134 return native
135
137 """Cut characters from str that are entirely in accept."""
138 ret = ""
139 while idx < len(str) and str[idx] in accept:
140 ret += str[idx]
141 idx += 1
142
143 return (ret, idx)
144
146 """Return the comparison order of two characters."""
147 if idx >= len(str):
148 return 0
149 elif str[idx] == "~":
150 return -1
151 else:
152 return cmp_table.index(str[idx])
153
155 """Compare two strings in a deb version."""
156 idx = 0
157 while (idx < len(x)) or (idx < len(y)):
158 result = deb_order(x, idx) - deb_order(y, idx)
159 if result < 0:
160 return -1
161 elif result > 0:
162 return 1
163
164 idx += 1
165
166 return 0
167
169 """Implement the string comparison outlined by Debian policy."""
170 x_idx = y_idx = 0
171 while x_idx < len(x) or y_idx < len(y):
172
173 (x_str, x_idx) = strcut(x, x_idx, cmp_table)
174 (y_str, y_idx) = strcut(y, y_idx, cmp_table)
175 result = deb_cmp_str(x_str, y_str)
176 if result != 0: return result
177
178
179 (x_str, x_idx) = strcut(x, x_idx, "0123456789")
180 (y_str, y_idx) = strcut(y, y_idx, "0123456789")
181 result = cmp(int(x_str or "0"), int(y_str or "0"))
182 if result != 0: return result
183
184 return 0
185