@@ -790,23 +790,56 @@ def from_ossa_native(cls, string):
790790
791791 >>> str(PypiVersionRange.from_ossa_native("<20.2.1, >=21.0.0 <21.2.1, ==22.0.0"))
792792 'vers:pypi/<20.2.1|>=21.0.0|<21.2.1|22.0.0'
793+
794+ >>> str(PypiVersionRange.from_ossa_native(">=1.15.0<1.15.2, 1.16.0"))
795+ 'vers:pypi/>=1.15.0|<1.15.2|1.16.0'
793796 """
794797
795798 # Normalize "and" keyword to comma
796799 # "<=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"
797800 string = string .replace (" and " , "," )
798801
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 )
802+ # Split on commas then whitespace to get individual tokens
803+ # "<=5.0.3 >=6.0.0 " -> ["<=5.0.3", ">=6.0.0"]
804+ raw_tokens = [token for chunk in string .split ("," ) for token in chunk .split ()]
805+
806+ if not raw_tokens :
807+ raise InvalidVersionRange (f"Empty or invalid OSSA version string: { string !r} " )
808+
809+ # Merge tokens where the operator and version are separated by a space
810+ # ["<=", "5.0.3", ">=", "6.0.0"] -> ["<=5.0.3", ">=6.0.0"]
811+ raw_iter = iter (raw_tokens )
812+ fused_tokens = []
813+ for current in raw_iter :
814+ is_bare_operator = all (c in "<>=!" for c in current )
815+ if is_bare_operator :
816+ fused_tokens .append (current + next (raw_iter ))
817+ else :
818+ fused_tokens .append (current )
819+
820+ # Split tokens that contain multiple concatenated constraints without separator
821+ # ">=1.15.0<1.15.2" -> [">=1.15.0", "<1.15.2"]
822+ parts = []
823+ for token in fused_tokens :
824+
825+ token_len = len (token )
826+ pos = 0
827+ while pos < token_len and token [pos ] in "<>=!" :
828+ pos += 1
829+
830+ segment_start = 0
831+ while pos < token_len :
832+ if token [pos ] in "<>=!" :
833+ parts .append (token [segment_start :pos ])
834+ segment_start = pos
835+ while pos < token_len and token [pos ] in "<>=!" :
836+ pos += 1
837+ else :
838+ pos += 1
839+ parts .append (token [segment_start :])
807840
808841 constraints = []
809- for part in string . split ( "," ) :
842+ for part in parts :
810843
811844 # Default to exact match for bare version numbers
812845 # "1.16.0" -> "=1.16.0"
0 commit comments