Files

48 lines
1.6 KiB
Python

"""Compare stable and canary versions accepted by release feeds.
Canary identity uses SemVer build metadata (``X.Y.Z+canary.<stamp>``) and
compares equal to its stable. Channel records, not SemVer precedence, decide
which canary is newer.
"""
from __future__ import annotations
import re
from hermes_cli.update_channel import STABLE_TAG_RE
_CANARY_VERSION_RE = re.compile(r"^[0-9.]+[+]canary[.]20\d{6}T\d{6}Z$")
def is_canary_version(version: str) -> bool:
"""True for the canonical build-metadata canary identity."""
if not isinstance(version, str) or not _CANARY_VERSION_RE.fullmatch(version):
return False
return bool(STABLE_TAG_RE.fullmatch("v" + version.split("+", 1)[0]))
def is_stable_version(version: str) -> bool:
"""True for a final stable version."""
return isinstance(version, str) and bool(STABLE_TAG_RE.fullmatch("v" + version))
def is_valid_version(version: str) -> bool:
"""True for a stable version or canonical canary identity."""
return is_stable_version(version) or is_canary_version(version)
def is_release_version(version: str) -> bool:
"""True for a stable version or canonical canary identity."""
return is_valid_version(version)
def _core(version: str) -> list[int]:
return [int(part) for part in version.split("+", 1)[0].split(".")]
def compare(a: str, b: str) -> int:
"""Compare release versions while ignoring build metadata."""
if not is_release_version(a) or not is_release_version(b):
raise ValueError(f"invalid release version(s): {a!r}, {b!r}")
ka, kb = _core(a), _core(b)
return (ka > kb) - (ka < kb)