Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cargo version range #84

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 90 additions & 10 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from univers.utils import remove_spaces
from univers.version_constraint import VersionConstraint
from univers.version_constraint import contains_version
from univers.versions import CargoVersion


class InvalidVersionRange(Exception):
Expand Down Expand Up @@ -222,9 +223,9 @@ def __contains__(self, version):

def __eq__(self, other):
return (
self.scheme == other.scheme
and self.version_class == other.version_class
and self.constraints == other.constraints
self.scheme == other.scheme
and self.version_class == other.version_class
and self.constraints == other.constraints
)


Expand Down Expand Up @@ -299,7 +300,8 @@ class NpmVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
"=": "=",
# This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
ziadhany marked this conversation as resolved.
Show resolved Hide resolved
}

@classmethod
Expand Down Expand Up @@ -353,9 +355,9 @@ def from_native(cls, string):
)
else:
if (
constraint.endswith(".x")
or constraint.startswith("~")
or constraint.startswith("^")
constraint.endswith(".x")
or constraint.startswith("~")
or constraint.startswith("^")
):
constraints.extend(
get_npm_version_constraints_from_semver_npm_spec(
Expand Down Expand Up @@ -797,7 +799,8 @@ class ComposerVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
"=": "=",
# This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
ziadhany marked this conversation as resolved.
Show resolved Hide resolved
}


Expand Down Expand Up @@ -899,7 +902,8 @@ class GolangVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
"=": "=",
# This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
ziadhany marked this conversation as resolved.
Show resolved Hide resolved
}


Expand All @@ -922,7 +926,83 @@ class HexVersionRange(VersionRange):

class CargoVersionRange(VersionRange):
scheme = "cargo"
version_class = versions.SemverVersion
version_class = versions.CargoVersion

@classmethod
def from_native(cls, string):
"""
Return a VersionRange built from a scheme-specific, native version range
``string``. Subclasses can implement.
"""
if string == "*":
return cls(
constraints=(VersionConstraint(comparator="*", version_class=cls.version_class),)
)

constraints = []
cleaned_string = remove_spaces(string).lower()
for version in cleaned_string.split(","):
if not version:
raise InvalidVersionRange(f"InvalidVersionRange : {string}")

# wildcard
if "*" in version:
if "*" in version and not version.endswith("*") or constraints:
raise InvalidVersionRange(
f"Unsupported star in the middle of a version: it should be a trailing star only: {string}")

if version.endswith(".*.*"):
version = version.replace(".*.*", ".*")

segments_count = len(version.split("."))
lower_bound = cls.version_class(version.replace("*", "0"))
if segments_count == 2:
upper_bound = cls.version_class(str(lower_bound.next_major()))
elif segments_count == 3:
upper_bound = cls.version_class(str(lower_bound.next_minor()))
else:
raise InvalidVersionRange(f"Invalid version: not a semver version: {string}")

vstart = VersionConstraint(comparator=">=", version=lower_bound)
vend = VersionConstraint(comparator="<", version=upper_bound)
constraints.extend([vstart, vend])

# caret
elif version.startswith("^"):
version = version.lstrip("^")
upper_bound = None
lower_bound = cls.version_class(version)
if (lower_bound.major != 0 or version == "0") and version != "0.0":
upper_bound = cls.version_class(str(lower_bound.value.next_major()))
else:
if lower_bound.minor != 0 or version == "0.0":
upper_bound = cls.version_class(str(lower_bound.value.next_minor()))
elif lower_bound.patch != 0:
upper_bound = cls.version_class(str(lower_bound.value.next_patch()))

vstart = VersionConstraint(comparator=">=", version=lower_bound)
vend = VersionConstraint(comparator="<", version=upper_bound)
constraints.extend([vstart, vend])

# tilde
elif version.startswith("~"):
version = version.lstrip("~")
lower_bound = cls.version_class(version)
if lower_bound.minor == 0 and lower_bound.patch == 0:
upper_bound = cls.version_class(str(lower_bound.value.next_major()))
else:
upper_bound = cls.version_class(str(lower_bound.value.next_minor()))

vstart = VersionConstraint(comparator=">=", version=lower_bound)
vend = VersionConstraint(comparator="<", version=upper_bound)
constraints.extend([vstart, vend])

else:
comparator, version = VersionConstraint.split(version)
constraint = VersionConstraint(comparator=comparator, version=cls.version_class(version))
constraints.append(constraint)

return cls(constraints=tuple(constraints))


