Skip to content

types.semver

SemVer

Read-only representation a semantically versioned string. Reference: https://semver.org/spec/v2.0.0.html

Source code in /home/runner/work/arc/arc/arc/types/semver.py
class SemVer:
    """Read-only representation a semantically versioned string. Reference: https://semver.org/spec/v2.0.0.html"""

    _prerelease_prefix = "-"
    _build_prefix = "+"
    _metadata_sep = "."

    def __init__(
        self,
        major: int = 0,
        minor: int = 0,
        patch: int = 0,
        prerelease: t.Optional[str] = None,
        build: t.Optional[str] = None,
    ):
        self._major: int = major
        self._minor: int = minor
        self._patch: int = patch
        self._prerelease: tuple[str, ...] = (
            tuple(prerelease.split(self._metadata_sep)) if prerelease else tuple()
        )
        self._build: tuple[str, ...] = (
            tuple(build.split(self._metadata_sep)) if build else tuple()
        )

    def __str__(self) -> str:
        string = f"{self.major}.{self.minor}.{self.patch}"
        if self.prerelease:
            string += (
                f"{self._prerelease_prefix}{self._metadata_sep.join(self.prerelease)}"
            )
        if self.build:
            string += f"{self._build_prefix}{self._metadata_sep.join(self.build)}"

        return string

    def __repr__(self) -> str:
        return f"SemVer({str(self)!r})"

    def __iter__(self) -> t.Iterator[t.Any]:
        yield from (a for a in self.tuple() if a)

    @property
    def major(self) -> int:
        return self._major

    @property
    def minor(self) -> int:
        return self._minor

    @property
    def patch(self) -> int:
        return self._patch

    @property
    def prerelease(self) -> tuple[str, ...]:
        return self._prerelease

    @property
    def build(self) -> tuple[str, ...]:
        return self._build

    # Comparison Operators --------------------------------------------------------------------

    def compare(self, other: object) -> CompareReturn:
        """Compares `self` with `other`

        Args:
            other (SemVer): the second version for comparison

        Returns:
            at.CompareReturn: is negative if self < other,
                zero if self == other and strictly positive if self > other
        """

        if not isinstance(other, SemVer):
            return NotImplemented

        tup1, tup2 = self.tuple(), other.tuple()

        # Their version numbers are different,
        # no need to check the prerelease data
        comp = cmp(tup1[:3], tup2[:3])
        if comp:
            return comp

        # Their version numbers are the same
        # and we need to compare the prerelease data
        pr1, pr2 = self.prerelease, other.prerelease

        # No prerelease data for one or both versions
        if not comp and not pr1 and not pr2:
            return comp
        elif not pr1:
            return 1
        elif not pr2:
            return -1

        pr1 = tuple(int(p) if p.isnumeric() else p for p in pr1)  # type: ignore
        pr2 = tuple(int(p) if p.isnumeric() else p for p in pr2)  # type: ignore

        for part1, part2 in zip(pr1, pr2):
            comp = self._cmp_prerelease_tag(part1, part2)
            if comp:
                return comp

        return cmp(len(pr1), len(pr2))

    def _cmp_prerelease_tag(
        self, a: t.Union[str, int], b: t.Union[str, int]
    ) -> CompareReturn:
        """Compares two prerelease tags given the following conditions:
        - Identifiers consisting of only digits are compared numerically.
        - Identifiers with letters or hyphens are compared lexically in ASCII sort order.
        - Numeric identifiers always have lower precedence than non-numeric identifiers.
        """
        # Compared numerically
        if isinstance(a, int) and isinstance(b, int):
            return cmp(a, b)
        # Numeric identifiers have lower priority
        elif isinstance(a, int):
            return -1
        elif isinstance(b, int):
            return 1

        # Compared lexically
        return cmp(a, b)

    def __eq__(self, other: object) -> bool:
        return self.compare(other) == 0

    def __ne__(self, other: object) -> bool:
        return self.compare(other) != 0

    def __lt__(self, other: object) -> bool:
        return self.compare(other) == -1

    def __le__(self, other: object) -> bool:
        return self.compare(other) <= 0

    def __gt__(self, other: object) -> bool:
        return self.compare(other) == 1

    def __ge__(self, other: object) -> bool:
        return self.compare(other) >= 0

    # Utility Functions -----------------------------------------------------------------------

    def tuple(self) -> tuple[int, int, int, tuple[str, ...], tuple[str, ...]]:
        return (self.major, self.minor, self.patch, self.prerelease, self.build)

    def dict(self) -> dict[str, t.Any]:
        return {
            "major": self.major,
            "minor": self.minor,
            "patch": self.patch,
            "prerelease": self.prerelease,
            "build": self.build,
        }

    def bump_major(self) -> SemVer:
        cls = type(self)
        return cls(self.major + 1)

    def bump_minor(self) -> SemVer:
        cls = type(self)
        return cls(self.major, self.minor + 1)

    def bump_patch(self) -> SemVer:
        cls = type(self)
        return cls(self.major, self.minor, self.patch + 1)

    # Conversion Utilities --------------------------------------------------------------------

    @classmethod
    def parse(cls, string: str) -> SemVer:
        match = SEMVAR_REGEX.match(string)
        if not match:
            raise ValueError("Invalid semantic version string")

        groups = match.groupdict()

        return cls(
            major=int(groups["major"]),
            minor=int(groups["minor"]),
            patch=int(groups["patch"]),
            prerelease=groups["prerelease"],
            build=groups["build"],
        )

    @classmethod
    def __convert__(cls, value: str) -> SemVer:
        try:
            return cls.parse(value)
        except ValueError as e:
            raise errors.ConversionError(value, str(e)) from e

