Skip to content

Commit

Permalink
Merge pull request #92 from TG1999/inverse
Browse files Browse the repository at this point in the history
Add function to invert constraints and ranges #91
  • Loading branch information
TG1999 authored Nov 23, 2022
2 parents 9c5e47a + 637d362 commit 5d0518c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ Changelog
=========


Version v30.9.1
----------------

- Add invert function to VersionRange.


Version v30.9.0
----------------

Expand Down
27 changes: 27 additions & 0 deletions src/univers/version_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,33 @@ def __lt__(self, other):
# we compare tuples, version first
return (self.version, self.comparator).__lt__((other.version, other.comparator))

def is_star(self):
return self.comparator == "*"

def invert(self):
"""
Return a new VersionConstraint instance with the comparator inverted.
For example::
>>> assert str(VersionConstraint(comparator=">=", version=Version("2.3")).invert()) == "<2.3"
"""
INVERTED_COMPARATORS = {
">=": "<",
"<=": ">",
"!=": "=",
"<": ">=",
">": "<=",
"=": "!=",
}

if self.is_star():
return None

inverted_comparator = INVERTED_COMPARATORS[self.comparator]
return self.__class__(
comparator=inverted_comparator,
version=self.version,
)

@classmethod
def from_string(cls, string, version_class):
"""
Expand Down
31 changes: 31 additions & 0 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class InvalidVersionRange(Exception):
"""


INVERTED_COMPARATORS = {
">=": "<",
"<=": ">",
"!=": "=",
"<": ">=",
">": "<=",
"=": "!=",
}


@attr.s(frozen=True, order=False, eq=True, hash=True)
class VersionRange:
"""
Expand Down Expand Up @@ -164,6 +174,27 @@ def from_versions(cls, sequence):
constraints.append(constraint)
return cls(constraints=constraints)

def is_star(self):
return len(self.constraints) == 1 and self.constraints[0].is_star()

def invert(self):
"""
Return the inverse or complement of this VersionRange. For example, if this range is
">=1.0.0", the inverse is "<1.0.0".
>>> str(VersionRange.from_string("vers:npm/>=1.0.0").invert())
'vers:npm/<1.0.0'
"""
inverted_constraints = []

if self.is_star():
# The inverse of "*" is an empty range.
return None

for constraint in self.constraints:
inverted_constraints.append(constraint.invert())

return self.__class__(constraints=inverted_constraints)

def __str__(self):
constraints = "|".join(str(c) for c in sorted(self.constraints))
return f"vers:{self.scheme}/{constraints}"
Expand Down
27 changes: 27 additions & 0 deletions tests/test_version_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,30 @@ def test_semver_comparison(version, spec, expected):
version_class=versions.SemverVersion,
)
assert (version in constraint) is expected


@pytest.mark.parametrize(
"original, inverted",
[
(">2.7.1", "<=2.7.1"),
("!=1.1.0", "=1.1.0"),
("=2.0.0", "!=2.0.0"),
("<=0.9999.9999", ">0.9999.9999"),
(">=0.2.9", "<0.2.9"),
("<1.9999.9999", ">=1.9999.9999"),
("*", None),
],
)
def test_invert_opertaion(original, inverted):
constraint = VersionConstraint.from_string(
string=original,
version_class=versions.SemverVersion,
)
if inverted:
inverted_constraint = VersionConstraint.from_string(
string=inverted,
version_class=versions.SemverVersion,
)
assert constraint.invert() == inverted_constraint
else:
assert constraint.invert() is None
20 changes: 20 additions & 0 deletions tests/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,23 @@ def test_npm_advisory_version_range_parse(test_case):
string=test_case["npm_native"],
)
assert str(result) == test_case["expected_vers"]


def test_invert():
vers_with_equal_operator = VersionRange.from_string("vers:gem/1.0")
assert str(vers_with_equal_operator.invert()) == "vers:gem/!=1.0"
assert VersionRange.from_string("vers:gem/!=1.0").invert() == vers_with_equal_operator

vers_with_less_than_operator = VersionRange.from_string("vers:gem/<1.0")
assert str(vers_with_less_than_operator.invert()) == "vers:gem/>=1.0"
assert VersionRange.from_string("vers:gem/>=1.0").invert() == vers_with_less_than_operator

vers_with_greater_than_operator = VersionRange.from_string("vers:gem/>1.0")
assert str(vers_with_greater_than_operator.invert()) == "vers:gem/<=1.0"
assert VersionRange.from_string("vers:gem/<=1.0").invert() == vers_with_greater_than_operator

vers_with_complex_constraints = VersionRange.from_string("vers:gem/<=1.0|>=3.0|<4.0|!=5.0")
assert str(vers_with_complex_constraints.invert()) == "vers:gem/>1.0|<3.0|>=4.0|5.0"

vers_with_star_operator = VersionRange.from_string("vers:gem/*")
assert vers_with_star_operator.invert() == None

0 comments on commit 5d0518c

Please sign in to comment.