Move _match to db.search module

This commit is contained in:
Kovid Goyal 2013-01-20 23:27:30 +05:30
parent e0df1634ab
commit d915e49815
5 changed files with 74 additions and 65 deletions

View File

@ -13,9 +13,15 @@ from datetime import timedelta
from calibre.utils.config_base import prefs from calibre.utils.config_base import prefs
from calibre.utils.date import parse_date, UNDEFINED_DATE, now from calibre.utils.date import parse_date, UNDEFINED_DATE, now
from calibre.utils.icu import primary_find
from calibre.utils.search_query_parser import SearchQueryParser, ParseException from calibre.utils.search_query_parser import SearchQueryParser, ParseException
# TODO: Thread safety of saved searches # TODO: Thread safety of saved searches
CONTAINS_MATCH = 0
EQUALS_MATCH = 1
REGEXP_MATCH = 2
# Utils {{{
def force_to_bool(val): def force_to_bool(val):
if isinstance(val, (str, unicode)): if isinstance(val, (str, unicode)):
@ -33,6 +39,45 @@ def force_to_bool(val):
val = None val = None
return val return val
def _match(query, value, matchkind, use_primary_find_in_search=True):
if query.startswith('..'):
query = query[1:]
sq = query[1:]
internal_match_ok = True
else:
internal_match_ok = False
for t in value:
try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished
t = icu_lower(t)
if (matchkind == EQUALS_MATCH):
if internal_match_ok:
if query == t:
return True
comps = [c.strip() for c in t.split('.') if c.strip()]
for comp in comps:
if sq == comp:
return True
elif query[0] == '.':
if t.startswith(query[1:]):
ql = len(query) - 1
if (len(t) == ql) or (t[ql:ql+1] == '.'):
return True
elif query == t:
return True
elif matchkind == REGEXP_MATCH:
if re.search(query, t, re.I|re.UNICODE):
return True
elif matchkind == CONTAINS_MATCH:
if use_primary_find_in_search:
if primary_find(query, t)[0] != -1:
return True
elif query in t:
return True
except re.error:
pass
return False
# }}}
class DateSearch(object): # {{{ class DateSearch(object): # {{{
def __init__(self): def __init__(self):

View File

@ -16,12 +16,12 @@ from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.book.base import SafeFormat from calibre.ebooks.metadata.book.base import SafeFormat
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks, device_prefs from calibre.utils.config import tweaks, device_prefs, prefs
from calibre.utils.date import dt_factory, qt_to_dt, as_local_time from calibre.utils.date import dt_factory, qt_to_dt, as_local_time
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import (_match, CONTAINS_MATCH, EQUALS_MATCH, from calibre.db.search import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
REGEXP_MATCH, MetadataBackup, force_to_bool) from calibre.library.caches import (MetadataBackup, force_to_bool)
from calibre.library.save_to_disk import find_plugboard from calibre.library.save_to_disk import find_plugboard
from calibre import strftime, isbytestring from calibre import strftime, isbytestring
from calibre.constants import filesystem_encoding, DEBUG from calibre.constants import filesystem_encoding, DEBUG
@ -1037,6 +1037,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{
} }
for x in ('author', 'format'): for x in ('author', 'format'):
q[x+'s'] = q[x] q[x+'s'] = q[x]
upf = prefs['use_primary_find_in_search']
for index, row in enumerate(self.model.db): for index, row in enumerate(self.model.db):
for locvalue in locations: for locvalue in locations:
accessor = q[locvalue] accessor = q[locvalue]
@ -1063,7 +1064,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{
vals = accessor(row).split(',') vals = accessor(row).split(',')
else: else:
vals = [accessor(row)] vals = [accessor(row)]
if _match(query, vals, m): if _match(query, vals, m, use_primary_find_in_search=upf):
matches.add(index) matches.add(index)
break break
except ValueError: # Unicode errors except ValueError: # Unicode errors

View File

@ -10,8 +10,8 @@ from PyQt4.Qt import (Qt, QAbstractItemModel, QIcon, QVariant, QModelIndex, QSiz
from calibre.gui2 import NONE from calibre.gui2 import NONE
from calibre.customize.ui import is_disabled, disable_plugin, enable_plugin from calibre.customize.ui import is_disabled, disable_plugin, enable_plugin
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ from calibre.db.search import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
REGEXP_MATCH from calibre.utils.config_base import prefs
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
@ -60,13 +60,13 @@ class Matches(QAbstractItemModel):
index = self.createIndex(i, 0) index = self.createIndex(i, 0)
data = QVariant(True) data = QVariant(True)
self.setData(index, data, Qt.CheckStateRole) self.setData(index, data, Qt.CheckStateRole)
def enable_none(self): def enable_none(self):
for i in xrange(len(self.matches)): for i in xrange(len(self.matches)):
index = self.createIndex(i, 0) index = self.createIndex(i, 0)
data = QVariant(False) data = QVariant(False)
self.setData(index, data, Qt.CheckStateRole) self.setData(index, data, Qt.CheckStateRole)
def enable_invert(self): def enable_invert(self):
for i in xrange(len(self.matches)): for i in xrange(len(self.matches)):
self.toggle_plugin(self.createIndex(i, 0)) self.toggle_plugin(self.createIndex(i, 0))
@ -243,6 +243,7 @@ class SearchFilter(SearchQueryParser):
'name': lambda x : x.name.lower(), 'name': lambda x : x.name.lower(),
} }
q['formats'] = q['format'] q['formats'] = q['format']
upf = prefs['use_primary_find_in_search']
for sr in self.srs: for sr in self.srs:
for locvalue in locations: for locvalue in locations:
accessor = q[locvalue] accessor = q[locvalue]
@ -276,7 +277,7 @@ class SearchFilter(SearchQueryParser):
vals = accessor(sr).split(',') vals = accessor(sr).split(',')
else: else:
vals = [accessor(sr)] vals = [accessor(sr)]
if _match(query, vals, m): if _match(query, vals, m, use_primary_find_in_search=upf):
matches.add(sr) matches.add(sr)
break break
except ValueError: # Unicode errors except ValueError: # Unicode errors

