관리-도구
편집 파일: version_range.py
from typing import List import semver from .empty_constraint import EmptyConstraint from .version_constraint import VersionConstraint from .version_union import VersionUnion class VersionRange(VersionConstraint): def __init__( self, min=None, max=None, include_min=False, include_max=False, always_include_max_prerelease=False, ): full_max = max if ( always_include_max_prerelease and not include_max and not full_max.is_prerelease() and not full_max.build and (min is None or not min.is_prerelease() or not min.equals_without_prerelease(full_max)) ): full_max = full_max.first_prerelease self._min = min self._max = max self._full_max = full_max self._include_min = include_min self._include_max = include_max @property def min(self): return self._min @property def max(self): return self._max @property def full_max(self): return self._full_max @property def include_min(self): return self._include_min @property def include_max(self): return self._include_max def is_empty(self): return False def is_any(self): return self._min is None and self._max is None def allows(self, other): # type: (semver.Version) -> bool if self._min is not None: if other < self._min: return False if not self._include_min and other == self._min: return False if self._max is not None: if other > self._max: return False if not self._include_max and other == self._max: return False return True def allows_all(self, other): # type: (VersionConstraint) -> bool from .version import Version if other.is_empty(): return True if isinstance(other, Version): return self.allows(other) if isinstance(other, VersionUnion): return all([self.allows_all(constraint) for constraint in other.ranges]) if isinstance(other, VersionRange): return not other.allows_lower(self) and not other.allows_higher(self) raise ValueError("Unknown VersionConstraint type {}.".format(other)) def allows_any(self, other): # type: (VersionConstraint) -> bool from .version import Version if other.is_empty(): return False if isinstance(other, Version): return self.allows(other) if isinstance(other, VersionUnion): return any([self.allows_any(constraint) for constraint in other.ranges]) if isinstance(other, VersionRange): return not other.is_strictly_lower(self) and not other.is_strictly_higher(self) raise ValueError("Unknown VersionConstraint type {}.".format(other)) def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint from .version import Version if other.is_empty(): return other if isinstance(other, VersionUnion): return other.intersect(self) # A range and a Version just yields the version if it's in the range. if isinstance(other, Version): if self.allows(other): return other return EmptyConstraint() if not isinstance(other, VersionRange): raise ValueError("Unknown VersionConstraint type {}.".format(other)) if self.allows_lower(other): if self.is_strictly_lower(other): return EmptyConstraint() intersect_min = other.min intersect_include_min = other.include_min else: if other.is_strictly_lower(self): return EmptyConstraint() intersect_min = self._min intersect_include_min = self._include_min if self.allows_higher(other): intersect_max = other.max intersect_include_max = other.include_max else: intersect_max = self._max intersect_include_max = self._include_max if intersect_min is None and intersect_max is None: return VersionRange() # If the range is just a single version. if intersect_min == intersect_max: # Because we already verified that the lower range isn't strictly # lower, there must be some overlap. assert intersect_include_min and intersect_include_max return intersect_min # If we got here, there is an actual range. return VersionRange(intersect_min, intersect_max, intersect_include_min, intersect_include_max) def union(self, other): # type: (VersionConstraint) -> VersionConstraint from .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 difference(self, other): # type: (VersionConstraint) -> VersionConstraint from .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)) def allows_lower(self, other): # type: (VersionRange) -> bool if self.min is None: return other.min is not None if other.min is None: return False if self.min < other.min: return True if self.min > other.min: return False return self.include_min and not other.include_min def allows_higher(self, other): # type: (VersionRange) -> bool if self.max is None: return other.max is not None if other.max is None: return False if self.max < other.max: return False if self.max > other.max: return True return self.include_max and not other.include_max def is_strictly_lower(self, other): # type: (VersionRange) -> bool if self.max is None or other.min is None: return False if self.full_max < other.min: return True if self.full_max > other.min: return False return not self.include_max or not other.include_min def is_strictly_higher(self, other): # type: (VersionRange) -> bool return other.is_strictly_lower(self) def is_adjacent_to(self, other): # type: (VersionRange) -> bool if self.max != other.min: return False return self.include_max and not other.include_min or not self.include_max and other.include_min def __eq__(self, other): if not isinstance(other, VersionRange): return False return ( self._min == other.min and self._max == other.max and self._include_min == other.include_min and self._include_max == other.include_max ) def __lt__(self, other): return self._cmp(other) < 0 def __le__(self, other): return self._cmp(other) <= 0 def __gt__(self, other): return self._cmp(other) > 0 def __ge__(self, other): return self._cmp(other) >= 0 def _cmp(self, other): # type: (VersionRange) -> int if self.min is None: if other.min is None: return self._compare_max(other) return -1 elif other.min is None: return 1 result = self.min._cmp(other.min) if result != 0: return result if self.include_min != other.include_min: return -1 if self.include_min else 1 return self._compare_max(other) def _compare_max(self, other): # type: (VersionRange) -> int if self.max is None: if other.max is None: return 0 return 1 elif other.max is None: return -1 result = self.max._cmp(other.max) if result != 0: return result if self.include_max != other.include_max: return 1 if self.include_max else -1 return 0 def __str__(self): text = "" if self.min is not None: text += ">=" if self.include_min else ">" text += self.min.text if self.max is not None: if self.min is not None: text += "," text += "{}{}".format("<=" if self.include_max else "<", self.max.text) if self.min is None and self.max is None: return "*" return text def __repr__(self): return "<VersionRange ({})>".format(str(self)) def __hash__(self): return hash((self.min, self.max, self.include_min, self.include_max))