<reponame>jthop/bumper
#!/usr/bin/env python3
# ---------------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
# any purpose.
#
#  Author: <NAME> <<EMAIL>>
# --------------------------------------------------------------------------


import re


class SemVer:
    """
    :param major: version when you make incompatible API changes.
    :param minor: version when you add functionality in
                  a backwards-compatible manner.
    :param patch: version when you make backwards-compatible bug fixes.
    :param prerelease: an optional prerelease string
    :param build: an optional build string
    """

    RE_VER = re.compile(
        r"""
            ^
            (?P<major>0|[1-9]\d*)
            \.
            (?P<minor>0|[1-9]\d*)
            \.
            (?P<patch>0|[1-9]\d*)
            (?:-(?P<prerelease>
                (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
                (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
            ))?
            (?:\+(?P<build>
                [0-9a-zA-Z-]+
                (?:\.[0-9a-zA-Z-]+)*
            ))?
            $
        """,
        re.VERBOSE,
    )

    #: Regex for number in a prerelease
    RE_LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+")

    def __init__(self, major=0, minor=0, patch=0, prerelease=None, build=None):
        """
        instance initiated by parse() or you can manually
        v = SemVer(major=1, minor=1, patch=0)
        """

        self._major = int(major)
        self._minor = int(minor)
        self._patch = int(patch)
        self._prerelease = prerelease
        self._build = build

    @property
    def major(self):
        return self._major

    @major.setter
    def major(self, value):
        raise AttributeError("major is readonly")

    @property
    def minor(self):
        return self._minor

    @minor.setter
    def minor(self, value):
        raise AttributeError("minor is readonly")

    @property
    def patch(self):
        return self._patch

    @patch.setter
    def patch(self, value):
        raise AttributeError("patch is readonly")

    @property
    def prerelease(self):
        return self._prerelease

    @prerelease.setter
    def prerelease(self, value):
        raise AttributeError("prerelease is readonly")

    @property
    def build(self):
        return self._build

    @build.setter
    def build(self, value):
        raise AttributeError("build is readonly")

    def bump_major(self):
        self._major = self._major + 1
        self._minor = 0
        self._patch = 0
        self._prerelease = None
        self.bump_build()

    def bump_minor(self):
        self._minor = self._minor + 1
        self._patch = 0
        self._prerelease = None
        self.bump_build()

    def bump_patch(self):
        self._patch = self._patch + 1
        self._prerelease = None
        self.bump_build()

    def bump_prerelease(self):
        """
        Raise the build part of the version
        """
        cls = type(self)
        prerelease = cls._increment_string(self._prerelease or ("beta.0"))
        self._prerelease = prerelease

    def bump_build(self):
        """
        Raise the build part of the version
        """
        cls = type(self)
        build = cls._increment_string(self._build or ("build.0"))
        self._build = build

    def to_tuple(self):
        return (self.major, self.minor, self.patch, self.prerelease, self.build)

    def __iter__(self):
        """Return iter(self)."""
        yield from self.to_tuple()

    def __str__(self):
        version = f"{self._major}.{self._minor}.{self._patch}"
        if self._prerelease:
            version += f"-{self._prerelease}"
        if self._build:
            version += f"+{self._build}"
        return version

    def get_clean(self):
        """
        sometimes you don't need all the prerelease and build mess
        """
        clean = f"{self._major}.{self._minor}.{self._patch}"
        return clean

    @staticmethod
    def _increment_string(string):
        """
        Look for the last sequence of number(s) in a string and increment.
        :param string: the string to search for.
        :return: the incremented string
        Source:
        http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1
        """
        match = SemVer.RE_LAST_NUMBER.search(string)
        if match:
            next_ = str(int(match.group(1)) + 1)
            start, end = match.span(1)
            string = string[: max(end - len(next_), start)] + next_ + string[end:]
        return string

    @classmethod
    def parse(cls, version):
        """
        Parse version string to a Version instance.
        """
        if isinstance(version, bytes):
            version = version.decode("UTF-8")
        elif not isinstance(version, str):  # type: ignore
            raise TypeError(f"not expecting type {type(version)}")

        match = cls.RE_VER.match(version)
        if match is None:
            raise ValueError(f"{version} is not valid SemVer string")

        matched_version_parts = match.groupdict()
        return cls(**matched_version_parts)


#!/usr/bin/env python3
# ---------------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
# any purpose.
#
#  Author: <NAME> <<EMAIL>>
# --------------------------------------------------------------------------


import re


