Skip to content

Commit d710d22

Browse files
committed
Add support for CargoVersion and CargoVersionRange
Signed-off-by: ziadhany <[email protected]>
1 parent 7a4e715 commit d710d22

File tree

4 files changed

+307
-4
lines changed

4 files changed

+307
-4
lines changed

src/univers/version_range.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from univers.utils import remove_spaces
1919
from univers.version_constraint import VersionConstraint
2020
from univers.version_constraint import contains_version
21+
from univers.versions import CargoVersion
2122

2223

2324
class InvalidVersionRange(Exception):
@@ -299,7 +300,8 @@ class NpmVersionRange(VersionRange):
299300
">=": ">=",
300301
"<": "<",
301302
">": ">",
302-
"=": "=", # This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
303+
"=": "=",
304+
# This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
303305
}
304306

305307
@classmethod
@@ -797,7 +799,8 @@ class ComposerVersionRange(VersionRange):
797799
">=": ">=",
798800
"<": "<",
799801
">": ">",
800-
"=": "=", # This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
802+
"=": "=",
803+
# This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
801804
}
802805

803806

@@ -899,7 +902,8 @@ class GolangVersionRange(VersionRange):
899902
">=": ">=",
900903
"<": "<",
901904
">": ">",
902-
"=": "=", # This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
905+
"=": "=",
906+
# This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
903907
}
904908

905909

@@ -922,7 +926,51 @@ class HexVersionRange(VersionRange):
922926

923927
class CargoVersionRange(VersionRange):
924928
scheme = "cargo"
925-
version_class = versions.SemverVersion
929+
version_class = versions.CargoVersion
930+
931+
@classmethod
932+
def from_native(cls, string):
933+
"""
934+
Return a VersionRange built from a scheme-specific, native version range
935+
``string``. Subclasses can implement.
936+
"""
937+
if string == "*":
938+
return cls(
939+
constraints=[VersionConstraint(comparator="*", version_class=cls.version_class)]
940+
)
941+
942+
constraint_strings = string.split(",")
943+
constraints = []
944+
945+
# caret
946+
if string.startswith("^"):
947+
string = string.replace("^", "=")
948+
949+
# tilde
950+
if string.startswith("~"):
951+
version = string.lstrip("~")
952+
lower_bound = CargoVersion(version)
953+
if lower_bound.minor == 0 and lower_bound.patch == 0:
954+
upper_bound = CargoVersion(str(lower_bound.value.next_major()))
955+
else:
956+
upper_bound = CargoVersion(str(lower_bound.value.next_minor()))
957+
958+
return cls(
959+
constraints=(
960+
VersionConstraint(comparator=">=", version=lower_bound),
961+
VersionConstraint(comparator="<", version=upper_bound),
962+
)
963+
)
964+
965+
for constraint in constraint_strings:
966+
if not constraint:
967+
raise InvalidVersionRange
968+
else:
969+
vs = VersionConstraint.split(string)
970+
version = cls.version_class(vs[1])
971+
constraint = VersionConstraint(comparator=vs[0], version=version)
972+
constraints.append(constraint)
973+
return cls(constraints=tuple(constraints))
926974

927975

928976
class MozillaVersionRange(VersionRange):

