def union(self, other): # type: (VersionConstraint) -> VersionConstraint from pipgrip.libs.semver.version import Version if isinstance(other, Version): if self.allows(other): return self if other == self.min: return VersionRange( self.min, self.max, include_min=True, include_max=self.include_max ) if other == self.max: return VersionRange( self.min, self.max, include_min=self.include_min, include_max=True ) return VersionUnion.of(self, other) if isinstance(other, VersionRange): # If the two ranges don't overlap, we won't be able to create a single # VersionRange for both of them. edges_touch = ( self.max == other.min and (self.include_max or other.include_min) ) or (self.min == other.max and (self.include_min or other.include_max)) if not edges_touch and not self.allows_any(other): return VersionUnion.of(self, other) if self.allows_lower(other): union_min = self.min union_include_min = self.include_min else: union_min = other.min union_include_min = other.include_min if self.allows_higher(other): union_max = self.max union_include_max = self.include_max else: union_max = other.max union_include_max = other.include_max return VersionRange( union_min, union_max, include_min=union_include_min, include_max=union_include_max, ) return VersionUnion.of(self, other)
def parse_constraint(constraints): # type: (str) -> VersionConstraint if constraints == "*": return VersionRange() or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) or_groups = [] for constraints in or_constraints: and_constraints = re.split( "(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints ) constraint_objects = [] if len(and_constraints) > 1: for constraint in and_constraints: constraint_objects.append(parse_single_constraint(constraint)) else: constraint_objects.append(parse_single_constraint(and_constraints[0])) if len(constraint_objects) == 1: constraint = constraint_objects[0] else: constraint = constraint_objects[0] for next_constraint in constraint_objects[1:]: constraint = constraint.intersect(next_constraint) or_groups.append(constraint) if len(or_groups) == 1: return or_groups[0] else: return VersionUnion.of(*or_groups)
def union(self, other): # type: (VersionConstraint) -> VersionConstraint from pipgrip.libs.semver.version_range import VersionRange if other.allows(self): return other if isinstance(other, VersionRange): if other.min == self: return VersionRange( other.min, other.max, include_min=True, include_max=other.include_max, ) if other.max == self: return VersionRange( other.min, other.max, include_min=other.include_min, include_max=True, ) return VersionUnion.of(self, other)
def parse_single_constraint(constraint): # type: (str) -> VersionConstraint m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint) if m: return VersionRange() # Tilde range m = TILDE_CONSTRAINT.match(constraint) if m: version = Version.parse(m.group(1)) high = version.stable.next_minor if len(m.group(1).split(".")) == 1: high = version.stable.next_major return VersionRange( version, high, include_min=True, always_include_max_prerelease=True ) # PEP 440 Tilde range (~=) m = TILDE_PEP440_CONSTRAINT.match(constraint) if m: precision = 1 if m.group(3): precision += 1 if m.group(4): precision += 1 version = Version.parse(m.group(1)) if precision == 2: low = version high = version.stable.next_major else: low = version high = version.stable.next_minor return VersionRange( low, high, include_min=True, always_include_max_prerelease=True ) # Caret range m = CARET_CONSTRAINT.match(constraint) if m: version = Version.parse(m.group(1)) return VersionRange( version, version.next_breaking, include_min=True, always_include_max_prerelease=True, ) # X Range m = X_CONSTRAINT.match(constraint) if m: op = m.group(1) major = int(m.group(2)) minor = m.group(3) if minor is not None: version = Version(major, int(minor), 0) result = VersionRange( version, version.next_minor, include_min=True, always_include_max_prerelease=True, ) else: if major == 0: result = VersionRange(max=Version(1, 0, 0)) else: version = Version(major, 0, 0) result = VersionRange( version, version.next_major, include_min=True, always_include_max_prerelease=True, ) if op == "!=": result = VersionRange().difference(result) return result # Basic comparator m = BASIC_CONSTRAINT.match(constraint) if m: op = m.group(1) version = m.group(2) if version == "dev": version = "0.0-dev" try: version = Version.parse(version) except ValueError: raise ValueError( "Could not parse version constraint: {}".format(constraint) ) if op == "<": return VersionRange(max=version) elif op == "<=": return VersionRange(max=version, include_max=True) elif op == ">": return VersionRange(min=version) elif op == ">=": return VersionRange(min=version, include_min=True) elif op == "!=": return VersionUnion(VersionRange(max=version), VersionRange(min=version)) else: return version raise ValueError("Could not parse version constraint: {}".format(constraint))
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint from pipgrip.libs.semver.version import Version if other.is_empty(): return self if isinstance(other, Version): if not self.allows(other): return self if other == self.min: if not self.include_min: return self return VersionRange(self.min, self.max, False, self.include_max) if other == self.max: if not self.include_max: return self return VersionRange(self.min, self.max, self.include_min, False) return VersionUnion.of( VersionRange(self.min, other, self.include_min, False), VersionRange(other, self.max, False, self.include_max), ) elif isinstance(other, VersionRange): if not self.allows_any(other): return self if not self.allows_lower(other): before = None elif self.min == other.min: before = self.min else: before = VersionRange( self.min, other.min, self.include_min, not other.include_min ) if not self.allows_higher(other): after = None elif self.max == other.max: after = self.max else: after = VersionRange( other.max, self.max, not other.include_max, self.include_max ) if before is None and after is None: return EmptyConstraint() if before is None: return after if after is None: return before return VersionUnion.of(before, after) elif isinstance(other, VersionUnion): ranges = [] # type: List[VersionRange] current = self for range_ in other.ranges: # Skip any ranges that are strictly lower than [current]. if range_.is_strictly_lower(current): continue # If we reach a range strictly higher than [current], no more ranges # will be relevant so we can bail early. if range_.is_strictly_higher(current): break difference = current.difference(range_) if difference.is_empty(): return EmptyConstraint() elif isinstance(difference, VersionUnion): # If [range] split [current] in half, we only need to continue # checking future ranges against the latter half. ranges.append(difference.ranges[0]) current = difference.ranges[-1] else: current = difference if not ranges: return current return VersionUnion.of(*(ranges + [current])) raise ValueError("Unknown VersionConstraint type {}.".format(other))