|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import logging |
|
import re |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
SEMVER_SPEC_VERSION = "2.0.0" |
|
|
|
string_type = str |
|
|
|
|
|
class _R: |
|
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 = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NUMERICIDENTIFIER = R() |
|
src[NUMERICIDENTIFIER] = "0|[1-9]\\d*" |
|
|
|
NUMERICIDENTIFIERLOOSE = R() |
|
src[NUMERICIDENTIFIERLOOSE] = "[0-9]+" |
|
|
|
|
|
|
|
|
|
|
|
|
|
NONNUMERICIDENTIFIER = R() |
|
src[NONNUMERICIDENTIFIER] = "\\d*[a-zA-Z-][a-zA-Z0-9-]*" |
|
|
|
|
|
|
|
|
|
MAINVERSION = R() |
|
src[MAINVERSION] = ( |
|
"(" |
|
+ src[NUMERICIDENTIFIER] |
|
+ ")\\." |
|
+ "(" |
|
+ src[NUMERICIDENTIFIER] |
|
+ ")\\." |
|
+ "(" |
|
+ src[NUMERICIDENTIFIER] |
|
+ ")" |
|
) |
|
|
|
MAINVERSIONLOOSE = R() |
|
src[MAINVERSIONLOOSE] = ( |
|
"(" |
|
+ src[NUMERICIDENTIFIERLOOSE] |
|
+ ")\\." |
|
+ "(" |
|
+ src[NUMERICIDENTIFIERLOOSE] |
|
+ ")\\." |
|
+ "(" |
|
+ src[NUMERICIDENTIFIERLOOSE] |
|
+ ")" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
PRERELEASEIDENTIFIER = R() |
|
src[PRERELEASEIDENTIFIER] = "(?:" + src[NUMERICIDENTIFIER] + "|" + src[NONNUMERICIDENTIFIER] + ")" |
|
|
|
PRERELEASEIDENTIFIERLOOSE = R() |
|
src[PRERELEASEIDENTIFIERLOOSE] = ( |
|
"(?:" + src[NUMERICIDENTIFIERLOOSE] + "|" + src[NONNUMERICIDENTIFIER] + ")" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
PRERELEASE = R() |
|
src[PRERELEASE] = ( |
|
"(?:-(" + src[PRERELEASEIDENTIFIER] + "(?:\\." + src[PRERELEASEIDENTIFIER] + ")*))" |
|
) |
|
|
|
PRERELEASELOOSE = R() |
|
src[PRERELEASELOOSE] = ( |
|
"(?:-?(" + src[PRERELEASEIDENTIFIERLOOSE] + "(?:\\." + src[PRERELEASEIDENTIFIERLOOSE] + ")*))" |
|
) |
|
|
|
|
|
|
|
|
|
BUILDIDENTIFIER = R() |
|
src[BUILDIDENTIFIER] = "[0-9A-Za-z-]+" |
|
|
|
|
|
|
|
|
|
|
|
BUILD = R() |
|
src[BUILD] = "(?:\\+(" + src[BUILDIDENTIFIER] + "(?:\\." + src[BUILDIDENTIFIER] + ")*))" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FULL = R() |
|
FULLPLAIN = "v?" + src[MAINVERSION] + src[PRERELEASE] + "?" + src[BUILD] + "?" |
|
|
|
src[FULL] = "^" + FULLPLAIN + "$" |
|
|
|
|
|
|
|
|
|
LOOSEPLAIN = "[v=\\s]*" + src[MAINVERSIONLOOSE] + src[PRERELEASELOOSE] + "?" + src[BUILD] + "?" |
|
|
|
LOOSE = R() |
|
src[LOOSE] = "^" + LOOSEPLAIN + "$" |
|
|
|
GTLT = R() |
|
src[GTLT] = "((?:<|>)?=?)" |
|
|
|
|
|
|
|
|
|
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] + "$" |
|
|
|
|
|
|
|
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] + "$" |
|
|
|
|
|
|
|
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] + "$" |
|
|
|
|
|
COMPARATORLOOSE = R() |
|
src[COMPARATORLOOSE] = "^" + src[GTLT] + "\\s*(" + LOOSEPLAIN + ")$|^$" |
|
COMPARATOR = R() |
|
src[COMPARATOR] = "^" + src[GTLT] + "\\s*(" + FULLPLAIN + ")$|^$" |
|
|
|
|
|
|
|
|
|
COMPARATORTRIM = R() |
|
src[COMPARATORTRIM] = "(\\s*)" + src[GTLT] + "\\s*(" + LOOSEPLAIN + "|" + src[XRANGEPLAIN] + ")" |
|
|
|
|
|
regexp[COMPARATORTRIM] = re.compile(src[COMPARATORTRIM], re.M) |
|
comparatorTrimReplace = r"\1\2\3" |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = R() |
|
src[STAR] = "(<|>)?=?\\s*\\*" |
|
|
|
|
|
RECOVERYVERSIONNAME = R() |
|
_n = src[NUMERICIDENTIFIER] |
|
_pre = src[PRERELEASELOOSE] |
|
src[RECOVERYVERSIONNAME] = f"v?({_n})(?:\\.({_n}))?{_pre}?" |
|
|
|
|
|
|
|
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): |
|
r = regexp[LOOSE] if loose else 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(r"^\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): |
|
raise ValueError(f"Invalid Version: {version}") |
|
|
|
""" |
|
if (!(this instanceof SemVer)) |
|
return new SemVer(version, loose); |
|
""" |
|
return SemVer(version, loose) |
|
|
|
|
|
make_semver = semver |
|
|
|
|
|
class SemVer: |
|
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(f"Invalid Version: {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: |
|
|
|
self.major = int(m.group(1)) |
|
self.minor = int(m.group(2)) |
|
self.patch = int(m.group(3)) |
|
|
|
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() |
|
|
|
def format(self): |
|
self.version = f"{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 f"<SemVer {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) |
|
|
|
|
|
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": |
|
|
|
|
|
|
|
self.prerelease = [] |
|
self.inc("patch", identifier=identifier) |
|
self.inc("pre", identifier=identifier) |
|
elif release == "prerelease": |
|
|
|
|
|
if len(self.prerelease) == 0: |
|
self.inc("patch", identifier=identifier) |
|
self.inc("pre", identifier=identifier) |
|
elif release == "major": |
|
|
|
|
|
|
|
|
|
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 self.patch != 0 or len(self.prerelease) == 0: |
|
self.minor += 1 |
|
self.patch = 0 |
|
self.prerelease = [] |
|
elif release == "patch": |
|
|
|
|
|
|
|
|
|
if len(self.prerelease) == 0: |
|
self.patch += 1 |
|
self.prerelease = [] |
|
elif release == "pre": |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
if identifier is not None: |
|
|
|
|
|
if self.prerelease[0] == identifier: |
|
if not isinstance(self.prerelease[1], int): |
|
self.prerelease = [identifier, 0] |
|
else: |
|
self.prerelease = [identifier, 0] |
|
else: |
|
raise ValueError(f"invalid increment argument: {release}") |
|
self.format() |
|
self.raw = self.version |
|
return self |
|
|
|
|
|
def inc(version, release, loose, identifier=None): |
|
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: |
|
|
|
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(f"Invalid operator: {op}") |
|
|
|
|
|
def comparator(comp, loose): |
|
if isinstance(comp, Comparator): |
|
if comp.loose == loose: |
|
return comp |
|
else: |
|
comp = comp.value |
|
|
|
|
|
|
|
return Comparator(comp, loose) |
|
|
|
|
|
make_comparator = comparator |
|
|
|
ANY = object() |
|
|
|
|
|
class Comparator: |
|
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): |
|
r = regexp[COMPARATORLOOSE] if self.loose else regexp[COMPARATOR] |
|
logger.debug("parse comp=%s", comp) |
|
m = r.search(comp) |
|
|
|
if m is None: |
|
raise ValueError(f"Invalid comparator: {comp}") |
|
|
|
self.operator = m.group(1) |
|
|
|
if m.group(2) is None: |
|
self.semver = ANY |
|
else: |
|
self.semver = semver(m.group(2), self.loose) |
|
|
|
def __repr__(self): |
|
return f'<SemVer Comparator "{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_ |
|
|
|
|
|
|
|
return Range(range_, loose) |
|
|
|
|
|
class Range: |
|
def __init__(self, range_, loose): |
|
self.loose = loose |
|
|
|
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(f"Invalid SemVer Range: {range_}") |
|
|
|
self.format() |
|
|
|
def __repr__(self): |
|
return f'<SemVer Range "{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) |
|
|
|
hr = regexp[HYPHENRANGELOOSE] if loose else regexp[HYPHENRANGE] |
|
|
|
range_ = hr.sub( |
|
hyphen_replace, |
|
range_, |
|
) |
|
logger.debug("hyphen replace %s", range_) |
|
|
|
|
|
range_ = regexp[COMPARATORTRIM].sub(comparatorTrimReplace, range_) |
|
logger.debug("comparator trim %s, %s", range_, regexp[COMPARATORTRIM]) |
|
|
|
|
|
range_ = regexp[TILDETRIM].sub(tildeTrimReplace, range_) |
|
|
|
|
|
range_ = regexp[CARETTRIM].sub(caretTrimReplace, range_) |
|
|
|
|
|
range_ = " ".join(re.split(r"\s+", range_)) |
|
|
|
|
|
|
|
comp_re = regexp[COMPARATORLOOSE] if loose else regexp[COMPARATOR] |
|
set_ = re.split( |
|
r"\s+", " ".join([parse_comparator(comp, loose) for comp in range_.split(" ")]) |
|
) |
|
if self.loose: |
|
|
|
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: |
|
return False |
|
|
|
if isinstance(version, string_type): |
|
version = make_semver(version, loose=self.loose) |
|
|
|
return any(test_set(e, version) for e in self.set) |
|
|
|
|
|
|
|
def to_comparators(range_, loose): |
|
return [ |
|
" ".join([c.value for c in comp]).strip().split(" ") |
|
for comp in make_range(range_, loose).set |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_ == "*" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def replace_tildes(comp, loose): |
|
return " ".join([replace_tilde(c, loose) for c in re.split(r"\s+", comp.strip())]) |
|
|
|
|
|
def replace_tilde(comp, loose): |
|
r = regexp[TILDELOOSE] if loose else 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): |
|
|
|
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: |
|
|
|
ret = ">=" + M + "." + m + "." + p + " <" + M + "." + str(int(m) + 1) + ".0" |
|
logger.debug("tilde return, %s", ret) |
|
return ret |
|
|
|
return r.sub(repl, comp) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def replace_carets(comp, loose): |
|
return " ".join([replace_caret(c, loose) for c in re.split(r"\s+", comp.strip())]) |
|
|
|
|
|
def replace_caret(comp, loose): |
|
r = regexp[CARETLOOSE] if loose else 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(r"\s+", comp.strip())]) |
|
|
|
|
|
def replace_xrange(comp, loose): |
|
comp = comp.strip() |
|
r = regexp[XRANGELOOSE] if loose else 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 == "<": |
|
|
|
ret = "<0.0.0" |
|
else: |
|
ret = "*" |
|
elif gtlt and any_x: |
|
|
|
if xm: |
|
m = 0 |
|
if xp: |
|
p = 0 |
|
|
|
if gtlt == ">": |
|
|
|
|
|
|
|
gtlt = ">=" |
|
if xm: |
|
M = int(M) + 1 |
|
m = 0 |
|
p = 0 |
|
elif xp: |
|
m = int(m) + 1 |
|
p = 0 |
|
elif gtlt == "<=": |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
def replace_stars(comp, loose): |
|
logger.debug("replaceStars %s %s", comp, loose) |
|
|
|
return regexp[STAR].sub("", comp.strip()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
return False |
|
return True |
|
|
|
|
|
def satisfies(version, range_, loose=False): |
|
try: |
|
range_ = make_range(range_, loose) |
|
except Exception: |
|
return False |
|
return range_.test(version) |
|
|
|
|
|
def max_satisfying(versions, range_, loose=False): |
|
try: |
|
range_ob = make_range(range_, loose=loose) |
|
except Exception: |
|
return None |
|
max_ = None |
|
max_sv = None |
|
for v in versions: |
|
if range_ob.test(v): |
|
if max_ is None or max_sv.compare(v) == -1: |
|
max_ = v |
|
max_sv = make_semver(max_, loose=loose) |
|
return max_ |
|
|
|
|
|
def valid_range(range_, loose): |
|
try: |
|
|
|
|
|
return make_range(range_, loose).range or "*" |
|
except Exception: |
|
return None |
|
|
|
|
|
|
|
def ltr(version, range_, loose): |
|
return outside(version, range_, "<", loose) |
|
|
|
|
|
|
|
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 satisfies(version, range_, loose): |
|
return False |
|
|
|
|
|
|
|
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 high.operator == comp or high.operator == ecomp: |
|
return False |
|
|
|
|
|
|
|
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 |
|
|