"""Semantic Versioning 2.0.0 parsing and comparison. Handles the tricky bits: prerelease precedence (numeric vs identifier), build metadata ignored in equality, leading zeros forbidden in numeric identifiers, and whitespace rejection. """ import re _SEMVER_RE = re.compile( r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)" r"(?:-(?P
[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?"
    r"(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$"
)


class SemVerError(ValueError):
    pass


class SemVer:
    __slots__ = ("major", "minor", "patch", "pre", "build")

    def __init__(self, major, minor, patch, pre=(), build=()):
        self.major = major
        self.minor = minor
        self.patch = patch
        self.pre = tuple(pre)
        self.build = tuple(build)

    @classmethod
    def parse(cls, s):
        if not isinstance(s, str) or s != s.strip() or not s:
            raise SemVerError("version must be a non-empty trimmed string")
        m = _SEMVER_RE.fullmatch(s)
        if not m:
            raise SemVerError(f"invalid semver: {s!r}")
        pre = tuple(m.group("pre").split(".")) if m.group("pre") else ()
        build = tuple(m.group("build").split(".")) if m.group("build") else ()
        # validate numeric identifiers have no leading zeros
        for ident in pre:
            if ident.isdigit() and len(ident) > 1 and ident[0] == "0":
                raise SemVerError(f"leading zero in numeric prerelease id: {ident!r}")
        for ident in build:
            if ident.isdigit() and len(ident) > 1 and ident[0] == "0":
                raise SemVerError(f"leading zero in numeric build id: {ident!r}")
        return cls(int(m.group("major")), int(m.group("minor")), int(m.group("patch")), pre, build)

    def _pre_key(self):
        # A version without prerelease has HIGHER precedence than one with.
        if not self.pre:
            return (1, )
        out = (0, )
        for ident in self.pre:
            if ident.isdigit():
                out += (0, int(ident))
            else:
                out += (1, ident)
        return out

    def __eq__(self, other):
        if not isinstance(other, SemVer):
            return NotImplemented
        return (self.major, self.minor, self.patch, self.pre) == (other.major, other.minor, other.patch, other.pre)

    def __lt__(self, other):
        if not isinstance(other, SemVer):
            return NotImplemented
        if (self.major, self.minor, self.patch) != (other.major, other.minor, other.patch):
            return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
        return self._pre_key() < other._pre_key()

    def __le__(self, other):
        return self == other or self < other

    def __gt__(self, other):
        return not (self <= other)

    def __ge__(self, other):
        return not (self < other)

    def __hash__(self):
        return hash((self.major, self.minor, self.patch, self.pre))

    def __str__(self):
        s = f"{self.major}.{self.minor}.{self.patch}"
        if self.pre:
            s += "-" + ".".join(self.pre)
        if self.build:
            s += "+" + ".".join(self.build)
        return s

    def __repr__(self):
        return f"SemVer({self!s})"

    def bump(self, part):
        if part == "major":
            return SemVer(self.major + 1, 0, 0)
        if part == "minor":
            return SemVer(self.major, self.minor + 1, 0)
        if part == "patch":
            return SemVer(self.major, self.minor, self.patch + 1)
        raise SemVerError(f"unknown bump part: {part!r}")