class native_StrExactMatch(object): """ exact string comparison match """ __slots__ = __attr_comparison__ = ('_hash', 'exact', 'case_sensitive', 'negate') __metaclass__ = generic_equality def __init__(self, exact, case_sensitive=True, negate=False): """ :param exact: exact string to match :param case_sensitive: should the match be case sensitive? :param negate: should the match results be negated? """ sf = object.__setattr__ sf(self, "negate", negate) sf(self, "case_sensitive", case_sensitive) if not case_sensitive: sf(self, "exact", str(exact).lower()) else: sf(self, "exact", str(exact)) sf(self, "_hash", hash((self.exact, self.negate, self.case_sensitive))) def match(self, value): if self.case_sensitive: return (self.exact == value) != self.negate else: return (self.exact == value.lower()) != self.negate __hash__ = reflective_hash('_hash')
def hashed_base(name, bases, scope): scope.setdefault("__hash__", reflective_hash('_hash')) slots = scope.get("__slots__", None) if slots is not None: if "_hash" not in slots: slots = scope["__slots__"] = slots + ("_hash",) scope.setdefault("__attr_comparison__", slots) return generic_equality(name, bases, scope)
def hashed_base(name, bases, scope): scope.setdefault("__hash__", reflective_hash('_hash')) slots = scope.get("__slots__", None) if slots is not None: if "_hash" not in slots: slots = scope["__slots__"] = slots + ("_hash", ) scope.setdefault("__attr_comparison__", slots) return generic_equality(name, bases, scope)
class StrExactMatch(base, metaclass=generic_equality): """exact string comparison match""" __slots__ = __attr_comparison__ = ('_hash', 'exact', 'case_sensitive', 'negate') __inst_caching__ = True def __init__(self, exact, case_sensitive=True, negate=False): """ :param exact: exact string to match :param case_sensitive: should the match be case sensitive? :param negate: should the match results be negated? """ sf = object.__setattr__ sf(self, "negate", negate) sf(self, "case_sensitive", case_sensitive) if not case_sensitive: sf(self, "exact", str(exact).lower()) else: sf(self, "exact", str(exact)) sf(self, "_hash", hash((self.exact, self.negate, self.case_sensitive))) def match(self, value): value = str(value) if self.case_sensitive: return (self.exact == value) != self.negate else: return (self.exact == value.lower()) != self.negate def intersect(self, other): s1, s2 = self.exact, other.exact if other.case_sensitive and not self.case_sensitive: s1 = s1.lower() elif self.case_sensitive and not other.case_sensitive: s2 = s2.lower() if s1 == s2 and self.negate == other.negate: if other.case_sensitive: return other return self return None def __repr__(self): if self.negate: string = '<%s %r negated @%#8x>' else: string = '<%s %r @%#8x>' return string % (self.__class__.__name__, self.exact, id(self)) def __str__(self): if self.negate: return f'!= {self.exact}' return f'== {self.exact}' __hash__ = reflective_hash('_hash')
class atom(boolean.AndRestriction): """Currently implements gentoo ebuild atom parsing. Should be converted into an agnostic dependency base. """ # note we don't need _hash __slots__ = ("blocks", "blocks_strongly", "op", "cpvstr", "negate_vers", "use", "slot_operator", "slot", "subslot", "category", "version", "revision", "fullver", "package", "key", "repo_id", "_hash") type = packages.package_type negate = False _evaluate_collapse = True __attr_comparison__ = ("cpvstr", "op", "blocks", "negate_vers", "use", "slot", "subslot", "slot_operator", "repo_id") inject_richcmp_methods_from_cmp(locals()) # hack; combine these 2 metaclasses at some point... locals().pop("__eq__", None) locals().pop("__ne__", None) __metaclass__ = generic_equality __inst_caching__ = True locals().update(atom_overrides.iteritems()) # overrided in child class if it's supported evaluate_depset = None @property def blocks_temp_ignorable(self): return not self.blocks_strongly weak_blocker = alias_attr("blocks_temp_ignorable") def __repr__(self): if self.op == '=*': atom = "=%s*" % self.cpvstr else: atom = self.op + self.cpvstr if self.blocks: atom = '!' + atom if self.blocks: if self.blocks_strongly: atom = '!!' + atom else: atom = '!' + atom attrs = [atom] if self.use: attrs.append('use=%r' % (self.use, )) if self.slot is not None: attrs.append('slot=%r' % (self.slot, )) if self.subslot is not None: attrs.append('subslot=%r' % (self.subslot, )) if self.repo_id is not None: attrs.append('repo_id=%r' % (self.repo_id, )) return '<%s %s @#%x>' % (self.__class__.__name__, ' '.join(attrs), id(self)) def __reduce__(self): return (atom, (str(self), self.negate_vers)) def iter_dnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_dnf_solutions(self, True) return iter([[self]]) def iter_cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_cnf_solutions(self, True) return iter([[self]]) def cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.cnf_solutions(self, True) return [[self]] @property def is_simple(self): return len(self.restrictions) == 2 def __str__(self): if self.op == '=*': s = "=%s*" % self.cpvstr else: s = self.op + self.cpvstr if self.blocks: if self.blocks_strongly: s = '!!' + s else: s = '!' + s if self.slot: s += ":%s" % self.slot if self.subslot and self.slot_operator == "=": s += "/%s=" % self.subslot if self.repo_id: s += "::%s" % self.repo_id if self.use: s += "[%s]" % ",".join(self.use) return s __hash__ = reflective_hash('_hash') def __iter__(self): return iter(self.restrictions) def __getitem__(self, index): return self.restrictions[index] def __cmp__(self, other): if not isinstance(other, self.__class__): raise TypeError("other isn't of %s type, is %s" % (self.__class__, other.__class__)) c = cmp(self.category, other.category) if c: return c c = cmp(self.package, other.package) if c: return c c = cmp(self.op, other.op) if c: return c c = cpv.ver_cmp(self.version, self.revision, other.version, other.revision) if c: return c c = cmp(self.blocks, other.blocks) if c: # invert it; cmp(True, False) == 1 # want non blockers then blockers. return -c c = cmp(self.blocks_strongly, other.blocks_strongly) if c: # want !! prior to ! return c c = cmp(self.negate_vers, other.negate_vers) if c: return c def f(v): return '' if v is None else v c = cmp(f(self.slot), f(other.slot)) if c: return c c = cmp(self.use, other.use) if c: return c return cmp(self.repo_id, other.repo_id) def intersects(self, other): """Check if a passed in atom "intersects" this restriction's atom. Two atoms "intersect" if a package can be constructed that matches both: - if you query for just "dev-lang/python" it "intersects" both "dev-lang/python" and ">=dev-lang/python-2.4" - if you query for "=dev-lang/python-2.4" it "intersects" ">=dev-lang/python-2.4" and "dev-lang/python" but not "<dev-lang/python-2.3" USE and slot deps are also taken into account. The block/nonblock state of the atom is ignored. """ # Our "key" (cat/pkg) must match exactly: if self.key != other.key: return False # Slot dep only matters if we both have one. If we do they # must be identical: if (self.slot is not None and other.slot is not None and self.slot != other.slot): return False if (self.repo_id is not None and other.repo_id is not None and self.repo_id != other.repo_id): return False # Use deps are similar: if one of us forces a flag on and the # other forces it off we do not intersect. If only one of us # cares about a flag it is irrelevant. # Skip the (very common) case of one of us not having use deps: if self.use and other.use: # Set of flags we do not have in common: flags = set(self.use) ^ set(other.use) for flag in flags: # If this is unset and we also have the set version we fail: if flag[0] == '-' and flag[1:] in flags: return False # Remaining thing to check is version restrictions. Get the # ones we can check without actual version comparisons out of # the way first. # If one of us is unversioned we intersect: if not self.op or not other.op: return True # If we are both "unbounded" in the same direction we intersect: if (('<' in self.op and '<' in other.op) or ('>' in self.op and '>' in other.op)): return True # Trick used here: just use the atoms as sufficiently # package-like object to pass to these functions (all that is # needed is a version and revision attr). # If one of us is an exact match we intersect if the other matches it: if self.op == '=': if other.op == '=*': return self.fullver.startswith(other.fullver) return restricts.VersionMatch(other.op, other.version, other.revision).match(self) if other.op == '=': if self.op == '=*': return other.fullver.startswith(self.fullver) return restricts.VersionMatch(self.op, self.version, self.revision).match(other) # If we are both ~ matches we match if we are identical: if self.op == other.op == '~': return (self.version == other.version and self.revision == other.revision) # If we are both glob matches we match if one of us matches the other. if self.op == other.op == '=*': return (self.fullver.startswith(other.fullver) or other.fullver.startswith(self.fullver)) # If one of us is a glob match and the other a ~ we match if the glob # matches the ~ (ignoring a revision on the glob): if self.op == '=*' and other.op == '~': return other.fullver.startswith(self.version) if other.op == '=*' and self.op == '~': return self.fullver.startswith(other.version) # If we get here at least one of us is a <, <=, > or >=: if self.op in ('<', '<=', '>', '>='): ranged, other = self, other else: ranged, other = other, self if '<' in other.op or '>' in other.op: # We are both ranged, and in the opposite "direction" (or # we would have matched above). We intersect if we both # match the other's endpoint (just checking one endpoint # is not enough, it would give a false positive on <=2 vs >2) return (restricts.VersionMatch(other.op, other.version, other.revision).match(ranged) and restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other)) if other.op == '~': # Other definitely matches its own version. If ranged also # does we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True # The only other case where we intersect is if ranged is a # > or >= on other's version and a nonzero revision. In # that case other will match ranged. Be careful not to # give a false positive for ~2 vs <2 here: return ranged.op in ('>', '>=') and restricts.VersionMatch( other.op, other.version, other.revision).match(ranged) if other.op == '=*': # The fun one, since glob matches do not correspond to a # single contiguous region of versions. # a glob match definitely matches its own version, so if # ranged does too we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True if '<' in ranged.op: # Remaining cases where this intersects: there is a # package smaller than ranged.fullver and # other.fullver that they both match. # If other.revision is not None then other does not # match anything smaller than its own fullver: if other.revision is not None: return False # If other.revision is None then we can always # construct a package smaller than other.fullver by # tagging e.g. an _alpha1 on, since # cat/pkg_beta2_alpha1_alpha1 is a valid version. # (Yes, really. Try it if you don't believe me.) # If and only if other also matches ranged then # ranged will also match one of those smaller packages. # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) else: # Remaining cases where this intersects: there is a # package greater than ranged.fullver and # other.fullver that they both match. # We can always construct a package greater than # other.fullver by adding a digit to it. # If and only if other also matches ranged then # ranged will match such a larger package # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) # Handled all possible ops. raise NotImplementedError( 'Someone added an op to atom without adding it to intersects') def evaluate_conditionals(self, parent_cls, parent_seq, enabled, tristate=None): parent_seq.append(self)
class atom(boolean.AndRestriction, metaclass=klass.generic_equality): """Currently implements gentoo ebuild atom parsing. Should be converted into an agnostic dependency base. """ # note we don't need _hash __slots__ = ( "blocks", "blocks_strongly", "op", "cpvstr", "negate_vers", "use", "slot_operator", "slot", "subslot", "repo_id", "_hash", "_cpv", "_restrictions", ) type = restriction.package_type negate = False _evaluate_collapse = True __attr_comparison__ = ("cpvstr", "op", "blocks", "negate_vers", "use", "slot", "subslot", "slot_operator", "repo_id") klass.inject_richcmp_methods_from_cmp(locals()) # hack; combine these 2 metaclasses at some point... locals().pop("__eq__", None) locals().pop("__ne__", None) __inst_caching__ = True # overrided in child class if it's supported evaluate_depset = None def __init__(self, atom, negate_vers=False, eapi='-1'): """ :param atom: string, see gentoo ebuild atom syntax :keyword negate_vers: boolean controlling whether the version should be inverted for restriction matching :keyword eapi: string/int controlling what eapi to enforce for this atom """ if not atom: raise errors.MalformedAtom(atom) sf = object.__setattr__ orig_atom = atom override_kls = False use_start = atom.find("[") slot_start = atom.find(":") if use_start != -1: # use dep use_end = atom.find("]", use_start) if use_end == -1: raise errors.MalformedAtom(orig_atom, "use restriction isn't completed") elif use_end != len(atom) - 1: raise errors.MalformedAtom(orig_atom, "trailing garbage after use dep") sf(self, "use", tuple(sorted(atom[use_start + 1:use_end].split(',')))) for x in self.use: # stripped purely for validation reasons try: if x[-1] in "=?": override_kls = True x = x[:-1] if x[0] == '!': x = x[1:] if x[0] == '-': raise errors.MalformedAtom( orig_atom, f"malformed use flag: {x}") elif x[0] == '-': x = x[1:] if x[-1] == ')' and eapi not in ('0', '1', '2', '3'): # use defaults. if x[-3:] in ("(+)", "(-)"): x = x[:-3] if not x: raise errors.MalformedAtom(orig_atom, 'empty use dep detected') if not valid_use_flag.match(x): raise errors.MalformedAtom(orig_atom, f'invalid USE flag: {x!r}') except IndexError: raise errors.MalformedAtom(orig_atom, 'empty use dep detected') if override_kls: sf(self, '__class__', transitive_use_atom) atom = atom[0:use_start] + atom[use_end + 1:] else: sf(self, "use", None) if slot_start != -1: i2 = atom.find("::", slot_start) if i2 != -1: repo_id = atom[i2 + 2:] if not repo_id: raise errors.MalformedAtom(orig_atom, "repo_id must not be empty") elif repo_id[0] in '-': raise errors.MalformedAtom( orig_atom, f"invalid first char of repo_id '{repo_id}' (must not begin with a hyphen)" ) elif not valid_repo_chars.issuperset(repo_id): raise errors.MalformedAtom( orig_atom, f"repo_id may contain only [a-Z0-9_-/], found {repo_id!r}" ) atom = atom[:i2] sf(self, "repo_id", repo_id) else: sf(self, "repo_id", None) # slot dep. slot = atom[slot_start + 1:] slot_operator = subslot = None if not slot: # if the slot char came in only due to repo_id, force slots to None if i2 == -1: raise errors.MalformedAtom( orig_atom, "Empty slot targets aren't allowed") slot = None else: slots = (slot, ) if eapi not in ('0', '1', '2', '3', '4'): if slot[0:1] in ("*", "="): if len(slot) > 1: raise errors.MalformedAtom( orig_atom, "Slot operators '*' and '=' do not take slot targets" ) slot_operator = slot slot, slots = None, () else: if slot.endswith('='): slot_operator = '=' slot = slot[:-1] slots = slot.split('/', 1) elif eapi == '0': raise errors.MalformedAtom( orig_atom, "slot dependencies aren't allowed in EAPI 0") for chunk in slots: if not chunk: raise errors.MalformedAtom( orig_atom, "Empty slot targets aren't allowed") if chunk[0] in '-.': raise errors.MalformedAtom( orig_atom, "Slot targets must not start with a hypen or dot: {chunk!r}" ) elif not valid_slot_chars.issuperset(chunk): invalid_chars = ', '.join( map( repr, sorted( set(chunk).difference(valid_slot_chars)))) raise errors.MalformedAtom( orig_atom, f"Invalid character(s) in slot target: {invalid_chars}" ) if len(slots) == 2: slot, subslot = slots sf(self, "slot_operator", slot_operator) sf(self, "slot", slot) sf(self, "subslot", subslot) atom = atom[:slot_start] else: sf(self, "slot_operator", None) sf(self, "slot", None) sf(self, "subslot", None) sf(self, "repo_id", None) sf(self, "blocks", atom[0] == "!") if self.blocks: atom = atom[1:] # hackish/slow, but lstrip doesn't take a 'prune this many' arg # open to alternatives if eapi not in ('0', '1') and atom.startswith("!"): atom = atom[1:] sf(self, "blocks_strongly", True) else: sf(self, "blocks_strongly", False) else: sf(self, "blocks_strongly", False) if atom[0] in ('<', '>'): if atom[1] == '=': sf(self, 'op', atom[:2]) atom = atom[2:] else: sf(self, 'op', atom[0]) atom = atom[1:] elif atom[0] == '=': if atom[-1] == '*': sf(self, 'op', '=*') atom = atom[1:-1] else: atom = atom[1:] sf(self, 'op', '=') elif atom[0] == '~': sf(self, 'op', '~') atom = atom[1:] else: sf(self, 'op', '') sf(self, 'cpvstr', atom) if eapi == '0': for x in ('use', 'slot'): if getattr(self, x) is not None: raise errors.MalformedAtom( orig_atom, f"{x} atoms aren't supported for EAPI 0") elif eapi == '1': if self.use is not None: raise errors.MalformedAtom( orig_atom, "use atoms aren't supported for EAPI < 2") if eapi != '-1': if self.repo_id is not None: raise errors.MalformedAtom( orig_atom, f"repo_id atoms aren't supported for EAPI {eapi}") if use_start != -1 and slot_start != -1 and use_start < slot_start: raise errors.MalformedAtom(orig_atom, "slot restriction must proceed use") try: sf(self, "_cpv", cpv.CPV(self.cpvstr, versioned=bool(self.op))) except errors.InvalidCPV as e: raise errors.MalformedAtom(orig_atom) from e if self.op: if self.version is None: raise errors.MalformedAtom(orig_atom, "operator requires a version") elif self.op == '~' and self.revision: raise errors.MalformedAtom( orig_atom, "~ revision operater cannot be combined with a revision") elif self.version is not None: raise errors.MalformedAtom(orig_atom, 'versioned atom requires an operator') sf(self, "_hash", hash(orig_atom)) sf(self, "negate_vers", negate_vers) __getattr__ = klass.GetAttrProxy("_cpv") __dir__ = klass.DirProxy("_cpv") @property def blocks_temp_ignorable(self): return not self.blocks_strongly weak_blocker = klass.alias_attr("blocks_temp_ignorable") def __repr__(self): if self.op == '=*': atom = f"={self.cpvstr}*" else: atom = self.op + self.cpvstr if self.blocks: atom = '!' + atom if self.blocks: if self.blocks_strongly: atom = '!!' + atom else: atom = '!' + atom attrs = [atom] if self.use: attrs.append(f'use={self.use!r}') if self.slot is not None: attrs.append(f'slot={self.slot!r}') if self.subslot is not None: attrs.append(f'subslot={self.subslot!r}') if self.repo_id is not None: attrs.append(f'repo_id={self.repo_id!r}') return '<%s %s @#%x>' % (self.__class__.__name__, ' '.join(attrs), id(self)) def __reduce__(self): return (atom, (str(self), self.negate_vers)) def iter_dnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_dnf_solutions(self, True) return iter([[self]]) def iter_cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.iter_cnf_solutions(self, True) return iter([[self]]) def cnf_solutions(self, full_solution_expansion=False): if full_solution_expansion: return boolean.AndRestriction.cnf_solutions(self, True) return [[self]] @property def is_simple(self): return len(self.restrictions) == 2 @klass.jit_attr def restrictions(self): # ordering here matters; against 24702 ebuilds for # a non matchable atom with package as the first restriction # 10 loops, best of 3: 206 msec per loop # with category as the first(the obvious ordering) # 10 loops, best of 3: 209 msec per loop # why? because category is more likely to collide; # at the time of this profiling, there were 151 categories. # over 11k packages however. r = [ restricts.PackageDep(self.package), restricts.CategoryDep(self.category) ] if self.repo_id is not None: r.insert(0, restricts.RepositoryDep(self.repo_id)) if self.fullver is not None: if self.op == '=*': r.append( packages.PackageRestriction( "fullver", values.StrGlobMatch(self.fullver))) else: r.append( restricts.VersionMatch(self.op, self.version, self.revision, negate=self.negate_vers)) if self.slot is not None: r.append(restricts.SlotDep(self.slot)) if self.subslot is not None: r.append(restricts.SubSlotDep(self.subslot)) if self.use is not None: r.extend(restricts._parse_nontransitive_use(self.use)) return tuple(r) def __str__(self): if self.op == '=*': s = f"={self.cpvstr}*" else: s = self.op + self.cpvstr if self.blocks: if self.blocks_strongly: s = '!!' + s else: s = '!' + s if self.slot: s += f":{self.slot}" if self.subslot: s += f"/{self.subslot}" if self.slot_operator == "=": s += self.slot_operator elif self.slot_operator: s += f":{self.slot_operator}" if self.repo_id: s += f"::{self.repo_id}" if self.use: use = ','.join(self.use) s += f"[{use}]" return s __hash__ = klass.reflective_hash('_hash') def __iter__(self): return iter(self.restrictions) def __getitem__(self, index): return self.restrictions[index] def __cmp__(self, other): if not isinstance(other, atom): raise TypeError( f"other isn't of {atom!r} type, is {other.__class__}") c = cmp(self.category, other.category) if c: return c c = cmp(self.package, other.package) if c: return c c = cmp(self.op, other.op) if c: return c c = cpv.ver_cmp(self.version, self.revision, other.version, other.revision) if c: return c c = cmp(self.blocks, other.blocks) if c: # invert it; cmp(True, False) == 1 # want non blockers then blockers. return -c c = cmp(self.blocks_strongly, other.blocks_strongly) if c: # want !! prior to ! return c c = cmp(self.negate_vers, other.negate_vers) if c: return c def f(v): return '' if v is None else v c = cmp(f(self.slot), f(other.slot)) if c: return c c = cmp(self.use, other.use) if c: return c return cmp(self.repo_id, other.repo_id) no_usedeps = klass.alias_attr("get_atom_without_use_deps") @property def get_atom_without_use_deps(self): """Return atom object stripped of USE dependencies.""" if not self.use: return self if self.op == '=*': s = f'={self.cpvstr}*' else: s = self.op + self.cpvstr if self.blocks: s = '!' + s if not self.blocks_temp_ignorable: s = '!' + s if self.slot: s += f':{self.slot}' return atom(s) def intersects(self, other): """Check if a passed in atom "intersects" this restriction's atom. Two atoms "intersect" if a package can be constructed that matches both: - if you query for just "dev-lang/python" it "intersects" both "dev-lang/python" and ">=dev-lang/python-2.4" - if you query for "=dev-lang/python-2.4" it "intersects" ">=dev-lang/python-2.4" and "dev-lang/python" but not "<dev-lang/python-2.3" USE and slot deps are also taken into account. The block/nonblock state of the atom is ignored. """ # Our "key" (cat/pkg) must match exactly: if self.key != other.key: return False # Slot dep only matters if we both have one. If we do they # must be identical: if (self.slot is not None and other.slot is not None and self.slot != other.slot): return False # Subslot dep only matters if we both have one. If we do they # must be identical: if (self.subslot is not None and other.subslot is not None and self.subslot != other.subslot): return False if (self.repo_id is not None and other.repo_id is not None and self.repo_id != other.repo_id): return False # Use deps are similar: if one of us forces a flag on and the # other forces it off we do not intersect. If only one of us # cares about a flag it is irrelevant. # Skip the (very common) case of one of us not having use deps: if self.use and other.use: # Set of flags we do not have in common: flags = set(self.use) ^ set(other.use) for flag in flags: # If this is unset and we also have the set version we fail: if flag[0] == '-' and flag[1:] in flags: return False # Remaining thing to check is version restrictions. Get the # ones we can check without actual version comparisons out of # the way first. # If one of us is unversioned we intersect: if not self.op or not other.op: return True # If we are both "unbounded" in the same direction we intersect: if (('<' in self.op and '<' in other.op) or ('>' in self.op and '>' in other.op)): return True # Trick used here: just use the atoms as sufficiently # package-like object to pass to these functions (all that is # needed is a version and revision attr). # If one of us is an exact match we intersect if the other matches it: if self.op == '=': if other.op == '=*': return self.fullver.startswith(other.fullver) return restricts.VersionMatch(other.op, other.version, other.revision).match(self) if other.op == '=': if self.op == '=*': return other.fullver.startswith(self.fullver) return restricts.VersionMatch(self.op, self.version, self.revision).match(other) # If we are both ~ matches we match if we are identical: if self.op == other.op == '~': return (self.version == other.version and self.revision == other.revision) # If we are both glob matches we match if one of us matches the other. if self.op == other.op == '=*': return (self.fullver.startswith(other.fullver) or other.fullver.startswith(self.fullver)) # If one of us is a glob match and the other a ~ we match if the glob # matches the ~ (ignoring a revision on the glob): if self.op == '=*' and other.op == '~': return other.fullver.startswith(self.version) if other.op == '=*' and self.op == '~': return self.fullver.startswith(other.version) # If we get here at least one of us is a <, <=, > or >=: if self.op in ('<', '<=', '>', '>='): ranged, other = self, other else: ranged, other = other, self if '<' in other.op or '>' in other.op: # We are both ranged, and in the opposite "direction" (or # we would have matched above). We intersect if we both # match the other's endpoint (just checking one endpoint # is not enough, it would give a false positive on <=2 vs >2) return (restricts.VersionMatch(other.op, other.version, other.revision).match(ranged) and restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other)) if other.op == '~': # Other definitely matches its own version. If ranged also # does we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True # The only other case where we intersect is if ranged is a # > or >= on other's version and a nonzero revision. In # that case other will match ranged. Be careful not to # give a false positive for ~2 vs <2 here: return ranged.op in ('>', '>=') and restricts.VersionMatch( other.op, other.version, other.revision).match(ranged) if other.op == '=*': # The fun one, since glob matches do not correspond to a # single contiguous region of versions. # a glob match definitely matches its own version, so if # ranged does too we're done: if restricts.VersionMatch(ranged.op, ranged.version, ranged.revision).match(other): return True if '<' in ranged.op: # Remaining cases where this intersects: there is a # package smaller than ranged.fullver and # other.fullver that they both match. # If other.revision is not None or 0 then other does not match # anything smaller than its own fullver: if other.revision: return False # If other.revision is None or 0 then we can always # construct a package smaller than other.fullver by # tagging e.g. an _alpha1 on, since # cat/pkg_beta2_alpha1_alpha1 is a valid version. # (Yes, really. Try it if you don't believe me.) # If and only if other also matches ranged then # ranged will also match one of those smaller packages. # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) else: # Remaining cases where this intersects: there is a # package greater than ranged.fullver and # other.fullver that they both match. # We can always construct a package greater than # other.fullver by adding a digit to it. # If and only if other also matches ranged then # ranged will match such a larger package # XXX (I think, need to try harder to verify this.) return ranged.fullver.startswith(other.version) # Handled all possible ops. raise NotImplementedError( 'Someone added an op to atom without adding it to intersects') def evaluate_conditionals(self, parent_cls, parent_seq, enabled, tristate=None): parent_seq.append(self)