diff --git a/src/univers/intdot.py b/src/univers/intdot.py new file mode 100644 index 00000000..b48af4d5 --- /dev/null +++ b/src/univers/intdot.py @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. + +import re + + +class IntdotVersion: + """ + intdot version. + + The regex pattern for the intdot version is any number (>0) of integers separated by dots, followed by an arbitrary number of other characters, e.g., 1.2.3.5543, 123.234-prerelease, 1.2.3alpha + """ + + VERSION_PATTERN = r"^(\d+(\.\d+)*)(.*)$" + + def __init__(self, version): + if not self.is_valid(version): + raise InvalidVersionError(version) + + version = str(version).strip() + self.original = version + + def __eq__(self, other): + return self.original == other.original + + def __lt__(self, other): + return self.__cmp__(other) < 0 + + def __le__(self, other): + return self.__cmp__(other) <= 0 + + def __gt__(self, other): + return self.__cmp__(other) > 0 + + def __ge__(self, other): + return self.__cmp__(other) >= 0 + + @classmethod + def is_valid(cls, string): + return re.compile(IntdotVersion.VERSION_PATTERN).match(string) + + def extract_numeric_labels(self, version): + """ + Check if the version matches the pattern; if it matches, extract the first group (identified by parentheses) which is the numeric part of the version + """ + match = re.match(IntdotVersion.VERSION_PATTERN, version) + + if match: + version_labels = match.group(1) + return version_labels + else: + raise InvalidVersionError(version) + + def __cmp__(self, other): + """ + Compare this version with ``other`` returning -1, 0, or 1 if the + other version is larger, the same, or smaller than this + one. + """ + if isinstance(other, str): + other = IntdotVersion(other) + + if not isinstance(other, IntdotVersion): + raise InvalidVersionError + + if self.original == other.original: + return 0 + + lhlabels = self.extract_numeric_labels(self.original) + rhlabels = self.extract_numeric_labels(other.original) + + if lhlabels == rhlabels: + return 0 + + lhsize = len(lhlabels) + rhsize = len(rhlabels) + + if lhsize > rhsize: + limit = lhsize + else: + limit = rhsize + + limit -= 1 + + i = 0 + + while i <= limit: + try: + lhs = lhlabels[i] + except IndexError: + lhs = 0 + + try: + rhs = rhlabels[i] + except IndexError: + rhs = 0 + + i += 1 + + if lhs == rhs: + continue + + if int(lhs) > int(rhs): + return 1 + if int(lhs) < int(rhs): + return -1 + return 0 + + +class InvalidVersionError(ValueError): + pass diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 4c2a0baf..76539006 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -968,6 +968,11 @@ class GolangVersionRange(VersionRange): } +class IntdotVersionRange(VersionRange): + scheme = "intdot" + version_class = versions.IntdotVersion + + class GenericVersionRange(VersionRange): scheme = "generic" version_class = versions.SemverVersion @@ -1440,6 +1445,7 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) "conan": ConanVersionRange, "all": AllVersionRange, "none": NoneVersionRange, + "intdot": IntdotVersionRange, } PURL_TYPE_BY_GITLAB_SCHEME = { diff --git a/src/univers/versions.py b/src/univers/versions.py index c06f4ff0..cf8c0bc7 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -12,6 +12,7 @@ from univers import debian from univers import gem from univers import gentoo +from univers import intdot from univers import maven from univers import nuget from univers import rpm @@ -145,6 +146,16 @@ def is_valid(cls, string): return string == "vers:none/*" +class IntdotVersion(Version): + @classmethod + def build_value(cls, string): + return intdot.IntdotVersion(string) + + @classmethod + def is_valid(cls, string): + return intdot.IntdotVersion.is_valid(string) + + class GenericVersion(Version): @classmethod def is_valid(cls, string): @@ -714,4 +725,5 @@ def bump(self, index): OpensslVersion, LegacyOpensslVersion, AlpineLinuxVersion, + IntdotVersion, ] diff --git a/tests/test_version_range.py b/tests/test_version_range.py index be4d9215..16cac2dc 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -13,6 +13,7 @@ from univers.version_constraint import VersionConstraint from univers.version_range import PURL_TYPE_BY_GITLAB_SCHEME from univers.version_range import RANGE_CLASS_BY_SCHEMES +from univers.version_range import IntdotVersionRange from univers.version_range import InvalidVersionRange from univers.version_range import MattermostVersionRange from univers.version_range import OpensslVersionRange @@ -20,6 +21,7 @@ from univers.version_range import VersionRange from univers.version_range import build_range_from_snyk_advisory_string from univers.version_range import from_gitlab_native +from univers.versions import IntdotVersion from univers.versions import OpensslVersion from univers.versions import PypiVersion from univers.versions import SemverVersion @@ -355,3 +357,12 @@ def test_version_range_none(): VersionRange.from_string("vers:none/!1.2.3") with pytest.raises(Exception): VersionRange.from_string("vers:none/*|>1.2.3") + + +def test_version_range_intdot(): + intdot_range = IntdotVersionRange.from_string("vers:intdot/>1.2.3.4") + assert IntdotVersion("1.3.3") in intdot_range + assert IntdotVersion("0.3.3") not in intdot_range + assert IntdotVersion("1.3.3alpha") in intdot_range + assert IntdotVersion("1.2.2.pre") not in intdot_range + assert IntdotVersion("1010.23.234203.0") in IntdotVersionRange.from_string("vers:intdot/*") diff --git a/tests/test_versions.py b/tests/test_versions.py index 57ded4c0..7ceb80cb 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -12,6 +12,7 @@ from univers.versions import EnhancedSemanticVersion from univers.versions import GentooVersion from univers.versions import GolangVersion +from univers.versions import IntdotVersion from univers.versions import MavenVersion from univers.versions import NginxVersion from univers.versions import NugetVersion @@ -218,3 +219,14 @@ def test_golang_version(): assert GolangVersion("v0.1.1") >= GolangVersion("v0.1.1") assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.1") assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.2") + + +def test_intdot_version(): + assert IntdotVersion("1.2.3.4.5") == IntdotVersion("1.2.3.4.5") + assert IntdotVersion("1.2.3.4.6") > IntdotVersion("1.2.3.4.5") + assert IntdotVersion("1.2.3.4.6") < IntdotVersion("2.2.3.4.5") + assert IntdotVersion("1.2.3.4.6") <= IntdotVersion("2.2.3.4.5") + assert IntdotVersion("1.2.3.4.6-pre") <= IntdotVersion("2.2.3.4.5") + assert IntdotVersion("1.2.3.4.6-pre") <= IntdotVersion("2.2.3.4.5.pre") + assert IntdotVersion("1.2.3.4.6-pre") <= IntdotVersion("2.2.3.4.5-10") + assert IntdotVersion("1.2.3.4.6-pre") <= IntdotVersion("2.2.3.4.5-10")