From 5c0119d1fb3f0b600f86a1b71e0124b0baa51216 Mon Sep 17 00:00:00 2001 From: "Kunz, Immanuel" Date: Thu, 21 Nov 2024 17:25:27 +0100 Subject: [PATCH 1/4] intdot versioning scheme Signed-off-by: Kunz, Immanuel --- src/univers/intdot.py | 112 +++++++++++++++++++++++++++++++++++ src/univers/version_range.py | 7 ++- src/univers/versions.py | 12 ++++ tests/test_version_range.py | 10 ++++ tests/test_versions.py | 12 ++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/univers/intdot.py 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..1bc77bf1 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,7 +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 = { "gem": "gem", 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..abf64ce4 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -355,3 +355,13 @@ 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") From 7e3ee26864e4bc1f1ca6038a46a0e8027a24f165 Mon Sep 17 00:00:00 2001 From: "Kunz, Immanuel" Date: Mon, 18 Aug 2025 17:44:06 +0200 Subject: [PATCH 2/4] code style; fix typo Signed-off-by: Kunz, Immanuel --- src/univers/version_range.py | 1 + tests/test_version_range.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 1bc77bf1..76539006 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -1446,6 +1446,7 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List]) "all": AllVersionRange, "none": NoneVersionRange, "intdot": IntdotVersionRange, +} PURL_TYPE_BY_GITLAB_SCHEME = { "gem": "gem", diff --git a/tests/test_version_range.py b/tests/test_version_range.py index abf64ce4..fff4630c 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -364,4 +364,3 @@ def test_version_range_intdot(): 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/*") - From 322e482f1e85fd7a7bd8779b953ec1e8075f91e5 Mon Sep 17 00:00:00 2001 From: "Kunz, Immanuel" Date: Mon, 18 Aug 2025 17:53:49 +0200 Subject: [PATCH 3/4] add missing import Signed-off-by: Kunz, Immanuel --- tests/test_version_range.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_version_range.py b/tests/test_version_range.py index fff4630c..f4078aaf 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -10,9 +10,11 @@ import pytest from tests import SchemaDrivenVersTest +from univers.intdot import IntdotVersion 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 From 9a4d062c5abab1e0ecfd5549671ea3d77a1cab40 Mon Sep 17 00:00:00 2001 From: "Kunz, Immanuel" Date: Mon, 18 Aug 2025 18:17:24 +0200 Subject: [PATCH 4/4] fix failing test Signed-off-by: Kunz, Immanuel --- tests/test_version_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_version_range.py b/tests/test_version_range.py index f4078aaf..16cac2dc 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -10,7 +10,6 @@ import pytest from tests import SchemaDrivenVersTest -from univers.intdot import IntdotVersion 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 @@ -22,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