View File

@ -11,13 +11,13 @@ from operator import attrgetter
from PyQt4.Qt import (Qt, QAbstractItemModel, QModelIndex, QVariant, pyqtSignal) from PyQt4.Qt import (Qt, QAbstractItemModel, QModelIndex, QVariant, pyqtSignal)
from calibre.gui2 import NONE from calibre.gui2 import NONE
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ from calibre.db.search import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
REGEXP_MATCH from calibre.utils.config_base import prefs
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
class BooksModel(QAbstractItemModel): class BooksModel(QAbstractItemModel):
total_changed = pyqtSignal(int) total_changed = pyqtSignal(int)
HEADERS = [_('Title'), _('Author(s)'), _('Format')] HEADERS = [_('Title'), _('Author(s)'), _('Format')]
@ -37,8 +37,8 @@ class BooksModel(QAbstractItemModel):
return self.books[row] return self.books[row]
else: else:
return None return None
def search(self, filter): def search(self, filter):
self.filter = filter.strip() self.filter = filter.strip()
if not self.filter: if not self.filter:
self.books = self.all_books self.books = self.all_books
@ -50,7 +50,7 @@ class BooksModel(QAbstractItemModel):
self.layoutChanged.emit() self.layoutChanged.emit()
self.sort(self.sort_col, self.sort_order) self.sort(self.sort_col, self.sort_order)
self.total_changed.emit(self.rowCount()) self.total_changed.emit(self.rowCount())
def index(self, row, column, parent=QModelIndex()): def index(self, row, column, parent=QModelIndex()):
return self.createIndex(row, column) return self.createIndex(row, column)
@ -64,7 +64,7 @@ class BooksModel(QAbstractItemModel):
def columnCount(self, *args): def columnCount(self, *args):
return len(self.HEADERS) return len(self.HEADERS)
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role != Qt.DisplayRole: if role != Qt.DisplayRole:
return NONE return NONE
@ -112,7 +112,7 @@ class BooksModel(QAbstractItemModel):
class SearchFilter(SearchQueryParser): class SearchFilter(SearchQueryParser):
USABLE_LOCATIONS = [ USABLE_LOCATIONS = [
'all', 'all',
'author', 'author',
@ -161,6 +161,7 @@ class SearchFilter(SearchQueryParser):
} }
for x in ('author', 'format'): for x in ('author', 'format'):
q[x+'s'] = q[x] q[x+'s'] = q[x]
upf = prefs['use_primary_find_in_search']
for sr in self.srs: for sr in self.srs:
for locvalue in locations: for locvalue in locations:
accessor = q[locvalue] accessor = q[locvalue]
@ -182,7 +183,7 @@ class SearchFilter(SearchQueryParser):
m = matchkind m = matchkind
vals = [accessor(sr)] vals = [accessor(sr)]
if _match(query, vals, m): if _match(query, vals, m, use_primary_find_in_search=upf):
matches.add(sr) matches.add(sr)
break break
except ValueError: # Unicode errors except ValueError: # Unicode errors

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re, itertools, time, traceback, locale import itertools, time, traceback, locale
from itertools import repeat, izip, imap from itertools import repeat, izip, imap
from datetime import timedelta from datetime import timedelta
from threading import Thread from threading import Thread
@ -16,10 +16,10 @@ from calibre.utils.date import parse_date, now, UNDEFINED_DATE, clean_date_for_s
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.utils.localization import (canonicalize_lang, lang_map, get_udc) from calibre.utils.localization import (canonicalize_lang, lang_map, get_udc)
from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match
from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre import prints from calibre import prints
from calibre.utils.icu import primary_find
class MetadataBackup(Thread): # {{{ class MetadataBackup(Thread): # {{{
''' '''
@ -118,7 +118,6 @@ class MetadataBackup(Thread): # {{{
# }}} # }}}
### Global utility function for get_match here and in gui2/library.py ### Global utility function for get_match here and in gui2/library.py
# This is a global for performance # This is a global for performance
pref_use_primary_find_in_search = False pref_use_primary_find_in_search = False
@ -127,47 +126,6 @@ def set_use_primary_find_in_search(toWhat):
global pref_use_primary_find_in_search global pref_use_primary_find_in_search
pref_use_primary_find_in_search = toWhat pref_use_primary_find_in_search = toWhat
CONTAINS_MATCH = 0
EQUALS_MATCH = 1
REGEXP_MATCH = 2
def _match(query, value, matchkind):
if query.startswith('..'):
query = query[1:]
sq = query[1:]
internal_match_ok = True
else:
internal_match_ok = False
for t in value:
try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished
t = icu_lower(t)
if (matchkind == EQUALS_MATCH):
if internal_match_ok:
if query == t:
return True
comps = [c.strip() for c in t.split('.') if c.strip()]
for comp in comps:
if sq == comp:
return True
elif query[0] == '.':
if t.startswith(query[1:]):
ql = len(query) - 1
if (len(t) == ql) or (t[ql:ql+1] == '.'):
return True
elif query == t:
return True
elif matchkind == REGEXP_MATCH:
if re.search(query, t, re.I|re.UNICODE):
return True
elif matchkind == CONTAINS_MATCH:
if pref_use_primary_find_in_search:
if primary_find(query, t)[0] != -1:
return True
elif query in t:
return True
except re.error:
pass
return False
def force_to_bool(val): def force_to_bool(val):
if isinstance(val, (str, unicode)): if isinstance(val, (str, unicode)):
try: try:
@ -576,7 +534,8 @@ class ResultCache(SearchQueryParser): # {{{
continue continue
k = parts[:1] k = parts[:1]
v = parts[1:] v = parts[1:]
if keyq and not _match(keyq, k, keyq_mkind): if keyq and not _match(keyq, k, keyq_mkind,
use_primary_find_in_search=pref_use_primary_find_in_search):
continue continue
if valq: if valq:
if valq == 'true': if valq == 'true':
@ -586,7 +545,8 @@ class ResultCache(SearchQueryParser): # {{{
if v: if v:
add_if_nothing_matches = False add_if_nothing_matches = False
continue continue
elif not _match(valq, v, valq_mkind): elif not _match(valq, v, valq_mkind,
use_primary_find_in_search=pref_use_primary_find_in_search):
continue continue
matches.add(id_) matches.add(id_)
@ -851,7 +811,8 @@ class ResultCache(SearchQueryParser): # {{{
vals = [v.strip() for v in item[loc].split(is_multiple_cols[loc])] vals = [v.strip() for v in item[loc].split(is_multiple_cols[loc])]
else: else:
vals = [item[loc]] ### make into list to make _match happy vals = [item[loc]] ### make into list to make _match happy
if _match(q, vals, matchkind): if _match(q, vals, matchkind,
use_primary_find_in_search=pref_use_primary_find_in_search):
matches.add(item[0]) matches.add(item[0])
continue continue
current_candidates -= matches current_candidates -= matches