cloze refactor wip

This commit is contained in:
Damien Elmes 2012-04-18 16:07:42 +09:00
parent 20ecd7359d
commit 41fa9a9896
6 changed files with 76 additions and 55 deletions

View file

@ -4,6 +4,7 @@
import time import time
from anki.utils import intTime, hexifyID, timestampID from anki.utils import intTime, hexifyID, timestampID
from anki.consts import *
# Cards # Cards
########################################################################## ##########################################################################
@ -129,7 +130,11 @@ lapses=?, left=?, odue=?, odid=?, did=? where id = ?""",
return self.col.models.get(self.note().mid) return self.col.models.get(self.note().mid)
def template(self): def template(self):
return self.model()['tmpls'][self.ord] m = self.model()
if m['type'] == MODEL_STD:
return self.model()['tmpls'][self.ord]
else:
return self.model()['tmpls'][0]
def startTimer(self): def startTimer(self):
self.timerStarted = time.time() self.timerStarted = time.time()

View file

@ -266,9 +266,19 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
"Return (active), non-empty templates." "Return (active), non-empty templates."
model = note.model() model = note.model()
avail = self.models.availOrds(model, joinFields(note.fields)) avail = self.models.availOrds(model, joinFields(note.fields))
return self._tmplsFromOrds(model, avail)
def _tmplsFromOrds(self, model, avail):
ok = [] ok = []
for t in model['tmpls']: if model['type'] == MODEL_STD:
if t['ord'] in avail: for t in model['tmpls']:
if t['ord'] in avail:
ok.append(t)
else:
# cloze - generate temporary templates from first
for ord in avail:
t = copy.copy(model['tmpls'][0])
t['ord'] = ord
ok.append(t) ok.append(t)
return ok return ok
@ -304,20 +314,21 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
model = self.models.get(mid) model = self.models.get(mid)
avail = self.models.availOrds(model, flds) avail = self.models.availOrds(model, flds)
did = dids.get(nid) or model['did'] did = dids.get(nid) or model['did']
for t in model['tmpls']: # add any missing cards
for t in self._tmplsFromOrds(model, avail):
doHave = nid in have and t['ord'] in have[nid] doHave = nid in have and t['ord'] in have[nid]
# if have ord but empty, add cid to remove list if not doHave:
# (may not have nid if generating before any cards added)
if doHave and t['ord'] not in avail:
rem.append(have[nid][t['ord']])
# if missing ord and is available, generate
if not doHave and t['ord'] in avail:
# we'd like to use the same due# as sibling cards, but we # we'd like to use the same due# as sibling cards, but we
# can't retrieve that quickly, so we give it a new id # can't retrieve that quickly, so we give it a new id
# instead # instead
data.append((ts, nid, t['did'] or did, t['ord'], data.append((ts, nid, t['did'] or did, t['ord'],
now, usn, self.nextID("pos"))) now, usn, self.nextID("pos")))
ts += 1 ts += 1
# note any cards that need removing
if nid in have:
for ord, id in have[nid].items():
if ord not in avail:
rem.append(id)
# bulk update # bulk update
self.db.executemany(""" self.db.executemany("""
insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""", insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
@ -440,20 +451,28 @@ select id from notes where id in %s and id not in (select nid from cards)""" %
flist = splitFields(data[6]) flist = splitFields(data[6])
fields = {} fields = {}
model = self.models.get(data[2]) model = self.models.get(data[2])
firstName = None
for (name, (idx, conf)) in self.models.fieldMap(model).items(): for (name, (idx, conf)) in self.models.fieldMap(model).items():
if idx == 0:
firstName = name
fields[name] = flist[idx] fields[name] = flist[idx]
fields['Tags'] = data[5] fields['Tags'] = data[5]
fields['Type'] = model['name'] fields['Type'] = model['name']
fields['Deck'] = self.decks.name(data[3]) fields['Deck'] = self.decks.name(data[3])
template = model['tmpls'][data[4]] if model['type'] == MODEL_STD:
template = model['tmpls'][data[4]]
else:
template = model['tmpls'][0]
fields['Card'] = template['name'] fields['Card'] = template['name']
# render q & a # render q & a
d = dict(id=data[0]) d = dict(id=data[0])
for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])): for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
if type == "q": if type == "q":
format = format.replace("cloze:", "cq:") format = format.replace("{{Cloze}}", "{{cq:%d:%s}}" % (
data[4]+1, firstName))
else: else:
format = format.replace("cloze:", "ca:") format = format.replace("{{Cloze}}", "{{ca:%d:%s}}" % (
data[4]+1, firstName))
fields = runFilter("mungeFields", fields, model, data, self) fields = runFilter("mungeFields", fields, model, data, self)
html = anki.template.render(format, fields) html = anki.template.render(format, fields)
d[type] = runFilter( d[type] = runFilter(

View file

@ -37,11 +37,15 @@ DYN_FAILED = 5
DYN_ADDED = 6 DYN_ADDED = 6
DYN_DUE = 7 DYN_DUE = 7
# model types
MODEL_STD = 0
MODEL_CLOZE = 1
# deck schema & syncing vars # deck schema & syncing vars
SCHEMA_VERSION = 3 SCHEMA_VERSION = 4
SYNC_ZIP_SIZE = int(2.5*1024*1024) SYNC_ZIP_SIZE = int(2.5*1024*1024)
SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/" SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/"
SYNC_VER = 1 SYNC_VER = 2
# Labels # Labels
########################################################################## ##########################################################################

View file

@ -28,6 +28,7 @@ defaultModel = {
'mod': 0, 'mod': 0,
'usn': 0, 'usn': 0,
'vers': [], 'vers': [],
'type': MODEL_STD,
} }
defaultField = { defaultField = {
@ -425,34 +426,21 @@ select id from notes where mid = ?)""" % " ".join(map),
########################################################################## ##########################################################################
def _updateRequired(self, m): def _updateRequired(self, m):
if m['type'] == MODEL_CLOZE:
# nothing to do
return
req = [] req = []
flds = [f['name'] for f in m['flds']] flds = [f['name'] for f in m['flds']]
cloze = False
for t in m['tmpls']: for t in m['tmpls']:
ret = self._reqForTemplate(m, flds, t) ret = self._reqForTemplate(m, flds, t)
if ret[2]: req.append((t['ord'], ret[0], ret[1]))
cloze = True
req.append((t['ord'], ret[0], ret[1], ret[2]))
m['req'] = req m['req'] = req
m['cloze'] = cloze
def _reqForTemplate(self, m, flds, t): def _reqForTemplate(self, m, flds, t):
a = [] a = []
b = [] b = []
cloze = "cloze" in t['qfmt']
reqstrs = []
if cloze:
# need a cloze-specific filler
cloze = ""
nums = re.findall("\{\{cloze:(\d+):", t['qfmt'])
for n in nums:
n = int(n)
cloze += "{{c%d::foo}}" % n
# record that we require a specific string for generation
reqstrs.append("{{c%d::" % n)
return 'all', [], reqstrs
for f in flds: for f in flds:
a.append(cloze if cloze else "1") a.append("1")
b.append("") b.append("")
data = [1, 1, m['id'], 1, t['ord'], "", joinFields(a)] data = [1, 1, m['id'], 1, t['ord'], "", joinFields(a)]
full = self.col._renderQA(data)['q'] full = self.col._renderQA(data)['q']
@ -472,7 +460,7 @@ select id from notes where mid = ?)""" % " ".join(map),
if self.col._renderQA(data)['q'] == empty: if self.col._renderQA(data)['q'] == empty:
req.append(i) req.append(i)
if req: if req:
return type, req, reqstrs return type, req
# if there are no required fields, switch to any mode # if there are no required fields, switch to any mode
type = 'any' type = 'any'
req = [] req = []
@ -483,15 +471,17 @@ select id from notes where mid = ?)""" % " ".join(map),
# if not the same as empty, this field can make the card non-blank # if not the same as empty, this field can make the card non-blank
if self.col._renderQA(data)['q'] != empty: if self.col._renderQA(data)['q'] != empty:
req.append(i) req.append(i)
return type, req, reqstrs return type, req
def availOrds(self, m, flds): def availOrds(self, m, flds):
"Given a joined field string, return available template ordinals." "Given a joined field string, return available template ordinals."
if m['type'] == MODEL_CLOZE:
return self._availClozeOrds(m, flds)
fields = {} fields = {}
for c, f in enumerate(splitFields(flds)): for c, f in enumerate(splitFields(flds)):
fields[c] = f.strip() fields[c] = f.strip()
avail = [] avail = []
for ord, type, req, reqstrs in m['req']: for ord, type, req in m['req']:
# unsatisfiable template # unsatisfiable template
if type == "none": if type == "none":
continue continue
@ -514,18 +504,14 @@ select id from notes where mid = ?)""" % " ".join(map),
break break
if not ok: if not ok:
continue continue
# extra cloze requirement?
ok = True
for s in reqstrs:
if s not in flds:
# required cloze string was missing
ok = False
break
if not ok:
continue
avail.append(ord) avail.append(ord)
return avail return avail
def _availClozeOrds(self, m, flds):
ret = [int(m)-1 for m in re.findall(
"{{c(\d)::[^}]*?}}", splitFields(flds)[0])]
return list(set(ret))
# Sync handling # Sync handling
########################################################################## ##########################################################################

View file

@ -3,6 +3,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from anki.lang import _ from anki.lang import _
from anki.consts import MODEL_CLOZE
models = [] models = []
@ -31,20 +32,21 @@ models.append((_("Basic"), addBasicModel))
def addClozeModel(col): def addClozeModel(col):
mm = col.models mm = col.models
m = mm.new(_("Cloze")) m = mm.new(_("Cloze"))
fm = mm.newField(("Text")) m['type'] = MODEL_CLOZE
fm = mm.newField(_("Text"))
mm.addField(m, fm) mm.addField(m, fm)
for i in range(8): fm = mm.newField(_("Extra"))
n = i+1 mm.addField(m, fm)
t = mm.newTemplate(_("Cloze") + " %d" % n) t = mm.newTemplate(_("Cloze"))
fmt = "{{cloze:%d:Text}}" % n fmt = "{{Cloze}}"
t['css'] += """ t['css'] += """
.cloze { .cloze {
font-weight: bold; font-weight: bold;
color: blue; color: blue;
}""" }"""
t['qfmt'] = fmt t['qfmt'] = fmt
t['afmt'] = fmt t['afmt'] = fmt + "<br>\n{{%s}}" % _("Extra")
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.add(m) mm.add(m)
return m return m

View file

@ -44,7 +44,6 @@ def Collection(path, lock=True, server=False, sync=True):
col.lock() col.lock()
return col return col
# no upgrades necessary at the moment
def _upgradeSchema(db): def _upgradeSchema(db):
ver = db.scalar("select ver from col") ver = db.scalar("select ver from col")
if ver == SCHEMA_VERSION: if ver == SCHEMA_VERSION:
@ -81,6 +80,12 @@ def _upgrade(col, ver):
d['dyn'] = 0 d['dyn'] = 0
d['collapsed'] = False d['collapsed'] = False
col.decks.save(d) col.decks.save(d)
if ver < 4:
for m in col.models.all():
if not "{{cloze::" in m['tmpls'][0]['qfmt']:
m['type'] = MODEL_STD
else:
pass
# Creating a new collection # Creating a new collection
###################################################################### ######################################################################