Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
111 changes: 108 additions & 3 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,8 +842,11 @@ class NugetVersionRange(MavenVersionRange):


class ComposerVersionRange(VersionRange):
# TODO composer may need its own scheme see https//github.com/aboutcode-org/univers/issues/5
# and https//getcomposer.org/doc/articles/versions.md
"""
Composer version range as documented at
https://getcomposer.org/doc/articles/versions.md
"""

scheme = "composer"
version_class = versions.ComposerVersion

Expand All @@ -856,6 +859,75 @@ class ComposerVersionRange(VersionRange):
"=": "=", # This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
}

@classmethod
def from_native(cls, string):
"""
Parse a Composer version range string into a version range object.
"""
string = string.strip()

if string.startswith("^"):
base_version = string[1:]
base_version_obj = cls.version_class(base_version)
base_parts = base_version.split(".")
if base_parts[0] == "0":
upper_constraint = VersionConstraint(
comparator="<", version=cls.version_class(f"0.{int(base_parts[1]) + 1}.0")
)
else:
upper_constraint = VersionConstraint(
comparator="<", version=cls.version_class(f"{int(base_parts[0]) + 1}.0.0")
)
lower_constraint = VersionConstraint(comparator=">=", version=base_version_obj)
return cls(constraints=[lower_constraint, upper_constraint])

if string.startswith("~"):
base_version = string[1:]
base_version_obj = cls.version_class(base_version)
base_parts = base_version.split(".")

if len(base_parts) == 3:
upper_constraint = VersionConstraint(
comparator="<",
version=cls.version_class(f"{base_parts[0]}.{int(base_parts[1]) + 1}.0"),
)
else:
upper_constraint = VersionConstraint(
comparator="<", version=cls.version_class(f"{int(base_parts[0]) + 1}.0.0")
)

lower_constraint = VersionConstraint(comparator=">=", version=base_version_obj)
return cls(constraints=[lower_constraint, upper_constraint])

if ".*" in string:
base_version = string.replace(".*", ".0")
base_version_obj = cls.version_class(base_version)
base_parts = base_version.split(".")
upper_constraint = VersionConstraint(
comparator="<",
version=cls.version_class(f"{base_parts[0]}.{int(base_parts[1]) + 1}.0"),
)
lower_constraint = VersionConstraint(comparator=">=", version=base_version_obj)
return cls(constraints=[lower_constraint, upper_constraint])

constraints = []

segments = string.split("||")

for segment in segments:
if not any(op in string for op in cls.vers_by_native_comparators):
segment = "==" + segment
specifiers = SpecifierSet(segment)
for spec in specifiers:
operator = spec.operator
version = spec.version
version = cls.version_class(version)
comparator = cls.vers_by_native_comparators.get(operator, "=")
constraint = VersionConstraint(comparator=comparator, version=version)
constraints.append(constraint)

return cls(constraints=constraints)


class RpmVersionRange(VersionRange):
# http://ftp.rpm.org/api/4.4.2.2/dependencies.html
Expand Down Expand Up @@ -942,7 +1014,7 @@ def from_natives(cls, strings):

class GolangVersionRange(VersionRange):
"""
Go modules use strict semver with pseudo numbering for Git repos
Go modules use strict semver with pseudo numbering for Git commits.
https://go.dev/doc/modules/version-numbers
"""

Expand All @@ -958,6 +1030,39 @@ class GolangVersionRange(VersionRange):
"=": "=", # This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
}

@classmethod
def from_native(cls, string):
"""
Parse a native GoLang version range into a set of constraints.
"""
constraints = []

segments = string.split("||")
for segment in segments:

if not any(op in string for op in cls.vers_by_native_comparators):
segment = "==" + segment

specifiers = SpecifierSet(segment)
for spec in specifiers:
operator = spec.operator
version = spec.version
version = cls.version_class(version)
comparator = cls.vers_by_native_comparators.get(operator, "=")
constraint = VersionConstraint(comparator=comparator, version=version)
constraints.append(constraint)

return cls(constraints=constraints)

@classmethod
def from_natives(cls, strings):
if isinstance(strings, str):
return cls.from_native(strings)
constraints = []
for rel in strings:
constraints.extend(cls.from_native(rel).constraints)
return cls(constraints=constraints)


class GenericVersionRange(VersionRange):
scheme = "generic"
Expand Down
112 changes: 112 additions & 0 deletions tests/test_composer_version_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright (c) nexB Inc. and others.
# SPDX-License-Identifier: Apache-2.0
#
# Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download.

from univers.version_constraint import VersionConstraint
from univers.version_range import ComposerVersionRange
from univers.versions import ComposerVersion


def test_composer_exact_version():
version_range = ComposerVersionRange.from_native("1.3.2")
assert version_range == ComposerVersionRange(
constraints=(VersionConstraint(comparator="=", version=ComposerVersion(string="1.3.2")),)
)


def test_composer_greater_than_or_equal():
version_range = ComposerVersionRange.from_native(">=1.3.2")
assert version_range == ComposerVersionRange(
constraints=(VersionConstraint(comparator=">=", version=ComposerVersion(string="1.3.2")),)
)


