You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/jupyterlab/semver.py

1130 lines
34 KiB

# -*- 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