Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(194)

Side by Side Diff: recipe_engine/third_party/setuptools/_vendor/packaging/version.py

Issue 1344583003: Recipe package system. (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Recompiled proto Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2014 Donald Stufft
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 from __future__ import absolute_import, division, print_function
15
16 import collections
17 import itertools
18 import re
19
20 from ._structures import Infinity
21
22
23 __all__ = [
24 "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
25 ]
26
27
28 _Version = collections.namedtuple(
29 "_Version",
30 ["epoch", "release", "dev", "pre", "post", "local"],
31 )
32
33
34 def parse(version):
35 """
36 Parse the given version string and return either a :class:`Version` object
37 or a :class:`LegacyVersion` object depending on if the given version is
38 a valid PEP 440 version or a legacy version.
39 """
40 try:
41 return Version(version)
42 except InvalidVersion:
43 return LegacyVersion(version)
44
45
46 class InvalidVersion(ValueError):
47 """
48 An invalid version was found, users should refer to PEP 440.
49 """
50
51
52 class _BaseVersion(object):
53
54 def __hash__(self):
55 return hash(self._key)
56
57 def __lt__(self, other):
58 return self._compare(other, lambda s, o: s < o)
59
60 def __le__(self, other):
61 return self._compare(other, lambda s, o: s <= o)
62
63 def __eq__(self, other):
64 return self._compare(other, lambda s, o: s == o)
65
66 def __ge__(self, other):
67 return self._compare(other, lambda s, o: s >= o)
68
69 def __gt__(self, other):
70 return self._compare(other, lambda s, o: s > o)
71
72 def __ne__(self, other):
73 return self._compare(other, lambda s, o: s != o)
74
75 def _compare(self, other, method):
76 if not isinstance(other, _BaseVersion):
77 return NotImplemented
78
79 return method(self._key, other._key)
80
81
82 class LegacyVersion(_BaseVersion):
83
84 def __init__(self, version):
85 self._version = str(version)
86 self._key = _legacy_cmpkey(self._version)
87
88 def __str__(self):
89 return self._version
90
91 def __repr__(self):
92 return "<LegacyVersion({0})>".format(repr(str(self)))
93
94 @property
95 def public(self):
96 return self._version
97
98 @property
99 def local(self):
100 return None
101
102 @property
103 def is_prerelease(self):
104 return False
105
106
107 _legacy_version_component_re = re.compile(
108 r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
109 )
110
111 _legacy_version_replacement_map = {
112 "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
113 }
114
115
116 def _parse_version_parts(s):
117 for part in _legacy_version_component_re.split(s):
118 part = _legacy_version_replacement_map.get(part, part)
119
120 if not part or part == ".":
121 continue
122
123 if part[:1] in "0123456789":
124 # pad for numeric comparison
125 yield part.zfill(8)
126 else:
127 yield "*" + part
128
129 # ensure that alpha/beta/candidate are before final
130 yield "*final"
131
132
133 def _legacy_cmpkey(version):
134 # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
135 # greater than or equal to 0. This will effectively put the LegacyVersion,
136 # which uses the defacto standard originally implemented by setuptools,
137 # as before all PEP 440 versions.
138 epoch = -1
139
140 # This scheme is taken from pkg_resources.parse_version setuptools prior to
141 # it's adoption of the packaging library.
142 parts = []
143 for part in _parse_version_parts(version.lower()):
144 if part.startswith("*"):
145 # remove "-" before a prerelease tag
146 if part < "*final":
147 while parts and parts[-1] == "*final-":
148 parts.pop()
149
150 # remove trailing zeros from each series of numeric parts
151 while parts and parts[-1] == "00000000":
152 parts.pop()
153
154 parts.append(part)
155 parts = tuple(parts)
156
157 return epoch, parts
158
159 # Deliberately not anchored to the start and end of the string, to make it
160 # easier for 3rd party code to reuse
161 VERSION_PATTERN = r"""
162 v?
163 (?:
164 (?:(?P<epoch>[0-9]+)!)? # epoch
165 (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
166 (?P<pre> # pre-release
167 [-_\.]?
168 (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
169 [-_\.]?
170 (?P<pre_n>[0-9]+)?
171 )?
172 (?P<post> # post release
173 (?:-(?P<post_n1>[0-9]+))
174 |
175 (?:
176 [-_\.]?
177 (?P<post_l>post|rev|r)
178 [-_\.]?
179 (?P<post_n2>[0-9]+)?
180 )
181 )?
182 (?P<dev> # dev release
183 [-_\.]?
184 (?P<dev_l>dev)
185 [-_\.]?
186 (?P<dev_n>[0-9]+)?
187 )?
188 )
189 (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
190 """
191
192
193 class Version(_BaseVersion):
194
195 _regex = re.compile(
196 r"^\s*" + VERSION_PATTERN + r"\s*$",
197 re.VERBOSE | re.IGNORECASE,
198 )
199
200 def __init__(self, version):
201 # Validate the version and parse it into pieces
202 match = self._regex.search(version)
203 if not match:
204 raise InvalidVersion("Invalid version: '{0}'".format(version))
205
206 # Store the parsed out pieces of the version
207 self._version = _Version(
208 epoch=int(match.group("epoch")) if match.group("epoch") else 0,
209 release=tuple(int(i) for i in match.group("release").split(".")),
210 pre=_parse_letter_version(
211 match.group("pre_l"),
212 match.group("pre_n"),
213 ),
214 post=_parse_letter_version(
215 match.group("post_l"),
216 match.group("post_n1") or match.group("post_n2"),
217 ),
218 dev=_parse_letter_version(
219 match.group("dev_l"),
220 match.group("dev_n"),
221 ),
222 local=_parse_local_version(match.group("local")),
223 )
224
225 # Generate a key which will be used for sorting
226 self._key = _cmpkey(
227 self._version.epoch,
228 self._version.release,
229 self._version.pre,
230 self._version.post,
231 self._version.dev,
232 self._version.local,
233 )
234
235 def __repr__(self):
236 return "<Version({0})>".format(repr(str(self)))
237
238 def __str__(self):
239 parts = []
240
241 # Epoch
242 if self._version.epoch != 0:
243 parts.append("{0}!".format(self._version.epoch))
244
245 # Release segment
246 parts.append(".".join(str(x) for x in self._version.release))
247
248 # Pre-release
249 if self._version.pre is not None:
250 parts.append("".join(str(x) for x in self._version.pre))
251
252 # Post-release
253 if self._version.post is not None:
254 parts.append(".post{0}".format(self._version.post[1]))
255
256 # Development release
257 if self._version.dev is not None:
258 parts.append(".dev{0}".format(self._version.dev[1]))
259
260 # Local version segment
261 if self._version.local is not None:
262 parts.append(
263 "+{0}".format(".".join(str(x) for x in self._version.local))
264 )
265
266 return "".join(parts)
267
268 @property
269 def public(self):
270 return str(self).split("+", 1)[0]
271
272 @property
273 def local(self):
274 version_string = str(self)
275 if "+" in version_string:
276 return version_string.split("+", 1)[1]
277
278 @property
279 def is_prerelease(self):
280 return bool(self._version.dev or self._version.pre)
281
282
283 def _parse_letter_version(letter, number):
284 if letter:
285 # We consider there to be an implicit 0 in a pre-release if there is
286 # not a numeral associated with it.
287 if number is None:
288 number = 0
289
290 # We normalize any letters to their lower case form
291 letter = letter.lower()
292
293 # We consider some words to be alternate spellings of other words and
294 # in those cases we want to normalize the spellings to our preferred
295 # spelling.
296 if letter == "alpha":
297 letter = "a"
298 elif letter == "beta":
299 letter = "b"
300 elif letter in ["c", "pre", "preview"]:
301 letter = "rc"
302
303 return letter, int(number)
304 if not letter and number:
305 # We assume if we are given a number, but we are not given a letter
306 # then this is using the implicit post release syntax (e.g. 1.0-1)
307 letter = "post"
308
309 return letter, int(number)
310
311
312 _local_version_seperators = re.compile(r"[\._-]")
313
314
315 def _parse_local_version(local):
316 """
317 Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
318 """
319 if local is not None:
320 return tuple(
321 part.lower() if not part.isdigit() else int(part)
322 for part in _local_version_seperators.split(local)
323 )
324
325
326 def _cmpkey(epoch, release, pre, post, dev, local):
327 # When we compare a release version, we want to compare it with all of the
328 # trailing zeros removed. So we'll use a reverse the list, drop all the now
329 # leading zeros until we come to something non zero, then take the rest
330 # re-reverse it back into the correct order and make it a tuple and use
331 # that for our sorting key.
332 release = tuple(
333 reversed(list(
334 itertools.dropwhile(
335 lambda x: x == 0,
336 reversed(release),
337 )
338 ))
339 )
340
341 # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
342 # We'll do this by abusing the pre segment, but we _only_ want to do this
343 # if there is not a pre or a post segment. If we have one of those then
344 # the normal sorting rules will handle this case correctly.
345 if pre is None and post is None and dev is not None:
346 pre = -Infinity
347 # Versions without a pre-release (except as noted above) should sort after
348 # those with one.
349 elif pre is None:
350 pre = Infinity
351
352 # Versions without a post segment should sort before those with one.
353 if post is None:
354 post = -Infinity
355
356 # Versions without a development segment should sort after those with one.
357 if dev is None:
358 dev = Infinity
359
360 if local is None:
361 # Versions without a local segment should sort before those with one.
362 local = -Infinity
363 else:
364 # Versions with a local segment need that segment parsed to implement
365 # the sorting rules in PEP440.
366 # - Alpha numeric segments sort before numeric segments
367 # - Alpha numeric segments sort lexicographically
368 # - Numeric segments sort numerically
369 # - Shorter versions sort before longer versions when the prefixes
370 # match exactly
371 local = tuple(
372 (i, "") if isinstance(i, int) else (-Infinity, i)
373 for i in local
374 )
375
376 return epoch, release, pre, post, dev, local
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698