compare(other)

Compares self with other

Parameters:

Name Type Description Default
other SemVer

the second version for comparison

required

Returns:

Type Description
CompareReturn

at.CompareReturn: is negative if self < other, zero if self == other and strictly positive if self > other

Source code in /home/runner/work/arc/arc/arc/types/semver.py
def compare(self, other: object) -> CompareReturn:
    """Compares `self` with `other`

    Args:
        other (SemVer): the second version for comparison

    Returns:
        at.CompareReturn: is negative if self < other,
            zero if self == other and strictly positive if self > other
    """

    if not isinstance(other, SemVer):
        return NotImplemented

    tup1, tup2 = self.tuple(), other.tuple()

    # Their version numbers are different,
    # no need to check the prerelease data
    comp = cmp(tup1[:3], tup2[:3])
    if comp:
        return comp

    # Their version numbers are the same
    # and we need to compare the prerelease data
    pr1, pr2 = self.prerelease, other.prerelease

    # No prerelease data for one or both versions
    if not comp and not pr1 and not pr2:
        return comp
    elif not pr1:
        return 1
    elif not pr2:
        return -1

    pr1 = tuple(int(p) if p.isnumeric() else p for p in pr1)  # type: ignore
    pr2 = tuple(int(p) if p.isnumeric() else p for p in pr2)  # type: ignore

    for part1, part2 in zip(pr1, pr2):
        comp = self._cmp_prerelease_tag(part1, part2)
        if comp:
            return comp

    return cmp(len(pr1), len(pr2))

cmp(a, b)

Compare two values

Parameters:

Name Type Description Default
a Any

First value

required
b Any

Second value

required

Returns:

Type Description
CompareReturn
  • a < b => -1
CompareReturn
  • a == b => 0
CompareReturn
  • a > b => 1
Source code in /home/runner/work/arc/arc/arc/types/semver.py
def cmp(a: t.Any, b: t.Any) -> CompareReturn:
    """Compare two values

    Args:
        a (Any): First value
        b (Any): Second value

    Returns:
        - `a < b  => -1`
        - `a == b =>  0`
        - `a > b  =>  1`
    """
    return (a > b) - (a < b)