Skip to content
Open
Show file tree
Hide file tree
Changes from all 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: 91 additions & 9 deletions src/univers/gentoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,111 @@

revision_regexp = re.compile(r".*(-r\d+)")

slot_regexp = re.compile(r":([A-Za-z0-9_][A-Za-z0-9+_.-]*(?:/[A-Za-z0-9_][A-Za-z0-9+_.-]*)?=?|\*)$")

suffix_value = {"pre": -2, "p": 1, "alpha": -4, "beta": -3, "rc": -1}

"""
gentoo ebuild version comparison
"""


def parse_parts(version_string):
"""
Return a tuple of (base_version, revision_int, slot_str, has_wildcard_bool).
"""
if not version_string:
return "", 0, None, False

version = remove_spaces(version_string)

slot = None
if ":" in version:
match = slot_regexp.search(version)
if match:
slot = match.group(1)
version = version[: match.start()]
else:
return "", 0, None, False

wildcard = False
if version.endswith("*"):
wildcard = True
version = version[:-1]
Comment thread
Adnan9-63 marked this conversation as resolved.
Resolved

revision = 0
match = revision_regexp.search(version)
if match:
revision = int(match.group(1)[2:])
version = version[: match.span(1)[0]]

return version, revision, slot, wildcard


def strip_slot(version_string):
"""
Return the version string with any slot qualifier stripped.
"""
if not version_string:
return ""
version = remove_spaces(version_string)
if ":" in version:
match = slot_regexp.search(version)
if match:
return version[: match.start()]
return ""
return version


def match_wildcard(ver1, ver2):
"""
Return True if one of the versions is a Gentoo wildcard version and
the other matches it according to Gentoo PMS wildcard matching semantics.
If both are wildcards, return True only if their wildcard specifications match.
"""
_, _, _, wild1 = parse_parts(ver1)
_, _, _, wild2 = parse_parts(ver2)

if not (wild1 or wild2):
return False

if wild1 and wild2:
s1 = strip_slot(ver1).rstrip("*")
s2 = strip_slot(ver2).rstrip("*")
return s1 == s2

wild_str = ver1 if wild1 else ver2
concrete_str = ver2 if wild1 else ver1

prefix = strip_slot(wild_str).rstrip("*")
target = strip_slot(concrete_str)

if target == prefix:
return True

if target.startswith(prefix):
next_char = target[len(prefix)]
if next_char in (".", "_", "-") or next_char.isalpha():
return True

return False


def is_valid(string):
version, _ = parse_version_and_revision(remove_spaces(string))
return _is_gentoo_version(version)
if not string:
return False
version, _, _, _ = parse_parts(string)
if not version:
return False
return bool(_is_gentoo_version(version))


def parse_version_and_revision(version_string):
"""
Return a tuple of (version string, revision int) given a ``version_string``.
Also strips any slot or wildcard suffix so that vercmp operates cleanly.
"""
revision = 0
version = version_string
match = revision_regexp.search(version_string)
if match:
revision = int(match.group(1)[2:])
version = version_string[: match.span(1)[0]]

version, revision, _, _ = parse_parts(version_string)
return version, revision


Expand Down
14 changes: 14 additions & 0 deletions src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,23 @@ class GentooVersion(Version):
def is_valid(cls, string):
return gentoo.is_valid(string)

@property
def slot(self):
"""Return the slot string if present, else None."""
_, _, slot, _ = gentoo.parse_parts(self.value)
return slot

@property
def is_wildcard(self):
"""Return True if this version ends with a wildcard '*'."""
_, _, _, wildcard = gentoo.parse_parts(self.value)
return wildcard

def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
if self.is_wildcard or other.is_wildcard:
return gentoo.match_wildcard(self.value, other.value)
return gentoo.vercmp(self.value, other.value) == 0

def __lt__(self, other):
Expand Down
79 changes: 79 additions & 0 deletions tests/test_gentoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,82 @@ def comparison(self):
def test_gentoo_vers_cmp(test_case):
avc = GentooVersionComp.from_data(data=test_case)
avc.assert_result()


def test_gentoo_slots_and_wildcards():
# Issue #182 reported versions
assert GentooVersion.is_valid("1.3*")
assert GentooVersion.is_valid("3.24.48:3")
assert GentooVersion.is_valid("6.9.3:6")
assert GentooVersion.is_valid("7.3*")
assert GentooVersion.is_valid("7.4*")

v_slot = GentooVersion("3.24.48:3")
assert v_slot.slot == "3"
assert not v_slot.is_wildcard
assert v_slot == GentooVersion("3.24.48:3")
assert v_slot == GentooVersion("3.24.48")
assert v_slot == GentooVersion("3.24.48:10")
assert v_slot < GentooVersion("3.24.49:3")
assert v_slot > GentooVersion("3.24.47:3")

v_slot6 = GentooVersion("6.9.3:6")
assert v_slot6.slot == "6"

# Sub-slot and slot operators
assert GentooVersion.is_valid("2.4.52:0/2.4")
assert GentooVersion("2.4.52:0/2.4").slot == "0/2.4"
assert GentooVersion.is_valid("3.0:3=")
assert GentooVersion("3.0:3=").slot == "3="

# Wildcards
v_wild = GentooVersion("1.3*")
assert v_wild.is_wildcard
assert v_wild.slot is None
assert v_wild == GentooVersion("1.3*")
assert v_wild == GentooVersion("1.3")
assert v_wild == GentooVersion("1.3.1")
assert v_wild == GentooVersion("1.3a")
assert v_wild == GentooVersion("1.3_alpha4")
assert v_wild == GentooVersion("1.3-r1")
assert v_wild != GentooVersion("1.30")
assert v_wild != GentooVersion("1.4")
assert v_wild < GentooVersion("1.4")
assert v_wild > GentooVersion("1.2")
assert GentooVersion("7.3*") < GentooVersion("7.4*")

# Constraint satisfaction and round-tripping through EbuildVersionRange
from univers.version_constraint import VersionConstraint
from univers.version_range import EbuildVersionRange
from univers.version_range import VersionRange

c_gt = VersionConstraint(comparator=">", version=GentooVersion("3.24.47:3"))
assert GentooVersion("3.24.48:3") in c_gt
assert GentooVersion("3.24.48") in c_gt
assert GentooVersion("3.24.49") in c_gt
assert GentooVersion("3.24.47") not in c_gt

c_eq = VersionConstraint(comparator="=", version=GentooVersion("1.3*"))
assert GentooVersion("1.3*") in c_eq
assert GentooVersion("1.3") in c_eq
assert GentooVersion("1.3.1") in c_eq
assert GentooVersion("1.3a") in c_eq
assert GentooVersion("1.3_alpha4") in c_eq
assert GentooVersion("1.3-r1") in c_eq
assert GentooVersion("1.30") not in c_eq
assert GentooVersion("1.4") not in c_eq

vr = EbuildVersionRange(constraints=[c_eq, c_gt])
vr_str = str(vr)
assert vr_str == "vers:ebuild/1.3*|>3.24.47:3"
assert VersionRange.from_string(vr_str) == vr
assert GentooVersion("1.3.1") in vr
assert GentooVersion("1.30") not in vr

# Invalid formats
assert not GentooVersion.is_valid("*")
assert not GentooVersion.is_valid("1.3**")
assert not GentooVersion.is_valid(":3")
assert not GentooVersion.is_valid("1:")
assert not GentooVersion.is_valid("3.24.48:")
assert not GentooVersion.is_valid("3.24.48:invalid!slot")