From d51cd5a433567d144648d628b9adf41d771a0350 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 14 Apr 2011 03:10:50 +0900 Subject: [PATCH] find & replace --- anki/deck.py | 9 +++---- anki/find.py | 66 ++++++++++++++++++++++++---------------------- tests/test_find.py | 27 +++++++++++++++++++ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/anki/deck.py b/anki/deck.py index a198eaac2..20283b5da 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -15,7 +15,7 @@ from anki.errors import AnkiError import anki.latex # sets up hook import anki.cards, anki.facts, anki.models, anki.template, anki.cram, \ - anki.groups + anki.groups, anki.find # Settings related to queue building. These may be loaded without the rest of # the config to check due counts faster on mobile clients. @@ -641,15 +641,12 @@ update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res ########################################################################## def findCards(self, query): - import anki.find return anki.find.Finder(self).findCards(query) - def findReplace(self, *args, **kwargs): - import anki.find - return anki.find.findReplace(self, *args, **kwargs) + def findReplace(self, fids, src, dst, regex=None, field=None): + return anki.find.findReplace(self, fids, src, dst, regex, field) def findDuplicates(self, fmids): - import anki.find return anki.find.findDuplicates(self, fmids) # Stats diff --git a/anki/find.py b/anki/find.py index e833d37cf..ed27d2095 100644 --- a/anki/find.py +++ b/anki/find.py @@ -3,7 +3,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import re -from anki.utils import ids2str, splitFields +from anki.utils import ids2str, splitFields, joinFields SEARCH_TAG = 0 SEARCH_TYPE = 1 @@ -344,41 +344,43 @@ where mid in %s and flds like ? escape '\\'""" % ( # Find and replace ########################################################################## -def findReplace(deck, fids, src, dst, isRe=False, field=None): +def findReplace(deck, fids, src, dst, regex=False, field=None): "Find and replace fields in a fact." - # find - s = "select id, fid, value from fdata where fid in %s" - if isRe: - isRe = re.compile(src) - else: - s += " and value like :v" + mmap = {} if field: - s += " and fmid = :fmid" - rows = deck.db.all(s % ids2str(fids), - v="%"+src.replace("%", "%%")+"%", - fmid=field) - modded = [] - if isRe: - modded = [ - {'id': id, 'fid': fid, 'val': re.sub(isRe, dst, val)} - for (id, fid, val) in rows - if isRe.search(val)] - else: - modded = [ - {'id': id, 'fid': fid, 'val': val.replace(src, dst)} - for (id, fid, val) in rows - if val.find(src) != -1] - # update - if modded: - deck.db.executemany( - 'update fdata set value = :val where id = :id', modded) - deck.updateCardQACacheFromIds([f['fid'] for f in modded], - type="facts") + for m in deck.models().values(): + for f in m.fields: + if f['name'] == field: + mmap[m.id] = f['ord'] + if not mmap: + return 0 + # find and gather replacements + if not regex: + src = re.escape(src) + regex = re.compile("(?i)"+src) + def repl(str): + return re.sub(regex, dst, str) + d = [] + for fid, mid, flds in deck.db.execute( + "select id, mid, flds from facts where id in "+ids2str(fids)): + origFlds = flds + # does it match? + sflds = splitFields(flds) if field: - deck.updateFieldChecksums(field) + ord = mmap[mid] + sflds[ord] = repl(sflds[ord]) else: - deck.updateAllFieldChecksums() - return len(set([f['fid'] for f in modded])) + for c in range(len(sflds)): + sflds[c] = repl(sflds[c]) + flds = joinFields(sflds) + if flds != origFlds: + d.append(dict(fid=fid, flds=flds)) + if not d: + return 0 + # replace + deck.db.executemany("update facts set flds = :flds where id=:fid", d) + deck.updateFieldCache(fids) + return len(d) # Find duplicates ########################################################################## diff --git a/tests/test_find.py b/tests/test_find.py index 471c6a0c9..8fad54e2b 100644 --- a/tests/test_find.py +++ b/tests/test_find.py @@ -101,3 +101,30 @@ def test_findCards(): assert len(deck.findCards("group:default")) == 5 assert len(deck.findCards("-group:default")) == 0 assert len(deck.findCards("-group:foo")) == 5 + +def test_findReplace(): + deck = getEmptyDeck() + f = deck.newFact() + f['Front'] = u'foo' + f['Back'] = u'bar' + deck.addFact(f) + f2 = deck.newFact() + f2['Front'] = u'baz' + f2['Back'] = u'foo' + deck.addFact(f2) + fids = [f.id, f2.id] + # should do nothing + assert deck.findReplace(fids, "abc", "123") == 0 + # global replace + assert deck.findReplace(fids, "foo", "qux") == 2 + f.load(); assert f['Front'] == "qux" + f2.load(); assert f2['Back'] == "qux" + # single field replace + assert deck.findReplace(fids, "qux", "foo", field="Front") == 1 + f.load(); assert f['Front'] == "foo" + f2.load(); assert f2['Back'] == "qux" + # regex replace + assert deck.findReplace(fids, "B.r", "reg") == 0 + f.load(); assert f['Back'] != "reg" + assert deck.findReplace(fids, "B.r", "reg", regex=True) == 1 + f.load(); assert f['Back'] == "reg"