from __future__ import annotations
import re
import sys
SEMVER_RE = re.compile(
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)"
r"(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$"
)
def parse(version: str) -> tuple[int, int, int, list[str]]:
match = SEMVER_RE.fullmatch(version)
if not match:
raise ValueError(f"not valid SemVer: {version!r}")
major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3))
pre = match.group(4)
prerelease = pre.split(".") if pre else []
return (major, minor, patch, prerelease)
def _compare_prerelease(a: list[str], b: list[str]) -> int:
if not a and not b:
return 0
if not a:
return 1
if not b:
return -1
for x, y in zip(a, b):
if x == y:
continue
x_is_num = x.isdigit()
y_is_num = y.isdigit()
if x_is_num and y_is_num:
xi, yi = int(x), int(y)
return -1 if xi < yi else (1 if xi > yi else 0)
if x_is_num and not y_is_num:
return -1 if y_is_num and not x_is_num:
return 1
return -1 if x < y else 1
return -1 if len(a) < len(b) else (1 if len(a) > len(b) else 0)
def compare(current: str, previous: str) -> int:
c = parse(current)
p = parse(previous)
for i in range(3):
if c[i] != p[i]:
return -1 if c[i] < p[i] else 1
return _compare_prerelease(c[3], p[3])
def main() -> int:
if len(sys.argv) != 3:
print(
"usage: release_version_compare.py CURRENT PREVIOUS",
file=sys.stderr,
)
return 2
current, previous = sys.argv[1], sys.argv[2]
try:
result = compare(current, previous)
except ValueError as error:
print(f"release_version_compare: {error}", file=sys.stderr)
return 1
if result <= 0:
print(
f"release version {current} must be newer than {previous}",
file=sys.stderr,
)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())