src/univers/versions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Each subclass primary responsibility to is be comparable and orderable
2626
"""
2727

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

688689

690+
class CargoVersion(SemverVersion):
691+
pass
692+
693+
689694
AVAILABLE_VERSIONS = [
690695
SemverVersion,
691696
GolangVersion,
@@ -702,4 +707,5 @@ def bump(self, index):
702707
OpensslVersion,
703708
LegacyOpensslVersion,
704709
AlpineLinuxVersion,
710+
CargoVersion,
705711
]

tests/test_cargo.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import pytest
2+
3+
from univers.versions import CargoVersion
4+
5+
6+
def test_compare():
7+
"""
8+
1.2.3 := >=1.2.3, <2.0.0
9+
1.2 := >=1.2.0, <2.0.0
10+
1 := >=1.0.0, <2.0.0
11+
0.2.3 := >=0.2.3, <0.3.0
12+
0.2 := >=0.2.0, <0.3.0
13+
0.0.3 := >=0.0.3, <0.0.4
14+
0.0 := >=0.0.0, <0.1.0
15+
0 := >=0.0.0, <1.0.0
16+
"""
17+
18+
assert CargoVersion("1.2.3") >= CargoVersion("1.2.3")
19+
assert CargoVersion("1.2.3") < CargoVersion("2.0.0")
20+
21+
assert CargoVersion("1.2") >= CargoVersion("1.2.0")
22+
assert CargoVersion("1.2") < CargoVersion("2.0.0")
23+
24+
assert CargoVersion("1") >= CargoVersion("1.0.0")
25+
assert CargoVersion("1") < CargoVersion("2.0.0")
26+
27+
assert CargoVersion("0.2.3") >= CargoVersion("0.2.3")
28+
assert CargoVersion("0.2.3") < CargoVersion("0.3.0")
29+
30+
assert CargoVersion("0.2") >= CargoVersion("0.2.0")
31+
assert CargoVersion("0.2") < CargoVersion("0.3.0")
32+
33+
assert CargoVersion("0.0.3") >= CargoVersion("0.0.3")
34+
assert CargoVersion("0.0.3") < CargoVersion("0.0.4")
35+
36+
assert CargoVersion("0.0") >= CargoVersion("0.0.0")
37+
assert CargoVersion("0.0") < CargoVersion("0.1.0")
38+
39+
assert CargoVersion("0") >= CargoVersion("0.0.0")
40+
assert CargoVersion("0") < CargoVersion("1.0.0")
41+
42+
43+
version_list = [
44+
("1.2.3", 1, 2, 3, (), ()),
45+
("1.2.3-alpha1", 1, 2, 3, ("alpha1",), ()),
46+
("1.2.3+build5", 1, 2, 3, (), ("build5",)),
47+
("1.2.3+5build", 1, 2, 3, (), ("5build",)),
48+
("1.2.3-alpha1+build5", 1, 2, 3, ("alpha1",), ("build5",)),
49+
(
50+
"1.2.3-1.alpha1.9+build5.7.3aedf",
51+
1,
52+
2,
53+
3,
54+
(
55+
"1",
56+
"alpha1",
57+
"9",
58+
),
59+
(
60+
"build5",
61+
"7",
62+
"3aedf",
63+
),
64+
),
65+
(
66+
"1.2.3-0a.alpha1.9+05build.7.3aedf",
67+
1,
68+
2,
69+
3,
70+
(
71+
"0a",
72+
"alpha1",
73+
"9",
74+
),
75+
(
76+
"05build",
77+
"7",
78+
"3aedf",
79+
),
80+
),
81+
(
82+
"0.4.0-beta.1+0851523",
83+
0,
84+
4,
85+
0,
86+
(
87+
"beta",
88+
"1",
89+
),
90+
("0851523",),
91+
),
92+
("1.1.0-beta-10", 1, 1, 0, ("beta-10",), ()),
93+
]
94+
95+
96+
@pytest.mark.parametrize(
97+
"version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build",
98+
version_list,
99+
)
100+
def test_cargo(
101+
version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build
102+
):
103+
# https://github.com/dtolnay/semver/blob/master/tests/test_version.rs :
104+
v1 = CargoVersion(version)
105+
assert v1.major == expected_major
106+
assert v1.minor == expected_minor
107+
assert v1.patch == expected_patch
108+
assert v1.prerelease == expected_prerelease
109+
assert v1.build == expected_build
110+
111+
112+
def test_cargo1():
113+
assert CargoVersion("1.2.3") == CargoVersion("1.2.3")
114+
assert CargoVersion("1.2.3-alpha1") == CargoVersion("1.2.3-alpha1")
115+
assert CargoVersion("1.2.3+build.42") == CargoVersion("1.2.3+build.42")
116+
assert CargoVersion("1.2.3-alpha1+42") == CargoVersion("1.2.3-alpha1+42")
117+
assert CargoVersion("0.0.0") != CargoVersion("0.0.1")
118+
assert CargoVersion("0.0.0") != CargoVersion("0.1.0")
119+
assert CargoVersion("0.0.0") != CargoVersion("1.0.0")
120+
assert CargoVersion("1.2.3-alpha") != CargoVersion("1.2.3-beta")
121+
assert CargoVersion("1.2.3+23") != CargoVersion("1.2.3+42")
122+
123+
assert CargoVersion("0.0.0") < CargoVersion("1.2.3-alpha2")
124+
assert CargoVersion("1.0.0") < CargoVersion("1.2.3-alpha2")
125+
assert CargoVersion("1.2.0") < CargoVersion("1.2.3-alpha2")
126+
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3")
127+
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3-alpha2")
128+
assert not (CargoVersion("1.2.3-alpha2") < CargoVersion("1.2.3-alpha2"))
129+
assert CargoVersion("1.2.3+23") < CargoVersion("1.2.3+42")
130+
131+
assert CargoVersion("0.0.0") <= CargoVersion("1.2.3-alpha2")
132+
assert CargoVersion("1.0.0") <= CargoVersion("1.2.3-alpha2")
133+
assert CargoVersion("1.2.0") <= CargoVersion("1.2.3-alpha2")
134+
assert CargoVersion("1.2.3-alpha1") <= CargoVersion("1.2.3-alpha2")
135+
assert CargoVersion("1.2.3-alpha2") <= CargoVersion("1.2.3-alpha2")
136+
assert CargoVersion("1.2.3+23") <= CargoVersion("1.2.3+42")
137+
138+
assert CargoVersion("1.2.3-alpha2") > CargoVersion("0.0.0")
139+
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.0.0")
140+
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.0")
141+
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha1")
142+
assert CargoVersion("1.2.3") > CargoVersion("1.2.3-alpha2")
143+
assert not (CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha2"))
144+
assert not (CargoVersion("1.2.3+23") > CargoVersion("1.2.3+42"))
145+
146+
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("0.0.0")
147+
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.0.0")
148+
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.0")
149+
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha1")
150+
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha2")
151+
assert not (CargoVersion("1.2.3+23") >= CargoVersion("1.2.3+42"))

tests/test_cargo_version_range.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pytest
2+
3+
from univers.version_range import CargoVersionRange
4+
from univers.version_range import InvalidVersionRange
5+
from univers.versions import CargoVersion
6+
7+
values = [
8+
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
9+
# caret
10+
["^1.2.3", [[["=", "1.2.3"]]], ["1.2.3"], ["1.2.4"]],
11+
# tilde
12+
["~1.2.3", [[[">=", "1.2.3"], ["<", "1.3.0"]]], ["1.2.4"], ["2.0.1"]],
13+
["~1.2", [[[">=", "1.2.0"], ["<", "1.3.0"]]], ["1.2.5"], ["1.3.1"]],
14+
[
15+
"~1",
16+
[[[">=", "1.0.0"], ["<", "2.0.0"]]],
17+
["1.3.0", "1.8.1"],
18+
["2.1.0", "2.2"],
19+
], # tilde increment the major
20+
# wildcard
21+
["*", [[[">=", "0.0.0"]]], ["1.0.0", "2.0.0"], []],
22+
# ["1.*", [[[">=", "1.0.0"]]], ["1.0.0"], ["2", "1.0.1"]],
23+
# ["1.2.*", [[[">=", "1.2.0"], ["<", "1.3.0"]]], ["1.2", "1.2.1"], ["2.1.0", "2.2"]],
24+
# https://github.com/dtolnay/semver/blob/master/tests/test_version_req.rs :
25+
["=1.0.0", [["=", "1.0.0"]], ["1.0.0"], ["1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"]],
26+
["=0.9.0", [["=", "0.9.0"]], ["0.9.0"], ["0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]],
27+
["=0.0.2", [["=", "0.0.2"]], ["0.0.2"], ["0.0.1", "0.0.3", "0.0.2-pre"]],
28+
[
29+
"=0.1.0-beta2.a",
30+
[["=", "0.1.0-beta2.a"]],
31+
["0.1.0-beta2.a"],
32+
["0.9.1", "0.1.0", "0.1.1-beta2.a", "0.1.0-beta2"],
33+
],
34+
# ["=0.1.0+meta", [["=", "0.1.0+meta"]], ["0.1.0", "0.1.0+meta", "0.1.0+any"], []],
35+
# ["<1.0.0", [["<", "1.0.0"]], ["0.1.0", "0.0.1"], ["1.0.0", "1.0.0-beta", "1.0.1", "0.9.9-alpha"]],
36+
# [
37+
# "<= 2.1.0-alpha2",
38+
# [["<", "2.1.0-alpha2"], ["=", "2.1.0-alpha2"]],
39+
# ["2.1.0-alpha2", "2.1.0-alpha1", "2.0.0", "1.0.0"],
40+
# ["2.1.0", "2.2.0-alpha1", "2.0.0-alpha2", "1.0.0-alpha2"],
41+
# ],
42+
# [">1.0.0-alpha, <1.0.0", [[[">", "2.1.0-alpha2"], ["<", "1.0.0"]]], ["1.0.0-beta"], []],
43+
# [">1.0.0-alpha, <1.0", [[[">", "1.0.0-alpha"], ["<", "1.0"]]], ["1.0.0-beta"], []],
44+
# [">1.0.0-alpha, <1", [[[">", "1.0.0-alpha"], ["<", "1"]]], ["1.0.0-beta"], []],
45+
# [
46+
# ">=0.5.1-alpha3, <0.6",
47+
# [[[">", "0.5.1-alpha3"], ["=", "0.5.1-alpha3"], ["<", "0.6"]]],
48+
# ["0.5.1-alpha3", "0.5.1-alpha4", "0.5.1-beta", "0.5.1", "0.5.5"],
49+
# ["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"],
50+
# ],
51+
["~1", [], ["1.0.0", "1.0.1", "1.1.1"], ["0.9.1", "2.9.0", "0.0.9"]],
52+
["~1.2", [], ["1.2.0", "1.2.1"], ["1.1.1", "1.3.0", "0.0.9"]],
53+
["~1.2.2", [], ["1.2.2", "1.2.4"], ["1.2.1", "1.9.0", "1.0.9", "2.0.1", "0.1.3"]],
54+
# [
55+
# "~1.2.3-beta.2",
56+
# [],
57+
# ["1.2.3", "1.2.4", "1.2.3-beta.2", "1.2.3-beta.4"],
58+
# ["1.3.3", "1.1.4", "1.2.3-beta.1", "1.2.4-beta.2"],
59+
# ],
60+
]
61+
62+
error_list = [
63+
"> 0.1.0,",
64+
"> 0.3.0, ,",
65+
# "1.2.3 - 2.3.4",
66+
# "> 0.0.9 <= 2.5.3",
67+
# "=1.2.3 || =2.3.4",
68+
# "1.1 || =1.2.3",
69+
# "6.* || 8.* || >= 10.*",
70+
# ">= >= 0.0.2",
71+
# ">== 0.0.2",
72+
# "a.0.0",
73+
# "1.0.0-",
74+
# ">=",
75+
# "*.1",
76+
# "1.*.1",
77+
# ">=1.*.1",
78+
# "*, 0.20.0-any",
79+
# "0.20.0-any, *" "0.20.0-any, *, 1.0",
80+
]
81+
82+
83+
@pytest.mark.parametrize("version_range, conditions, versions_in, versions_out", values)
84+
def test_range(version_range, conditions, versions_in, versions_out):
85+
r = CargoVersionRange.from_native(version_range)
86+
# TODO test Version Constraints
87+
88+
for v in versions_in:
89+
assert CargoVersion(v) in r
90+
91+
for v in versions_out:
92+
assert CargoVersion(v) not in r
93+
94+
95+
def test_error():
96+
for i in error_list:
97+
with pytest.raises(InvalidVersionRange):
98+
CargoVersionRange.from_native(i)

0 commit comments

Comments
 (0)