Source code for pkgcore.restrictions.values

"""value restrictions

Works hand in hand with :obj:`pkgcore.restrictions.packages`, these
classes match against a value handed in, package restrictions pull the
attr from a package instance and hand it to their wrapped restriction
(which is a value restriction).
"""

import re

from snakeoil.klass import generic_equality, reflective_hash
from snakeoil.sequences import iflatten_instance

from . import boolean, packages, restriction


[docs] class base(restriction.base): """Base restriction matching object for values. Beware: do not check for instances of this to detect value restrictions! Use the C{type} attribute instead. """ __slots__ = () type = restriction.value_type
[docs] def force_True(self, pkg, attr, val): return self.match(val)
[docs] def force_False(self, pkg, attr, val): return not self.match(val)
[docs] 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)
[docs] class GetAttrRestriction(packages.PackageRestriction): """Restriction pulling an attribute and applying a child restriction.""" __slots__ = () type = restriction.value_type # XXX this needs further thought. # # The api for force_{True,False} is a ValueRestriction gets called # with a package instance, the attribute name (string), and the # current attribute value. We cannot really provide a child # restriction with a sensible pkg and a sensible attribute name, # so we just punt and return True/False depending on the current # state without "forcing" anything (default implementation in # "base").
[docs] def force_True(self, pkg, attr, val): return self.match(val)
[docs] def force_False(self, pkg, attr, val): return not self.match(val)
[docs] class VersionRestriction(base): """use this as base for version restrictions. Gives a clue to what the restriction does. """ __slots__ = ()
[docs] class StrRegex(base, metaclass=hashed_base): """regex based matching""" __slots__ = ("_hash", "flags", "regex", "_matchfunc", "ismatch", "negate") __inst_caching__ = True def __init__(self, regex, case_sensitive=True, match=False, negate=False): """ :param regex: regex pattern to match :param case_sensitive: should the match be case sensitive? :param match: should C{re.match} be used instead of C{re.search}? :param negate: should the match results be negated? """ sf = object.__setattr__ sf(self, "regex", regex) sf(self, "ismatch", match) sf(self, "negate", negate) flags = 0 if not case_sensitive: flags = re.I sf(self, "flags", flags) try: compiled_re = re.compile(regex, flags) except re.error as e: raise ValueError(f"invalid regex: {regex!r}, {e}") if match: sf(self, "_matchfunc", compiled_re.match) else: sf(self, "_matchfunc", compiled_re.search) sf(self, "_hash", hash((self.regex, self.negate, self.flags, self.ismatch)))
[docs] def match(self, value): if not isinstance(value, str): # Be too clever for our own good --marienz if value is None: value = "" else: value = str(value) return (self._matchfunc(value) is not None) != self.negate
def __repr__(self): result = [self.__class__.__name__, repr(self.regex)] if self.negate: result.append("negated") if self.ismatch: result.append("match") else: result.append("search") result.append(f"@{id(self):#8x}") result = " ".join(result) return f"<{result}>" def __str__(self): if self.ismatch: result = "match " else: result = "search " result += self.regex if self.negate: return f"not {result}" return result
[docs] 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)))
[docs] 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
[docs] 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")
[docs] class StrGlobMatch(base, metaclass=hashed_base): """globbing matches; essentially startswith and endswith matches""" __slots__ = ("_hash", "glob", "prefix", "negate", "flags") __inst_caching__ = True def __init__(self, glob, case_sensitive=True, prefix=True, negate=False): """ :param glob: string chunk that must be matched :param case_sensitive: should the match be case sensitive? :param prefix: should the glob be a prefix check for matching, or postfix matching :param negate: should the match results be negated? """ sf = object.__setattr__ sf(self, "negate", negate) if not case_sensitive: sf(self, "flags", re.I) sf(self, "glob", str(glob).lower()) else: sf(self, "flags", 0) sf(self, "glob", str(glob)) sf(self, "prefix", prefix) sf(self, "_hash", hash((self.glob, self.negate, self.flags, self.prefix)))
[docs] def match(self, value): value = str(value) if self.flags == re.I: value = value.lower() if self.prefix: f = value.startswith else: f = value.endswith return f(self.glob) ^ self.negate
def __repr__(self): if self.negate: string = "<%s %r case_sensitive=%r negated @%#8x>" else: string = "<%s %r case_sensitive=%r @%#8x>" if self.prefix: g = f"{self.glob}.*" else: g = f".*{self.glob}" return string % ( self.__class__.__name__, g, self.flags == re.I and True or False, id(self), ) def __str__(self): s = "" if self.negate: s = "not " if self.prefix: return f"{s}{self.glob}*" return "{s}*{self.glob}"
[docs] class EqualityMatch(base, metaclass=generic_equality): __slots__ = ("negate", "data") __attr_comparison__ = __slots__ def __init__(self, data, negate=False): """ :param data: data to base comparison against :param negate: should the results be negated? """ sf = object.__setattr__ sf(self, "negate", negate) sf(self, "data", data) def __hash__(self): return hash((self.__class__, self.negate, self.data))
[docs] def match(self, actual_val): return (self.data == actual_val) != self.negate
def __repr__(self): return f"<{self.__class__.__name__} {self.data!r} negate={self.negate!r} @{id(self):#8x}>" def __str__(self): if self.negate: return f"EqualityMatch: !={self.data}" return f"EqualityMatch: ={self.data}"
[docs] class ContainmentMatch(base, metaclass=hashed_base): """Used for an 'in' style operation. For example, 'x86' in ['x86', '~x86']. Note that negation of this *does* not result in a true NAND when all is on. """ __slots__ = ("_hash", "vals", "all", "negate") __inst_caching__ = True def __init__(self, vals, match_all=False, negate=False): """ :param vals: what values to look for during match :keyword all: must all vals be present, or just one for a match to succeed? :keyword negate: should the match results be negated? """ sf = object.__setattr__ sf(self, "all", bool(match_all)) sf(self, "vals", frozenset((vals,) if isinstance(vals, str) else vals)) sf(self, "negate", bool(negate)) sf(self, "_hash", hash((self.all, self.negate, self.vals)))
[docs] def match(self, val, _values_override=None): vals = _values_override if _values_override is None: vals = self.vals if isinstance(val, str): for fval in vals: if fval in val: return not self.negate return self.negate # this can, and should be optimized to do len checks- iterate # over the smaller of the two see above about special casing # bits. need the same protection here, on the offchance (as # contents sets do), the __getitem__ is non standard. try: if self.all: return vals.issubset(val) != self.negate # if something intersects, then we return the inverse of negate- # if negate=False, something is found, result is True return vals.isdisjoint(val) == self.negate except TypeError: # isn't iterable, try the other way around. rely on contains. if self.all: for k in vals: if k not in val: return self.negate return not self.negate for k in vals: if k in val: return not self.negate
[docs] def force_False(self, pkg, attr, val, _values_override=None): # "More than one statement on a single line" # pylint: disable-msg=C0321 vals = _values_override if _values_override is None: vals = self.vals # XXX pretty much positive this isn't working. if isinstance(val, str) or not getattr(pkg, "configurable", False): # unchangable return not self.match(val) if self.negate: if self.all: def filter(truths): return False in truths def true(r, pvals): return pkg.request_enable(attr, r) def false(r, pvals): return pkg.request_disable(attr, r) truths = [x in val for x in vals] for x in boolean.iterative_quad_toggling( pkg, None, list(vals), 0, len(vals), truths, filter, desired_false=false, desired_true=true, ): return True elif pkg.request_disable(attr, *vals): return True return False if not self.all: return pkg.request_disable(attr, *vals) l = len(vals) def filter(truths): return truths.count(True) < l def true(r, pvals): return pkg.request_enable(attr, r) def false(r, pvals): return pkg.request_disable(attr, r) truths = [x in val for x in vals] for x in boolean.iterative_quad_toggling( pkg, None, list(vals), 0, l, truths, filter, desired_false=false, desired_true=true, ): return True return False
[docs] def force_True(self, pkg, attr, val, _values_override=None): # "More than one statement on a single line" # pylint: disable-msg=C0321 # XXX pretty much positive this isn't working. vals = _values_override if _values_override is None: vals = self.vals if isinstance(val, str) or not getattr(pkg, "configurable", False): # unchangable return self.match(val) if not self.negate: if not self.all: def filter(truths): return True in truths def true(r, pvals): return pkg.request_enable(attr, r) def false(r, pvals): return pkg.request_disable(attr, r) truths = [x in val for x in vals] for x in boolean.iterative_quad_toggling( pkg, None, list(vals), 0, len(vals), truths, filter, desired_false=false, desired_true=true, ): return True else: if pkg.request_enable(attr, *vals): return True return False # negation if not self.all: if pkg.request_disable(attr, *vals): return True else: def filter(truths): return True not in truths def true(r, pvals): return pkg.request_enable(attr, r) def false(r, pvals): return pkg.request_disable(attr, r) truths = [x in val for x in vals] for x in boolean.iterative_quad_toggling( pkg, None, list(vals), 0, len(vals), truths, filter, desired_false=false, desired_true=true, ): return True return False
def __repr__(self): if self.negate: string = "<%s %r all=%s negated @%#8x>" else: string = "<%s %r all=%s @%#8x>" return string % (self.__class__.__name__, tuple(self.vals), self.all, id(self)) def __str__(self): restricts_str = ", ".join(map(str, self.vals)) negate = "!" if self.negate else "" return f"{negate}{restricts_str}"
# ContainmentMatch2 was added in f1d3c6f to deprecate ContainmentMatch; # cleanup took a while (2021). This ContainmentMatch2 can be removed # by 2023 at latest (pkgcheck is the only known dependency on this). ContainmentMatch2 = ContainmentMatch
[docs] class FlatteningRestriction(base, metaclass=generic_equality): """Flatten the values passed in and apply the nested restriction.""" __slots__ = __attr_comparison__ = ("dont_iter", "restriction", "negate") __hash__ = object.__hash__ def __init__(self, dont_iter, childrestriction, negate=False): """ :type dont_iter: type or tuple of types :param dont_iter: type(s) not to flatten. Passed to :obj:`snakeoil.sequences.iflatten_instance`. :type childrestriction: restriction :param childrestriction: restriction applied to the flattened list. """ object.__setattr__(self, "negate", negate) object.__setattr__(self, "dont_iter", dont_iter) object.__setattr__(self, "restriction", childrestriction)
[docs] def match(self, val): return ( self.restriction.match(iflatten_instance(val, self.dont_iter)) != self.negate )
def __str__(self): return ( "flattening_restriction: " f"dont_iter = {self.dont_iter}, restriction = {self.restriction}" ) def __repr__(self): return "<%s restriction=%r dont_iter=%r negate=%r @%#8x>" % ( self.__class__.__name__, self.restriction, self.dont_iter, self.negate, id(self), )
[docs] class FunctionRestriction(base, metaclass=generic_equality): """Convenience class for creating special restrictions.""" __attr_comparison__ = __slots__ = ("func", "negate") __hash__ = object.__hash__ def __init__(self, func, negate=False): """ C{func} is used as match function. It will usually be impossible for the backend to optimize this restriction. So even though you can implement an arbitrary restriction using this class you should only use it if it is very unlikely backend-specific optimizations will be possible. """ object.__setattr__(self, "negate", negate) object.__setattr__(self, "func", func)
[docs] def match(self, val): return self.func(val) != self.negate
def __repr__(self): return "<%s func=%r negate=%r @%#8x>" % ( self.__class__.__name__, self.func, self.negate, id(self), )
[docs] class StrConversion(base, metaclass=generic_equality): """convert passed in data to a str object""" __hash__ = object.__hash__ __attr_comparison__ = __slots__ = ("restrict",) def __init__(self, restrict): object.__setattr__(self, "restrict", restrict)
[docs] def match(self, val): return self.restrict.match(str(val))
[docs] class UnicodeConversion(StrConversion): """convert passed in data to a unicode obj""" __slots__ = ()
[docs] def match(self, val): return self.restrict.match(str(val))
[docs] class AnyMatch(restriction.AnyMatch): __slots__ = () __hash__ = object.__hash__ def __init__(self, childrestriction, negate=False): # Hack: skip calling base.__init__. Doing this would make # restriction.base.__init__ run twice. restriction.AnyMatch.__init__( self, childrestriction, restriction.value_type, negate=negate )
[docs] def force_True(self, pkg, attr, val): return self.match(val)
[docs] def force_False(self, pkg, attr, val): return not self.match(val)
# "Invalid name" (pylint uses the module const regexp, not the class regexp) # pylint: disable-msg=C0103 AndRestriction = restriction.curry_node_type( boolean.AndRestriction, restriction.value_type ) OrRestriction = restriction.curry_node_type( boolean.OrRestriction, restriction.value_type ) AlwaysBool = restriction.curry_node_type(restriction.AlwaysBool, restriction.value_type) AlwaysTrue = AlwaysBool(negate=True) AlwaysFalse = AlwaysBool(negate=False)