class MozillaVersionRange(VersionRange):
Expand Down
6 changes: 6 additions & 0 deletions src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Each subclass primary responsibility to is be comparable and orderable
"""


# TODO: Add mozilla versions https://github.com/mozilla-releng/mozilla-version
# TODO: Add conda versions https://github.com/conda/conda/blob/master/conda/models/version.py
# and https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#build-version-spec
Expand Down Expand Up @@ -686,6 +687,10 @@ def bump(self, index):
return self.value and self.value.bump(index)


class CargoVersion(SemverVersion):
pass


AVAILABLE_VERSIONS = [
SemverVersion,
GolangVersion,
Expand All @@ -702,4 +707,5 @@ def bump(self, index):
OpensslVersion,
LegacyOpensslVersion,
AlpineLinuxVersion,
CargoVersion,
]
151 changes: 151 additions & 0 deletions tests/test_cargo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import pytest

from univers.versions import CargoVersion


def test_compare():
"""
1.2.3 := >=1.2.3, <2.0.0
1.2 := >=1.2.0, <2.0.0
1 := >=1.0.0, <2.0.0
0.2.3 := >=0.2.3, <0.3.0
0.2 := >=0.2.0, <0.3.0
0.0.3 := >=0.0.3, <0.0.4
0.0 := >=0.0.0, <0.1.0
0 := >=0.0.0, <1.0.0
"""

assert CargoVersion("1.2.3") >= CargoVersion("1.2.3")
assert CargoVersion("1.2.3") < CargoVersion("2.0.0")

assert CargoVersion("1.2") >= CargoVersion("1.2.0")
assert CargoVersion("1.2") < CargoVersion("2.0.0")

assert CargoVersion("1") >= CargoVersion("1.0.0")
assert CargoVersion("1") < CargoVersion("2.0.0")

assert CargoVersion("0.2.3") >= CargoVersion("0.2.3")
assert CargoVersion("0.2.3") < CargoVersion("0.3.0")

assert CargoVersion("0.2") >= CargoVersion("0.2.0")
assert CargoVersion("0.2") < CargoVersion("0.3.0")

assert CargoVersion("0.0.3") >= CargoVersion("0.0.3")
assert CargoVersion("0.0.3") < CargoVersion("0.0.4")

assert CargoVersion("0.0") >= CargoVersion("0.0.0")
assert CargoVersion("0.0") < CargoVersion("0.1.0")

assert CargoVersion("0") >= CargoVersion("0.0.0")
assert CargoVersion("0") < CargoVersion("1.0.0")


version_list = [
("1.2.3", 1, 2, 3, (), ()),
("1.2.3-alpha1", 1, 2, 3, ("alpha1",), ()),
("1.2.3+build5", 1, 2, 3, (), ("build5",)),
("1.2.3+5build", 1, 2, 3, (), ("5build",)),
("1.2.3-alpha1+build5", 1, 2, 3, ("alpha1",), ("build5",)),
(
"1.2.3-1.alpha1.9+build5.7.3aedf",
1,
2,
3,
(
"1",
"alpha1",
"9",
),
(
"build5",
"7",
"3aedf",
),
),
(
"1.2.3-0a.alpha1.9+05build.7.3aedf",
1,
2,
3,
(
"0a",
"alpha1",
"9",
),
(
"05build",
"7",
"3aedf",
),
),
(
"0.4.0-beta.1+0851523",
0,
4,
0,
(
"beta",
"1",
),
("0851523",),
),
("1.1.0-beta-10", 1, 1, 0, ("beta-10",), ()),
]


@pytest.mark.parametrize(
"version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build",
version_list,
)
def test_cargo(
version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build
):
# https://github.com/dtolnay/semver/blob/master/tests/test_version.rs :
v1 = CargoVersion(version)
assert v1.major == expected_major
assert v1.minor == expected_minor
assert v1.patch == expected_patch
assert v1.prerelease == expected_prerelease
assert v1.build == expected_build


def test_cargo1():
assert CargoVersion("1.2.3") == CargoVersion("1.2.3")
assert CargoVersion("1.2.3-alpha1") == CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3+build.42") == CargoVersion("1.2.3+build.42")
assert CargoVersion("1.2.3-alpha1+42") == CargoVersion("1.2.3-alpha1+42")
assert CargoVersion("0.0.0") != CargoVersion("0.0.1")
assert CargoVersion("0.0.0") != CargoVersion("0.1.0")
assert CargoVersion("0.0.0") != CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha") != CargoVersion("1.2.3-beta")
assert CargoVersion("1.2.3+23") != CargoVersion("1.2.3+42")

assert CargoVersion("0.0.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.0.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3")
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3-alpha2") < CargoVersion("1.2.3-alpha2"))
assert CargoVersion("1.2.3+23") < CargoVersion("1.2.3+42")

assert CargoVersion("0.0.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.0.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha1") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha2") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3+23") <= CargoVersion("1.2.3+42")

assert CargoVersion("1.2.3-alpha2") > CargoVersion("0.0.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3") > CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha2"))
assert not (CargoVersion("1.2.3+23") > CargoVersion("1.2.3+42"))

assert CargoVersion("1.2.3-alpha2") >= CargoVersion("0.0.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3+23") >= CargoVersion("1.2.3+42"))
Loading
Loading