pull/613/head
Tim Head 2019-03-07 23:53:01 +01:00
rodzic 6e2a718d15
commit b3451d3bc7
1 zmienionych plików z 75 dodań i 84 usunięć

Wyświetl plik

@ -1,15 +1,16 @@
# This file implements the julia-specific logic for handling SemVer (Semantic
# Versioning) strings in .toml files.
#
# It uses the python "semver" package to do most version string comparisons, but
# the places where julia's SemVer handling differs from the semver package have
# been implemented directly.
#
# Here, we use tuples to represent a Version, and functors as "matchers". The matcher
# functors take a version string and return true if it passes its constraints.
"""
Julia specific handling of SemVer strings
It uses the python "semver" package to do most version string comparisons, but
the places where julia's SemVer handling differs from the semver package have
been implemented directly.
"""
# We use tuples to represent a Version, and functors as "matchers". The
# matcher functors take a version string and return True if it passes its
# constraints.
import re
from enum import Enum
import semver
@ -29,70 +30,6 @@ def str_to_version(vstr):
return tuple([int(n) for n in vstr.split(".")])
# --- Matcher interface -------------------------------------------
class AbstractMatcher:
def match(self, v):
pass
class SemverMatcher(AbstractMatcher):
""" Match a version tuple to a given constraint_str using the `semver` package. """
def __init__(self, constraint_str):
self.constraint_str = constraint_str
def match(self, v):
while len(v) < 3:
v = v + (0,)
v_str = ".".join(map(str, v))
return semver.match(v_str, self.constraint_str)
def __eq__(self, rhs):
return self.constraint_str == rhs.constraint_str
def __repr__(self):
return self.constraint_str
# --- Custom matcher for julia-specific SemVer handling: ---------
class Exclusivity(Enum):
EXCLUSIVE = 1
INCLUSIVE = 2
class VersionRange(AbstractMatcher):
"""Match a version tuple between lower and upper bounds"""
def __init__(self, lower, upper, upper_exclusivity):
self.lower = lower
self.upper = upper
self.upper_exclusivity = upper_exclusivity
def match(self, v):
if self.upper_exclusivity == Exclusivity.EXCLUSIVE:
return self.lower <= v < self.upper
else:
return self.lower <= v <= self.upper
def __eq__(self, rhs):
return (
self.lower == rhs.lower
and self.upper == rhs.upper
and self.upper_exclusivity == rhs.upper_exclusivity
)
def __repr__(self):
return (
"["
+ ".".join(map(str, self.lower))
+ "-"
+ ".".join(map(str, self.upper))
+ (")" if self.upper_exclusivity == Exclusivity.EXCLUSIVE else "]")
)
# Helpers
def major(v):
return v[0]
@ -106,13 +43,10 @@ def patch(v):
return v[2] if len(v) >= 3 else 0
# --- main constraint parser function ------------------------------------
def create_semver_matcher(constraint_str):
"""
Returns a derived-class instance of AbstractMatcher that matches version
tuples against the provided constraint_str.
"""Create a matcher that can be used to match version tuples
Version tuples are matched against the provided `constraint_str`.
"""
constraint_str = constraint_str.strip()
first_digit = re.search(r"\d", constraint_str)
@ -129,17 +63,18 @@ def create_semver_matcher(constraint_str):
# Also, julia treats pre-1.0 releases specially, as if the first
# non-zero number is actually a major number:
# https://docs.julialang.org/en/latest/stdlib/Pkg/#Caret-specifiers-1
# So we need to handle it separately by bumping the first non-zero number.
# So we need to handle it separately by bumping the first non-zero
# enumber.
for i, n in enumerate(constraint):
if (
n != 0 or i == len(constraint) - 1
): # (using the last existing number handles situations like "^0.0" or "^0")
upper = constraint[0:i] + (n + 1,)
break
return VersionRange(constraint, upper, Exclusivity.EXCLUSIVE)
return VersionRange(constraint, upper, True)
else:
return VersionRange(
constraint, (major(constraint) + 1,), Exclusivity.EXCLUSIVE
constraint, (major(constraint) + 1,), True
)
# '~' matching (only allowed to bump the last present number by one)
@ -147,7 +82,7 @@ def create_semver_matcher(constraint_str):
return VersionRange(
constraint,
constraint[:-1] + (constraint[-1] + 1,),
Exclusivity.INCLUSIVE,
upper_exclusivity=False
)
# Use semver package's comparisons for everything else:
@ -165,3 +100,59 @@ def create_semver_matcher(constraint_str):
constraint_str = re.sub(r"(^|\b)=\b", "==", constraint_str)
return SemverMatcher(constraint_str)
class SemverMatcher:
"""Match a version tuple to a given `constraint_str`
The matching is handled via the `semver` package.
"""
def __init__(self, constraint_str):
self.constraint_str = constraint_str
def match(self, v):
while len(v) < 3:
v = v + (0,)
v_str = ".".join(map(str, v))
return semver.match(v_str, self.constraint_str)
def __eq__(self, rhs):
return self.constraint_str == rhs.constraint_str
def __repr__(self):
return self.constraint_str
class VersionRange:
"""Match a version tuple between lower and upper bounds
`upper_exclusivity` specifies if the upper bound is inclusive or exclusive.
"""
def __init__(self, lower, upper, upper_exclusivity):
self.lower = lower
self.upper = upper
self.upper_exclusivity = upper_exclusivity
def match(self, v):
if self.upper_exclusivity:
return self.lower <= v < self.upper
else:
return self.lower <= v <= self.upper
def __eq__(self, rhs):
return (
self.lower == rhs.lower
and self.upper == rhs.upper
and self.upper_exclusivity == rhs.upper_exclusivity
)
def __repr__(self):
return (
"["
+ ".".join(map(str, self.lower))
+ "-"
+ ".".join(map(str, self.upper))
+ (")" if self.upper_exclusivity else "]")
)