Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/distlib/version.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
1 # -*- coding: utf-8 -*- | |
2 # | |
3 # Copyright (C) 2012-2017 The Python Software Foundation. | |
4 # See LICENSE.txt and CONTRIBUTORS.txt. | |
5 # | |
6 """ | |
7 Implementation of a flexible versioning scheme providing support for PEP-440, | |
8 setuptools-compatible and semantic versioning. | |
9 """ | |
10 | |
11 import logging | |
12 import re | |
13 | |
14 from .compat import string_types | |
15 from .util import parse_requirement | |
16 | |
17 __all__ = ['NormalizedVersion', 'NormalizedMatcher', | |
18 'LegacyVersion', 'LegacyMatcher', | |
19 'SemanticVersion', 'SemanticMatcher', | |
20 'UnsupportedVersionError', 'get_scheme'] | |
21 | |
22 logger = logging.getLogger(__name__) | |
23 | |
24 | |
25 class UnsupportedVersionError(ValueError): | |
26 """This is an unsupported version.""" | |
27 pass | |
28 | |
29 | |
30 class Version(object): | |
31 def __init__(self, s): | |
32 self._string = s = s.strip() | |
33 self._parts = parts = self.parse(s) | |
34 assert isinstance(parts, tuple) | |
35 assert len(parts) > 0 | |
36 | |
37 def parse(self, s): | |
38 raise NotImplementedError('please implement in a subclass') | |
39 | |
40 def _check_compatible(self, other): | |
41 if type(self) != type(other): | |
42 raise TypeError('cannot compare %r and %r' % (self, other)) | |
43 | |
44 def __eq__(self, other): | |
45 self._check_compatible(other) | |
46 return self._parts == other._parts | |
47 | |
48 def __ne__(self, other): | |
49 return not self.__eq__(other) | |
50 | |
51 def __lt__(self, other): | |
52 self._check_compatible(other) | |
53 return self._parts < other._parts | |
54 | |
55 def __gt__(self, other): | |
56 return not (self.__lt__(other) or self.__eq__(other)) | |
57 | |
58 def __le__(self, other): | |
59 return self.__lt__(other) or self.__eq__(other) | |
60 | |
61 def __ge__(self, other): | |
62 return self.__gt__(other) or self.__eq__(other) | |
63 | |
64 # See http://docs.python.org/reference/datamodel#object.__hash__ | |
65 def __hash__(self): | |
66 return hash(self._parts) | |
67 | |
68 def __repr__(self): | |
69 return "%s('%s')" % (self.__class__.__name__, self._string) | |
70 | |
71 def __str__(self): | |
72 return self._string | |
73 | |
74 @property | |
75 def is_prerelease(self): | |
76 raise NotImplementedError('Please implement in subclasses.') | |
77 | |
78 | |
79 class Matcher(object): | |
80 version_class = None | |
81 | |
82 # value is either a callable or the name of a method | |
83 _operators = { | |
84 '<': lambda v, c, p: v < c, | |
85 '>': lambda v, c, p: v > c, | |
86 '<=': lambda v, c, p: v == c or v < c, | |
87 '>=': lambda v, c, p: v == c or v > c, | |
88 '==': lambda v, c, p: v == c, | |
89 '===': lambda v, c, p: v == c, | |
90 # by default, compatible => >=. | |
91 '~=': lambda v, c, p: v == c or v > c, | |
92 '!=': lambda v, c, p: v != c, | |
93 } | |
94 | |
95 # this is a method only to support alternative implementations | |
96 # via overriding | |
97 def parse_requirement(self, s): | |
98 return parse_requirement(s) | |
99 | |
100 def __init__(self, s): | |
101 if self.version_class is None: | |
102 raise ValueError('Please specify a version class') | |
103 self._string = s = s.strip() | |
104 r = self.parse_requirement(s) | |
105 if not r: | |
106 raise ValueError('Not valid: %r' % s) | |
107 self.name = r.name | |
108 self.key = self.name.lower() # for case-insensitive comparisons | |
109 clist = [] | |
110 if r.constraints: | |
111 # import pdb; pdb.set_trace() | |
112 for op, s in r.constraints: | |
113 if s.endswith('.*'): | |
114 if op not in ('==', '!='): | |
115 raise ValueError('\'.*\' not allowed for ' | |
116 '%r constraints' % op) | |
117 # Could be a partial version (e.g. for '2.*') which | |
118 # won't parse as a version, so keep it as a string | |
119 vn, prefix = s[:-2], True | |
120 # Just to check that vn is a valid version | |
121 self.version_class(vn) | |
122 else: | |
123 # Should parse as a version, so we can create an | |
124 # instance for the comparison | |
125 vn, prefix = self.version_class(s), False | |
126 clist.append((op, vn, prefix)) | |
127 self._parts = tuple(clist) | |
128 | |
129 def match(self, version): | |
130 """ | |
131 Check if the provided version matches the constraints. | |
132 | |
133 :param version: The version to match against this instance. | |
134 :type version: String or :class:`Version` instance. | |
135 """ | |
136 if isinstance(version, string_types): | |
137 version = self.version_class(version) | |
138 for operator, constraint, prefix in self._parts: | |
139 f = self._operators.get(operator) | |
140 if isinstance(f, string_types): | |
141 f = getattr(self, f) | |
142 if not f: | |
143 msg = ('%r not implemented ' | |
144 'for %s' % (operator, self.__class__.__name__)) | |
145 raise NotImplementedError(msg) | |
146 if not f(version, constraint, prefix): | |
147 return False | |
148 return True | |
149 | |
150 @property | |
151 def exact_version(self): | |
152 result = None | |
153 if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='): | |
154 result = self._parts[0][1] | |
155 return result | |
156 | |
157 def _check_compatible(self, other): | |
158 if type(self) != type(other) or self.name != other.name: | |
159 raise TypeError('cannot compare %s and %s' % (self, other)) | |
160 | |
161 def __eq__(self, other): | |
162 self._check_compatible(other) | |
163 return self.key == other.key and self._parts == other._parts | |
164 | |
165 def __ne__(self, other): | |
166 return not self.__eq__(other) | |
167 | |
168 # See http://docs.python.org/reference/datamodel#object.__hash__ | |
169 def __hash__(self): | |
170 return hash(self.key) + hash(self._parts) | |
171 | |
172 def __repr__(self): | |
173 return "%s(%r)" % (self.__class__.__name__, self._string) | |
174 | |
175 def __str__(self): | |
176 return self._string | |
177 | |
178 | |
179 PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|b|c|rc)(\d+))?' | |
180 r'(\.(post)(\d+))?(\.(dev)(\d+))?' | |
181 r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$') | |
182 | |
183 | |
184 def _pep_440_key(s): | |
185 s = s.strip() | |
186 m = PEP440_VERSION_RE.match(s) | |
187 if not m: | |
188 raise UnsupportedVersionError('Not a valid version: %s' % s) | |
189 groups = m.groups() | |
190 nums = tuple(int(v) for v in groups[1].split('.')) | |
191 while len(nums) > 1 and nums[-1] == 0: | |
192 nums = nums[:-1] | |
193 | |
194 if not groups[0]: | |
195 epoch = 0 | |
196 else: | |
197 epoch = int(groups[0]) | |
198 pre = groups[4:6] | |
199 post = groups[7:9] | |
200 dev = groups[10:12] | |
201 local = groups[13] | |
202 if pre == (None, None): | |
203 pre = () | |
204 else: | |
205 pre = pre[0], int(pre[1]) | |
206 if post == (None, None): | |
207 post = () | |
208 else: | |
209 post = post[0], int(post[1]) | |
210 if dev == (None, None): | |
211 dev = () | |
212 else: | |
213 dev = dev[0], int(dev[1]) | |
214 if local is None: | |
215 local = () | |
216 else: | |
217 parts = [] | |
218 for part in local.split('.'): | |
219 # to ensure that numeric compares as > lexicographic, avoid | |
220 # comparing them directly, but encode a tuple which ensures | |
221 # correct sorting | |
222 if part.isdigit(): | |
223 part = (1, int(part)) | |
224 else: | |
225 part = (0, part) | |
226 parts.append(part) | |
227 local = tuple(parts) | |
228 if not pre: | |
229 # either before pre-release, or final release and after | |
230 if not post and dev: | |
231 # before pre-release | |
232 pre = ('a', -1) # to sort before a0 | |
233 else: | |
234 pre = ('z',) # to sort after all pre-releases | |
235 # now look at the state of post and dev. | |
236 if not post: | |
237 post = ('_',) # sort before 'a' | |
238 if not dev: | |
239 dev = ('final',) | |
240 | |
241 #print('%s -> %s' % (s, m.groups())) | |
242 return epoch, nums, pre, post, dev, local | |
243 | |
244 | |
245 _normalized_key = _pep_440_key | |
246 | |
247 | |
248 class NormalizedVersion(Version): | |
249 """A rational version. | |
250 | |
251 Good: | |
252 1.2 # equivalent to "1.2.0" | |
253 1.2.0 | |
254 1.2a1 | |
255 1.2.3a2 | |
256 1.2.3b1 | |
257 1.2.3c1 | |
258 1.2.3.4 | |
259 TODO: fill this out | |
260 | |
261 Bad: | |
262 1 # minimum two numbers | |
263 1.2a # release level must have a release serial | |
264 1.2.3b | |
265 """ | |
266 def parse(self, s): | |
267 result = _normalized_key(s) | |
268 # _normalized_key loses trailing zeroes in the release | |
269 # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0 | |
270 # However, PEP 440 prefix matching needs it: for example, | |
271 # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0). | |
272 m = PEP440_VERSION_RE.match(s) # must succeed | |
273 groups = m.groups() | |
274 self._release_clause = tuple(int(v) for v in groups[1].split('.')) | |
275 return result | |
276 | |
277 PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev']) | |
278 | |
279 @property | |
280 def is_prerelease(self): | |
281 return any(t[0] in self.PREREL_TAGS for t in self._parts if t) | |
282 | |
283 | |
284 def _match_prefix(x, y): | |
285 x = str(x) | |
286 y = str(y) | |
287 if x == y: | |
288 return True | |
289 if not x.startswith(y): | |
290 return False | |
291 n = len(y) | |
292 return x[n] == '.' | |
293 | |
294 | |
295 class NormalizedMatcher(Matcher): | |
296 version_class = NormalizedVersion | |
297 | |
298 # value is either a callable or the name of a method | |
299 _operators = { | |
300 '~=': '_match_compatible', | |
301 '<': '_match_lt', | |
302 '>': '_match_gt', | |
303 '<=': '_match_le', | |
304 '>=': '_match_ge', | |
305 '==': '_match_eq', | |
306 '===': '_match_arbitrary', | |
307 '!=': '_match_ne', | |
308 } | |
309 | |
310 def _adjust_local(self, version, constraint, prefix): | |
311 if prefix: | |
312 strip_local = '+' not in constraint and version._parts[-1] | |
313 else: | |
314 # both constraint and version are | |
315 # NormalizedVersion instances. | |
316 # If constraint does not have a local component, | |
317 # ensure the version doesn't, either. | |
318 strip_local = not constraint._parts[-1] and version._parts[-1] | |
319 if strip_local: | |
320 s = version._string.split('+', 1)[0] | |
321 version = self.version_class(s) | |
322 return version, constraint | |
323 | |
324 def _match_lt(self, version, constraint, prefix): | |
325 version, constraint = self._adjust_local(version, constraint, prefix) | |
326 if version >= constraint: | |
327 return False | |
328 release_clause = constraint._release_clause | |
329 pfx = '.'.join([str(i) for i in release_clause]) | |
330 return not _match_prefix(version, pfx) | |
331 | |
332 def _match_gt(self, version, constraint, prefix): | |
333 version, constraint = self._adjust_local(version, constraint, prefix) | |
334 if version <= constraint: | |
335 return False | |
336 release_clause = constraint._release_clause | |
337 pfx = '.'.join([str(i) for i in release_clause]) | |
338 return not _match_prefix(version, pfx) | |
339 | |
340 def _match_le(self, version, constraint, prefix): | |
341 version, constraint = self._adjust_local(version, constraint, prefix) | |
342 return version <= constraint | |
343 | |
344 def _match_ge(self, version, constraint, prefix): | |
345 version, constraint = self._adjust_local(version, constraint, prefix) | |
346 return version >= constraint | |
347 | |
348 def _match_eq(self, version, constraint, prefix): | |
349 version, constraint = self._adjust_local(version, constraint, prefix) | |
350 if not prefix: | |
351 result = (version == constraint) | |
352 else: | |
353 result = _match_prefix(version, constraint) | |
354 return result | |
355 | |
356 def _match_arbitrary(self, version, constraint, prefix): | |
357 return str(version) == str(constraint) | |
358 | |
359 def _match_ne(self, version, constraint, prefix): | |
360 version, constraint = self._adjust_local(version, constraint, prefix) | |
361 if not prefix: | |
362 result = (version != constraint) | |
363 else: | |
364 result = not _match_prefix(version, constraint) | |
365 return result | |
366 | |
367 def _match_compatible(self, version, constraint, prefix): | |
368 version, constraint = self._adjust_local(version, constraint, prefix) | |
369 if version == constraint: | |
370 return True | |
371 if version < constraint: | |
372 return False | |
373 # if not prefix: | |
374 # return True | |
375 release_clause = constraint._release_clause | |
376 if len(release_clause) > 1: | |
377 release_clause = release_clause[:-1] | |
378 pfx = '.'.join([str(i) for i in release_clause]) | |
379 return _match_prefix(version, pfx) | |
380 | |
381 _REPLACEMENTS = ( | |
382 (re.compile('[.+-]$'), ''), # remove trailing puncts | |
383 (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start | |
384 (re.compile('^[.-]'), ''), # remove leading puncts | |
385 (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses | |
386 (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion) | |
387 (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion) | |
388 (re.compile('[.]{2,}'), '.'), # multiple runs of '.' | |
389 (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha | |
390 (re.compile(r'\b(pre-alpha|prealpha)\b'), | |
391 'pre.alpha'), # standardise | |
392 (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses | |
393 ) | |
394 | |
395 _SUFFIX_REPLACEMENTS = ( | |
396 (re.compile('^[:~._+-]+'), ''), # remove leading puncts | |
397 (re.compile('[,*")([\\]]'), ''), # remove unwanted chars | |
398 (re.compile('[~:+_ -]'), '.'), # replace illegal chars | |
399 (re.compile('[.]{2,}'), '.'), # multiple runs of '.' | |
400 (re.compile(r'\.$'), ''), # trailing '.' | |
401 ) | |
402 | |
403 _NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)') | |
404 | |
405 | |
406 def _suggest_semantic_version(s): | |
407 """ | |
408 Try to suggest a semantic form for a version for which | |
409 _suggest_normalized_version couldn't come up with anything. | |
410 """ | |
411 result = s.strip().lower() | |
412 for pat, repl in _REPLACEMENTS: | |
413 result = pat.sub(repl, result) | |
414 if not result: | |
415 result = '0.0.0' | |
416 | |
417 # Now look for numeric prefix, and separate it out from | |
418 # the rest. | |
419 #import pdb; pdb.set_trace() | |
420 m = _NUMERIC_PREFIX.match(result) | |
421 if not m: | |
422 prefix = '0.0.0' | |
423 suffix = result | |
424 else: | |
425 prefix = m.groups()[0].split('.') | |
426 prefix = [int(i) for i in prefix] | |
427 while len(prefix) < 3: | |
428 prefix.append(0) | |
429 if len(prefix) == 3: | |
430 suffix = result[m.end():] | |
431 else: | |
432 suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():] | |
433 prefix = prefix[:3] | |
434 prefix = '.'.join([str(i) for i in prefix]) | |
435 suffix = suffix.strip() | |
436 if suffix: | |
437 #import pdb; pdb.set_trace() | |
438 # massage the suffix. | |
439 for pat, repl in _SUFFIX_REPLACEMENTS: | |
440 suffix = pat.sub(repl, suffix) | |
441 | |
442 if not suffix: | |
443 result = prefix | |
444 else: | |
445 sep = '-' if 'dev' in suffix else '+' | |
446 result = prefix + sep + suffix | |
447 if not is_semver(result): | |
448 result = None | |
449 return result | |
450 | |
451 | |
452 def _suggest_normalized_version(s): | |
453 """Suggest a normalized version close to the given version string. | |
454 | |
455 If you have a version string that isn't rational (i.e. NormalizedVersion | |
456 doesn't like it) then you might be able to get an equivalent (or close) | |
457 rational version from this function. | |
458 | |
459 This does a number of simple normalizations to the given string, based | |
460 on observation of versions currently in use on PyPI. Given a dump of | |
461 those version during PyCon 2009, 4287 of them: | |
462 - 2312 (53.93%) match NormalizedVersion without change | |
463 with the automatic suggestion | |
464 - 3474 (81.04%) match when using this suggestion method | |
465 | |
466 @param s {str} An irrational version string. | |
467 @returns A rational version string, or None, if couldn't determine one. | |
468 """ | |
469 try: | |
470 _normalized_key(s) | |
471 return s # already rational | |
472 except UnsupportedVersionError: | |
473 pass | |
474 | |
475 rs = s.lower() | |
476 | |
477 # part of this could use maketrans | |
478 for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), | |
479 ('beta', 'b'), ('rc', 'c'), ('-final', ''), | |
480 ('-pre', 'c'), | |
481 ('-release', ''), ('.release', ''), ('-stable', ''), | |
482 ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), | |
483 ('final', '')): | |
484 rs = rs.replace(orig, repl) | |
485 | |
486 # if something ends with dev or pre, we add a 0 | |
487 rs = re.sub(r"pre$", r"pre0", rs) | |
488 rs = re.sub(r"dev$", r"dev0", rs) | |
489 | |
490 # if we have something like "b-2" or "a.2" at the end of the | |
491 # version, that is probably beta, alpha, etc | |
492 # let's remove the dash or dot | |
493 rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs) | |
494 | |
495 # 1.0-dev-r371 -> 1.0.dev371 | |
496 # 0.1-dev-r79 -> 0.1.dev79 | |
497 rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) | |
498 | |
499 # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 | |
500 rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) | |
501 | |
502 # Clean: v0.3, v1.0 | |
503 if rs.startswith('v'): | |
504 rs = rs[1:] | |
505 | |
506 # Clean leading '0's on numbers. | |
507 #TODO: unintended side-effect on, e.g., "2003.05.09" | |
508 # PyPI stats: 77 (~2%) better | |
509 rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) | |
510 | |
511 # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers | |
512 # zero. | |
513 # PyPI stats: 245 (7.56%) better | |
514 rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) | |
515 | |
516 # the 'dev-rNNN' tag is a dev tag | |
517 rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) | |
518 | |
519 # clean the - when used as a pre delimiter | |
520 rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) | |
521 | |
522 # a terminal "dev" or "devel" can be changed into ".dev0" | |
523 rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) | |
524 | |
525 # a terminal "dev" can be changed into ".dev0" | |
526 rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) | |
527 | |
528 # a terminal "final" or "stable" can be removed | |
529 rs = re.sub(r"(final|stable)$", "", rs) | |
530 | |
531 # The 'r' and the '-' tags are post release tags | |
532 # 0.4a1.r10 -> 0.4a1.post10 | |
533 # 0.9.33-17222 -> 0.9.33.post17222 | |
534 # 0.9.33-r17222 -> 0.9.33.post17222 | |
535 rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) | |
536 | |
537 # Clean 'r' instead of 'dev' usage: | |
538 # 0.9.33+r17222 -> 0.9.33.dev17222 | |
539 # 1.0dev123 -> 1.0.dev123 | |
540 # 1.0.git123 -> 1.0.dev123 | |
541 # 1.0.bzr123 -> 1.0.dev123 | |
542 # 0.1a0dev.123 -> 0.1a0.dev123 | |
543 # PyPI stats: ~150 (~4%) better | |
544 rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) | |
545 | |
546 # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: | |
547 # 0.2.pre1 -> 0.2c1 | |
548 # 0.2-c1 -> 0.2c1 | |
549 # 1.0preview123 -> 1.0c123 | |
550 # PyPI stats: ~21 (0.62%) better | |
551 rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) | |
552 | |
553 # Tcl/Tk uses "px" for their post release markers | |
554 rs = re.sub(r"p(\d+)$", r".post\1", rs) | |
555 | |
556 try: | |
557 _normalized_key(rs) | |
558 except UnsupportedVersionError: | |
559 rs = None | |
560 return rs | |
561 | |
562 # | |
563 # Legacy version processing (distribute-compatible) | |
564 # | |
565 | |
566 _VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I) | |
567 _VERSION_REPLACE = { | |
568 'pre': 'c', | |
569 'preview': 'c', | |
570 '-': 'final-', | |
571 'rc': 'c', | |
572 'dev': '@', | |
573 '': None, | |
574 '.': None, | |
575 } | |
576 | |
577 | |
578 def _legacy_key(s): | |
579 def get_parts(s): | |
580 result = [] | |
581 for p in _VERSION_PART.split(s.lower()): | |
582 p = _VERSION_REPLACE.get(p, p) | |
583 if p: | |
584 if '0' <= p[:1] <= '9': | |
585 p = p.zfill(8) | |
586 else: | |
587 p = '*' + p | |
588 result.append(p) | |
589 result.append('*final') | |
590 return result | |
591 | |
592 result = [] | |
593 for p in get_parts(s): | |
594 if p.startswith('*'): | |
595 if p < '*final': | |
596 while result and result[-1] == '*final-': | |
597 result.pop() | |
598 while result and result[-1] == '00000000': | |
599 result.pop() | |
600 result.append(p) | |
601 return tuple(result) | |
602 | |
603 | |
604 class LegacyVersion(Version): | |
605 def parse(self, s): | |
606 return _legacy_key(s) | |
607 | |
608 @property | |
609 def is_prerelease(self): | |
610 result = False | |
611 for x in self._parts: | |
612 if (isinstance(x, string_types) and x.startswith('*') and | |
613 x < '*final'): | |
614 result = True | |
615 break | |
616 return result | |
617 | |
618 | |
619 class LegacyMatcher(Matcher): | |
620 version_class = LegacyVersion | |
621 | |
622 _operators = dict(Matcher._operators) | |
623 _operators['~='] = '_match_compatible' | |
624 | |
625 numeric_re = re.compile(r'^(\d+(\.\d+)*)') | |
626 | |
627 def _match_compatible(self, version, constraint, prefix): | |
628 if version < constraint: | |
629 return False | |
630 m = self.numeric_re.match(str(constraint)) | |
631 if not m: | |
632 logger.warning('Cannot compute compatible match for version %s ' | |
633 ' and constraint %s', version, constraint) | |
634 return True | |
635 s = m.groups()[0] | |
636 if '.' in s: | |
637 s = s.rsplit('.', 1)[0] | |
638 return _match_prefix(version, s) | |
639 | |
640 # | |
641 # Semantic versioning | |
642 # | |
643 | |
644 _SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)' | |
645 r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?' | |
646 r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I) | |
647 | |
648 | |
649 def is_semver(s): | |
650 return _SEMVER_RE.match(s) | |
651 | |
652 | |
653 def _semantic_key(s): | |
654 def make_tuple(s, absent): | |
655 if s is None: | |
656 result = (absent,) | |
657 else: | |
658 parts = s[1:].split('.') | |
659 # We can't compare ints and strings on Python 3, so fudge it | |
660 # by zero-filling numeric values so simulate a numeric comparison | |
661 result = tuple([p.zfill(8) if p.isdigit() else p for p in parts]) | |
662 return result | |
663 | |
664 m = is_semver(s) | |
665 if not m: | |
666 raise UnsupportedVersionError(s) | |
667 groups = m.groups() | |
668 major, minor, patch = [int(i) for i in groups[:3]] | |
669 # choose the '|' and '*' so that versions sort correctly | |
670 pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*') | |
671 return (major, minor, patch), pre, build | |
672 | |
673 | |
674 class SemanticVersion(Version): | |
675 def parse(self, s): | |
676 return _semantic_key(s) | |
677 | |
678 @property | |
679 def is_prerelease(self): | |
680 return self._parts[1][0] != '|' | |
681 | |
682 | |
683 class SemanticMatcher(Matcher): | |
684 version_class = SemanticVersion | |
685 | |
686 | |
687 class VersionScheme(object): | |
688 def __init__(self, key, matcher, suggester=None): | |
689 self.key = key | |
690 self.matcher = matcher | |
691 self.suggester = suggester | |
692 | |
693 def is_valid_version(self, s): | |
694 try: | |
695 self.matcher.version_class(s) | |
696 result = True | |
697 except UnsupportedVersionError: | |
698 result = False | |
699 return result | |
700 | |
701 def is_valid_matcher(self, s): | |
702 try: | |
703 self.matcher(s) | |
704 result = True | |
705 except UnsupportedVersionError: | |
706 result = False | |
707 return result | |
708 | |
709 def is_valid_constraint_list(self, s): | |
710 """ | |
711 Used for processing some metadata fields | |
712 """ | |
713 return self.is_valid_matcher('dummy_name (%s)' % s) | |
714 | |
715 def suggest(self, s): | |
716 if self.suggester is None: | |
717 result = None | |
718 else: | |
719 result = self.suggester(s) | |
720 return result | |
721 | |
722 _SCHEMES = { | |
723 'normalized': VersionScheme(_normalized_key, NormalizedMatcher, | |
724 _suggest_normalized_version), | |
725 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s), | |
726 'semantic': VersionScheme(_semantic_key, SemanticMatcher, | |
727 _suggest_semantic_version), | |
728 } | |
729 | |
730 _SCHEMES['default'] = _SCHEMES['normalized'] | |
731 | |
732 | |
733 def get_scheme(name): | |
734 if name not in _SCHEMES: | |
735 raise ValueError('unknown scheme name: %r' % name) | |
736 return _SCHEMES[name] |