1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129 |
- # -*- coding:utf-8 -*-
- # This file comes from https://github.com/podhmo/python-semver/blob/b42e9896e391e086b773fc621b23fa299d16b874/semver/__init__.py
- #
- # It is licensed under the following license:
- #
- # MIT License
- # Copyright (c) 2016 podhmo
- # Permission is hereby granted, free of charge, to any person obtaining a copy
- # of this software and associated documentation files (the "Software"), to deal
- # in the Software without restriction, including without limitation the rights
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the Software is
- # furnished to do so, subject to the following conditions:
- # The above copyright notice and this permission notice shall be included in all
- # copies or substantial portions of the Software.
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- # SOFTWARE.
- import logging
- import re
- logger = logging.getLogger(__name__)
- SEMVER_SPEC_VERSION = '2.0.0'
- # Python 2/3 compatibility
- try:
- string_type = basestring
- except NameError:
- string_type = str
- class _R(object):
- def __init__(self, i):
- self.i = i
- def __call__(self):
- v = self.i
- self.i += 1
- return v
- def value(self):
- return self.i
- class Extendlist(list):
- def __setitem__(self, i, v):
- try:
- list.__setitem__(self, i, v)
- except IndexError:
- if len(self) == i:
- self.append(v)
- else:
- raise
- def list_get(xs, i):
- try:
- return xs[i]
- except IndexError:
- return None
- R = _R(0)
- src = Extendlist()
- regexp = {}
- # The following Regular Expressions can be used for tokenizing,
- # validating, and parsing SemVer version strings.
- # ## Numeric Identifier
- # A single `0`, or a non-zero digit followed by zero or more digits.
- NUMERICIDENTIFIER = R()
- src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'
- NUMERICIDENTIFIERLOOSE = R()
- src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'
- # ## Non-numeric Identifier
- # Zero or more digits, followed by a letter or hyphen, and then zero or
- # more letters, digits, or hyphens.
- NONNUMERICIDENTIFIER = R()
- src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'
- # ## Main Version
- # Three dot-separated numeric identifiers.
- MAINVERSION = R()
- src[MAINVERSION] = ('(' + src[NUMERICIDENTIFIER] + ')\\.' +
- '(' + src[NUMERICIDENTIFIER] + ')\\.' +
- '(' + src[NUMERICIDENTIFIER] + ')')
- MAINVERSIONLOOSE = R()
- src[MAINVERSIONLOOSE] = ('(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' +
- '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' +
- '(' + src[NUMERICIDENTIFIERLOOSE] + ')')
- # ## Pre-release Version Identifier
- # A numeric identifier, or a non-numeric identifier.
- PRERELEASEIDENTIFIER = R()
- src[PRERELEASEIDENTIFIER] = ('(?:' + src[NUMERICIDENTIFIER] +
- '|' + src[NONNUMERICIDENTIFIER] + ')')
- PRERELEASEIDENTIFIERLOOSE = R()
- src[PRERELEASEIDENTIFIERLOOSE] = ('(?:' + src[NUMERICIDENTIFIERLOOSE] +
- '|' + src[NONNUMERICIDENTIFIER] + ')')
- # ## Pre-release Version
- # Hyphen, followed by one or more dot-separated pre-release version
- # identifiers.
- PRERELEASE = R()
- src[PRERELEASE] = ('(?:-(' + src[PRERELEASEIDENTIFIER] +
- '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))')
- PRERELEASELOOSE = R()
- src[PRERELEASELOOSE] = ('(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] +
- '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))')
- # ## Build Metadata Identifier
- # Any combination of digits, letters, or hyphens.
- BUILDIDENTIFIER = R()
- src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'
- # ## Build Metadata
- # Plus sign, followed by one or more period-separated build metadata
- # identifiers.
- BUILD = R()
- src[BUILD] = ('(?:\\+(' + src[BUILDIDENTIFIER] +
- '(?:\\.' + src[BUILDIDENTIFIER] + ')*))')
- # ## Full Version String
- # A main version, followed optionally by a pre-release version and
- # build metadata.
- # Note that the only major, minor, patch, and pre-release sections of
- # the version string are capturing groups. The build metadata is not a
- # capturing group, because it should not ever be used in version
- # comparison.
- FULL = R()
- FULLPLAIN = ('v?' + src[MAINVERSION] + src[PRERELEASE] + '?' + src[BUILD] + '?')
- src[FULL] = '^' + FULLPLAIN + '$'
- # like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
- # also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
- # common in the npm registry.
- LOOSEPLAIN = ('[v=\\s]*' + src[MAINVERSIONLOOSE] +
- src[PRERELEASELOOSE] + '?' +
- src[BUILD] + '?')
- LOOSE = R()
- src[LOOSE] = '^' + LOOSEPLAIN + '$'
- GTLT = R()
- src[GTLT] = '((?:<|>)?=?)'
- # Something like "2.*" or "1.2.x".
- # Note that "x.x" is a valid xRange identifier, meaning "any version"
- # Only the first item is strictly required.
- XRANGEIDENTIFIERLOOSE = R()
- src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'
- XRANGEIDENTIFIER = R()
- src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'
- XRANGEPLAIN = R()
- src[XRANGEPLAIN] = ('[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' +
- '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' +
- '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' +
- '(?:' + src[PRERELEASE] + ')?' +
- src[BUILD] + '?' +
- ')?)?')
- XRANGEPLAINLOOSE = R()
- src[XRANGEPLAINLOOSE] = ('[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' +
- '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' +
- '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' +
- '(?:' + src[PRERELEASELOOSE] + ')?' +
- src[BUILD] + '?' +
- ')?)?')
- XRANGE = R()
- src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'
- XRANGELOOSE = R()
- src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'
- # Tilde ranges.
- # Meaning is "reasonably at or greater than"
- LONETILDE = R()
- src[LONETILDE] = '(?:~>?)'
- TILDETRIM = R()
- src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'
- regexp[TILDETRIM] = re.compile(src[TILDETRIM], re.M)
- tildeTrimReplace = r'\1~'
- TILDE = R()
- src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'
- TILDELOOSE = R()
- src[TILDELOOSE] = ('^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$')
- # Caret ranges.
- # Meaning is "at least and backwards compatible with"
- LONECARET = R()
- src[LONECARET] = '(?:\\^)'
- CARETTRIM = R()
- src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'
- regexp[CARETTRIM] = re.compile(src[CARETTRIM], re.M)
- caretTrimReplace = r'\1^'
- CARET = R()
- src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'
- CARETLOOSE = R()
- src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'
- # A simple gt/lt/eq thing, or just "" to indicate "any version"
- COMPARATORLOOSE = R()
- src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'
- COMPARATOR = R()
- src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'
- # An expression to strip any whitespace between the gtlt and the thing
- # it modifies, so that `> 1.2.3` ==> `>1.2.3`
- COMPARATORTRIM = R()
- src[COMPARATORTRIM] = ('(\\s*)' + src[GTLT] +
- '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')')
- # this one has to use the /g flag
- regexp[COMPARATORTRIM] = re.compile(src[COMPARATORTRIM], re.M)
- comparatorTrimReplace = r'\1\2\3'
- # Something like `1.2.3 - 1.2.4`
- # Note that these all use the loose form, because they'll be
- # checked against either the strict or loose comparator form
- # later.
- HYPHENRANGE = R()
- src[HYPHENRANGE] = ('^\\s*(' + src[XRANGEPLAIN] + ')' +
- '\\s+-\\s+' +
- '(' + src[XRANGEPLAIN] + ')' +
- '\\s*$')
- HYPHENRANGELOOSE = R()
- src[HYPHENRANGELOOSE] = ('^\\s*(' + src[XRANGEPLAINLOOSE] + ')' +
- '\\s+-\\s+' +
- '(' + src[XRANGEPLAINLOOSE] + ')' +
- '\\s*$')
- # Star ranges basically just allow anything at all.
- STAR = R()
- src[STAR] = '(<|>)?=?\\s*\\*'
- # version name recovery for convenient
- RECOVERYVERSIONNAME = R()
- src[RECOVERYVERSIONNAME] = ('v?({n})(?:\\.({n}))?{pre}?'.format(n=src[NUMERICIDENTIFIER], pre=src[PRERELEASELOOSE]))
- # Compile to actual regexp objects.
- # All are flag-free, unless they were created above with a flag.
- for i in range(R.value()):
- logger.debug("genregxp %s %s", i, src[i])
- if i not in regexp:
- regexp[i] = re.compile(src[i])
- def parse(version, loose):
- if loose:
- r = regexp[LOOSE]
- else:
- r = regexp[FULL]
- m = r.search(version)
- if m:
- return semver(version, loose)
- else:
- return None
- def valid(version, loose):
- v = parse(version, loose)
- if v.version:
- return v
- else:
- return None
- def clean(version, loose):
- s = parse(version, loose)
- if s:
- return s.version
- else:
- return None
- NUMERIC = re.compile("^\d+$")
- def semver(version, loose):
- if isinstance(version, SemVer):
- if version.loose == loose:
- return version
- else:
- version = version.version
- elif not isinstance(version, string_type): # xxx:
- raise ValueError("Invalid Version: {}".format(version))
- """
- if (!(this instanceof SemVer))
- return new SemVer(version, loose);
- """
- return SemVer(version, loose)
- make_semver = semver
- class SemVer(object):
- def __init__(self, version, loose):
- logger.debug("SemVer %s, %s", version, loose)
- self.loose = loose
- self.raw = version
- m = regexp[LOOSE if loose else FULL].search(version.strip())
- if not m:
- if not loose:
- raise ValueError("Invalid Version: {}".format(version))
- m = regexp[RECOVERYVERSIONNAME].search(version.strip())
- self.major = int(m.group(1)) if m.group(1) else 0
- self.minor = int(m.group(2)) if m.group(2) else 0
- self.patch = 0
- if not m.group(3):
- self.prerelease = []
- else:
- self.prerelease = [(int(id) if NUMERIC.search(id) else id)
- for id in m.group(3).split(".")]
- else:
- # these are actually numbers
- self.major = int(m.group(1))
- self.minor = int(m.group(2))
- self.patch = int(m.group(3))
- # numberify any prerelease numeric ids
- if not m.group(4):
- self.prerelease = []
- else:
- self.prerelease = [(int(id) if NUMERIC.search(id) else id)
- for id in m.group(4).split(".")]
- if m.group(5):
- self.build = m.group(5).split(".")
- else:
- self.build = []
- self.format() # xxx:
- def format(self):
- self.version = "{}.{}.{}".format(self.major, self.minor, self.patch)
- if len(self.prerelease) > 0:
- self.version += ("-{}".format(".".join(str(v) for v in self.prerelease)))
- return self.version
- def __repr__(self):
- return "<SemVer {} >".format(self)
- def __str__(self):
- return self.version
- def compare(self, other):
- logger.debug('SemVer.compare %s %s %s', self.version, self.loose, other)
- if not isinstance(other, SemVer):
- other = make_semver(other, self.loose)
- result = self.compare_main(other) or self.compare_pre(other)
- logger.debug("compare result %s", result)
- return result
- def compare_main(self, other):
- if not isinstance(other, SemVer):
- other = make_semver(other, self.loose)
- return (compare_identifiers(str(self.major), str(other.major)) or
- compare_identifiers(str(self.minor), str(other.minor)) or
- compare_identifiers(str(self.patch), str(other.patch)))
- def compare_pre(self, other):
- if not isinstance(other, SemVer):
- other = make_semver(other, self.loose)
- # NOT having a prerelease is > having one
- is_self_more_than_zero = len(self.prerelease) > 0
- is_other_more_than_zero = len(other.prerelease) > 0
- if not is_self_more_than_zero and is_other_more_than_zero:
- return 1
- elif is_self_more_than_zero and not is_other_more_than_zero:
- return -1
- elif not is_self_more_than_zero and not is_other_more_than_zero:
- return 0
- i = 0
- while True:
- a = list_get(self.prerelease, i)
- b = list_get(other.prerelease, i)
- logger.debug("prerelease compare %s: %s %s", i, a, b)
- i += 1
- if a is None and b is None:
- return 0
- elif b is None:
- return 1
- elif a is None:
- return -1
- elif a == b:
- continue
- else:
- return compare_identifiers(str(a), str(b))
- def inc(self, release, identifier=None):
- logger.debug("inc release %s %s", self.prerelease, release)
- if release == 'premajor':
- self.prerelease = []
- self.patch = 0
- self.minor = 0
- self.major += 1
- self.inc('pre', identifier=identifier)
- elif release == "preminor":
- self.prerelease = []
- self.patch = 0
- self.minor += 1
- self.inc('pre', identifier=identifier)
- elif release == "prepatch":
- # If this is already a prerelease, it will bump to the next version
- # drop any prereleases that might already exist, since they are not
- # relevant at this point.
- self.prerelease = []
- self.inc('patch', identifier=identifier)
- self.inc('pre', identifier=identifier)
- elif release == 'prerelease':
- # If the input is a non-prerelease version, this acts the same as
- # prepatch.
- if len(self.prerelease) == 0:
- self.inc("patch", identifier=identifier)
- self.inc("pre", identifier=identifier)
- elif release == "major":
- # If this is a pre-major version, bump up to the same major version.
- # Otherwise increment major.
- # 1.0.0-5 bumps to 1.0.0
- # 1.1.0 bumps to 2.0.0
- if self.minor != 0 or self.patch != 0 or len(self.prerelease) == 0:
- self.major += 1
- self.minor = 0
- self.patch = 0
- self.prerelease = []
- elif release == "minor":
- # If this is a pre-minor version, bump up to the same minor version.
- # Otherwise increment minor.
- # 1.2.0-5 bumps to 1.2.0
- # 1.2.1 bumps to 1.3.0
- if self.patch != 0 or len(self.prerelease) == 0:
- self.minor += 1
- self.patch = 0
- self.prerelease = []
- elif release == "patch":
- # If this is not a pre-release version, it will increment the patch.
- # If it is a pre-release it will bump up to the same patch version.
- # 1.2.0-5 patches to 1.2.0
- # 1.2.0 patches to 1.2.1
- if len(self.prerelease) == 0:
- self.patch += 1
- self.prerelease = []
- elif release == "pre":
- # This probably shouldn't be used publicly.
- # 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction.
- logger.debug("inc prerelease %s", self.prerelease)
- if len(self.prerelease) == 0:
- self.prerelease = [0]
- else:
- i = len(self.prerelease) - 1
- while i >= 0:
- if isinstance(self.prerelease[i], int):
- self.prerelease[i] += 1
- i -= 2
- i -= 1
- # ## this is needless code in python ##
- # if i == -1: # didn't increment anything
- # self.prerelease.append(0)
- if identifier is not None:
- # 1.2.0-beta.1 bumps to 1.2.0-beta.2,
- # 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
- if self.prerelease[0] == identifier:
- if not isinstance(self.prerelease[1], int):
- self.prerelease = [identifier, 0]
- else:
- self.prerelease = [identifier, 0]
- else:
- raise ValueError('invalid increment argument: {}'.format(release))
- self.format()
- self.raw = self.version
- return self
- def inc(version, release, loose, identifier=None): # wow!
- try:
- return make_semver(version, loose).inc(release, identifier=identifier).version
- except Exception as e:
- logger.debug(e, exc_info=5)
- return None
- def compare_identifiers(a, b):
- anum = NUMERIC.search(a)
- bnum = NUMERIC.search(b)
- if anum and bnum:
- a = int(a)
- b = int(b)
- if anum and not bnum:
- return -1
- elif bnum and not anum:
- return 1
- elif a < b:
- return -1
- elif a > b:
- return 1
- else:
- return 0
- def rcompare_identifiers(a, b):
- return compare_identifiers(b, a)
- def compare(a, b, loose):
- return make_semver(a, loose).compare(b)
- def compare_loose(a, b):
- return compare(a, b, True)
- def rcompare(a, b, loose):
- return compare(b, a, loose)
- def make_key_function(loose):
- def key_function(version):
- v = make_semver(version, loose)
- key = (v.major, v.minor, v.patch)
- if v.prerelease:
- key = key + tuple(v.prerelease)
- else:
- # NOT having a prerelease is > having one
- key = key + (float('inf'),)
- return key
- return key_function
- loose_key_function = make_key_function(True)
- full_key_function = make_key_function(True)
- def sort(list, loose):
- keyf = loose_key_function if loose else full_key_function
- list.sort(key=keyf)
- return list
- def rsort(list, loose):
- keyf = loose_key_function if loose else full_key_function
- list.sort(key=keyf, reverse=True)
- return list
- def gt(a, b, loose):
- return compare(a, b, loose) > 0
- def lt(a, b, loose):
- return compare(a, b, loose) < 0
- def eq(a, b, loose):
- return compare(a, b, loose) == 0
- def neq(a, b, loose):
- return compare(a, b, loose) != 0
- def gte(a, b, loose):
- return compare(a, b, loose) >= 0
- def lte(a, b, loose):
- return compare(a, b, loose) <= 0
- def cmp(a, op, b, loose):
- logger.debug("cmp: %s", op)
- if op == "===":
- return a == b
- elif op == "!==":
- return a != b
- elif op == "" or op == "=" or op == "==":
- return eq(a, b, loose)
- elif op == "!=":
- return neq(a, b, loose)
- elif op == ">":
- return gt(a, b, loose)
- elif op == ">=":
- return gte(a, b, loose)
- elif op == "<":
- return lt(a, b, loose)
- elif op == "<=":
- return lte(a, b, loose)
- else:
- raise ValueError("Invalid operator: {}".format(op))
- def comparator(comp, loose):
- if isinstance(comp, Comparator):
- if(comp.loose == loose):
- return comp
- else:
- comp = comp.value
- # if (!(this instanceof Comparator))
- # return new Comparator(comp, loose)
- return Comparator(comp, loose)
- make_comparator = comparator
- ANY = object()
- class Comparator(object):
- semver = None
- def __init__(self, comp, loose):
- logger.debug("comparator: %s %s", comp, loose)
- self.loose = loose
- self.parse(comp)
- if self.semver == ANY:
- self.value = ""
- else:
- self.value = self.operator + self.semver.version
- def parse(self, comp):
- if self.loose:
- r = regexp[COMPARATORLOOSE]
- else:
- r = regexp[COMPARATOR]
- logger.debug("parse comp=%s", comp)
- m = r.search(comp)
- if m is None:
- raise ValueError("Invalid comparator: {}".format(comp))
- self.operator = m.group(1)
- # if it literally is just '>' or '' then allow anything.
- if m.group(2) is None:
- self.semver = ANY
- else:
- self.semver = semver(m.group(2), self.loose)
- def __repr__(self):
- return '<SemVer Comparator "{}">'.format(self)
- def __str__(self):
- return self.value
- def test(self, version):
- logger.debug('Comparator, test %s, %s', version, self.loose)
- if self.semver == ANY:
- return True
- else:
- return cmp(version, self.operator, self.semver, self.loose)
- def make_range(range_, loose):
- if isinstance(range_, Range) and range_.loose == loose:
- return range_
- # if (!(this instanceof Range))
- # return new Range(range, loose);
- return Range(range_, loose)
- class Range(object):
- def __init__(self, range_, loose):
- self.loose = loose
- # First, split based on boolean or ||
- self.raw = range_
- xs = [self.parse_range(r.strip()) for r in re.split(r"\s*\|\|\s*", range_)]
- self.set = [r for r in xs if r]
- if not len(self.set):
- raise ValueError("Invalid SemVer Range: {}".format(range_))
- self.format()
- def __repr__(self):
- return '<SemVer Range "{}">'.format(self.range)
- def format(self):
- self.range = "||".join([" ".join(c.value for c in comps).strip() for comps in self.set]).strip()
- logger.debug("Range format %s", self.range)
- return self.range
- def __str__(self):
- return self.range
- def parse_range(self, range_):
- loose = self.loose
- logger.debug('range %s %s', range_, loose)
- # `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
- if loose:
- hr = regexp[HYPHENRANGELOOSE]
- else:
- hr = regexp[HYPHENRANGE]
- range_ = hr.sub(hyphen_replace, range_,)
- logger.debug('hyphen replace %s', range_)
- # `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
- range_ = regexp[COMPARATORTRIM].sub(comparatorTrimReplace, range_)
- logger.debug('comparator trim %s, %s', range_, regexp[COMPARATORTRIM])
- # `~ 1.2.3` => `~1.2.3`
- range_ = regexp[TILDETRIM].sub(tildeTrimReplace, range_)
- # `^ 1.2.3` => `^1.2.3`
- range_ = regexp[CARETTRIM].sub(caretTrimReplace, range_)
- # normalize spaces
- range_ = " ".join(re.split("\s+", range_))
- # At this point, the range is completely trimmed and
- # ready to be split into comparators.
- if loose:
- comp_re = regexp[COMPARATORLOOSE]
- else:
- comp_re = regexp[COMPARATOR]
- set_ = re.split("\s+", ' '.join([parse_comparator(comp, loose) for comp in range_.split(" ")]))
- if self.loose:
- # in loose mode, throw out any that are not valid comparators
- set_ = [comp for comp in set_ if comp_re.search(comp)]
- set_ = [make_comparator(comp, loose) for comp in set_]
- return set_
- def test(self, version):
- if not version: # xxx
- return False
- if isinstance(version, string_type):
- version = make_semver(version, loose=self.loose)
- for e in self.set:
- if test_set(e, version):
- return True
- return False
- # Mostly just for testing and legacy API reasons
- def to_comparators(range_, loose):
- return [" ".join([c.value for c in comp]).strip().split(" ")
- for comp in make_range(range_, loose).set]
- # comprised of xranges, tildes, stars, and gtlt's at this point.
- # already replaced the hyphen ranges
- # turn into a set of JUST comparators.
- def parse_comparator(comp, loose):
- logger.debug('comp %s', comp)
- comp = replace_carets(comp, loose)
- logger.debug('caret %s', comp)
- comp = replace_tildes(comp, loose)
- logger.debug('tildes %s', comp)
- comp = replace_xranges(comp, loose)
- logger.debug('xrange %s', comp)
- comp = replace_stars(comp, loose)
- logger.debug('stars %s', comp)
- return comp
- def is_x(id):
- return id is None or id == "" or id.lower() == "x" or id == "*"
- # ~, ~> --> * (any, kinda silly)
- # ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
- # ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
- # ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
- # ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
- # ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
- def replace_tildes(comp, loose):
- return " ".join([replace_tilde(c, loose)
- for c in re.split("\s+", comp.strip())])
- def replace_tilde(comp, loose):
- if loose:
- r = regexp[TILDELOOSE]
- else:
- r = regexp[TILDE]
- def repl(mob):
- _ = mob.group(0)
- M, m, p, pr, _ = mob.groups()
- logger.debug("tilde %s %s %s %s %s %s", comp, _, M, m, p, pr)
- if is_x(M):
- ret = ""
- elif is_x(m):
- ret = '>=' + M + '.0.0 <' + str(int(M) + 1) + '.0.0'
- elif is_x(p):
- # ~1.2 == >=1.2.0 <1.3.0
- ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str(int(m) + 1) + '.0'
- elif pr:
- logger.debug("replaceTilde pr %s", pr)
- if (pr[0] != "-"):
- pr = '-' + pr
- ret = '>=' + M + '.' + m + '.' + p + pr + ' <' + M + '.' + str(int(m) + 1) + '.0'
- else:
- # ~1.2.3 == >=1.2.3 <1.3.0
- ret = '>=' + M + '.' + m + '.' + p + ' <' + M + '.' + str(int(m) + 1) + '.0'
- logger.debug('tilde return, %s', ret)
- return ret
- return r.sub(repl, comp)
- # ^ --> * (any, kinda silly)
- # ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
- # ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
- # ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
- # ^1.2.3 --> >=1.2.3 <2.0.0
- # ^1.2.0 --> >=1.2.0 <2.0.0
- def replace_carets(comp, loose):
- return " ".join([replace_caret(c, loose)
- for c in re.split("\s+", comp.strip())])
- def replace_caret(comp, loose):
- if loose:
- r = regexp[CARETLOOSE]
- else:
- r = regexp[CARET]
- def repl(mob):
- m0 = mob.group(0)
- M, m, p, pr, _ = mob.groups()
- logger.debug("caret %s %s %s %s %s %s", comp, m0, M, m, p, pr)
- if is_x(M):
- ret = ""
- elif is_x(m):
- ret = '>=' + M + '.0.0 <' + str((int(M) + 1)) + '.0.0'
- elif is_x(p):
- if M == "0":
- ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str((int(m) + 1)) + '.0'
- else:
- ret = '>=' + M + '.' + m + '.0 <' + str(int(M) + 1) + '.0.0'
- elif pr:
- logger.debug('replaceCaret pr %s', pr)
- if pr[0] != "-":
- pr = "-" + pr
- if M == "0":
- if m == "0":
- ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + M + '.' + m + "." + str(int(p or 0) + 1)
- else:
- ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + M + '.' + str(int(m) + 1) + '.0'
- else:
- ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + str(int(M) + 1) + '.0.0'
- else:
- if M == "0":
- if m == "0":
- ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + M + '.' + m + "." + str(int(p or 0) + 1)
- else:
- ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + M + '.' + str((int(m) + 1)) + '.0'
- else:
- ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + str(int(M) + 1) + '.0.0'
- logger.debug('caret return %s', ret)
- return ret
- return r.sub(repl, comp)
- def replace_xranges(comp, loose):
- logger.debug('replaceXRanges %s %s', comp, loose)
- return " ".join([replace_xrange(c, loose)
- for c in re.split("\s+", comp.strip())])
- def replace_xrange(comp, loose):
- comp = comp.strip()
- if loose:
- r = regexp[XRANGELOOSE]
- else:
- r = regexp[XRANGE]
- def repl(mob):
- ret = mob.group(0)
- gtlt, M, m, p, pr, _ = mob.groups()
- logger.debug("xrange %s %s %s %s %s %s %s", comp, ret, gtlt, M, m, p, pr)
- xM = is_x(M)
- xm = xM or is_x(m)
- xp = xm or is_x(p)
- any_x = xp
- if gtlt == "=" and any_x:
- gtlt = ""
- logger.debug("xrange gtlt=%s any_x=%s", gtlt, any_x)
- if xM:
- if gtlt == '>' or gtlt == '<':
- # nothing is allowed
- ret = '<0.0.0'
- else:
- ret = '*'
- elif gtlt and any_x:
- # replace X with 0, and then append the -0 min-prerelease
- if xm:
- m = 0
- if xp:
- p = 0
- if gtlt == ">":
- # >1 => >=2.0.0
- # >1.2 => >=1.3.0
- # >1.2.3 => >= 1.2.4
- gtlt = ">="
- if xm:
- M = int(M) + 1
- m = 0
- p = 0
- elif xp:
- m = int(m) + 1
- p = 0
- elif gtlt == '<=':
- # <=0.7.x is actually <0.8.0, since any 0.7.x should
- # pass. Similarly, <=7.x is actually <8.0.0, etc.
- gtlt = '<'
- if xm:
- M = int(M) + 1
- else:
- m = int(m) + 1
- ret = gtlt + str(M) + '.' + str(m) + '.' + str(p)
- elif xm:
- ret = '>=' + M + '.0.0 <' + str(int(M) + 1) + '.0.0'
- elif xp:
- ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str(int(m) + 1) + '.0'
- logger.debug('xRange return %s', ret)
- return ret
- return r.sub(repl, comp)
- # Because * is AND-ed with everything else in the comparator,
- # and '' means "any version", just remove the *s entirely.
- def replace_stars(comp, loose):
- logger.debug('replaceStars %s %s', comp, loose)
- # Looseness is ignored here. star is always as loose as it gets!
- return regexp[STAR].sub("", comp.strip())
- # This function is passed to string.replace(re[HYPHENRANGE])
- # M, m, patch, prerelease, build
- # 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
- # 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
- # 1.2 - 3.4 => >=1.2.0 <3.5.0
- def hyphen_replace(mob):
- from_, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr, tb = mob.groups()
- if is_x(fM):
- from_ = ""
- elif is_x(fm):
- from_ = '>=' + fM + '.0.0'
- elif is_x(fp):
- from_ = '>=' + fM + '.' + fm + '.0'
- else:
- from_ = ">=" + from_
- if is_x(tM):
- to = ""
- elif is_x(tm):
- to = '<' + str(int(tM) + 1) + '.0.0'
- elif is_x(tp):
- to = '<' + tM + '.' + str(int(tm) + 1) + '.0'
- elif tpr:
- to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr
- else:
- to = '<=' + to
- return (from_ + ' ' + to).strip()
- def test_set(set_, version):
- for e in set_:
- if not e.test(version):
- return False
- if len(version.prerelease) > 0:
- # Find the set of versions that are allowed to have prereleases
- # For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
- # That should allow `1.2.3-pr.2` to pass.
- # However, `1.2.4-alpha.notready` should NOT be allowed,
- # even though it's within the range set by the comparators.
- for e in set_:
- if e.semver == ANY:
- continue
- if len(e.semver.prerelease) > 0:
- allowed = e.semver
- if allowed.major == version.major and allowed.minor == version.minor and allowed.patch == version.patch:
- return True
- # Version has a -pre, but it's not one of the ones we like.
- return False
- return True
- def satisfies(version, range_, loose=False):
- try:
- range_ = make_range(range_, loose)
- except Exception as e:
- return False
- return range_.test(version)
- def max_satisfying(versions, range_, loose=False):
- try:
- range_ob = make_range(range_, loose=loose)
- except:
- return None
- max_ = None
- max_sv = None
- for v in versions:
- if range_ob.test(v): # satisfies(v, range_, loose=loose)
- if max_ is None or max_sv.compare(v) == -1: # compare(max, v, true)
- max_ = v
- max_sv = make_semver(max_, loose=loose)
- return max_
- def valid_range(range_, loose):
- try:
- # Return '*' instead of '' so that truthiness works.
- # This will throw if it's invalid anyway
- return make_range(range_, loose).range or "*"
- except:
- return None
- # Determine if version is less than all the versions possible in the range
- def ltr(version, range_, loose):
- return outside(version, range_, "<", loose)
- # Determine if version is greater than all the versions possible in the range.
- def rtr(version, range_, loose):
- return outside(version, range_, ">", loose)
- def outside(version, range_, hilo, loose):
- version = make_semver(version, loose)
- range_ = make_range(range_, loose)
- if hilo == ">":
- gtfn = gt
- ltefn = lte
- ltfn = lt
- comp = ">"
- ecomp = ">="
- elif hilo == "<":
- gtfn = lt
- ltefn = gte
- ltfn = gt
- comp = "<"
- ecomp = "<="
- else:
- raise ValueError("Must provide a hilo val of '<' or '>'")
- # If it satisifes the range it is not outside
- if satisfies(version, range_, loose):
- return False
- # From now on, variable terms are as if we're in "gtr" mode.
- # but note that everything is flipped for the "ltr" function.
- for comparators in range_.set:
- high = None
- low = None
- for comparator in comparators:
- high = high or comparator
- low = low or comparator
- if gtfn(comparator.semver, high.semver, loose):
- high = comparator
- elif ltfn(comparator.semver, low.semver, loose):
- low = comparator
- # If the edge version comparator has a operator then our version
- # isn't outside it
- if high.operator == comp or high.operator == ecomp:
- return False
- # If the lowest version comparator has an operator and our version
- # is less than it then it isn't higher than the range
- if (not low.operator or low.operator == comp) and ltefn(version, low.semver):
- return False
- elif low.operator == ecomp and ltfn(version, low.semver):
- return False
- return True
|