class SemVer:
    """
    :param major: version when you make incompatible API changes.
    :param minor: version when you add functionality in
                  a backwards-compatible manner.
    :param patch: version when you make backwards-compatible bug fixes.
    :param prerelease: an optional prerelease string
    :param build: an optional build string
    """

    RE_VER = re.compile(
        r"""
            ^
            (?P<major>0|[1-9]\d*)
            \.
            (?P<minor>0|[1-9]\d*)
            \.
            (?P<patch>0|[1-9]\d*)
            (?:-(?P<prerelease>
                (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
                (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
            ))?
            (?:\+(?P<build>
                [0-9a-zA-Z-]+
                (?:\.[0-9a-zA-Z-]+)*
            ))?
            $
        """,
        re.VERBOSE,
    )

    #: Regex for number in a prerelease
    RE_LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+")

    def __init__(self, major=0, minor=0, patch=0, prerelease=None, build=None):
        """
        instance initiated by parse() or you can manually
        v = SemVer(major=1, minor=1, patch=0)
        """

        self._major = int(major)
        self._minor = int(minor)
        self._patch = int(patch)
        self._prerelease = prerelease
        self._build = build

    @property
    def major(self):
        return self._major

    @major.setter
    def major(self, value):
        raise AttributeError("major is readonly")

    @property
    def minor(self):
        return self._minor

    @minor.setter
    def minor(self, value):
        raise AttributeError("minor is readonly")

    @property
    def patch(self):
        return self._patch

    @patch.setter
    def patch(self, value):
        raise AttributeError("patch is readonly")

    @property
    def prerelease(self):
        return self._prerelease

    @prerelease.setter
    def prerelease(self, value):
        raise AttributeError("prerelease is readonly")

    @property
    def build(self):
        return self._build

    @build.setter
    def build(self, value):
        raise AttributeError("build is readonly")

    def bump_major(self):
        self._major = self._major + 1
        self._minor = 0
        self._patch = 0
        self._prerelease = None
        self.bump_build()

    def bump_minor(self):
        self._minor = self._minor + 1
        self._patch = 0
        self._prerelease = None
        self.bump_build()

    def bump_patch(self):
        self._patch = self._patch + 1
        self._prerelease = None
        self.bump_build()

    def bump_prerelease(self):
        """
        Raise the build part of the version
        """
        cls = type(self)
        prerelease = cls._increment_string(self._prerelease or ("beta.0"))
        self._prerelease = prerelease

    def bump_build(self):
        """
        Raise the build part of the version
        """
        cls = type(self)
        build = cls._increment_string(self._build or ("build.0"))
        self._build = build

    def to_tuple(self):
        return (self.major, self.minor, self.patch, self.prerelease, self.build)

    def __iter__(self):
        """Return iter(self)."""
        yield from self.to_tuple()

    def __str__(self):
        version = f"{self._major}.{self._minor}.{self._patch}"
        if self._prerelease:
            version += f"-{self._prerelease}"
        if self._build:
            version += f"+{self._build}"
        return version

    def get_clean(self):
        """
        sometimes you don't need all the prerelease and build mess
        """
        clean = f"{self._major}.{self._minor}.{self._patch}"
        return clean

    @staticmethod
    def _increment_string(string):
        """
        Look for the last sequence of number(s) in a string and increment.
        :param string: the string to search for.
        :return: the incremented string
        Source:
        http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1
        """
        match = SemVer.RE_LAST_NUMBER.search(string)
        if match:
            next_ = str(int(match.group(1)) + 1)
            start, end = match.span(1)
            string = string[: max(end - len(next_), start)] + next_ + string[end:]
        return string

    @classmethod
    def parse(cls, version):
        """
        Parse version string to a Version instance.
        """
        if isinstance(version, bytes):
            version = version.decode("UTF-8")
        elif not isinstance(version, str):  # type: ignore
            raise TypeError(f"not expecting type {type(version)}")

        match = cls.RE_VER.match(version)
        if match is None:
            raise ValueError(f"{version} is not valid SemVer string")

        matched_version_parts = match.groupdict()
        return cls(**matched_version_parts)
#!/usr/bin/env python3 # --------------------------------------------------------------------------- # This software is in the public domain, furnished "as is", without technical # support, and with no warranty, express or implied, as to its usefulness for # any purpose. # #  Author: <NAME> <<EMAIL>> # -------------------------------------------------------------------------- :param major: version when you make incompatible API changes.     :param minor: version when you add functionality in                   a backwards-compatible manner.     :param patch: version when you make backwards-compatible bug fixes.     :param prerelease: an optional prerelease string     :param build: an optional build string ^             (?P<major>0|[1-9]\d*)             \.             (?P<minor>0|[1-9]\d*)             \.             (?P<patch>0|[1-9]\d*)             (?:-(?P<prerelease>                 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)                 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*             ))?             (?:\+(?P<build>                 [0-9a-zA-Z-]+                 (?:\.[0-9a-zA-Z-]+)*             ))?             $ #: Regex for number in a prerelease instance initiated by parse() or you can manually         v = SemVer(major=1, minor=1, patch=0) Raise the build part of the version Raise the build part of the version Return iter(self). sometimes you don't need all the prerelease and build mess Look for the last sequence of number(s) in a string and increment.         :param string: the string to search for.         :return: the incremented string         Source:         http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 Parse version string to a Version instance. # type: ignore