diff --git a/src/univers/swift_version_range.py b/src/univers/swift_version_range.py new file mode 100644 index 00000000..73cfcaf6 --- /dev/null +++ b/src/univers/swift_version_range.py @@ -0,0 +1,55 @@ +from univers.versions import SwiftVersion +from univers.version_range import VersionRange +from univers.version_constraint import VersionConstraint +import attr + +@attr.s(auto_attribs=True, frozen=True) +class SwiftVersionRange(VersionRange): + expression: str + scheme: str = 'swift' + version_class: type = SwiftVersion + constraints: list = attr.ib(init=False) + + def __attrs_post_init__(self): + object.__setattr__(self, 'constraints', self.parse(self.expression)) + + @staticmethod + def parse(expression): + constraints = [] + # Remove quotes for parsing. + expression = expression.replace('"', '').strip() + + # Handle different range types. + if "..<" in expression: + parts = expression.split("..<") + if len(parts) == 2: + lower = SwiftVersion(parts[0].strip()) + upper = SwiftVersion(parts[1].strip()) + constraints.append(VersionConstraint(comparator=">=", version=lower)) + constraints.append(VersionConstraint(comparator="<", version=upper)) + elif "..." in expression: + parts = expression.split("...") + if len(parts) == 2: + lower = SwiftVersion(parts[0].strip()) + upper = SwiftVersion(parts[1].strip()) + constraints.append(VersionConstraint(comparator=">=", version=lower)) + constraints.append(VersionConstraint(comparator="<=", version=upper)) + else: + # Handle other cases such as 'exact:' and 'from:' prefixes. + if 'exact:' in expression: + version = SwiftVersion(expression.split('exact:')[1].strip()) + constraints.append(VersionConstraint(comparator="=", version=version)) + elif 'from:' in expression: + version = SwiftVersion(expression.split('from:')[1].strip()) + next_major_version = version.next_major() + constraints.append(VersionConstraint(comparator=">=", version=version)) + constraints.append(VersionConstraint(comparator="<", version=next_major_version)) + else: + # Handle a single version without any prefix. + version = SwiftVersion(expression) + constraints.append(VersionConstraint(comparator="=", version=version)) + + return constraints + + def __str__(self): + return f"vers:swift/{'|'.join([c.to_string() for c in self.constraints])}" diff --git a/src/univers/versions.py b/src/univers/versions.py index 14b3e007..ca53175d 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -703,3 +703,20 @@ def bump(self, index): LegacyOpensslVersion, AlpineLinuxVersion, ] + +class SwiftVersion(Version): + @classmethod + def build_value(cls, string): + return semantic_version.Version(string) + + @classmethod + def is_valid(cls, string): + try: + cls.build_value(string) + return True + except ValueError: + return False + + def next_major(self): + """Increase the major version and reset minor and patch.""" + return SwiftVersion(str(self.value.next_major())) \ No newline at end of file diff --git a/tests/test_swift_versions.py b/tests/test_swift_versions.py new file mode 100644 index 00000000..d7c8a200 --- /dev/null +++ b/tests/test_swift_versions.py @@ -0,0 +1,35 @@ +import unittest +from univers.swift_version_range import SwiftVersionRange +from univers.versions import SwiftVersion + +class TestSwiftVersionRange(unittest.TestCase): + def test_exact_version(self): + vr = SwiftVersionRange('exact: "1.2.3"') + v = SwiftVersion("1.2.3") + self.assertTrue(v in vr) + + def test_from_version(self): + vr = SwiftVersionRange('from: "1.2.3"') + v1 = SwiftVersion("1.2.3") + v2 = SwiftVersion("2.0.0") + self.assertTrue(v1 in vr) + self.assertFalse(v2 in vr) + + def test_range_with_upper_bound(self): + vr = SwiftVersionRange('"1.2.3"..<"1.2.6"') + v1 = SwiftVersion("1.2.3") + v2 = SwiftVersion("1.2.6") + self.assertTrue(v1 in vr) + self.assertFalse(v2 in vr) + + def test_range_with_both_bounds(self): + vr = SwiftVersionRange('"1.2.3"..."1.2.6"') + v1 = SwiftVersion("1.2.3") + v2 = SwiftVersion("1.2.6") + v3 = SwiftVersion("1.2.7") + self.assertTrue(v1 in vr) + self.assertTrue(v2 in vr) + self.assertFalse(v3 in vr) + +if __name__ == '__main__': + unittest.main()