From f63d41142ae96d642a77f893b4dd1a4f1fce30e4 Mon Sep 17 00:00:00 2001 From: Dani Pinyol Date: Fri, 11 Oct 2024 11:55:03 +0200 Subject: [PATCH] Support prerelease in version --- src/juliapkg/compat.py | 60 ++++++++++++++++++++++++++++++++++-------- test/test_compat.py | 6 +++++ 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/juliapkg/compat.py b/src/juliapkg/compat.py index 4eb3bdc..19911e9 100644 --- a/src/juliapkg/compat.py +++ b/src/juliapkg/compat.py @@ -2,16 +2,26 @@ from semver import Version -_re_partial_version = re.compile(r"^([0-9]+)(?:\.([0-9]+)(?:\.([0-9]+))?)?$") +_re_digits_g = "[0-9]+" + + +def _re_group(inner="", numeric=True, delimiter="."): + capture = _re_digits_g if numeric else ".*" + return f"(?:{delimiter}({capture}){inner})?" + + +_re_prerelease = _re_group(numeric=False) +_re_optional = _re_group(_re_group(_re_prerelease)) +_re_partial_version = re.compile(rf"^({_re_digits_g}){_re_optional}$") def _parse_partial_version(x): m = _re_partial_version.match(x) if m is None: return None, None - major, minor, patch = m.groups() - v = Version(major, minor or 0, patch or 0) - n = 1 if minor is None else 2 if patch is None else 3 + major, minor, patch, prerelease = m.groups() + v = Version(major, minor or 0, patch or 0, prerelease) + n = 1 if minor is None else 2 if patch is None else 3 if prerelease is None else 4 return (v, n) @@ -87,6 +97,7 @@ def caret(cls, v, n): v.major, v.minor if n >= 2 else 0, v.patch if n >= 3 else 0, + v.prerelease if n >= 4 else None, ) hi = ( v.bump_major() @@ -94,13 +105,18 @@ def caret(cls, v, n): else v.bump_minor() if v.minor != 0 or n < 3 else v.bump_patch() + if v.patch != 0 or n < 4 + else v.bump_prerelease() ) return Range(lo, hi) @classmethod def equality(cls, v): lo = v - hi = v.bump_patch() + if lo.prerelease is not None: + hi = v.bump_prerelease() + else: + hi = v.bump_patch() return Range(lo, hi) @classmethod @@ -120,7 +136,7 @@ def parse(cls, x): elif x.startswith("="): # equality specifier v, n = _parse_partial_version(x[1:]) - if v is not None and n == 3: + if v is not None and n >= 3: return cls.equality(v) elif " - " in x: # range specifier @@ -140,26 +156,40 @@ def __str__(self): lo = self.lo hi = self.hi if self == Range.equality(lo): - return f"={lo.major}.{lo.minor}.{lo.patch}" + prerelease = f"-{lo.prerelease}" if lo.prerelease else "" + return f"={lo.major}.{lo.minor}.{lo.patch}{prerelease}" if self == Range.caret(lo, 1): return f"^{lo.major}" if self == Range.caret(lo, 2): return f"^{lo.major}.{lo.minor}" if self == Range.caret(lo, 3): return f"^{lo.major}.{lo.minor}.{lo.patch}" + if self == Range.caret(lo, 4): + return f"^{lo.major}.{lo.minor}.{lo.patch}-{lo.prerelease}" if self == Range.tilde(lo, 1): return f"~{lo.major}" if self == Range.tilde(lo, 2): return f"~{lo.major}.{lo.minor}" if self == Range.tilde(lo, 3): return f"~{lo.major}.{lo.minor}.{lo.patch}" - lostr = f"{lo.major}.{lo.minor}.{lo.patch}" + if self == Range.tilde(lo, 4): + return f"~{lo.major}.{lo.minor}.{lo.patch}-{lo.prerelease}" + if lo.prerelease is None: + lostr = f"{lo.major}.{lo.minor}.{lo.patch}" + else: + lostr = f"{lo.major}.{lo.minor}.{lo.patch}-{lo.prerelease}" + hi_str = "" if hi.major > 0 and hi.minor == 0 and hi.patch == 0: return f"{lostr} - {hi.major-1}" + hi_str += f"{hi.major}" if hi.minor > 0 and hi.patch == 0: - return f"{lostr} - {hi.major}.{hi.minor-1}" - if hi.patch > 0: - return f"{lostr} - {hi.major}.{hi.minor}.{hi.patch-1}" + return f"{lostr} - {hi_str}.{hi.minor-1}" + hi_str += f".{hi.minor}" + if hi.patch > 0 and hi.prerelease is None: + return f"{lostr} - {hi_str}.{hi.patch-1}" + hi_str += f".{hi.patch}" + if hi.prerelease is not None: + return f"{lostr} - {hi_str}-{self._decrease_string(hi.prerelease)}" raise ValueError("invalid range") def __repr__(self): @@ -180,3 +210,11 @@ def __eq__(self, other): def is_empty(self): return not (self.lo < self.hi) + + def _decrease_string(self, string): + match = Version._LAST_NUMBER.search(string) + if match: + prev_ = str(int(match.group(1)) - 1) + start, end = match.span(1) + string = string[: max(end - len(prev_), start)] + prev_ + string[end:] + return string diff --git a/test/test_compat.py b/test/test_compat.py index 3d15b31..d5564d6 100644 --- a/test/test_compat.py +++ b/test/test_compat.py @@ -1,4 +1,5 @@ import pytest + from juliapkg.compat import Compat, Range, Version v = Version.parse @@ -34,6 +35,7 @@ class TestRange: ("~0", Range(v("0.0.0"), v("1.0.0"))), # equality ("=1.2.3", Range(v("1.2.3"), v("1.2.4"))), + ("=1.2.3-rc4", Range(v("1.2.3-rc4"), v("1.2.3-rc5"))), # hyphen ("1.2.3 - 4.5.6", Range(v("1.2.3"), v("4.5.7"))), ("0.2.3 - 4.5.6", Range(v("0.2.3"), v("4.5.7"))), @@ -64,6 +66,7 @@ def test_parse(self, input, expected_output): [ (Range(v("0.0.3"), v("0.0.4")), "=0.0.3"), (Range(v("1.2.3"), v("1.2.4")), "=1.2.3"), + (Range(v("1.2.3-rc4"), v("1.2.3-rc5")), "=1.2.3-rc4"), (Range(v("1.2.3"), v("2.0.0")), "^1.2.3"), (Range(v("1.2.0"), v("2.0.0")), "^1.2"), (Range(v("1.0.0"), v("2.0.0")), "^1"), @@ -163,6 +166,7 @@ class TestCompat: [ ("", Compat([])), ("1.2.3", Compat([Range(v("1.2.3"), v("2.0.0"))])), + ("1.2.3-rc4", Compat([Range(v("1.2.3-rc4"), v("2.0.0"))])), ( "1, 2.3, 4.5.6", Compat( @@ -184,6 +188,8 @@ def test_parse(self, input, expected_output): [ (Compat([]), ""), (Compat([Range(v("1.2.3"), v("2.0.0"))]), "^1.2.3"), + (Compat([Range(v("1.2.3-rc4"), v("2.0.0"))]), "^1.2.3-rc4"), + (Compat([Range(v("1.2.2"), v("1.2.3-rc4"))]), "1.2.2 - 1.2.3-rc3"), ( Compat( [