semver.py (47080B)
1 # -*- coding: utf-8 -*- 2 # Copyright (c) The python-semanticversion project 3 # This code is distributed under the two-clause BSD License. 4 5 import functools 6 import re 7 import warnings 8 9 10 def _has_leading_zero(value): 11 return (value 12 and value[0] == '0' 13 and value.isdigit() 14 and value != '0') 15 16 17 class MaxIdentifier(object): 18 __slots__ = [] 19 20 def __repr__(self): 21 return 'MaxIdentifier()' 22 23 def __eq__(self, other): 24 return isinstance(other, self.__class__) 25 26 27 @functools.total_ordering 28 class NumericIdentifier(object): 29 __slots__ = ['value'] 30 31 def __init__(self, value): 32 self.value = int(value) 33 34 def __repr__(self): 35 return 'NumericIdentifier(%r)' % self.value 36 37 def __eq__(self, other): 38 if isinstance(other, NumericIdentifier): 39 return self.value == other.value 40 return NotImplemented 41 42 def __lt__(self, other): 43 if isinstance(other, MaxIdentifier): 44 return True 45 elif isinstance(other, AlphaIdentifier): 46 return True 47 elif isinstance(other, NumericIdentifier): 48 return self.value < other.value 49 else: 50 return NotImplemented 51 52 53 @functools.total_ordering 54 class AlphaIdentifier(object): 55 __slots__ = ['value'] 56 57 def __init__(self, value): 58 self.value = value.encode('ascii') 59 60 def __repr__(self): 61 return 'AlphaIdentifier(%r)' % self.value 62 63 def __eq__(self, other): 64 if isinstance(other, AlphaIdentifier): 65 return self.value == other.value 66 return NotImplemented 67 68 def __lt__(self, other): 69 if isinstance(other, MaxIdentifier): 70 return True 71 elif isinstance(other, NumericIdentifier): 72 return False 73 elif isinstance(other, AlphaIdentifier): 74 return self.value < other.value 75 else: 76 return NotImplemented 77 78 79 class Version(object): 80 81 version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$') 82 partial_version_re = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$') 83 84 def __init__( 85 self, 86 version_string=None, 87 major=None, 88 minor=None, 89 patch=None, 90 prerelease=None, 91 build=None, 92 partial=False): 93 if partial: 94 warnings.warn( 95 "Partial versions will be removed in 3.0; use SimpleSpec('1.x.x') instead.", 96 DeprecationWarning, 97 stacklevel=2, 98 ) 99 has_text = version_string is not None 100 has_parts = not (major is minor is patch is prerelease is build is None) 101 if not has_text ^ has_parts: 102 raise ValueError("Call either Version('1.2.3') or Version(major=1, ...).") 103 104 if has_text: 105 major, minor, patch, prerelease, build = self.parse(version_string, partial) 106 else: 107 # Convenience: allow to omit prerelease/build. 108 prerelease = tuple(prerelease or ()) 109 if not partial: 110 build = tuple(build or ()) 111 self._validate_kwargs(major, minor, patch, prerelease, build, partial) 112 113 self.major = major 114 self.minor = minor 115 self.patch = patch 116 self.prerelease = prerelease 117 self.build = build 118 119 self.partial = partial 120 121 @classmethod 122 def _coerce(cls, value, allow_none=False): 123 if value is None and allow_none: 124 return value 125 return int(value) 126 127 def next_major(self): 128 if self.prerelease and self.minor == self.patch == 0: 129 return Version( 130 major=self.major, 131 minor=0, 132 patch=0, 133 partial=self.partial, 134 ) 135 else: 136 return Version( 137 major=self.major + 1, 138 minor=0, 139 patch=0, 140 partial=self.partial, 141 ) 142 143 def next_minor(self): 144 if self.prerelease and self.patch == 0: 145 return Version( 146 major=self.major, 147 minor=self.minor, 148 patch=0, 149 partial=self.partial, 150 ) 151 else: 152 return Version( 153 major=self.major, 154 minor=self.minor + 1, 155 patch=0, 156 partial=self.partial, 157 ) 158 159 def next_patch(self): 160 if self.prerelease: 161 return Version( 162 major=self.major, 163 minor=self.minor, 164 patch=self.patch, 165 partial=self.partial, 166 ) 167 else: 168 return Version( 169 major=self.major, 170 minor=self.minor, 171 patch=self.patch + 1, 172 partial=self.partial, 173 ) 174 175 def truncate(self, level='patch'): 176 """Return a new Version object, truncated up to the selected level.""" 177 if level == 'build': 178 return self 179 elif level == 'prerelease': 180 return Version( 181 major=self.major, 182 minor=self.minor, 183 patch=self.patch, 184 prerelease=self.prerelease, 185 partial=self.partial, 186 ) 187 elif level == 'patch': 188 return Version( 189 major=self.major, 190 minor=self.minor, 191 patch=self.patch, 192 partial=self.partial, 193 ) 194 elif level == 'minor': 195 return Version( 196 major=self.major, 197 minor=self.minor, 198 patch=None if self.partial else 0, 199 partial=self.partial, 200 ) 201 elif level == 'major': 202 return Version( 203 major=self.major, 204 minor=None if self.partial else 0, 205 patch=None if self.partial else 0, 206 partial=self.partial, 207 ) 208 else: 209 raise ValueError("Invalid truncation level `%s`." % level) 210 211 @classmethod 212 def coerce(cls, version_string, partial=False): 213 """Coerce an arbitrary version string into a semver-compatible one. 214 215 The rule is: 216 - If not enough components, fill minor/patch with zeroes; unless 217 partial=True 218 - If more than 3 dot-separated components, extra components are "build" 219 data. If some "build" data already appeared, append it to the 220 extra components 221 222 Examples: 223 >>> Version.coerce('0.1') 224 Version(0, 1, 0) 225 >>> Version.coerce('0.1.2.3') 226 Version(0, 1, 2, (), ('3',)) 227 >>> Version.coerce('0.1.2.3+4') 228 Version(0, 1, 2, (), ('3', '4')) 229 >>> Version.coerce('0.1+2-3+4_5') 230 Version(0, 1, 0, (), ('2-3', '4-5')) 231 """ 232 base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?') 233 234 match = base_re.match(version_string) 235 if not match: 236 raise ValueError( 237 "Version string lacks a numerical component: %r" 238 % version_string 239 ) 240 241 version = version_string[:match.end()] 242 if not partial: 243 # We need a not-partial version. 244 while version.count('.') < 2: 245 version += '.0' 246 247 # Strip leading zeros in components 248 # Version is of the form nn, nn.pp or nn.pp.qq 249 version = '.'.join( 250 # If the part was '0', we end up with an empty string. 251 part.lstrip('0') or '0' 252 for part in version.split('.') 253 ) 254 255 if match.end() == len(version_string): 256 return Version(version, partial=partial) 257 258 rest = version_string[match.end():] 259 260 # Cleanup the 'rest' 261 rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest) 262 263 if rest[0] == '+': 264 # A 'build' component 265 prerelease = '' 266 build = rest[1:] 267 elif rest[0] == '.': 268 # An extra version component, probably 'build' 269 prerelease = '' 270 build = rest[1:] 271 elif rest[0] == '-': 272 rest = rest[1:] 273 if '+' in rest: 274 prerelease, build = rest.split('+', 1) 275 else: 276 prerelease, build = rest, '' 277 elif '+' in rest: 278 prerelease, build = rest.split('+', 1) 279 else: 280 prerelease, build = rest, '' 281 282 build = build.replace('+', '.') 283 284 if prerelease: 285 version = '%s-%s' % (version, prerelease) 286 if build: 287 version = '%s+%s' % (version, build) 288 289 return cls(version, partial=partial) 290 291 @classmethod 292 def parse(cls, version_string, partial=False, coerce=False): 293 """Parse a version string into a Version() object. 294 295 Args: 296 version_string (str), the version string to parse 297 partial (bool), whether to accept incomplete input 298 coerce (bool), whether to try to map the passed in string into a 299 valid Version. 300 """ 301 if not version_string: 302 raise ValueError('Invalid empty version string: %r' % version_string) 303 304 if partial: 305 version_re = cls.partial_version_re 306 else: 307 version_re = cls.version_re 308 309 match = version_re.match(version_string) 310 if not match: 311 raise ValueError('Invalid version string: %r' % version_string) 312 313 major, minor, patch, prerelease, build = match.groups() 314 315 if _has_leading_zero(major): 316 raise ValueError("Invalid leading zero in major: %r" % version_string) 317 if _has_leading_zero(minor): 318 raise ValueError("Invalid leading zero in minor: %r" % version_string) 319 if _has_leading_zero(patch): 320 raise ValueError("Invalid leading zero in patch: %r" % version_string) 321 322 major = int(major) 323 minor = cls._coerce(minor, partial) 324 patch = cls._coerce(patch, partial) 325 326 if prerelease is None: 327 if partial and (build is None): 328 # No build info, strip here 329 return (major, minor, patch, None, None) 330 else: 331 prerelease = () 332 elif prerelease == '': 333 prerelease = () 334 else: 335 prerelease = tuple(prerelease.split('.')) 336 cls._validate_identifiers(prerelease, allow_leading_zeroes=False) 337 338 if build is None: 339 if partial: 340 build = None 341 else: 342 build = () 343 elif build == '': 344 build = () 345 else: 346 build = tuple(build.split('.')) 347 cls._validate_identifiers(build, allow_leading_zeroes=True) 348 349 return (major, minor, patch, prerelease, build) 350 351 @classmethod 352 def _validate_identifiers(cls, identifiers, allow_leading_zeroes=False): 353 for item in identifiers: 354 if not item: 355 raise ValueError( 356 "Invalid empty identifier %r in %r" 357 % (item, '.'.join(identifiers)) 358 ) 359 360 if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes: 361 raise ValueError("Invalid leading zero in identifier %r" % item) 362 363 @classmethod 364 def _validate_kwargs(cls, major, minor, patch, prerelease, build, partial): 365 if ( 366 major != int(major) 367 or minor != cls._coerce(minor, partial) 368 or patch != cls._coerce(patch, partial) 369 or prerelease is None and not partial 370 or build is None and not partial 371 ): 372 raise ValueError( 373 "Invalid kwargs to Version(major=%r, minor=%r, patch=%r, " 374 "prerelease=%r, build=%r, partial=%r" % ( 375 major, minor, patch, prerelease, build, partial 376 )) 377 if prerelease is not None: 378 cls._validate_identifiers(prerelease, allow_leading_zeroes=False) 379 if build is not None: 380 cls._validate_identifiers(build, allow_leading_zeroes=True) 381 382 def __iter__(self): 383 return iter((self.major, self.minor, self.patch, self.prerelease, self.build)) 384 385 def __str__(self): 386 version = '%d' % self.major 387 if self.minor is not None: 388 version = '%s.%d' % (version, self.minor) 389 if self.patch is not None: 390 version = '%s.%d' % (version, self.patch) 391 392 if self.prerelease or (self.partial and self.prerelease == () and self.build is None): 393 version = '%s-%s' % (version, '.'.join(self.prerelease)) 394 if self.build or (self.partial and self.build == ()): 395 version = '%s+%s' % (version, '.'.join(self.build)) 396 return version 397 398 def __repr__(self): 399 return '%s(%r%s)' % ( 400 self.__class__.__name__, 401 str(self), 402 ', partial=True' if self.partial else '', 403 ) 404 405 def __hash__(self): 406 # We don't include 'partial', since this is strictly equivalent to having 407 # at least a field being `None`. 408 return hash((self.major, self.minor, self.patch, self.prerelease, self.build)) 409 410 @property 411 def precedence_key(self): 412 if self.prerelease: 413 prerelease_key = tuple( 414 NumericIdentifier(part) if re.match(r'^[0-9]+$', part) else AlphaIdentifier(part) 415 for part in self.prerelease 416 ) 417 else: 418 prerelease_key = ( 419 MaxIdentifier(), 420 ) 421 422 return ( 423 self.major, 424 self.minor, 425 self.patch, 426 prerelease_key, 427 ) 428 429 def __cmp__(self, other): 430 if not isinstance(other, self.__class__): 431 return NotImplemented 432 if self < other: 433 return -1 434 elif self > other: 435 return 1 436 elif self == other: 437 return 0 438 else: 439 return NotImplemented 440 441 def __eq__(self, other): 442 if not isinstance(other, self.__class__): 443 return NotImplemented 444 return ( 445 self.major == other.major 446 and self.minor == other.minor 447 and self.patch == other.patch 448 and (self.prerelease or ()) == (other.prerelease or ()) 449 and (self.build or ()) == (other.build or ()) 450 ) 451 452 def __ne__(self, other): 453 if not isinstance(other, self.__class__): 454 return NotImplemented 455 return tuple(self) != tuple(other) 456 457 def __lt__(self, other): 458 if not isinstance(other, self.__class__): 459 return NotImplemented 460 return self.precedence_key < other.precedence_key 461 462 def __le__(self, other): 463 if not isinstance(other, self.__class__): 464 return NotImplemented 465 return self.precedence_key <= other.precedence_key 466 467 def __gt__(self, other): 468 if not isinstance(other, self.__class__): 469 return NotImplemented 470 return self.precedence_key > other.precedence_key 471 472 def __ge__(self, other): 473 if not isinstance(other, self.__class__): 474 return NotImplemented 475 return self.precedence_key >= other.precedence_key 476 477 478 class SpecItem(object): 479 """A requirement specification.""" 480 481 KIND_ANY = '*' 482 KIND_LT = '<' 483 KIND_LTE = '<=' 484 KIND_EQUAL = '==' 485 KIND_SHORTEQ = '=' 486 KIND_EMPTY = '' 487 KIND_GTE = '>=' 488 KIND_GT = '>' 489 KIND_NEQ = '!=' 490 KIND_CARET = '^' 491 KIND_TILDE = '~' 492 KIND_COMPATIBLE = '~=' 493 494 # Map a kind alias to its full version 495 KIND_ALIASES = { 496 KIND_SHORTEQ: KIND_EQUAL, 497 KIND_EMPTY: KIND_EQUAL, 498 } 499 500 re_spec = re.compile(r'^(<|<=||=|==|>=|>|!=|\^|~|~=)(\d.*)$') 501 502 def __init__(self, requirement_string, _warn=True): 503 if _warn: 504 warnings.warn( 505 "The `SpecItem` class will be removed in 3.0.", 506 DeprecationWarning, 507 stacklevel=2, 508 ) 509 kind, spec = self.parse(requirement_string) 510 self.kind = kind 511 self.spec = spec 512 self._clause = Spec(requirement_string).clause 513 514 @classmethod 515 def parse(cls, requirement_string): 516 if not requirement_string: 517 raise ValueError("Invalid empty requirement specification: %r" % requirement_string) 518 519 # Special case: the 'any' version spec. 520 if requirement_string == '*': 521 return (cls.KIND_ANY, '') 522 523 match = cls.re_spec.match(requirement_string) 524 if not match: 525 raise ValueError("Invalid requirement specification: %r" % requirement_string) 526 527 kind, version = match.groups() 528 if kind in cls.KIND_ALIASES: 529 kind = cls.KIND_ALIASES[kind] 530 531 spec = Version(version, partial=True) 532 if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ): 533 raise ValueError( 534 "Invalid requirement specification %r: build numbers have no ordering." 535 % requirement_string 536 ) 537 return (kind, spec) 538 539 @classmethod 540 def from_matcher(cls, matcher): 541 if matcher == Always(): 542 return cls('*', _warn=False) 543 elif matcher == Never(): 544 return cls('<0.0.0-', _warn=False) 545 elif isinstance(matcher, Range): 546 return cls('%s%s' % (matcher.operator, matcher.target), _warn=False) 547 548 def match(self, version): 549 return self._clause.match(version) 550 551 def __str__(self): 552 return '%s%s' % (self.kind, self.spec) 553 554 def __repr__(self): 555 return '<SpecItem: %s %r>' % (self.kind, self.spec) 556 557 def __eq__(self, other): 558 if not isinstance(other, SpecItem): 559 return NotImplemented 560 return self.kind == other.kind and self.spec == other.spec 561 562 def __hash__(self): 563 return hash((self.kind, self.spec)) 564 565 566 def compare(v1, v2): 567 return Version(v1).__cmp__(Version(v2)) 568 569 570 def match(spec, version): 571 return Spec(spec).match(Version(version)) 572 573 574 def validate(version_string): 575 """Validates a version string against the SemVer specification.""" 576 try: 577 Version.parse(version_string) 578 return True 579 except ValueError: 580 return False 581 582 583 DEFAULT_SYNTAX = 'simple' 584 585 586 class BaseSpec(object): 587 """A specification of compatible versions. 588 589 Usage: 590 >>> Spec('>=1.0.0', syntax='npm') 591 592 A version matches a specification if it matches any 593 of the clauses of that specification. 594 595 Internally, a Spec is AnyOf( 596 AllOf(Matcher, Matcher, Matcher), 597 AllOf(...), 598 ) 599 """ 600 SYNTAXES = {} 601 602 @classmethod 603 def register_syntax(cls, subclass): 604 syntax = subclass.SYNTAX 605 if syntax is None: 606 raise ValueError("A Spec needs its SYNTAX field to be set.") 607 elif syntax in cls.SYNTAXES: 608 raise ValueError( 609 "Duplicate syntax for %s: %r, %r" 610 % (syntax, cls.SYNTAXES[syntax], subclass) 611 ) 612 cls.SYNTAXES[syntax] = subclass 613 return subclass 614 615 def __init__(self, expression): 616 super(BaseSpec, self).__init__() 617 self.expression = expression 618 self.clause = self._parse_to_clause(expression) 619 620 @classmethod 621 def parse(cls, expression, syntax=DEFAULT_SYNTAX): 622 """Convert a syntax-specific expression into a BaseSpec instance.""" 623 return cls.SYNTAXES[syntax](expression) 624 625 @classmethod 626 def _parse_to_clause(cls, expression): 627 """Converts an expression to a clause.""" 628 raise NotImplementedError() 629 630 def filter(self, versions): 631 """Filter an iterable of versions satisfying the Spec.""" 632 for version in versions: 633 if self.match(version): 634 yield version 635 636 def match(self, version): 637 """Check whether a Version satisfies the Spec.""" 638 return self.clause.match(version) 639 640 def select(self, versions): 641 """Select the best compatible version among an iterable of options.""" 642 options = list(self.filter(versions)) 643 if options: 644 return max(options) 645 return None 646 647 def __contains__(self, version): 648 """Whether `version in self`.""" 649 if isinstance(version, Version): 650 return self.match(version) 651 return False 652 653 def __eq__(self, other): 654 if not isinstance(other, self.__class__): 655 return NotImplemented 656 657 return self.clause == other.clause 658 659 def __hash__(self): 660 return hash(self.clause) 661 662 def __str__(self): 663 return self.expression 664 665 def __repr__(self): 666 return '<%s: %r>' % (self.__class__.__name__, self.expression) 667 668 669 class Clause(object): 670 __slots__ = [] 671 672 def match(self, version): 673 raise NotImplementedError() 674 675 def __and__(self, other): 676 raise NotImplementedError() 677 678 def __or__(self, other): 679 raise NotImplementedError() 680 681 def __eq__(self, other): 682 raise NotImplementedError() 683 684 def prettyprint(self, indent='\t'): 685 """Pretty-print the clause. 686 """ 687 return '\n'.join(self._pretty()).replace('\t', indent) 688 689 def _pretty(self): 690 """Actual pretty-printing logic. 691 692 Yields: 693 A list of string. Indentation is performed with \t. 694 """ 695 yield repr(self) 696 697 def __ne__(self, other): 698 return not self == other 699 700 def simplify(self): 701 return self 702 703 704 class AnyOf(Clause): 705 __slots__ = ['clauses'] 706 707 def __init__(self, *clauses): 708 super(AnyOf, self).__init__() 709 self.clauses = frozenset(clauses) 710 711 def match(self, version): 712 return any(c.match(version) for c in self.clauses) 713 714 def simplify(self): 715 subclauses = set() 716 for clause in self.clauses: 717 simplified = clause.simplify() 718 if isinstance(simplified, AnyOf): 719 subclauses |= simplified.clauses 720 elif simplified == Never(): 721 continue 722 else: 723 subclauses.add(simplified) 724 if len(subclauses) == 1: 725 return subclauses.pop() 726 return AnyOf(*subclauses) 727 728 def __hash__(self): 729 return hash((AnyOf, self.clauses)) 730 731 def __iter__(self): 732 return iter(self.clauses) 733 734 def __eq__(self, other): 735 return isinstance(other, self.__class__) and self.clauses == other.clauses 736 737 def __and__(self, other): 738 if isinstance(other, AllOf): 739 return other & self 740 elif isinstance(other, Matcher) or isinstance(other, AnyOf): 741 return AllOf(self, other) 742 else: 743 return NotImplemented 744 745 def __or__(self, other): 746 if isinstance(other, AnyOf): 747 clauses = list(self.clauses | other.clauses) 748 elif isinstance(other, Matcher) or isinstance(other, AllOf): 749 clauses = list(self.clauses | set([other])) 750 else: 751 return NotImplemented 752 return AnyOf(*clauses) 753 754 def __repr__(self): 755 return 'AnyOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses)) 756 757 def _pretty(self): 758 yield 'AnyOF(' 759 for clause in self.clauses: 760 lines = list(clause._pretty()) 761 for line in lines[:-1]: 762 yield '\t' + line 763 yield '\t' + lines[-1] + ',' 764 yield ')' 765 766 767 class AllOf(Clause): 768 __slots__ = ['clauses'] 769 770 def __init__(self, *clauses): 771 super(AllOf, self).__init__() 772 self.clauses = frozenset(clauses) 773 774 def match(self, version): 775 return all(clause.match(version) for clause in self.clauses) 776 777 def simplify(self): 778 subclauses = set() 779 for clause in self.clauses: 780 simplified = clause.simplify() 781 if isinstance(simplified, AllOf): 782 subclauses |= simplified.clauses 783 elif simplified == Always(): 784 continue 785 else: 786 subclauses.add(simplified) 787 if len(subclauses) == 1: 788 return subclauses.pop() 789 return AllOf(*subclauses) 790 791 def __hash__(self): 792 return hash((AllOf, self.clauses)) 793 794 def __iter__(self): 795 return iter(self.clauses) 796 797 def __eq__(self, other): 798 return isinstance(other, self.__class__) and self.clauses == other.clauses 799 800 def __and__(self, other): 801 if isinstance(other, Matcher) or isinstance(other, AnyOf): 802 clauses = list(self.clauses | set([other])) 803 elif isinstance(other, AllOf): 804 clauses = list(self.clauses | other.clauses) 805 else: 806 return NotImplemented 807 return AllOf(*clauses) 808 809 def __or__(self, other): 810 if isinstance(other, AnyOf): 811 return other | self 812 elif isinstance(other, Matcher): 813 return AnyOf(self, AllOf(other)) 814 elif isinstance(other, AllOf): 815 return AnyOf(self, other) 816 else: 817 return NotImplemented 818 819 def __repr__(self): 820 return 'AllOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses)) 821 822 def _pretty(self): 823 yield 'AllOF(' 824 for clause in self.clauses: 825 lines = list(clause._pretty()) 826 for line in lines[:-1]: 827 yield '\t' + line 828 yield '\t' + lines[-1] + ',' 829 yield ')' 830 831 832 class Matcher(Clause): 833 __slots__ = [] 834 835 def __and__(self, other): 836 if isinstance(other, AllOf): 837 return other & self 838 elif isinstance(other, Matcher) or isinstance(other, AnyOf): 839 return AllOf(self, other) 840 else: 841 return NotImplemented 842 843 def __or__(self, other): 844 if isinstance(other, AnyOf): 845 return other | self 846 elif isinstance(other, Matcher) or isinstance(other, AllOf): 847 return AnyOf(self, other) 848 else: 849 return NotImplemented 850 851 852 class Never(Matcher): 853 __slots__ = [] 854 855 def match(self, version): 856 return False 857 858 def __hash__(self): 859 return hash((Never,)) 860 861 def __eq__(self, other): 862 return isinstance(other, self.__class__) 863 864 def __and__(self, other): 865 return self 866 867 def __or__(self, other): 868 return other 869 870 def __repr__(self): 871 return 'Never()' 872 873 874 class Always(Matcher): 875 __slots__ = [] 876 877 def match(self, version): 878 return True 879 880 def __hash__(self): 881 return hash((Always,)) 882 883 def __eq__(self, other): 884 return isinstance(other, self.__class__) 885 886 def __and__(self, other): 887 return other 888 889 def __or__(self, other): 890 return self 891 892 def __repr__(self): 893 return 'Always()' 894 895 896 class Range(Matcher): 897 OP_EQ = '==' 898 OP_GT = '>' 899 OP_GTE = '>=' 900 OP_LT = '<' 901 OP_LTE = '<=' 902 OP_NEQ = '!=' 903 904 # <1.2.3 matches 1.2.3-a1 905 PRERELEASE_ALWAYS = 'always' 906 # <1.2.3 does not match 1.2.3-a1 907 PRERELEASE_NATURAL = 'natural' 908 # 1.2.3-a1 is only considered if target == 1.2.3-xxx 909 PRERELEASE_SAMEPATCH = 'same-patch' 910 911 # 1.2.3 matches 1.2.3+* 912 BUILD_IMPLICIT = 'implicit' 913 # 1.2.3 matches only 1.2.3, not 1.2.3+4 914 BUILD_STRICT = 'strict' 915 916 __slots__ = ['operator', 'target', 'prerelease_policy', 'build_policy'] 917 918 def __init__(self, operator, target, prerelease_policy=PRERELEASE_NATURAL, build_policy=BUILD_IMPLICIT): 919 super(Range, self).__init__() 920 if target.build and operator not in (self.OP_EQ, self.OP_NEQ): 921 raise ValueError( 922 "Invalid range %s%s: build numbers have no ordering." 923 % (operator, target)) 924 self.operator = operator 925 self.target = target 926 self.prerelease_policy = prerelease_policy 927 self.build_policy = self.BUILD_STRICT if target.build else build_policy 928 929 def match(self, version): 930 if self.build_policy != self.BUILD_STRICT: 931 version = version.truncate('prerelease') 932 933 if version.prerelease: 934 same_patch = self.target.truncate() == version.truncate() 935 936 if self.prerelease_policy == self.PRERELEASE_SAMEPATCH and not same_patch: 937 return False 938 939 if self.operator == self.OP_EQ: 940 if self.build_policy == self.BUILD_STRICT: 941 return ( 942 self.target.truncate('prerelease') == version.truncate('prerelease') 943 and version.build == self.target.build 944 ) 945 return version == self.target 946 elif self.operator == self.OP_GT: 947 return version > self.target 948 elif self.operator == self.OP_GTE: 949 return version >= self.target 950 elif self.operator == self.OP_LT: 951 if ( 952 version.prerelease 953 and self.prerelease_policy == self.PRERELEASE_NATURAL 954 and version.truncate() == self.target.truncate() 955 and not self.target.prerelease 956 ): 957 return False 958 return version < self.target 959 elif self.operator == self.OP_LTE: 960 return version <= self.target 961 else: 962 assert self.operator == self.OP_NEQ 963 if self.build_policy == self.BUILD_STRICT: 964 return not ( 965 self.target.truncate('prerelease') == version.truncate('prerelease') 966 and version.build == self.target.build 967 ) 968 969 if ( 970 version.prerelease 971 and self.prerelease_policy == self.PRERELEASE_NATURAL 972 and version.truncate() == self.target.truncate() 973 and not self.target.prerelease 974 ): 975 return False 976 return version != self.target 977 978 def __hash__(self): 979 return hash((Range, self.operator, self.target, self.prerelease_policy)) 980 981 def __eq__(self, other): 982 return ( 983 isinstance(other, self.__class__) 984 and self.operator == other.operator 985 and self.target == other.target 986 and self.prerelease_policy == other.prerelease_policy 987 ) 988 989 def __str__(self): 990 return '%s%s' % (self.operator, self.target) 991 992 def __repr__(self): 993 policy_part = ( 994 '' if self.prerelease_policy == self.PRERELEASE_NATURAL 995 else ', prerelease_policy=%r' % self.prerelease_policy 996 ) + ( 997 '' if self.build_policy == self.BUILD_IMPLICIT 998 else ', build_policy=%r' % self.build_policy 999 ) 1000 return 'Range(%r, %r%s)' % ( 1001 self.operator, 1002 self.target, 1003 policy_part, 1004 ) 1005 1006 1007 @BaseSpec.register_syntax 1008 class SimpleSpec(BaseSpec): 1009 1010 SYNTAX = 'simple' 1011 1012 @classmethod 1013 def _parse_to_clause(cls, expression): 1014 return cls.Parser.parse(expression) 1015 1016 class Parser: 1017 NUMBER = r'\*|0|[1-9][0-9]*' 1018 NAIVE_SPEC = re.compile(r"""^ 1019 (?P<op><|<=||=|==|>=|>|!=|\^|~|~=) 1020 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)? 1021 (?:-(?P<prerel>[a-z0-9A-Z.-]*))? 1022 (?:\+(?P<build>[a-z0-9A-Z.-]*))? 1023 $ 1024 """.format(nb=NUMBER), 1025 re.VERBOSE, 1026 ) 1027 1028 @classmethod 1029 def parse(cls, expression): 1030 blocks = expression.split(',') 1031 clause = Always() 1032 for block in blocks: 1033 if not cls.NAIVE_SPEC.match(block): 1034 raise ValueError("Invalid simple block %r" % block) 1035 clause &= cls.parse_block(block) 1036 1037 return clause 1038 1039 PREFIX_CARET = '^' 1040 PREFIX_TILDE = '~' 1041 PREFIX_COMPATIBLE = '~=' 1042 PREFIX_EQ = '==' 1043 PREFIX_NEQ = '!=' 1044 PREFIX_GT = '>' 1045 PREFIX_GTE = '>=' 1046 PREFIX_LT = '<' 1047 PREFIX_LTE = '<=' 1048 1049 PREFIX_ALIASES = { 1050 '=': PREFIX_EQ, 1051 '': PREFIX_EQ, 1052 } 1053 1054 EMPTY_VALUES = ['*', 'x', 'X', None] 1055 1056 @classmethod 1057 def parse_block(cls, expr): 1058 if not cls.NAIVE_SPEC.match(expr): 1059 raise ValueError("Invalid simple spec component: %r" % expr) 1060 prefix, major_t, minor_t, patch_t, prerel, build = cls.NAIVE_SPEC.match(expr).groups() 1061 prefix = cls.PREFIX_ALIASES.get(prefix, prefix) 1062 1063 major = None if major_t in cls.EMPTY_VALUES else int(major_t) 1064 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t) 1065 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t) 1066 1067 if major is None: # '*' 1068 target = Version(major=0, minor=0, patch=0) 1069 if prefix not in (cls.PREFIX_EQ, cls.PREFIX_GTE): 1070 raise ValueError("Invalid simple spec: %r" % expr) 1071 elif minor is None: 1072 target = Version(major=major, minor=0, patch=0) 1073 elif patch is None: 1074 target = Version(major=major, minor=minor, patch=0) 1075 else: 1076 target = Version( 1077 major=major, 1078 minor=minor, 1079 patch=patch, 1080 prerelease=prerel.split('.') if prerel else (), 1081 build=build.split('.') if build else (), 1082 ) 1083 1084 if (major is None or minor is None or patch is None) and (prerel or build): 1085 raise ValueError("Invalid simple spec: %r" % expr) 1086 1087 if build is not None and prefix not in (cls.PREFIX_EQ, cls.PREFIX_NEQ): 1088 raise ValueError("Invalid simple spec: %r" % expr) 1089 1090 if prefix == cls.PREFIX_CARET: 1091 # Accept anything with the same most-significant digit 1092 if target.major: 1093 high = target.next_major() 1094 elif target.minor: 1095 high = target.next_minor() 1096 else: 1097 high = target.next_patch() 1098 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 1099 1100 elif prefix == cls.PREFIX_TILDE: 1101 assert major is not None 1102 # Accept any higher patch in the same minor 1103 # Might go higher if the initial version was a partial 1104 if minor is None: 1105 high = target.next_major() 1106 else: 1107 high = target.next_minor() 1108 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 1109 1110 elif prefix == cls.PREFIX_COMPATIBLE: 1111 assert major is not None 1112 # ~1 is 1.0.0..2.0.0; ~=2.2 is 2.2.0..3.0.0; ~=1.4.5 is 1.4.5..1.5.0 1113 if minor is None or patch is None: 1114 # We got a partial version 1115 high = target.next_major() 1116 else: 1117 high = target.next_minor() 1118 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high) 1119 1120 elif prefix == cls.PREFIX_EQ: 1121 if major is None: 1122 return Range(Range.OP_GTE, target) 1123 elif minor is None: 1124 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_major()) 1125 elif patch is None: 1126 return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_patch()) 1127 elif build == '': 1128 return Range(Range.OP_EQ, target, build_policy=Range.BUILD_STRICT) 1129 else: 1130 return Range(Range.OP_EQ, target) 1131 1132 elif prefix == cls.PREFIX_NEQ: 1133 assert major is not None 1134 if minor is None: 1135 # !=1.x => <1.0.0 || >=2.0.0 1136 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_major()) 1137 elif patch is None: 1138 # !=1.2.x => <1.2.0 || >=1.3.0 1139 return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_minor()) 1140 elif prerel == '': 1141 # !=1.2.3- 1142 return Range(Range.OP_NEQ, target, prerelease_policy=Range.PRERELEASE_ALWAYS) 1143 elif build == '': 1144 # !=1.2.3+ or !=1.2.3-a2+ 1145 return Range(Range.OP_NEQ, target, build_policy=Range.BUILD_STRICT) 1146 else: 1147 return Range(Range.OP_NEQ, target) 1148 1149 elif prefix == cls.PREFIX_GT: 1150 assert major is not None 1151 if minor is None: 1152 # >1.x => >=2.0 1153 return Range(Range.OP_GTE, target.next_major()) 1154 elif patch is None: 1155 return Range(Range.OP_GTE, target.next_minor()) 1156 else: 1157 return Range(Range.OP_GT, target) 1158 1159 elif prefix == cls.PREFIX_GTE: 1160 return Range(Range.OP_GTE, target) 1161 1162 elif prefix == cls.PREFIX_LT: 1163 assert major is not None 1164 if prerel == '': 1165 # <1.2.3- 1166 return Range(Range.OP_LT, target, prerelease_policy=Range.PRERELEASE_ALWAYS) 1167 return Range(Range.OP_LT, target) 1168 1169 else: 1170 assert prefix == cls.PREFIX_LTE 1171 assert major is not None 1172 if minor is None: 1173 # <=1.x => <2.0 1174 return Range(Range.OP_LT, target.next_major()) 1175 elif patch is None: 1176 return Range(Range.OP_LT, target.next_minor()) 1177 else: 1178 return Range(Range.OP_LTE, target) 1179 1180 1181 class LegacySpec(SimpleSpec): 1182 def __init__(self, *expressions): 1183 warnings.warn( 1184 "The Spec() class will be removed in 3.1; use SimpleSpec() instead.", 1185 PendingDeprecationWarning, 1186 stacklevel=2, 1187 ) 1188 1189 if len(expressions) > 1: 1190 warnings.warn( 1191 "Passing 2+ arguments to SimpleSpec will be removed in 3.0; concatenate them with ',' instead.", 1192 DeprecationWarning, 1193 stacklevel=2, 1194 ) 1195 expression = ','.join(expressions) 1196 super(LegacySpec, self).__init__(expression) 1197 1198 @property 1199 def specs(self): 1200 return list(self) 1201 1202 def __iter__(self): 1203 warnings.warn( 1204 "Iterating over the components of a SimpleSpec object will be removed in 3.0.", 1205 DeprecationWarning, 1206 stacklevel=2, 1207 ) 1208 try: 1209 clauses = list(self.clause) 1210 except TypeError: # Not an iterable 1211 clauses = [self.clause] 1212 for clause in clauses: 1213 yield SpecItem.from_matcher(clause) 1214 1215 1216 Spec = LegacySpec 1217 1218 1219 @BaseSpec.register_syntax 1220 class NpmSpec(BaseSpec): 1221 SYNTAX = 'npm' 1222 1223 @classmethod 1224 def _parse_to_clause(cls, expression): 1225 return cls.Parser.parse(expression) 1226 1227 class Parser: 1228 JOINER = '||' 1229 HYPHEN = ' - ' 1230 1231 NUMBER = r'x|X|\*|0|[1-9][0-9]*' 1232 PART = r'[a-zA-Z0-9.-]*' 1233 NPM_SPEC_BLOCK = re.compile(r""" 1234 ^(?:v)? # Strip optional initial v 1235 (?P<op><|<=|>=|>|=|\^|~|) # Operator, can be empty 1236 (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)? 1237 (?:-(?P<prerel>{part}))? # Optional re-release 1238 (?:\+(?P<build>{part}))? # Optional build 1239 $""".format(nb=NUMBER, part=PART), 1240 re.VERBOSE, 1241 ) 1242 1243 @classmethod 1244 def range(cls, operator, target): 1245 return Range(operator, target, prerelease_policy=Range.PRERELEASE_SAMEPATCH) 1246 1247 @classmethod 1248 def parse(cls, expression): 1249 result = Never() 1250 groups = expression.split(cls.JOINER) 1251 for group in groups: 1252 group = group.strip() 1253 if not group: 1254 group = '>=0.0.0' 1255 1256 subclauses = [] 1257 if cls.HYPHEN in group: 1258 low, high = group.split(cls.HYPHEN, 2) 1259 subclauses = cls.parse_simple('>=' + low) + cls.parse_simple('<=' + high) 1260 1261 else: 1262 blocks = group.split(' ') 1263 for block in blocks: 1264 if not cls.NPM_SPEC_BLOCK.match(block): 1265 raise ValueError("Invalid NPM block in %r: %r" % (expression, block)) 1266 1267 subclauses.extend(cls.parse_simple(block)) 1268 1269 prerelease_clauses = [] 1270 non_prerel_clauses = [] 1271 for clause in subclauses: 1272 if clause.target.prerelease: 1273 if clause.operator in (Range.OP_GT, Range.OP_GTE): 1274 prerelease_clauses.append(Range( 1275 operator=Range.OP_LT, 1276 target=Version( 1277 major=clause.target.major, 1278 minor=clause.target.minor, 1279 patch=clause.target.patch + 1, 1280 ), 1281 prerelease_policy=Range.PRERELEASE_ALWAYS, 1282 )) 1283 elif clause.operator in (Range.OP_LT, Range.OP_LTE): 1284 prerelease_clauses.append(Range( 1285 operator=Range.OP_GTE, 1286 target=Version( 1287 major=clause.target.major, 1288 minor=clause.target.minor, 1289 patch=0, 1290 prerelease=(), 1291 ), 1292 prerelease_policy=Range.PRERELEASE_ALWAYS, 1293 )) 1294 prerelease_clauses.append(clause) 1295 non_prerel_clauses.append(cls.range( 1296 operator=clause.operator, 1297 target=clause.target.truncate(), 1298 )) 1299 else: 1300 non_prerel_clauses.append(clause) 1301 if prerelease_clauses: 1302 result |= AllOf(*prerelease_clauses) 1303 result |= AllOf(*non_prerel_clauses) 1304 1305 return result 1306 1307 PREFIX_CARET = '^' 1308 PREFIX_TILDE = '~' 1309 PREFIX_EQ = '=' 1310 PREFIX_GT = '>' 1311 PREFIX_GTE = '>=' 1312 PREFIX_LT = '<' 1313 PREFIX_LTE = '<=' 1314 1315 PREFIX_ALIASES = { 1316 '': PREFIX_EQ, 1317 } 1318 1319 PREFIX_TO_OPERATOR = { 1320 PREFIX_EQ: Range.OP_EQ, 1321 PREFIX_LT: Range.OP_LT, 1322 PREFIX_LTE: Range.OP_LTE, 1323 PREFIX_GTE: Range.OP_GTE, 1324 PREFIX_GT: Range.OP_GT, 1325 } 1326 1327 EMPTY_VALUES = ['*', 'x', 'X', None] 1328 1329 @classmethod 1330 def parse_simple(cls, simple): 1331 match = cls.NPM_SPEC_BLOCK.match(simple) 1332 1333 prefix, major_t, minor_t, patch_t, prerel, build = match.groups() 1334 1335 prefix = cls.PREFIX_ALIASES.get(prefix, prefix) 1336 major = None if major_t in cls.EMPTY_VALUES else int(major_t) 1337 minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t) 1338 patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t) 1339 1340 if build is not None and prefix not in [cls.PREFIX_EQ]: 1341 # Ignore the 'build' part when not comparing to a specific part. 1342 build = None 1343 1344 if major is None: # '*', 'x', 'X' 1345 target = Version(major=0, minor=0, patch=0) 1346 if prefix not in [cls.PREFIX_EQ, cls.PREFIX_GTE]: 1347 raise ValueError("Invalid expression %r" % simple) 1348 prefix = cls.PREFIX_GTE 1349 elif minor is None: 1350 target = Version(major=major, minor=0, patch=0) 1351 elif patch is None: 1352 target = Version(major=major, minor=minor, patch=0) 1353 else: 1354 target = Version( 1355 major=major, 1356 minor=minor, 1357 patch=patch, 1358 prerelease=prerel.split('.') if prerel else (), 1359 build=build.split('.') if build else (), 1360 ) 1361 1362 if (major is None or minor is None or patch is None) and (prerel or build): 1363 raise ValueError("Invalid NPM spec: %r" % simple) 1364 1365 if prefix == cls.PREFIX_CARET: 1366 if target.major: # ^1.2.4 => >=1.2.4 <2.0.0 ; ^1.x => >=1.0.0 <2.0.0 1367 high = target.truncate().next_major() 1368 elif target.minor: # ^0.1.2 => >=0.1.2 <0.2.0 1369 high = target.truncate().next_minor() 1370 elif minor is None: # ^0.x => >=0.0.0 <1.0.0 1371 high = target.truncate().next_major() 1372 elif patch is None: # ^0.2.x => >=0.2.0 <0.3.0 1373 high = target.truncate().next_minor() 1374 else: # ^0.0.1 => >=0.0.1 <0.0.2 1375 high = target.truncate().next_patch() 1376 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)] 1377 1378 elif prefix == cls.PREFIX_TILDE: 1379 assert major is not None 1380 if minor is None: # ~1.x => >=1.0.0 <2.0.0 1381 high = target.next_major() 1382 else: # ~1.2.x => >=1.2.0 <1.3.0; ~1.2.3 => >=1.2.3 <1.3.0 1383 high = target.next_minor() 1384 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)] 1385 1386 elif prefix == cls.PREFIX_EQ: 1387 if major is None: 1388 return [cls.range(Range.OP_GTE, target)] 1389 elif minor is None: 1390 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_major())] 1391 elif patch is None: 1392 return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_minor())] 1393 else: 1394 return [cls.range(Range.OP_EQ, target)] 1395 1396 elif prefix == cls.PREFIX_GT: 1397 assert major is not None 1398 if minor is None: # >1.x 1399 return [cls.range(Range.OP_GTE, target.next_major())] 1400 elif patch is None: # >1.2.x => >=1.3.0 1401 return [cls.range(Range.OP_GTE, target.next_minor())] 1402 else: 1403 return [cls.range(Range.OP_GT, target)] 1404 1405 elif prefix == cls.PREFIX_GTE: 1406 return [cls.range(Range.OP_GTE, target)] 1407 1408 elif prefix == cls.PREFIX_LT: 1409 assert major is not None 1410 return [cls.range(Range.OP_LT, target)] 1411 1412 else: 1413 assert prefix == cls.PREFIX_LTE 1414 assert major is not None 1415 if minor is None: # <=1.x => <2.0.0 1416 return [cls.range(Range.OP_LT, target.next_major())] 1417 elif patch is None: # <=1.2.x => <1.3.0 1418 return [cls.range(Range.OP_LT, target.next_minor())] 1419 else: 1420 return [cls.range(Range.OP_LTE, target)]