From be987eac406099888b356eca8ce9a5f9d3f76d74 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 27 Nov 2010 14:03:05 +0000 Subject: [PATCH] Enhancement #7559: tests on counts of items in multiple fields. --- src/calibre/library/caches.py | 39 ++++++++++++++++++++++++----------- src/calibre/manual/gui.rst | 8 +++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 075fbe664a..6148019906 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -403,7 +403,7 @@ class ResultCache(SearchQueryParser): # {{{ '<=':[2, lambda r, q: r <= q] } - def get_numeric_matches(self, location, query): + def get_numeric_matches(self, location, query, val_func = None): matches = set([]) if len(query) == 0: return matches @@ -419,7 +419,10 @@ class ResultCache(SearchQueryParser): # {{{ if relop is None: (p, relop) = self.numeric_search_relops['='] - loc = self.field_metadata[location]['rec_index'] + if val_func is None: + loc = self.field_metadata[location]['rec_index'] + val_func = lambda item, loc=loc: item[loc] + dt = self.field_metadata[location]['datatype'] if dt == 'int': cast = (lambda x: int (x)) @@ -430,6 +433,9 @@ class ResultCache(SearchQueryParser): # {{{ elif dt == 'float': cast = lambda x : float (x) adjust = lambda x: x + else: # count operation + cast = (lambda x: int (x)) + adjust = lambda x: x if len(query) > 1: mult = query[-1:].lower() @@ -446,10 +452,11 @@ class ResultCache(SearchQueryParser): # {{{ for item in self._data: if item is None: continue - if not item[loc]: + v = val_func(item) + if not v: i = 0 else: - i = adjust(item[loc]) + i = adjust(v) if relop(i, q): matches.add(item[0]) return matches @@ -467,15 +474,23 @@ class ResultCache(SearchQueryParser): # {{{ return matches raise ParseException(query, len(query), 'Recursive query group detected', self) - # take care of dates special case - if location in self.field_metadata and \ - self.field_metadata[location]['datatype'] == 'datetime': - return self.get_dates_matches(location, query.lower()) + if location in self.field_metadata: + fm = self.field_metadata[location] + # take care of dates special case + if fm['datatype'] == 'datetime': + return self.get_dates_matches(location, query.lower()) - # take care of numbers special case - if location in self.field_metadata and \ - self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'): - return self.get_numeric_matches(location, query.lower()) + # take care of numbers special case + if fm['datatype'] in ('rating', 'int', 'float'): + return self.get_numeric_matches(location, query.lower()) + + # take care of the 'count' operator for is_multiples + if fm['is_multiple'] and \ + len(query) > 1 and query.startswith('#') and \ + query[1:1] in '=<>!': + vf = lambda item, loc=fm['rec_index'], ms=fm['is_multiple']:\ + len(item[loc].split(ms)) if item[loc] is not None else 0 + return self.get_numeric_matches(location, query[1:], val_func=vf) # everything else, or 'all' matches matchkind = CONTAINS_MATCH diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index c81688ba8c..a91124b214 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -274,6 +274,14 @@ Searching for ``no`` or ``unchecked`` will find all books with ``No`` in the col :guilabel:`Advanced Search Dialog` +You can test for the number of items in multiple-value columns, such as tags, formats, authors, and tags-like custom columns. This is done using a syntax very similar to numeric tests (discussed above), except that the relational operator begins with a ``#`` character. For example:: + + tags:#>3 will give you books with more than three tags + tags:#!=3 will give you books that do not have three tags + authors:#=1 will give you books with exactly one author + #cust:#<5 will give you books with less than five items in custom column #cust + formats:#>1 will give you books with more than one format + Saving searches -----------------