|
4 | 4 | # |
5 | 5 | # Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download. |
6 | 6 |
|
| 7 | +import re |
7 | 8 | from typing import List |
8 | 9 | from typing import Union |
9 | 10 |
|
@@ -772,6 +773,71 @@ def from_native(cls, string): |
772 | 773 |
|
773 | 774 | return cls(constraints=constraints) |
774 | 775 |
|
| 776 | + @classmethod |
| 777 | + def from_ossa_native(cls, string): |
| 778 | + """ |
| 779 | + Returns a PypiVersionRange built from an OpenStack Security Advisory (OSSA) version constraint ``string``. |
| 780 | +
|
| 781 | + See: https://github.com/openstack/ossa |
| 782 | +
|
| 783 | + For example:: |
| 784 | +
|
| 785 | + >>> str(PypiVersionRange.from_ossa_native("<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0")) |
| 786 | + 'vers:pypi/<=5.0.3|>=6.0.0|<=6.1.0|7.0.0' |
| 787 | +
|
| 788 | + >>> str(PypiVersionRange.from_ossa_native("<=14.0.10, >=15.0.0 <=15.0.8, >=16.0.0 <=16.0.3")) |
| 789 | + 'vers:pypi/<=14.0.10|>=15.0.0|<=15.0.8|>=16.0.0|<=16.0.3' |
| 790 | +
|
| 791 | + >>> str(PypiVersionRange.from_ossa_native("<20.2.1, >=21.0.0 <21.2.1, ==22.0.0")) |
| 792 | + 'vers:pypi/<20.2.1|>=21.0.0|<21.2.1|22.0.0' |
| 793 | + """ |
| 794 | + |
| 795 | + # Normalize "and" keyword to comma |
| 796 | + # "<=5.0.3, >=6.0.0 <=6.1.0 and ==7.0.0" -> "<=5.0.3, >=6.0.0 <=6.1.0, ==7.0.0" |
| 797 | + string = string.replace(" and ", ",") |
| 798 | + |
| 799 | + # Remove spaces around operators |
| 800 | + # "<=5.0.3, >=6.0.0 <=6.1.0, ==7.0.0" -> "<=5.0.3,>=6.0.0<=6.1.0,==7.0.0" |
| 801 | + string = re.sub(r"\s+([<>=!]+)", r"\1", string) |
| 802 | + string = re.sub(r"([<>=!]+)\s+", r"\1", string) |
| 803 | + |
| 804 | + # Insert comma between consecutive constraints |
| 805 | + # "<=5.0.3,>=6.0.0<=6.1.0,==7.0.0" -> "<=5.0.3,>=6.0.0,<=6.1.0,==7.0.0" |
| 806 | + string = re.sub(r"(\d)([<>=!])", r"\1,\2", string) |
| 807 | + |
| 808 | + constraints = [] |
| 809 | + for part in string.split(","): |
| 810 | + |
| 811 | + # Default to exact match for bare version numbers |
| 812 | + # "1.16.0" -> "=1.16.0" |
| 813 | + comparator = "=" |
| 814 | + version = part |
| 815 | + |
| 816 | + for op, vers_op in cls.vers_by_native_comparators.items(): |
| 817 | + if part.startswith(op): |
| 818 | + comparator = vers_op |
| 819 | + version = part[len(op) :] |
| 820 | + break |
| 821 | + |
| 822 | + # Handle bare "=" for exact match |
| 823 | + # "=18.0.0" -> "18.0.0" |
| 824 | + if version.startswith("="): |
| 825 | + version = version[1:] |
| 826 | + |
| 827 | + try: |
| 828 | + constraints.append( |
| 829 | + VersionConstraint( |
| 830 | + comparator=comparator, |
| 831 | + version=cls.version_class(version), |
| 832 | + ) |
| 833 | + ) |
| 834 | + except (ValueError, TypeError) as e: |
| 835 | + raise InvalidVersionRange( |
| 836 | + f"Invalid version constraint {part!r} in OSSA version string {string!r}: {e}" |
| 837 | + ) from e |
| 838 | + |
| 839 | + return cls(constraints=constraints) |
| 840 | + |
775 | 841 |
|
776 | 842 | class MavenVersionRange(VersionRange): |
777 | 843 | """ |
|
0 commit comments