Commits

Michał Górny committed 38eb527

Re-introduce keyword matching.

Comments (0)

Files changed (3)

gentoopm/basepm/filter.py

 # Released under the terms of the 2-clause BSD license.
 
 from abc import abstractmethod
+from operator import attrgetter
+import itertools
 
 from ..util import ABCObject, FillMissingNotEqual
 
 		@rtype: bool
 		"""
 		pass
+
+class SmartAttrGetter(object):
+	"""
+	A wrapper on attrgetter, supporting dots replaced by underscores. Uses the
+	first package matched to distinguish between real underscores and dots.
+	"""
+
+	def __init__(self, key):
+		self._k = key
+		self._getter = None
+
+	def __call__(self, obj):
+		if self._getter is not None:
+			return self._getter(obj)
+
+		def get_variants(args):
+			prev = None
+			for a in args:
+				if prev is not None:
+					yield ('%s_' % prev, '%s.' % prev)
+				prev = a
+			else:
+				yield (prev,)
+
+		variants = itertools.product(*get_variants(self._k.split('_')))
+		for v in variants:
+			self._getter = attrgetter(''.join(v))
+			try:
+				return self._getter(obj)
+			except AttributeError:
+				pass
+		else:
+			raise KeyError('Invalid keyword argument: %s' % self._k)
+
+class PMTransformedKeywordFilter(PMPackageMatcher):
+	def __init__(self, key, val):
+		self._getter = SmartAttrGetter(key)
+		self._val = val
+
+	def __call__(self, pkg):
+		return self._val == self._getter(pkg)
+
+def transform_keyword_filters(kwargs):
+	"""
+	Transform a number of keyword filters into positional args.
+
+	@param kwargs: keyword arguments, as passed to L{PMPackageSet.filter()}
+	@type kwargs: dict
+	@return: positional arguments representing the keyword filters
+	@rtype: tuple
+	"""
+
+	return tuple([PMTransformedKeywordFilter(k, v)
+			for k, v in kwargs.items()])

gentoopm/basepm/pkg.py

 	in the package tree.
 	"""
 
-	def _matches(self, *args, **kwargs):
+	def _matches(self, *args):
 		"""
 		Check whether the package matches passed filters. Please note that this
 		method may not be called at all if PM is capable of a more efficient
 			else:
 				raise ValueError('Incorrect positional argument: %s' % f)
 
-		for k, m in kwargs.items():
-			try:
-				v = self.metadata[k]
-			except KeyError:
-				raise KeyError('Unmatched keyword argument: %s' % k)
-			else:
-				if not m == v:
-					return False
-
 		return True
 
 	@property

gentoopm/basepm/pkgset.py

 
 from abc import abstractmethod
 
+from .filter import transform_keyword_filters
+
 from ..exceptions import EmptyPackageSetError, AmbiguousPackageSetError
 from ..util import ABCObject, BoolCompat
 
 		Filter the packages based on arguments. Return a filtered package set.
 
 		The positional arguments can provide a number of L{PMPackageMatcher}s
-		and/or a L{PMAtom} instance. The keyword arguments match metadata keys
-		using '==' comparison with passed string (or L{PMKeywordMatcher}s).
+		(which are basically lambdas with a L{PMPackage} instance argument)
+		and/or a L{PMAtom} instance.
+
+		The keyword arguments match metadata keys using C{==} comparison with
+		the passed string (or L{PMKeywordMatcher}s). Keys are supposed to be
+		L{PMPackage} property names in Python; dots can be replaced by
+		underscores (e.g. C{description_short}) or passed using C{**} operator.
 
 		Multiple filters will be AND-ed together. Same applies for .filter()
 		called multiple times. You should, however, avoid passing multiple
 class PMFilteredPackageSet(PMPackageSet):
 	def __init__(self, src, args, kwargs):
 		self._src = src
-		self._args = args
-		self._kwargs = kwargs
+		self._args = args + transform_keyword_filters(kwargs)
 
 	def __iter__(self):
 		for el in self._src:
-			if el._matches(*self._args, **self._kwargs):
+			if el._matches(*self._args):
 				yield el
 
 class PMSortedPackageSet(PMPackageSet):