def test_composer_less_than():
version_range = ComposerVersionRange.from_native("<1.3.2")
assert version_range == ComposerVersionRange(
constraints=(VersionConstraint(comparator="<", version=ComposerVersion(string="1.3.2")),)
)


def test_composer_wildcard():
version_range = ComposerVersionRange.from_native("1.3.*")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.3.0")),
VersionConstraint(comparator="<", version=ComposerVersion(string="1.4.0")),
)
)


def test_composer_tilde_patch():
version_range = ComposerVersionRange.from_native("~1.3.2")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.3.2")),
VersionConstraint(comparator="<", version=ComposerVersion(string="1.4.0")),
)
)


def test_composer_tilde_minor():
version_range = ComposerVersionRange.from_native("~1.3")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.3.0")),
VersionConstraint(comparator="<", version=ComposerVersion(string="2.0.0")),
)
)


def test_composer_caret_patch():
version_range = ComposerVersionRange.from_native("^1.3.2")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.3.2")),
VersionConstraint(comparator="<", version=ComposerVersion(string="2.0.0")),
)
)


def test_composer_caret_zero_minor():
version_range = ComposerVersionRange.from_native("^0.3.2")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="0.3.2")),
VersionConstraint(comparator="<", version=ComposerVersion(string="0.4.0")),
)
)


def test_composer_range_with_multiple_constraints():
version_range = ComposerVersionRange.from_native(">=1.2.3, <2.0.0")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.2.3")),
VersionConstraint(comparator="<", version=ComposerVersion(string="2.0.0")),
)
)


def test_composer_range_with_or_constraints():
version_range = ComposerVersionRange.from_native(">=1.0.0 || <2.0.0")
assert version_range == ComposerVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=ComposerVersion(string="1.0.0")),
VersionConstraint(comparator="<", version=ComposerVersion(string="2.0.0")),
)
)


def test_composer_invalid_syntax():
try:
ComposerVersionRange.from_native(">1.0.0 <2.0.0")
assert False, "Should have raised a ValueError"
except ValueError:
assert True


def test_composer_range_str_representation():
version_range = ComposerVersionRange.from_native(">=1.0.0, <2.0.0")
assert str(version_range) == "vers:composer/>=1.0.0|<2.0.0"
99 changes: 99 additions & 0 deletions tests/test_golang_version_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright (c) nexB Inc. and others.
# SPDX-License-Identifier: Apache-2.0
#
# Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download.

from univers.version_constraint import VersionConstraint
from univers.version_range import GolangVersionRange
from univers.versions import GolangVersion


def test_golang_exact_version():
version_range = GolangVersionRange.from_native("v1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="=", version=GolangVersion(string="v1.2.3")),)
)


def test_golang_greater_than():
version_range = GolangVersionRange.from_native(">v1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator=">", version=GolangVersion(string="v1.2.3")),)
)


def test_golang_greater_than_or_equal():
version_range = GolangVersionRange.from_native(">=v1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator=">=", version=GolangVersion(string="v1.2.3")),)
)


def test_golang_less_than():
version_range = GolangVersionRange.from_native("<v1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="<", version=GolangVersion(string="v1.2.3")),)
)


def test_golang_less_than_or_equal():
version_range = GolangVersionRange.from_native("<=v1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="<=", version=GolangVersion(string="v1.2.3")),)
)


def test_golang_version_range_with_multiple_constraints():
version_range = GolangVersionRange.from_native(">=v1.2.3, <v2.0.0")
assert version_range == GolangVersionRange(
constraints=(
VersionConstraint(comparator=">=", version=GolangVersion(string="v1.2.3")),
VersionConstraint(comparator="<", version=GolangVersion(string="v2.0.0")),
)
)


def test_golang_version_with_prerelease():
version_range = GolangVersionRange.from_native("v1.2.3-beta.1")
assert version_range == GolangVersionRange(
constraints=(
VersionConstraint(comparator="=", version=GolangVersion(string="v1.2.3-beta.1")),
)
)


def test_golang_range_string_representation():
version_range = GolangVersionRange.from_native(">=v1.2.3, <v2.0.0")
assert str(version_range) == "vers:golang/>=1.2.3|<2.0.0"


def test_golang_version_range_with_pre_and_build():
version_range = GolangVersionRange.from_native("v1.2.3-alpha+build123")
assert version_range == GolangVersionRange(
constraints=(
VersionConstraint(
comparator="=", version=GolangVersion(string="v1.2.3-alpha+build123")
),
)
)


def test_golang_version_with_major_zero():
version_range = GolangVersionRange.from_native("v0.1.5")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="=", version=GolangVersion(string="v0.1.5")),)
)


def test_golang_version_with_only_major():
version_range = GolangVersionRange.from_native("v1")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="=", version=GolangVersion(string="v1")),)
)


def test_golang_version_with_upper_case():
version_range = GolangVersionRange.from_native("V1.2.3")
assert version_range == GolangVersionRange(
constraints=(VersionConstraint(comparator="=", version=GolangVersion(string="v1.2.3")),)
)