| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420 |
- # -*- coding: utf-8 -*-
- # Copyright (c) The python-semanticversion project
- # This code is distributed under the two-clause BSD License.
- import functools
- import re
- import warnings
- def _has_leading_zero(value):
- return (value
- and value[0] == '0'
- and value.isdigit()
- and value != '0')
- class MaxIdentifier(object):
- __slots__ = []
- def __repr__(self):
- return 'MaxIdentifier()'
- def __eq__(self, other):
- return isinstance(other, self.__class__)
- @functools.total_ordering
- class NumericIdentifier(object):
- __slots__ = ['value']
- def __init__(self, value):
- self.value = int(value)
- def __repr__(self):
- return 'NumericIdentifier(%r)' % self.value
- def __eq__(self, other):
- if isinstance(other, NumericIdentifier):
- return self.value == other.value
- return NotImplemented
- def __lt__(self, other):
- if isinstance(other, MaxIdentifier):
- return True
- elif isinstance(other, AlphaIdentifier):
- return True
- elif isinstance(other, NumericIdentifier):
- return self.value < other.value
- else:
- return NotImplemented
- @functools.total_ordering
- class AlphaIdentifier(object):
- __slots__ = ['value']
- def __init__(self, value):
- self.value = value.encode('ascii')
- def __repr__(self):
- return 'AlphaIdentifier(%r)' % self.value
- def __eq__(self, other):
- if isinstance(other, AlphaIdentifier):
- return self.value == other.value
- return NotImplemented
- def __lt__(self, other):
- if isinstance(other, MaxIdentifier):
- return True
- elif isinstance(other, NumericIdentifier):
- return False
- elif isinstance(other, AlphaIdentifier):
- return self.value < other.value
- else:
- return NotImplemented
- class Version(object):
- version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9a-zA-Z.-]+))?(?:\+([0-9a-zA-Z.-]+))?$')
- partial_version_re = re.compile(r'^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-([0-9a-zA-Z.-]*))?(?:\+([0-9a-zA-Z.-]*))?$')
- def __init__(
- self,
- version_string=None,
- major=None,
- minor=None,
- patch=None,
- prerelease=None,
- build=None,
- partial=False):
- if partial:
- warnings.warn(
- "Partial versions will be removed in 3.0; use SimpleSpec('1.x.x') instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- has_text = version_string is not None
- has_parts = not (major is minor is patch is prerelease is build is None)
- if not has_text ^ has_parts:
- raise ValueError("Call either Version('1.2.3') or Version(major=1, ...).")
- if has_text:
- major, minor, patch, prerelease, build = self.parse(version_string, partial)
- else:
- # Convenience: allow to omit prerelease/build.
- prerelease = tuple(prerelease or ())
- if not partial:
- build = tuple(build or ())
- self._validate_kwargs(major, minor, patch, prerelease, build, partial)
- self.major = major
- self.minor = minor
- self.patch = patch
- self.prerelease = prerelease
- self.build = build
- self.partial = partial
- @classmethod
- def _coerce(cls, value, allow_none=False):
- if value is None and allow_none:
- return value
- return int(value)
- def next_major(self):
- if self.prerelease and self.minor == self.patch == 0:
- return Version(
- major=self.major,
- minor=0,
- patch=0,
- partial=self.partial,
- )
- else:
- return Version(
- major=self.major + 1,
- minor=0,
- patch=0,
- partial=self.partial,
- )
- def next_minor(self):
- if self.prerelease and self.patch == 0:
- return Version(
- major=self.major,
- minor=self.minor,
- patch=0,
- partial=self.partial,
- )
- else:
- return Version(
- major=self.major,
- minor=self.minor + 1,
- patch=0,
- partial=self.partial,
- )
- def next_patch(self):
- if self.prerelease:
- return Version(
- major=self.major,
- minor=self.minor,
- patch=self.patch,
- partial=self.partial,
- )
- else:
- return Version(
- major=self.major,
- minor=self.minor,
- patch=self.patch + 1,
- partial=self.partial,
- )
- def truncate(self, level='patch'):
- """Return a new Version object, truncated up to the selected level."""
- if level == 'build':
- return self
- elif level == 'prerelease':
- return Version(
- major=self.major,
- minor=self.minor,
- patch=self.patch,
- prerelease=self.prerelease,
- partial=self.partial,
- )
- elif level == 'patch':
- return Version(
- major=self.major,
- minor=self.minor,
- patch=self.patch,
- partial=self.partial,
- )
- elif level == 'minor':
- return Version(
- major=self.major,
- minor=self.minor,
- patch=None if self.partial else 0,
- partial=self.partial,
- )
- elif level == 'major':
- return Version(
- major=self.major,
- minor=None if self.partial else 0,
- patch=None if self.partial else 0,
- partial=self.partial,
- )
- else:
- raise ValueError("Invalid truncation level `%s`." % level)
- @classmethod
- def coerce(cls, version_string, partial=False):
- """Coerce an arbitrary version string into a semver-compatible one.
- The rule is:
- - If not enough components, fill minor/patch with zeroes; unless
- partial=True
- - If more than 3 dot-separated components, extra components are "build"
- data. If some "build" data already appeared, append it to the
- extra components
- Examples:
- >>> Version.coerce('0.1')
- Version(0, 1, 0)
- >>> Version.coerce('0.1.2.3')
- Version(0, 1, 2, (), ('3',))
- >>> Version.coerce('0.1.2.3+4')
- Version(0, 1, 2, (), ('3', '4'))
- >>> Version.coerce('0.1+2-3+4_5')
- Version(0, 1, 0, (), ('2-3', '4-5'))
- """
- base_re = re.compile(r'^\d+(?:\.\d+(?:\.\d+)?)?')
- match = base_re.match(version_string)
- if not match:
- raise ValueError(
- "Version string lacks a numerical component: %r"
- % version_string
- )
- version = version_string[:match.end()]
- if not partial:
- # We need a not-partial version.
- while version.count('.') < 2:
- version += '.0'
- # Strip leading zeros in components
- # Version is of the form nn, nn.pp or nn.pp.qq
- version = '.'.join(
- # If the part was '0', we end up with an empty string.
- part.lstrip('0') or '0'
- for part in version.split('.')
- )
- if match.end() == len(version_string):
- return Version(version, partial=partial)
- rest = version_string[match.end():]
- # Cleanup the 'rest'
- rest = re.sub(r'[^a-zA-Z0-9+.-]', '-', rest)
- if rest[0] == '+':
- # A 'build' component
- prerelease = ''
- build = rest[1:]
- elif rest[0] == '.':
- # An extra version component, probably 'build'
- prerelease = ''
- build = rest[1:]
- elif rest[0] == '-':
- rest = rest[1:]
- if '+' in rest:
- prerelease, build = rest.split('+', 1)
- else:
- prerelease, build = rest, ''
- elif '+' in rest:
- prerelease, build = rest.split('+', 1)
- else:
- prerelease, build = rest, ''
- build = build.replace('+', '.')
- if prerelease:
- version = '%s-%s' % (version, prerelease)
- if build:
- version = '%s+%s' % (version, build)
- return cls(version, partial=partial)
- @classmethod
- def parse(cls, version_string, partial=False, coerce=False):
- """Parse a version string into a Version() object.
- Args:
- version_string (str), the version string to parse
- partial (bool), whether to accept incomplete input
- coerce (bool), whether to try to map the passed in string into a
- valid Version.
- """
- if not version_string:
- raise ValueError('Invalid empty version string: %r' % version_string)
- if partial:
- version_re = cls.partial_version_re
- else:
- version_re = cls.version_re
- match = version_re.match(version_string)
- if not match:
- raise ValueError('Invalid version string: %r' % version_string)
- major, minor, patch, prerelease, build = match.groups()
- if _has_leading_zero(major):
- raise ValueError("Invalid leading zero in major: %r" % version_string)
- if _has_leading_zero(minor):
- raise ValueError("Invalid leading zero in minor: %r" % version_string)
- if _has_leading_zero(patch):
- raise ValueError("Invalid leading zero in patch: %r" % version_string)
- major = int(major)
- minor = cls._coerce(minor, partial)
- patch = cls._coerce(patch, partial)
- if prerelease is None:
- if partial and (build is None):
- # No build info, strip here
- return (major, minor, patch, None, None)
- else:
- prerelease = ()
- elif prerelease == '':
- prerelease = ()
- else:
- prerelease = tuple(prerelease.split('.'))
- cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
- if build is None:
- if partial:
- build = None
- else:
- build = ()
- elif build == '':
- build = ()
- else:
- build = tuple(build.split('.'))
- cls._validate_identifiers(build, allow_leading_zeroes=True)
- return (major, minor, patch, prerelease, build)
- @classmethod
- def _validate_identifiers(cls, identifiers, allow_leading_zeroes=False):
- for item in identifiers:
- if not item:
- raise ValueError(
- "Invalid empty identifier %r in %r"
- % (item, '.'.join(identifiers))
- )
- if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes:
- raise ValueError("Invalid leading zero in identifier %r" % item)
- @classmethod
- def _validate_kwargs(cls, major, minor, patch, prerelease, build, partial):
- if (
- major != int(major)
- or minor != cls._coerce(minor, partial)
- or patch != cls._coerce(patch, partial)
- or prerelease is None and not partial
- or build is None and not partial
- ):
- raise ValueError(
- "Invalid kwargs to Version(major=%r, minor=%r, patch=%r, "
- "prerelease=%r, build=%r, partial=%r" % (
- major, minor, patch, prerelease, build, partial
- ))
- if prerelease is not None:
- cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
- if build is not None:
- cls._validate_identifiers(build, allow_leading_zeroes=True)
- def __iter__(self):
- return iter((self.major, self.minor, self.patch, self.prerelease, self.build))
- def __str__(self):
- version = '%d' % self.major
- if self.minor is not None:
- version = '%s.%d' % (version, self.minor)
- if self.patch is not None:
- version = '%s.%d' % (version, self.patch)
- if self.prerelease or (self.partial and self.prerelease == () and self.build is None):
- version = '%s-%s' % (version, '.'.join(self.prerelease))
- if self.build or (self.partial and self.build == ()):
- version = '%s+%s' % (version, '.'.join(self.build))
- return version
- def __repr__(self):
- return '%s(%r%s)' % (
- self.__class__.__name__,
- str(self),
- ', partial=True' if self.partial else '',
- )
- def __hash__(self):
- # We don't include 'partial', since this is strictly equivalent to having
- # at least a field being `None`.
- return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
- @property
- def precedence_key(self):
- if self.prerelease:
- prerelease_key = tuple(
- NumericIdentifier(part) if re.match(r'^[0-9]+$', part) else AlphaIdentifier(part)
- for part in self.prerelease
- )
- else:
- prerelease_key = (
- MaxIdentifier(),
- )
- return (
- self.major,
- self.minor,
- self.patch,
- prerelease_key,
- )
- def __cmp__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- if self < other:
- return -1
- elif self > other:
- return 1
- elif self == other:
- return 0
- else:
- return NotImplemented
- def __eq__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return (
- self.major == other.major
- and self.minor == other.minor
- and self.patch == other.patch
- and (self.prerelease or ()) == (other.prerelease or ())
- and (self.build or ()) == (other.build or ())
- )
- def __ne__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return tuple(self) != tuple(other)
- def __lt__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.precedence_key < other.precedence_key
- def __le__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.precedence_key <= other.precedence_key
- def __gt__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.precedence_key > other.precedence_key
- def __ge__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.precedence_key >= other.precedence_key
- class SpecItem(object):
- """A requirement specification."""
- KIND_ANY = '*'
- KIND_LT = '<'
- KIND_LTE = '<='
- KIND_EQUAL = '=='
- KIND_SHORTEQ = '='
- KIND_EMPTY = ''
- KIND_GTE = '>='
- KIND_GT = '>'
- KIND_NEQ = '!='
- KIND_CARET = '^'
- KIND_TILDE = '~'
- KIND_COMPATIBLE = '~='
- # Map a kind alias to its full version
- KIND_ALIASES = {
- KIND_SHORTEQ: KIND_EQUAL,
- KIND_EMPTY: KIND_EQUAL,
- }
- re_spec = re.compile(r'^(<|<=||=|==|>=|>|!=|\^|~|~=)(\d.*)$')
- def __init__(self, requirement_string, _warn=True):
- if _warn:
- warnings.warn(
- "The `SpecItem` class will be removed in 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- kind, spec = self.parse(requirement_string)
- self.kind = kind
- self.spec = spec
- self._clause = Spec(requirement_string).clause
- @classmethod
- def parse(cls, requirement_string):
- if not requirement_string:
- raise ValueError("Invalid empty requirement specification: %r" % requirement_string)
- # Special case: the 'any' version spec.
- if requirement_string == '*':
- return (cls.KIND_ANY, '')
- match = cls.re_spec.match(requirement_string)
- if not match:
- raise ValueError("Invalid requirement specification: %r" % requirement_string)
- kind, version = match.groups()
- if kind in cls.KIND_ALIASES:
- kind = cls.KIND_ALIASES[kind]
- spec = Version(version, partial=True)
- if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ):
- raise ValueError(
- "Invalid requirement specification %r: build numbers have no ordering."
- % requirement_string
- )
- return (kind, spec)
- @classmethod
- def from_matcher(cls, matcher):
- if matcher == Always():
- return cls('*', _warn=False)
- elif matcher == Never():
- return cls('<0.0.0-', _warn=False)
- elif isinstance(matcher, Range):
- return cls('%s%s' % (matcher.operator, matcher.target), _warn=False)
- def match(self, version):
- return self._clause.match(version)
- def __str__(self):
- return '%s%s' % (self.kind, self.spec)
- def __repr__(self):
- return '<SpecItem: %s %r>' % (self.kind, self.spec)
- def __eq__(self, other):
- if not isinstance(other, SpecItem):
- return NotImplemented
- return self.kind == other.kind and self.spec == other.spec
- def __hash__(self):
- return hash((self.kind, self.spec))
- def compare(v1, v2):
- return Version(v1).__cmp__(Version(v2))
- def match(spec, version):
- return Spec(spec).match(Version(version))
- def validate(version_string):
- """Validates a version string againt the SemVer specification."""
- try:
- Version.parse(version_string)
- return True
- except ValueError:
- return False
- DEFAULT_SYNTAX = 'simple'
- class BaseSpec(object):
- """A specification of compatible versions.
- Usage:
- >>> Spec('>=1.0.0', syntax='npm')
- A version matches a specification if it matches any
- of the clauses of that specification.
- Internally, a Spec is AnyOf(
- AllOf(Matcher, Matcher, Matcher),
- AllOf(...),
- )
- """
- SYNTAXES = {}
- @classmethod
- def register_syntax(cls, subclass):
- syntax = subclass.SYNTAX
- if syntax is None:
- raise ValueError("A Spec needs its SYNTAX field to be set.")
- elif syntax in cls.SYNTAXES:
- raise ValueError(
- "Duplicate syntax for %s: %r, %r"
- % (syntax, cls.SYNTAXES[syntax], subclass)
- )
- cls.SYNTAXES[syntax] = subclass
- return subclass
- def __init__(self, expression):
- super(BaseSpec, self).__init__()
- self.expression = expression
- self.clause = self._parse_to_clause(expression)
- @classmethod
- def parse(cls, expression, syntax=DEFAULT_SYNTAX):
- """Convert a syntax-specific expression into a BaseSpec instance."""
- return cls.SYNTAXES[syntax](expression)
- @classmethod
- def _parse_to_clause(cls, expression):
- """Converts an expression to a clause."""
- raise NotImplementedError()
- def filter(self, versions):
- """Filter an iterable of versions satisfying the Spec."""
- for version in versions:
- if self.match(version):
- yield version
- def match(self, version):
- """Check whether a Version satisfies the Spec."""
- return self.clause.match(version)
- def select(self, versions):
- """Select the best compatible version among an iterable of options."""
- options = list(self.filter(versions))
- if options:
- return max(options)
- return None
- def __contains__(self, version):
- """Whether `version in self`."""
- if isinstance(version, Version):
- return self.match(version)
- return False
- def __eq__(self, other):
- if not isinstance(other, self.__class__):
- return NotImplemented
- return self.clause == other.clause
- def __hash__(self):
- return hash(self.clause)
- def __str__(self):
- return self.expression
- def __repr__(self):
- return '<%s: %r>' % (self.__class__.__name__, self.expression)
- class Clause(object):
- __slots__ = []
- def match(self, version):
- raise NotImplementedError()
- def __and__(self, other):
- raise NotImplementedError()
- def __or__(self, other):
- raise NotImplementedError()
- def __eq__(self, other):
- raise NotImplementedError()
- def prettyprint(self, indent='\t'):
- """Pretty-print the clause.
- """
- return '\n'.join(self._pretty()).replace('\t', indent)
- def _pretty(self):
- """Actual pretty-printing logic.
- Yields:
- A list of string. Indentation is performed with \t.
- """
- yield repr(self)
- def __ne__(self, other):
- return not self == other
- def simplify(self):
- return self
- class AnyOf(Clause):
- __slots__ = ['clauses']
- def __init__(self, *clauses):
- super(AnyOf, self).__init__()
- self.clauses = frozenset(clauses)
- def match(self, version):
- return any(c.match(version) for c in self.clauses)
- def simplify(self):
- subclauses = set()
- for clause in self.clauses:
- simplified = clause.simplify()
- if isinstance(simplified, AnyOf):
- subclauses |= simplified.clauses
- elif simplified == Never():
- continue
- else:
- subclauses.add(simplified)
- if len(subclauses) == 1:
- return subclauses.pop()
- return AnyOf(*subclauses)
- def __hash__(self):
- return hash((AnyOf, self.clauses))
- def __iter__(self):
- return iter(self.clauses)
- def __eq__(self, other):
- return isinstance(other, self.__class__) and self.clauses == other.clauses
- def __and__(self, other):
- if isinstance(other, AllOf):
- return other & self
- elif isinstance(other, Matcher) or isinstance(other, AnyOf):
- return AllOf(self, other)
- else:
- return NotImplemented
- def __or__(self, other):
- if isinstance(other, AnyOf):
- clauses = list(self.clauses | other.clauses)
- elif isinstance(other, Matcher) or isinstance(other, AllOf):
- clauses = list(self.clauses | set([other]))
- else:
- return NotImplemented
- return AnyOf(*clauses)
- def __repr__(self):
- return 'AnyOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses))
- def _pretty(self):
- yield 'AnyOF('
- for clause in self.clauses:
- lines = list(clause._pretty())
- for line in lines[:-1]:
- yield '\t' + line
- yield '\t' + lines[-1] + ','
- yield ')'
- class AllOf(Clause):
- __slots__ = ['clauses']
- def __init__(self, *clauses):
- super(AllOf, self).__init__()
- self.clauses = frozenset(clauses)
- def match(self, version):
- return all(clause.match(version) for clause in self.clauses)
- def simplify(self):
- subclauses = set()
- for clause in self.clauses:
- simplified = clause.simplify()
- if isinstance(simplified, AllOf):
- subclauses |= simplified.clauses
- elif simplified == Always():
- continue
- else:
- subclauses.add(simplified)
- if len(subclauses) == 1:
- return subclauses.pop()
- return AllOf(*subclauses)
- def __hash__(self):
- return hash((AllOf, self.clauses))
- def __iter__(self):
- return iter(self.clauses)
- def __eq__(self, other):
- return isinstance(other, self.__class__) and self.clauses == other.clauses
- def __and__(self, other):
- if isinstance(other, Matcher) or isinstance(other, AnyOf):
- clauses = list(self.clauses | set([other]))
- elif isinstance(other, AllOf):
- clauses = list(self.clauses | other.clauses)
- else:
- return NotImplemented
- return AllOf(*clauses)
- def __or__(self, other):
- if isinstance(other, AnyOf):
- return other | self
- elif isinstance(other, Matcher):
- return AnyOf(self, AllOf(other))
- elif isinstance(other, AllOf):
- return AnyOf(self, other)
- else:
- return NotImplemented
- def __repr__(self):
- return 'AllOf(%s)' % ', '.join(sorted(repr(c) for c in self.clauses))
- def _pretty(self):
- yield 'AllOF('
- for clause in self.clauses:
- lines = list(clause._pretty())
- for line in lines[:-1]:
- yield '\t' + line
- yield '\t' + lines[-1] + ','
- yield ')'
- class Matcher(Clause):
- __slots__ = []
- def __and__(self, other):
- if isinstance(other, AllOf):
- return other & self
- elif isinstance(other, Matcher) or isinstance(other, AnyOf):
- return AllOf(self, other)
- else:
- return NotImplemented
- def __or__(self, other):
- if isinstance(other, AnyOf):
- return other | self
- elif isinstance(other, Matcher) or isinstance(other, AllOf):
- return AnyOf(self, other)
- else:
- return NotImplemented
- class Never(Matcher):
- __slots__ = []
- def match(self, version):
- return False
- def __hash__(self):
- return hash((Never,))
- def __eq__(self, other):
- return isinstance(other, self.__class__)
- def __and__(self, other):
- return self
- def __or__(self, other):
- return other
- def __repr__(self):
- return 'Never()'
- class Always(Matcher):
- __slots__ = []
- def match(self, version):
- return True
- def __hash__(self):
- return hash((Always,))
- def __eq__(self, other):
- return isinstance(other, self.__class__)
- def __and__(self, other):
- return other
- def __or__(self, other):
- return self
- def __repr__(self):
- return 'Always()'
- class Range(Matcher):
- OP_EQ = '=='
- OP_GT = '>'
- OP_GTE = '>='
- OP_LT = '<'
- OP_LTE = '<='
- OP_NEQ = '!='
- # <1.2.3 matches 1.2.3-a1
- PRERELEASE_ALWAYS = 'always'
- # <1.2.3 does not match 1.2.3-a1
- PRERELEASE_NATURAL = 'natural'
- # 1.2.3-a1 is only considered if target == 1.2.3-xxx
- PRERELEASE_SAMEPATCH = 'same-patch'
- # 1.2.3 matches 1.2.3+*
- BUILD_IMPLICIT = 'implicit'
- # 1.2.3 matches only 1.2.3, not 1.2.3+4
- BUILD_STRICT = 'strict'
- __slots__ = ['operator', 'target', 'prerelease_policy', 'build_policy']
- def __init__(self, operator, target, prerelease_policy=PRERELEASE_NATURAL, build_policy=BUILD_IMPLICIT):
- super(Range, self).__init__()
- if target.build and operator not in (self.OP_EQ, self.OP_NEQ):
- raise ValueError(
- "Invalid range %s%s: build numbers have no ordering."
- % (operator, target))
- self.operator = operator
- self.target = target
- self.prerelease_policy = prerelease_policy
- self.build_policy = self.BUILD_STRICT if target.build else build_policy
- def match(self, version):
- if self.build_policy != self.BUILD_STRICT:
- version = version.truncate('prerelease')
- if version.prerelease:
- same_patch = self.target.truncate() == version.truncate()
- if self.prerelease_policy == self.PRERELEASE_SAMEPATCH and not same_patch:
- return False
- if self.operator == self.OP_EQ:
- if self.build_policy == self.BUILD_STRICT:
- return (
- self.target.truncate('prerelease') == version.truncate('prerelease')
- and version.build == self.target.build
- )
- return version == self.target
- elif self.operator == self.OP_GT:
- return version > self.target
- elif self.operator == self.OP_GTE:
- return version >= self.target
- elif self.operator == self.OP_LT:
- if (
- version.prerelease
- and self.prerelease_policy == self.PRERELEASE_NATURAL
- and version.truncate() == self.target.truncate()
- and not self.target.prerelease
- ):
- return False
- return version < self.target
- elif self.operator == self.OP_LTE:
- return version <= self.target
- else:
- assert self.operator == self.OP_NEQ
- if self.build_policy == self.BUILD_STRICT:
- return not (
- self.target.truncate('prerelease') == version.truncate('prerelease')
- and version.build == self.target.build
- )
- if (
- version.prerelease
- and self.prerelease_policy == self.PRERELEASE_NATURAL
- and version.truncate() == self.target.truncate()
- and not self.target.prerelease
- ):
- return False
- return version != self.target
- def __hash__(self):
- return hash((Range, self.operator, self.target, self.prerelease_policy))
- def __eq__(self, other):
- return (
- isinstance(other, self.__class__)
- and self.operator == other.operator
- and self.target == other.target
- and self.prerelease_policy == other.prerelease_policy
- )
- def __str__(self):
- return '%s%s' % (self.operator, self.target)
- def __repr__(self):
- policy_part = (
- '' if self.prerelease_policy == self.PRERELEASE_NATURAL
- else ', prerelease_policy=%r' % self.prerelease_policy
- ) + (
- '' if self.build_policy == self.BUILD_IMPLICIT
- else ', build_policy=%r' % self.build_policy
- )
- return 'Range(%r, %r%s)' % (
- self.operator,
- self.target,
- policy_part,
- )
- @BaseSpec.register_syntax
- class SimpleSpec(BaseSpec):
- SYNTAX = 'simple'
- @classmethod
- def _parse_to_clause(cls, expression):
- return cls.Parser.parse(expression)
- class Parser:
- NUMBER = r'\*|0|[1-9][0-9]*'
- NAIVE_SPEC = re.compile(r"""^
- (?P<op><|<=||=|==|>=|>|!=|\^|~|~=)
- (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)?
- (?:-(?P<prerel>[a-z0-9A-Z.-]*))?
- (?:\+(?P<build>[a-z0-9A-Z.-]*))?
- $
- """.format(nb=NUMBER),
- re.VERBOSE,
- )
- @classmethod
- def parse(cls, expression):
- blocks = expression.split(',')
- clause = Always()
- for block in blocks:
- if not cls.NAIVE_SPEC.match(block):
- raise ValueError("Invalid simple block %r" % block)
- clause &= cls.parse_block(block)
- return clause
- PREFIX_CARET = '^'
- PREFIX_TILDE = '~'
- PREFIX_COMPATIBLE = '~='
- PREFIX_EQ = '=='
- PREFIX_NEQ = '!='
- PREFIX_GT = '>'
- PREFIX_GTE = '>='
- PREFIX_LT = '<'
- PREFIX_LTE = '<='
- PREFIX_ALIASES = {
- '=': PREFIX_EQ,
- '': PREFIX_EQ,
- }
- EMPTY_VALUES = ['*', 'x', 'X', None]
- @classmethod
- def parse_block(cls, expr):
- if not cls.NAIVE_SPEC.match(expr):
- raise ValueError("Invalid simple spec component: %r" % expr)
- prefix, major_t, minor_t, patch_t, prerel, build = cls.NAIVE_SPEC.match(expr).groups()
- prefix = cls.PREFIX_ALIASES.get(prefix, prefix)
- major = None if major_t in cls.EMPTY_VALUES else int(major_t)
- minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t)
- patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t)
- if major is None: # '*'
- target = Version(major=0, minor=0, patch=0)
- if prefix not in (cls.PREFIX_EQ, cls.PREFIX_GTE):
- raise ValueError("Invalid simple spec: %r" % expr)
- elif minor is None:
- target = Version(major=major, minor=0, patch=0)
- elif patch is None:
- target = Version(major=major, minor=minor, patch=0)
- else:
- target = Version(
- major=major,
- minor=minor,
- patch=patch,
- prerelease=prerel.split('.') if prerel else (),
- build=build.split('.') if build else (),
- )
- if (major is None or minor is None or patch is None) and (prerel or build):
- raise ValueError("Invalid simple spec: %r" % expr)
- if build is not None and prefix not in (cls.PREFIX_EQ, cls.PREFIX_NEQ):
- raise ValueError("Invalid simple spec: %r" % expr)
- if prefix == cls.PREFIX_CARET:
- # Accept anything with the same most-significant digit
- if target.major:
- high = target.next_major()
- elif target.minor:
- high = target.next_minor()
- else:
- high = target.next_patch()
- return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
- elif prefix == cls.PREFIX_TILDE:
- assert major is not None
- # Accept any higher patch in the same minor
- # Might go higher if the initial version was a partial
- if minor is None:
- high = target.next_major()
- else:
- high = target.next_minor()
- return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
- elif prefix == cls.PREFIX_COMPATIBLE:
- assert major is not None
- # ~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
- if minor is None or patch is None:
- # We got a partial version
- high = target.next_major()
- else:
- high = target.next_minor()
- return Range(Range.OP_GTE, target) & Range(Range.OP_LT, high)
- elif prefix == cls.PREFIX_EQ:
- if major is None:
- return Range(Range.OP_GTE, target)
- elif minor is None:
- return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_major())
- elif patch is None:
- return Range(Range.OP_GTE, target) & Range(Range.OP_LT, target.next_minor())
- elif build == '':
- return Range(Range.OP_EQ, target, build_policy=Range.BUILD_STRICT)
- else:
- return Range(Range.OP_EQ, target)
- elif prefix == cls.PREFIX_NEQ:
- assert major is not None
- if minor is None:
- # !=1.x => <1.0.0 || >=2.0.0
- return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_major())
- elif patch is None:
- # !=1.2.x => <1.2.0 || >=1.3.0
- return Range(Range.OP_LT, target) | Range(Range.OP_GTE, target.next_minor())
- elif prerel == '':
- # !=1.2.3-
- return Range(Range.OP_NEQ, target, prerelease_policy=Range.PRERELEASE_ALWAYS)
- elif build == '':
- # !=1.2.3+ or !=1.2.3-a2+
- return Range(Range.OP_NEQ, target, build_policy=Range.BUILD_STRICT)
- else:
- return Range(Range.OP_NEQ, target)
- elif prefix == cls.PREFIX_GT:
- assert major is not None
- if minor is None:
- # >1.x => >=2.0
- return Range(Range.OP_GTE, target.next_major())
- elif patch is None:
- return Range(Range.OP_GTE, target.next_minor())
- else:
- return Range(Range.OP_GT, target)
- elif prefix == cls.PREFIX_GTE:
- return Range(Range.OP_GTE, target)
- elif prefix == cls.PREFIX_LT:
- assert major is not None
- if prerel == '':
- # <1.2.3-
- return Range(Range.OP_LT, target, prerelease_policy=Range.PRERELEASE_ALWAYS)
- return Range(Range.OP_LT, target)
- else:
- assert prefix == cls.PREFIX_LTE
- assert major is not None
- if minor is None:
- # <=1.x => <2.0
- return Range(Range.OP_LT, target.next_major())
- elif patch is None:
- return Range(Range.OP_LT, target.next_minor())
- else:
- return Range(Range.OP_LTE, target)
- class LegacySpec(SimpleSpec):
- def __init__(self, *expressions):
- warnings.warn(
- "The Spec() class will be removed in 3.1; use SimpleSpec() instead.",
- PendingDeprecationWarning,
- stacklevel=2,
- )
- if len(expressions) > 1:
- warnings.warn(
- "Passing 2+ arguments to SimpleSpec will be removed in 3.0; concatenate them with ',' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- expression = ','.join(expressions)
- super(LegacySpec, self).__init__(expression)
- @property
- def specs(self):
- return list(self)
- def __iter__(self):
- warnings.warn(
- "Iterating over the components of a SimpleSpec object will be removed in 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- try:
- clauses = list(self.clause)
- except TypeError: # Not an iterable
- clauses = [self.clause]
- for clause in clauses:
- yield SpecItem.from_matcher(clause)
- Spec = LegacySpec
- @BaseSpec.register_syntax
- class NpmSpec(BaseSpec):
- SYNTAX = 'npm'
- @classmethod
- def _parse_to_clause(cls, expression):
- return cls.Parser.parse(expression)
- class Parser:
- JOINER = '||'
- HYPHEN = ' - '
- NUMBER = r'x|X|\*|0|[1-9][0-9]*'
- PART = r'[a-zA-Z0-9.-]*'
- NPM_SPEC_BLOCK = re.compile(r"""
- ^(?:v)? # Strip optional initial v
- (?P<op><|<=|>=|>|=|\^|~|) # Operator, can be empty
- (?P<major>{nb})(?:\.(?P<minor>{nb})(?:\.(?P<patch>{nb}))?)?
- (?:-(?P<prerel>{part}))? # Optional re-release
- (?:\+(?P<build>{part}))? # Optional build
- $""".format(nb=NUMBER, part=PART),
- re.VERBOSE,
- )
- @classmethod
- def range(cls, operator, target):
- return Range(operator, target, prerelease_policy=Range.PRERELEASE_SAMEPATCH)
- @classmethod
- def parse(cls, expression):
- result = Never()
- groups = expression.split(cls.JOINER)
- for group in groups:
- group = group.strip()
- if not group:
- group = '>=0.0.0'
- subclauses = []
- if cls.HYPHEN in group:
- low, high = group.split(cls.HYPHEN, 2)
- subclauses = cls.parse_simple('>=' + low) + cls.parse_simple('<=' + high)
- else:
- blocks = group.split(' ')
- for block in blocks:
- if not cls.NPM_SPEC_BLOCK.match(block):
- raise ValueError("Invalid NPM block in %r: %r" % (expression, block))
- subclauses.extend(cls.parse_simple(block))
- prerelease_clauses = []
- non_prerel_clauses = []
- for clause in subclauses:
- if clause.target.prerelease:
- if clause.operator in (Range.OP_GT, Range.OP_GTE):
- prerelease_clauses.append(Range(
- operator=Range.OP_LT,
- target=Version(
- major=clause.target.major,
- minor=clause.target.minor,
- patch=clause.target.patch + 1,
- ),
- prerelease_policy=Range.PRERELEASE_ALWAYS,
- ))
- elif clause.operator in (Range.OP_LT, Range.OP_LTE):
- prerelease_clauses.append(Range(
- operator=Range.OP_GTE,
- target=Version(
- major=clause.target.major,
- minor=clause.target.minor,
- patch=0,
- prerelease=(),
- ),
- prerelease_policy=Range.PRERELEASE_ALWAYS,
- ))
- prerelease_clauses.append(clause)
- non_prerel_clauses.append(cls.range(
- operator=clause.operator,
- target=clause.target.truncate(),
- ))
- else:
- non_prerel_clauses.append(clause)
- if prerelease_clauses:
- result |= AllOf(*prerelease_clauses)
- result |= AllOf(*non_prerel_clauses)
- return result
- PREFIX_CARET = '^'
- PREFIX_TILDE = '~'
- PREFIX_EQ = '='
- PREFIX_GT = '>'
- PREFIX_GTE = '>='
- PREFIX_LT = '<'
- PREFIX_LTE = '<='
- PREFIX_ALIASES = {
- '': PREFIX_EQ,
- }
- PREFIX_TO_OPERATOR = {
- PREFIX_EQ: Range.OP_EQ,
- PREFIX_LT: Range.OP_LT,
- PREFIX_LTE: Range.OP_LTE,
- PREFIX_GTE: Range.OP_GTE,
- PREFIX_GT: Range.OP_GT,
- }
- EMPTY_VALUES = ['*', 'x', 'X', None]
- @classmethod
- def parse_simple(cls, simple):
- match = cls.NPM_SPEC_BLOCK.match(simple)
- prefix, major_t, minor_t, patch_t, prerel, build = match.groups()
- prefix = cls.PREFIX_ALIASES.get(prefix, prefix)
- major = None if major_t in cls.EMPTY_VALUES else int(major_t)
- minor = None if minor_t in cls.EMPTY_VALUES else int(minor_t)
- patch = None if patch_t in cls.EMPTY_VALUES else int(patch_t)
- if build is not None and prefix not in [cls.PREFIX_EQ]:
- # Ignore the 'build' part when not comparing to a specific part.
- build = None
- if major is None: # '*', 'x', 'X'
- target = Version(major=0, minor=0, patch=0)
- if prefix not in [cls.PREFIX_EQ, cls.PREFIX_GTE]:
- raise ValueError("Invalid expression %r" % simple)
- prefix = cls.PREFIX_GTE
- elif minor is None:
- target = Version(major=major, minor=0, patch=0)
- elif patch is None:
- target = Version(major=major, minor=minor, patch=0)
- else:
- target = Version(
- major=major,
- minor=minor,
- patch=patch,
- prerelease=prerel.split('.') if prerel else (),
- build=build.split('.') if build else (),
- )
- if (major is None or minor is None or patch is None) and (prerel or build):
- raise ValueError("Invalid NPM spec: %r" % simple)
- if prefix == cls.PREFIX_CARET:
- if target.major: # ^1.2.4 => >=1.2.4 <2.0.0 ; ^1.x => >=1.0.0 <2.0.0
- high = target.truncate().next_major()
- elif target.minor: # ^0.1.2 => >=0.1.2 <0.2.0
- high = target.truncate().next_minor()
- elif minor is None: # ^0.x => >=0.0.0 <1.0.0
- high = target.truncate().next_major()
- elif patch is None: # ^0.2.x => >=0.2.0 <0.3.0
- high = target.truncate().next_minor()
- else: # ^0.0.1 => >=0.0.1 <0.0.2
- high = target.truncate().next_patch()
- return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)]
- elif prefix == cls.PREFIX_TILDE:
- assert major is not None
- if minor is None: # ~1.x => >=1.0.0 <2.0.0
- high = target.next_major()
- else: # ~1.2.x => >=1.2.0 <1.3.0; ~1.2.3 => >=1.2.3 <1.3.0
- high = target.next_minor()
- return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, high)]
- elif prefix == cls.PREFIX_EQ:
- if major is None:
- return [cls.range(Range.OP_GTE, target)]
- elif minor is None:
- return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_major())]
- elif patch is None:
- return [cls.range(Range.OP_GTE, target), cls.range(Range.OP_LT, target.next_minor())]
- else:
- return [cls.range(Range.OP_EQ, target)]
- elif prefix == cls.PREFIX_GT:
- assert major is not None
- if minor is None: # >1.x
- return [cls.range(Range.OP_GTE, target.next_major())]
- elif patch is None: # >1.2.x => >=1.3.0
- return [cls.range(Range.OP_GTE, target.next_minor())]
- else:
- return [cls.range(Range.OP_GT, target)]
- elif prefix == cls.PREFIX_GTE:
- return [cls.range(Range.OP_GTE, target)]
- elif prefix == cls.PREFIX_LT:
- assert major is not None
- return [cls.range(Range.OP_LT, target)]
- else:
- assert prefix == cls.PREFIX_LTE
- assert major is not None
- if minor is None: # <=1.x => <2.0.0
- return [cls.range(Range.OP_LT, target.next_major())]
- elif patch is None: # <=1.2.x => <1.3.0
- return [cls.range(Range.OP_LT, target.next_minor())]
- else:
- return [cls.range(Range.OP_LTE, target)]
|