def _eq(a, b): _print("'%s' == '%s'" % (a, b)) a_range = VersionRange(a) b_range = VersionRange(b) self.assertTrue(a_range == b_range) self.assertTrue(a_range.issuperset(a_range)) self.assertTrue(a_range.issuperset(b_range)) self.assertTrue(VersionRange(str(a_range)) == a_range) self.assertTrue(VersionRange(str(b_range)) == a_range) self.assertTrue(hash(a_range) == hash(b_range)) a_ = a.replace('.', '-') a_ = a_.replace("--", "..") a_range_ = VersionRange(a_) self.assertTrue(a_range_ == a_range) self.assertTrue(hash(a_range_) == hash(a_range)) range_strs = a.split('|') ranges = [VersionRange(x) for x in range_strs] ranges_ = ranges[0].union(ranges[1:]) self.assertTrue(ranges_ == a_range) self.assertTrue(a_range | b_range == a_range) self.assertTrue(a_range - b_range is None) self.assertTrue(b_range - a_range is None) self.assertTrue(VersionRange() & a_range == a_range) self.assertTrue(b_range.span() & a_range == a_range) a_inv = a_range.inverse() self.assertTrue(a_inv == ~b_range) if a_inv: self.assertTrue(~a_inv == a_range) self.assertTrue(a_range | a_inv == VersionRange()) self.assertTrue(a_range & a_inv is None) a_ranges = a_range.split() a_range_ = a_ranges[0].union(a_ranges[1:]) self.assertTrue(a_range_ == b_range)
def _eq(a, b): _print("'%s' == '%s'" % (a, b)) a_range = VersionRange(a) b_range = VersionRange(b) self.assertTrue(a_range == b_range) self.assertTrue(a_range.issuperset(a_range)) self.assertTrue(a_range.issuperset(b_range)) self.assertTrue(VersionRange(str(a_range)) == a_range) self.assertTrue(VersionRange(str(b_range)) == a_range) self.assertTrue(hash(a_range) == hash(b_range)) a_ = a.replace('.', '-') a_ = a_.replace("--", "..") a_range_ = VersionRange(a_) self.assertTrue(a_range_ == a_range) self.assertTrue(hash(a_range_) == hash(a_range)) range_strs = a.split('|') ranges = [VersionRange(x) for x in range_strs] ranges_ = ranges[0].union(ranges[1:]) self.assertTrue(ranges_ == a_range) self.assertTrue(a_range | b_range == a_range) self.assertTrue(a_range - b_range is None) self.assertTrue(b_range - a_range is None) self.assertTrue(VersionRange() & a_range == a_range) self.assertTrue(b_range.span() & a_range == a_range) a_inv = a_range.inverse() self.assertTrue(a_inv == ~b_range) if a_inv: self.assertTrue(~a_inv == a_range) self.assertTrue(a_range | a_inv == VersionRange()) self.assertTrue(a_range & a_inv is None) a_ranges = a_range.split() a_range_ = a_ranges[0].union(a_ranges[1:]) self.assertTrue(a_range_ == b_range)
class Requirement(_Common): """Requirement for a versioned object. Examples of valid requirement strings: foo-1.0 [email protected] foo#1.0 foo-1+ foo-1+<4.3 foo<3 foo==1.0.1 Defines a requirement for an object. For example, "foo-5+" means that you require any version of "foo", version 5 or greater. An unversioned requirement can also be used ("foo"), this means you require any version of foo. You can drop the hyphen between object name and version range if the version range starts with a non-alphanumeric character - eg "foo<2". There are two different prefixes that can be applied to a requirement: - "!": The conflict requirement. This means that you require this version range of an object NOT to be present. To conflict with all versions of an object, use "!foo". - "~": This is known as a "weak reference", and means, "I do not require this object, but if present, it must be within this range." It is equivalent to the *conflict of the inverse* of the given version range. There is one subtle case to be aware of. "~foo" is a requirement that has no effect - ie, it means "I do not require foo, but if foo is present, it can be any version." This statement is still valid, but will produce a Requirement object with a None range. """ sep_regex = re.compile(r'[-@#=<>]') def __init__(self, s, invalid_bound_error=True): self.name_ = None self.range_ = None self.negate_ = False self.conflict_ = False self._str = None self.sep_ = '-' if s is None: return self.conflict_ = s.startswith('!') if self.conflict_: s = s[1:] elif s.startswith('~'): s = s[1:] self.negate_ = True self.conflict_ = True m = self.sep_regex.search(s) if m: i = m.start() self.name_ = s[:i] req_str = s[i:] if req_str[0] in ('-', '@', '#'): self.sep_ = req_str[0] req_str = req_str[1:] self.range_ = VersionRange(req_str, invalid_bound_error=invalid_bound_error) if self.negate_: self.range_ = ~self.range_ elif self.negate_: self.name_ = s # rare case - '~foo' equates to no effect self.range_ = None else: self.name_ = s self.range_ = VersionRange() @classmethod def construct(cls, name, range=None): """Create a requirement directly from an object name and VersionRange. Args: name: Object name string. range: VersionRange object. If None, an unversioned requirement is created. """ other = Requirement(None) other.name_ = name other.range_ = VersionRange() if range is None else range return other @property def name(self): """Name of the required object.""" return self.name_ @property def range(self): """VersionRange of the requirement.""" return self.range_ @property def conflict(self): """True if the requirement is a conflict requirement, eg "!foo", "~foo-1". """ return self.conflict_ @property def weak(self): """True if the requirement is weak, eg "~foo". Note that weak requirements are also conflict requirements, but not necessarily the other way around. """ return self.negate_ def safe_str(self): """Return a string representation that is safe for the current filesystem, and guarantees that no two different Requirement objects will encode to the same value.""" return str(self) def conflicts_with(self, other): """Returns True if this requirement conflicts with another `Requirement` or `VersionedObject`.""" if isinstance(other, Requirement): if (self.name_ != other.name_) or (self.range is None) \ or (other.range is None): return False elif self.conflict: return False if other.conflict \ else self.range_.issuperset(other.range_) elif other.conflict: return other.range_.issuperset(self.range_) else: return not self.range_.intersects(other.range_) else: # VersionedObject if (self.name_ != other.name_) or (self.range is None): return False if self.conflict: return (other.version_ in self.range_) else: return (other.version_ not in self.range_) def merged(self, other): """Returns the merged result of two requirements. Two requirements can be in conflict and if so, this function returns None. For example, requests for "foo-4" and "foo-6" are in conflict, since both cannot be satisfied with a single version of foo. Some example successful requirements merges are: - "foo-3+" and "!foo-5+" == "foo-3+<5" - "foo-1" and "foo-1.5" == "foo-1.5" - "!foo-2" and "!foo-5" == "!foo-2|5" """ if self.name_ != other.name_: return None # cannot merge across object names def _r(r_): r = Requirement(None) r.name_ = r_.name_ r.negate_ = r_.negate_ r.conflict_ = r_.conflict_ r.sep_ = r_.sep_ return r if self.range is None: return other elif other.range is None: return self elif self.conflict: if other.conflict: r = _r(self) r.range_ = self.range_ | other.range_ r.negate_ = (self.negate_ and other.negate_ and not r.range_.is_any()) return r else: range_ = other.range - self.range if range_ is None: return None else: r = _r(other) r.range_ = range_ return r elif other.conflict: range_ = self.range_ - other.range_ if range_ is None: return None else: r = _r(self) r.range_ = range_ return r else: range_ = self.range_ & other.range_ if range_ is None: return None else: r = _r(self) r.range_ = range_ return r def __eq__(self, other): return (isinstance(other, Requirement) and (self.name_ == other.name_) and (self.range_ == other.range_) and (self.conflict_ == other.conflict_)) def __hash__(self): return hash(str(self)) def __str__(self): if self._str is None: pre_str = '~' if self.negate_ else ('!' if self.conflict_ else '') range_str = '' sep_str = '' range_ = self.range_ if self.negate_: range_ = ~range_ if range_ else VersionRange() if not range_.is_any(): range_str = str(range_) if range_str[0] not in ('=', '<', '>'): sep_str = self.sep_ self._str = pre_str + self.name_ + sep_str + range_str return self._str
class Requirement(_Common): """Requirement for a versioned object. Examples of valid requirement strings: foo-1.0 [email protected] foo#1.0 foo-1+ foo-1+<4.3 foo<3 foo==1.0.1 Defines a requirement for an object. For example, "foo-5+" means that you require any version of "foo", version 5 or greater. An unversioned requirement can also be used ("foo"), this means you require any version of foo. You can drop the hyphen between object name and version range if the version range starts with a non-alphanumeric character - eg "foo<2". There are two different prefixes that can be applied to a requirement: - "!": The conflict requirement. This means that you require this version range of an object NOT to be present. To conflict with all versions of an object, use "!foo". - "~": This is known as a "weak reference", and means, "I do not require this object, but if present, it must be within this range." It is equivalent to the *conflict of the inverse* of the given version range. There is one subtle case to be aware of. "~foo" is a requirement that has no effect - ie, it means "I do not require foo, but if foo is present, it can be any version." This statement is still valid, but will produce a Requirement object with a None range. """ sep_regex = re.compile(r'[-@#=<>]') def __init__(self, s): self.name_ = None self.range_ = None self.negate_ = False self.conflict_ = False self._str = None self.sep_ = '-' if s is None: return self.conflict_ = s.startswith('!') if self.conflict_: s = s[1:] elif s.startswith('~'): s = s[1:] self.negate_ = True self.conflict_ = True m = self.sep_regex.search(s) if m: i = m.start() self.name_ = s[:i] req_str = s[i:] if req_str[0] in ('-', '@', '#'): self.sep_ = req_str[0] req_str = req_str[1:] self.range_ = VersionRange(req_str) if self.negate_: self.range_ = ~self.range_ elif self.negate_: self.name_ = s # rare case - '~foo' equates to no effect self.range_ = None else: self.name_ = s self.range_ = VersionRange() @classmethod def construct(cls, name, range=None): """Create a requirement directly from an object name and VersionRange. Args: name: Object name string. range: VersionRange object. If None, an unversioned requirement is created. """ other = Requirement(None) other.name_ = name other.range_ = VersionRange() if range is None else range return other @property def name(self): """Name of the required object.""" return self.name_ @property def range(self): """VersionRange of the requirement.""" return self.range_ @property def conflict(self): """True if the requirement is a conflict requirement, eg "!foo", "~foo-1". """ return self.conflict_ @property def weak(self): """True if the requirement is weak, eg "~foo". Note that weak requirements are also conflict requirements, but not necessarily the other way around. """ return self.negate_ def safe_str(self): """Return a string representation that is safe for the current filesystem, and guarantees that no two different Requirement objects will encode to the same value.""" return str(self) def conflicts_with(self, other): """Returns True if this requirement conflicts with another `Requirement` or `VersionedObject`.""" if isinstance(other, Requirement): if (self.name_ != other.name_) or (self.range is None) \ or (other.range is None): return False elif self.conflict: return False if other.conflict \ else self.range_.issuperset(other.range_) elif other.conflict: return other.range_.issuperset(self.range_) else: return not self.range_.intersects(other.range_) else: # VersionedObject if (self.name_ != other.name_) or (self.range is None): return False if self.conflict: return (other.version_ in self.range_) else: return (other.version_ not in self.range_) def merged(self, other): """Returns the merged result of two requirements. Two requirements can be in conflict and if so, this function returns None. For example, requests for "foo-4" and "foo-6" are in conflict, since both cannot be satisfied with a single version of foo. Some example successful requirements merges are: - "foo-3+" and "!foo-5+" == "foo-3+<5" - "foo-1" and "foo-1.5" == "foo-1.5" - "!foo-2" and "!foo-5" == "!foo-2|5" """ if self.name_ != other.name_: return None # cannot merge across object names def _r(r_): r = Requirement(None) r.name_ = r_.name_ r.negate_ = r_.negate_ r.conflict_ = r_.conflict_ r.sep_ = r_.sep_ return r if self.range is None: return other elif other.range is None: return self elif self.conflict: if other.conflict: r = _r(self) r.range_ = self.range_ | other.range_ r.negate_ = (self.negate_ and other.negate_ and not r.range_.is_any()) return r else: range_ = other.range - self.range if range_ is None: return None else: r = _r(other) r.range_ = range_ return r elif other.conflict: range_ = self.range_ - other.range_ if range_ is None: return None else: r = _r(self) r.range_ = range_ return r else: range_ = self.range_ & other.range_ if range_ is None: return None else: r = _r(self) r.range_ = range_ return r def __eq__(self, other): return (isinstance(other, Requirement) and (self.name_ == other.name_) and (self.range_ == other.range_) and (self.conflict_ == other.conflict_)) def __hash__(self): return hash(str(self)) def __str__(self): if self._str is None: pre_str = '~' if self.negate_ else ('!' if self.conflict_ else '') range_str = '' sep_str = '' range_ = self.range_ if self.negate_: range_ = ~range_ if range_ else VersionRange() if not range_.is_any(): range_str = str(range_) if range_str[0] not in ('=', '<', '>'): sep_str = self.sep_ self._str = pre_str + self.name_ + sep_str + range_str return self._str