mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Merge pull request #369 from agentydragon/typecheck-models
Add types for models, templates and field dicts
This commit is contained in:
commit
079a00653e
6 changed files with 79 additions and 62 deletions
|
@ -33,6 +33,7 @@ from anki.sched import Scheduler as V1Scheduler
|
||||||
from anki.schedv2 import Scheduler as V2Scheduler
|
from anki.schedv2 import Scheduler as V2Scheduler
|
||||||
from anki.sound import stripSounds
|
from anki.sound import stripSounds
|
||||||
from anki.tags import TagManager
|
from anki.tags import TagManager
|
||||||
|
from anki.types import Model, Template
|
||||||
from anki.utils import (devMode, fieldChecksum, ids2str, intTime, joinFields,
|
from anki.utils import (devMode, fieldChecksum, ids2str, intTime, joinFields,
|
||||||
maxID, splitFields, stripHTMLMedia)
|
maxID, splitFields, stripHTMLMedia)
|
||||||
|
|
||||||
|
@ -354,7 +355,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
avail = self.models.availOrds(model, joinFields(note.fields))
|
avail = self.models.availOrds(model, joinFields(note.fields))
|
||||||
return self._tmplsFromOrds(model, avail)
|
return self._tmplsFromOrds(model, avail)
|
||||||
|
|
||||||
def _tmplsFromOrds(self, model: Dict[str, Any], avail: List[int]) -> List:
|
def _tmplsFromOrds(self, model: Model, avail: List[int]) -> List:
|
||||||
ok = []
|
ok = []
|
||||||
if model['type'] == MODEL_STD:
|
if model['type'] == MODEL_STD:
|
||||||
for t in model['tmpls']:
|
for t in model['tmpls']:
|
||||||
|
@ -456,7 +457,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
cards.append(self._newCard(note, template, 1, flush=False, did=did))
|
cards.append(self._newCard(note, template, 1, flush=False, did=did))
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
def _newCard(self, note: Note, template: Dict[str, Any], due: int, flush: bool = True, did: None = None) -> anki.cards.Card:
|
def _newCard(self, note: Note, template: Template, due: int, flush: bool = True, did: None = None) -> anki.cards.Card:
|
||||||
"Create a new card."
|
"Create a new card."
|
||||||
card = anki.cards.Card(self)
|
card = anki.cards.Card(self)
|
||||||
card.nid = note.id
|
card.nid = note.id
|
||||||
|
@ -465,7 +466,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
# Use template did (deck override) if valid, otherwise did in argument, otherwise model did
|
# Use template did (deck override) if valid, otherwise did in argument, otherwise model did
|
||||||
if not card.did:
|
if not card.did:
|
||||||
if template['did'] and str(template['did']) in self.decks.decks:
|
if template['did'] and str(template['did']) in self.decks.decks:
|
||||||
card.did = template['did']
|
card.did = int(template['did'])
|
||||||
elif did:
|
elif did:
|
||||||
card.did = did
|
card.did = did
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
from anki.types import Model
|
||||||
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
|
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
|
||||||
|
|
||||||
pngCommands = [
|
pngCommands = [
|
||||||
|
@ -42,7 +43,8 @@ def stripLatex(text) -> Any:
|
||||||
text = text.replace(match.group(), "")
|
text = text.replace(match.group(), "")
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]], model: Dict[str, Any], data: Optional[List[Union[int, str]]], col) -> Any:
|
def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]],
|
||||||
|
model: Model, data: Optional[List[Union[int, str]]], col) -> Any:
|
||||||
"Convert TEXT with embedded latex tags to image links."
|
"Convert TEXT with embedded latex tags to image links."
|
||||||
for match in regexps['standard'].finditer(html):
|
for match in regexps['standard'].finditer(html):
|
||||||
html = html.replace(match.group(), _imgLink(col, match.group(1), model))
|
html = html.replace(match.group(), _imgLink(col, match.group(1), model))
|
||||||
|
@ -55,7 +57,7 @@ def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]], mo
|
||||||
"\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model))
|
"\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model))
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def _imgLink(col, latex: str, model: Dict[str, Any]) -> Any:
|
def _imgLink(col, latex: str, model: Model) -> str:
|
||||||
"Return an img link for LATEX, creating if necesssary."
|
"Return an img link for LATEX, creating if necesssary."
|
||||||
txt = _latexFromHtml(col, latex)
|
txt = _latexFromHtml(col, latex)
|
||||||
|
|
||||||
|
@ -80,13 +82,13 @@ def _imgLink(col, latex: str, model: Dict[str, Any]) -> Any:
|
||||||
else:
|
else:
|
||||||
return link
|
return link
|
||||||
|
|
||||||
def _latexFromHtml(col, latex: str) -> Any:
|
def _latexFromHtml(col, latex: str) -> str:
|
||||||
"Convert entities and fix newlines."
|
"Convert entities and fix newlines."
|
||||||
latex = re.sub("<br( /)?>|<div>", "\n", latex)
|
latex = re.sub("<br( /)?>|<div>", "\n", latex)
|
||||||
latex = stripHTML(latex)
|
latex = stripHTML(latex)
|
||||||
return latex
|
return latex
|
||||||
|
|
||||||
def _buildImg(col, latex: str, fname: str, model: Dict[str, Any]) -> Any:
|
def _buildImg(col, latex: str, fname: str, model: Model) -> Optional[str]:
|
||||||
# add header/footer
|
# add header/footer
|
||||||
latex = (model["latexPre"] + "\n" +
|
latex = (model["latexPre"] + "\n" +
|
||||||
latex + "\n" +
|
latex + "\n" +
|
||||||
|
@ -129,7 +131,7 @@ package in the LaTeX header instead.""") % bad
|
||||||
return _errMsg(latexCmd[0], texpath)
|
return _errMsg(latexCmd[0], texpath)
|
||||||
# add to media
|
# add to media
|
||||||
shutil.copyfile(png, os.path.join(mdir, fname))
|
shutil.copyfile(png, os.path.join(mdir, fname))
|
||||||
return
|
return None
|
||||||
finally:
|
finally:
|
||||||
os.chdir(oldcwd)
|
os.chdir(oldcwd)
|
||||||
log.close()
|
log.close()
|
||||||
|
|
|
@ -11,6 +11,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
from anki.types import Field, Model, Template
|
||||||
from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
|
from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
|
@ -18,7 +19,7 @@ from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
|
||||||
|
|
||||||
# - careful not to add any lists/dicts/etc here, as they aren't deep copied
|
# - careful not to add any lists/dicts/etc here, as they aren't deep copied
|
||||||
|
|
||||||
defaultModel = {
|
defaultModel: Model = {
|
||||||
'sortf': 0,
|
'sortf': 0,
|
||||||
'did': 1,
|
'did': 1,
|
||||||
'latexPre': """\
|
'latexPre': """\
|
||||||
|
@ -46,7 +47,7 @@ defaultModel = {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultField: Dict[str, Any] = {
|
defaultField: Field = {
|
||||||
'name': "",
|
'name': "",
|
||||||
'ord': None,
|
'ord': None,
|
||||||
'sticky': False,
|
'sticky': False,
|
||||||
|
@ -59,7 +60,7 @@ defaultField: Dict[str, Any] = {
|
||||||
'media': [],
|
'media': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultTemplate = {
|
defaultTemplate: Template = {
|
||||||
'name': "",
|
'name': "",
|
||||||
'ord': None,
|
'ord': None,
|
||||||
'qfmt': "",
|
'qfmt': "",
|
||||||
|
@ -73,7 +74,7 @@ defaultTemplate = {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelManager:
|
class ModelManager:
|
||||||
models: Dict[str, Any]
|
models: Dict[str, Model]
|
||||||
|
|
||||||
# Saving/loading registry
|
# Saving/loading registry
|
||||||
#############################################################
|
#############################################################
|
||||||
|
@ -88,7 +89,7 @@ class ModelManager:
|
||||||
self.changed = False
|
self.changed = False
|
||||||
self.models = json.loads(json_)
|
self.models = json.loads(json_)
|
||||||
|
|
||||||
def save(self, m: Optional[Dict[str, Any]] = None, templates: bool = False, updateReqs: bool = True) -> None:
|
def save(self, m: Optional[Model] = None, templates: bool = False, updateReqs: bool = True) -> None:
|
||||||
"Mark M modified if provided, and schedule registry flush."
|
"Mark M modified if provided, and schedule registry flush."
|
||||||
if m and m['id']:
|
if m and m['id']:
|
||||||
m['mod'] = intTime()
|
m['mod'] = intTime()
|
||||||
|
@ -125,7 +126,7 @@ class ModelManager:
|
||||||
m = self.get(self.col.conf['curModel'])
|
m = self.get(self.col.conf['curModel'])
|
||||||
return m or list(self.models.values())[0]
|
return m or list(self.models.values())[0]
|
||||||
|
|
||||||
def setCurrent(self, m: Dict[str, Any]) -> None:
|
def setCurrent(self, m: Model) -> None:
|
||||||
self.col.conf['curModel'] = m['id']
|
self.col.conf['curModel'] = m['id']
|
||||||
self.col.setMod()
|
self.col.setMod()
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ class ModelManager:
|
||||||
if m['name'] == name:
|
if m['name'] == name:
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def new(self, name: str) -> Dict[str, Any]:
|
def new(self, name: str) -> Model:
|
||||||
"Create a new model, save it in the registry, and return it."
|
"Create a new model, save it in the registry, and return it."
|
||||||
# caller should call save() after modifying
|
# caller should call save() after modifying
|
||||||
m = defaultModel.copy()
|
m = defaultModel.copy()
|
||||||
|
@ -160,7 +161,7 @@ class ModelManager:
|
||||||
m['id'] = None
|
m['id'] = None
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def rem(self, m: Dict[str, Any]) -> None:
|
def rem(self, m: Model) -> None:
|
||||||
"Delete model, and all its cards/notes."
|
"Delete model, and all its cards/notes."
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
current = self.current()['id'] == m['id']
|
current = self.current()['id'] == m['id']
|
||||||
|
@ -175,26 +176,26 @@ select id from cards where nid in (select id from notes where mid = ?)""",
|
||||||
if current:
|
if current:
|
||||||
self.setCurrent(list(self.models.values())[0])
|
self.setCurrent(list(self.models.values())[0])
|
||||||
|
|
||||||
def add(self, m: Dict[str, Any]) -> None:
|
def add(self, m: Model) -> None:
|
||||||
self._setID(m)
|
self._setID(m)
|
||||||
self.update(m)
|
self.update(m)
|
||||||
self.setCurrent(m)
|
self.setCurrent(m)
|
||||||
self.save(m)
|
self.save(m)
|
||||||
|
|
||||||
def ensureNameUnique(self, m: Dict[str, Any]) -> None:
|
def ensureNameUnique(self, m: Model) -> None:
|
||||||
for mcur in self.all():
|
for mcur in self.all():
|
||||||
if (mcur['name'] == m['name'] and mcur['id'] != m['id']):
|
if (mcur['name'] == m['name'] and mcur['id'] != m['id']):
|
||||||
m['name'] += "-" + checksum(str(time.time()))[:5]
|
m['name'] += "-" + checksum(str(time.time()))[:5]
|
||||||
break
|
break
|
||||||
|
|
||||||
def update(self, m: Dict[str, Any]) -> None:
|
def update(self, m: Model) -> None:
|
||||||
"Add or update an existing model. Used for syncing and merging."
|
"Add or update an existing model. Used for syncing and merging."
|
||||||
self.ensureNameUnique(m)
|
self.ensureNameUnique(m)
|
||||||
self.models[str(m['id'])] = m
|
self.models[str(m['id'])] = m
|
||||||
# mark registry changed, but don't bump mod time
|
# mark registry changed, but don't bump mod time
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def _setID(self, m: Dict[str, Any]) -> None:
|
def _setID(self, m: Model) -> None:
|
||||||
while 1:
|
while 1:
|
||||||
id = str(intTime(1000))
|
id = str(intTime(1000))
|
||||||
if id not in self.models:
|
if id not in self.models:
|
||||||
|
@ -210,17 +211,17 @@ select id from cards where nid in (select id from notes where mid = ?)""",
|
||||||
# Tools
|
# Tools
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def nids(self, m: Dict[str, Any]) -> Any:
|
def nids(self, m: Model) -> Any:
|
||||||
"Note ids for M."
|
"Note ids for M."
|
||||||
return self.col.db.list(
|
return self.col.db.list(
|
||||||
"select id from notes where mid = ?", m['id'])
|
"select id from notes where mid = ?", m['id'])
|
||||||
|
|
||||||
def useCount(self, m: Dict[str, Any]) -> Any:
|
def useCount(self, m: Model) -> Any:
|
||||||
"Number of note using M."
|
"Number of note using M."
|
||||||
return self.col.db.scalar(
|
return self.col.db.scalar(
|
||||||
"select count() from notes where mid = ?", m['id'])
|
"select count() from notes where mid = ?", m['id'])
|
||||||
|
|
||||||
def tmplUseCount(self, m, ord) -> Any:
|
def tmplUseCount(self, m: Model, ord) -> Any:
|
||||||
return self.col.db.scalar("""
|
return self.col.db.scalar("""
|
||||||
select count() from cards, notes where cards.nid = notes.id
|
select count() from cards, notes where cards.nid = notes.id
|
||||||
and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
|
@ -228,7 +229,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
# Copying
|
# Copying
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def copy(self, m: Dict[str, Any]) -> Any:
|
def copy(self, m: Model) -> Any:
|
||||||
"Copy, save and return."
|
"Copy, save and return."
|
||||||
m2 = copy.deepcopy(m)
|
m2 = copy.deepcopy(m)
|
||||||
m2['name'] = _("%s copy") % m2['name']
|
m2['name'] = _("%s copy") % m2['name']
|
||||||
|
@ -238,30 +239,30 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
# Fields
|
# Fields
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def newField(self, name: str) -> Dict[str, Any]:
|
def newField(self, name: str) -> Field:
|
||||||
assert(isinstance(name, str))
|
assert(isinstance(name, str))
|
||||||
f = defaultField.copy()
|
f = defaultField.copy()
|
||||||
f['name'] = name
|
f['name'] = name
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def fieldMap(self, m: Dict[str, Any]) -> Dict[Any, Tuple[Any, Any]]:
|
def fieldMap(self, m: Model) -> Dict[str, Tuple[Any, Any]]:
|
||||||
"Mapping of field name -> (ord, field)."
|
"Mapping of field name -> (ord, field)."
|
||||||
return dict((f['name'], (f['ord'], f)) for f in m['flds'])
|
return dict((f['name'], (f['ord'], f)) for f in m['flds'])
|
||||||
|
|
||||||
def fieldNames(self, m) -> List:
|
def fieldNames(self, m: Model) -> List[str]:
|
||||||
return [f['name'] for f in m['flds']]
|
return [f['name'] for f in m['flds']]
|
||||||
|
|
||||||
def sortIdx(self, m: Dict[str, Any]) -> Any:
|
def sortIdx(self, m: Model) -> Any:
|
||||||
return m['sortf']
|
return m['sortf']
|
||||||
|
|
||||||
def setSortIdx(self, m, idx) -> None:
|
def setSortIdx(self, m: Model, idx: int) -> None:
|
||||||
assert 0 <= idx < len(m['flds'])
|
assert 0 <= idx < len(m['flds'])
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
m['sortf'] = idx
|
m['sortf'] = idx
|
||||||
self.col.updateFieldCache(self.nids(m))
|
self.col.updateFieldCache(self.nids(m))
|
||||||
self.save(m, updateReqs=False)
|
self.save(m, updateReqs=False)
|
||||||
|
|
||||||
def addField(self, m: Dict[str, Any], field: Dict[str, Any]) -> None:
|
def addField(self, m: Model, field: Field) -> None:
|
||||||
# only mod schema if model isn't new
|
# only mod schema if model isn't new
|
||||||
if m['id']:
|
if m['id']:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
|
@ -273,7 +274,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
return fields
|
return fields
|
||||||
self._transformFields(m, add)
|
self._transformFields(m, add)
|
||||||
|
|
||||||
def remField(self, m: Dict[str, Any], field: Dict[str, Any]) -> None:
|
def remField(self, m: Model, field: Field) -> None:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
# save old sort field
|
# save old sort field
|
||||||
sortFldName = m['flds'][m['sortf']]['name']
|
sortFldName = m['flds'][m['sortf']]['name']
|
||||||
|
@ -296,7 +297,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
# saves
|
# saves
|
||||||
self.renameField(m, field, None)
|
self.renameField(m, field, None)
|
||||||
|
|
||||||
def moveField(self, m: Dict[str, Any], field: Dict[str, Any], idx: int) -> None:
|
def moveField(self, m: Model, field: Field, idx: int) -> None:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
oldidx = m['flds'].index(field)
|
oldidx = m['flds'].index(field)
|
||||||
if oldidx == idx:
|
if oldidx == idx:
|
||||||
|
@ -317,7 +318,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
return fields
|
return fields
|
||||||
self._transformFields(m, move)
|
self._transformFields(m, move)
|
||||||
|
|
||||||
def renameField(self, m: Dict[str, Any], field: Dict[str, Any], newName: Optional[str]) -> None:
|
def renameField(self, m: Model, field: Field, newName: Optional[str]) -> None:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}'
|
pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}'
|
||||||
def wrap(txt):
|
def wrap(txt):
|
||||||
|
@ -335,11 +336,11 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
field['name'] = newName
|
field['name'] = newName
|
||||||
self.save(m)
|
self.save(m)
|
||||||
|
|
||||||
def _updateFieldOrds(self, m: Dict[str, Any]) -> None:
|
def _updateFieldOrds(self, m: Model) -> None:
|
||||||
for c, f in enumerate(m['flds']):
|
for c, f in enumerate(m['flds']):
|
||||||
f['ord'] = c
|
f['ord'] = c
|
||||||
|
|
||||||
def _transformFields(self, m: Dict[str, Any], fn: Callable) -> None:
|
def _transformFields(self, m: Model, fn: Callable) -> None:
|
||||||
# model hasn't been added yet?
|
# model hasn't been added yet?
|
||||||
if not m['id']:
|
if not m['id']:
|
||||||
return
|
return
|
||||||
|
@ -354,12 +355,12 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
# Templates
|
# Templates
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def newTemplate(self, name: str) -> Dict[str, Any]:
|
def newTemplate(self, name: str) -> Template:
|
||||||
t = defaultTemplate.copy()
|
t = defaultTemplate.copy()
|
||||||
t['name'] = name
|
t['name'] = name
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def addTemplate(self, m: Dict[str, Any], template: Dict[str, Union[str, None]]) -> None:
|
def addTemplate(self, m: Model, template: Template) -> None:
|
||||||
"Note: should col.genCards() afterwards."
|
"Note: should col.genCards() afterwards."
|
||||||
if m['id']:
|
if m['id']:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
|
@ -367,7 +368,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
|
||||||
self._updateTemplOrds(m)
|
self._updateTemplOrds(m)
|
||||||
self.save(m)
|
self.save(m)
|
||||||
|
|
||||||
def remTemplate(self, m: Dict[str, Any], template: Dict[str, Any]) -> bool:
|
def remTemplate(self, m: Model, template: Template) -> bool:
|
||||||
"False if removing template would leave orphan notes."
|
"False if removing template would leave orphan notes."
|
||||||
assert len(m['tmpls']) > 1
|
assert len(m['tmpls']) > 1
|
||||||
# find cards using this template
|
# find cards using this template
|
||||||
|
@ -397,11 +398,11 @@ update cards set ord = ord - 1, usn = ?, mod = ?
|
||||||
self.save(m)
|
self.save(m)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _updateTemplOrds(self, m: Dict[str, Any]) -> None:
|
def _updateTemplOrds(self, m: Model) -> None:
|
||||||
for c, t in enumerate(m['tmpls']):
|
for c, t in enumerate(m['tmpls']):
|
||||||
t['ord'] = c
|
t['ord'] = c
|
||||||
|
|
||||||
def moveTemplate(self, m, template, idx) -> None:
|
def moveTemplate(self, m: Model, template: Template, idx: int) -> None:
|
||||||
oldidx = m['tmpls'].index(template)
|
oldidx = m['tmpls'].index(template)
|
||||||
if oldidx == idx:
|
if oldidx == idx:
|
||||||
return
|
return
|
||||||
|
@ -420,7 +421,7 @@ update cards set ord = (case %s end),usn=?,mod=? where nid in (
|
||||||
select id from notes where mid = ?)""" % " ".join(map),
|
select id from notes where mid = ?)""" % " ".join(map),
|
||||||
self.col.usn(), intTime(), m['id'])
|
self.col.usn(), intTime(), m['id'])
|
||||||
|
|
||||||
def _syncTemplates(self, m: Dict[str, Any]) -> None:
|
def _syncTemplates(self, m: Model) -> None:
|
||||||
rem = self.col.genCards(self.nids(m))
|
rem = self.col.genCards(self.nids(m))
|
||||||
|
|
||||||
# Model changing
|
# Model changing
|
||||||
|
@ -428,7 +429,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
# - maps are ord->ord, and there should not be duplicate targets
|
# - maps are ord->ord, and there should not be duplicate targets
|
||||||
# - newModel should be self if model is not changing
|
# - newModel should be self if model is not changing
|
||||||
|
|
||||||
def change(self, m: Dict[str, Any], nids: List[int], newModel: Dict[str, Any], fmap: Any, cmap: Any) -> None:
|
def change(self, m: Model, nids: List[int], newModel: Model, fmap: Any, cmap: Any) -> None:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
assert newModel['id'] == m['id'] or (fmap and cmap)
|
assert newModel['id'] == m['id'] or (fmap and cmap)
|
||||||
if fmap:
|
if fmap:
|
||||||
|
@ -437,7 +438,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
self._changeCards(nids, m, newModel, cmap)
|
self._changeCards(nids, m, newModel, cmap)
|
||||||
self.col.genCards(nids)
|
self.col.genCards(nids)
|
||||||
|
|
||||||
def _changeNotes(self, nids: List[int], newModel: Dict[str, Any], map: Dict[int, Union[None, int]]) -> None:
|
def _changeNotes(self, nids: List[int], newModel: Model, map: Dict[int, Union[None, int]]) -> None:
|
||||||
d = []
|
d = []
|
||||||
nfields = len(newModel['flds'])
|
nfields = len(newModel['flds'])
|
||||||
for (nid, flds) in self.col.db.execute(
|
for (nid, flds) in self.col.db.execute(
|
||||||
|
@ -456,7 +457,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
"update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d)
|
"update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d)
|
||||||
self.col.updateFieldCache(nids)
|
self.col.updateFieldCache(nids)
|
||||||
|
|
||||||
def _changeCards(self, nids: List[int], oldModel: Dict[str, Any], newModel: Dict[str, Any], map: Dict[int, Union[None, int]]) -> None:
|
def _changeCards(self, nids: List[int], oldModel: Model, newModel: Model, map: Dict[int, Union[None, int]]) -> None:
|
||||||
d = []
|
d = []
|
||||||
deleted = []
|
deleted = []
|
||||||
for (cid, ord) in self.col.db.execute(
|
for (cid, ord) in self.col.db.execute(
|
||||||
|
@ -486,7 +487,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
# Schema hash
|
# Schema hash
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def scmhash(self, m: Dict[str, Any]) -> str:
|
def scmhash(self, m: Model) -> str:
|
||||||
"Return a hash of the schema, to see if models are compatible."
|
"Return a hash of the schema, to see if models are compatible."
|
||||||
s = ""
|
s = ""
|
||||||
for f in m['flds']:
|
for f in m['flds']:
|
||||||
|
@ -498,7 +499,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
# Required field/text cache
|
# Required field/text cache
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _updateRequired(self, m: Dict[str, Any]) -> None:
|
def _updateRequired(self, m: Model) -> None:
|
||||||
if m['type'] == MODEL_CLOZE:
|
if m['type'] == MODEL_CLOZE:
|
||||||
# nothing to do
|
# nothing to do
|
||||||
return
|
return
|
||||||
|
@ -509,7 +510,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
req.append([t['ord'], ret[0], ret[1]])
|
req.append([t['ord'], ret[0], ret[1]])
|
||||||
m['req'] = req
|
m['req'] = req
|
||||||
|
|
||||||
def _reqForTemplate(self, m: Dict[str, Any], flds: List[str], t: Dict[str, Any]) -> Tuple[Union[str, List[int]], ...]:
|
def _reqForTemplate(self, m: Model, flds: List[str], t: Template) -> Tuple[Union[str, List[int]], ...]:
|
||||||
a = []
|
a = []
|
||||||
b = []
|
b = []
|
||||||
for f in flds:
|
for f in flds:
|
||||||
|
@ -546,7 +547,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
req.append(i)
|
req.append(i)
|
||||||
return type, req
|
return type, req
|
||||||
|
|
||||||
def availOrds(self, m: Dict[str, Any], flds: str) -> List:
|
def availOrds(self, m: Model, flds: str) -> List:
|
||||||
"Given a joined field string, return available template ordinals."
|
"Given a joined field string, return available template ordinals."
|
||||||
if m['type'] == MODEL_CLOZE:
|
if m['type'] == MODEL_CLOZE:
|
||||||
return self._availClozeOrds(m, flds)
|
return self._availClozeOrds(m, flds)
|
||||||
|
@ -580,7 +581,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
avail.append(ord)
|
avail.append(ord)
|
||||||
return avail
|
return avail
|
||||||
|
|
||||||
def _availClozeOrds(self, m: Dict[str, Any], flds: str, allowEmpty: bool = True) -> List:
|
def _availClozeOrds(self, m: Model, flds: str, allowEmpty: bool = True) -> List:
|
||||||
sflds = splitFields(flds)
|
sflds = splitFields(flds)
|
||||||
map = self.fieldMap(m)
|
map = self.fieldMap(m)
|
||||||
ords = set()
|
ords = set()
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# 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 typing import Any, Dict
|
from typing import Any, Callable, List, Tuple
|
||||||
|
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
from anki.types import Model
|
||||||
|
|
||||||
models = []
|
models: List[Tuple[Callable[[], str], Callable[[Any], Model]]] = []
|
||||||
|
|
||||||
# Basic
|
# Basic
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _newBasicModel(col, name=None) -> Dict[str, Any]:
|
def _newBasicModel(col, name=None) -> Model:
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = mm.new(name or _("Basic"))
|
m = mm.new(name or _("Basic"))
|
||||||
fm = mm.newField(_("Front"))
|
fm = mm.newField(_("Front"))
|
||||||
|
@ -24,7 +25,7 @@ def _newBasicModel(col, name=None) -> Dict[str, Any]:
|
||||||
mm.addTemplate(m, t)
|
mm.addTemplate(m, t)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def addBasicModel(col) -> Dict[str, Any]:
|
def addBasicModel(col) -> Model:
|
||||||
m = _newBasicModel(col)
|
m = _newBasicModel(col)
|
||||||
col.models.add(m)
|
col.models.add(m)
|
||||||
return m
|
return m
|
||||||
|
@ -34,7 +35,7 @@ models.append((lambda: _("Basic"), addBasicModel))
|
||||||
# Basic w/ typing
|
# Basic w/ typing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def addBasicTypingModel(col) -> Dict[str, Any]:
|
def addBasicTypingModel(col) -> Model:
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = _newBasicModel(col, _("Basic (type in the answer)"))
|
m = _newBasicModel(col, _("Basic (type in the answer)"))
|
||||||
t = m['tmpls'][0]
|
t = m['tmpls'][0]
|
||||||
|
@ -48,7 +49,7 @@ models.append((lambda: _("Basic (type in the answer)"), addBasicTypingModel))
|
||||||
# Forward & Reverse
|
# Forward & Reverse
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _newForwardReverse(col, name=None) -> Dict[str, Any]:
|
def _newForwardReverse(col, name=None) -> Model:
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = _newBasicModel(col, name or _("Basic (and reversed card)"))
|
m = _newBasicModel(col, name or _("Basic (and reversed card)"))
|
||||||
t = mm.newTemplate(_("Card 2"))
|
t = mm.newTemplate(_("Card 2"))
|
||||||
|
@ -57,7 +58,7 @@ def _newForwardReverse(col, name=None) -> Dict[str, Any]:
|
||||||
mm.addTemplate(m, t)
|
mm.addTemplate(m, t)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def addForwardReverse(col) -> Dict[str, Any]:
|
def addForwardReverse(col) -> Model:
|
||||||
m = _newForwardReverse(col)
|
m = _newForwardReverse(col)
|
||||||
col.models.add(m)
|
col.models.add(m)
|
||||||
return m
|
return m
|
||||||
|
@ -67,7 +68,7 @@ models.append((lambda: _("Basic (and reversed card)"), addForwardReverse))
|
||||||
# Forward & Optional Reverse
|
# Forward & Optional Reverse
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def addForwardOptionalReverse(col) -> Dict[str, Any]:
|
def addForwardOptionalReverse(col) -> Model:
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = _newForwardReverse(col, _("Basic (optional reversed card)"))
|
m = _newForwardReverse(col, _("Basic (optional reversed card)"))
|
||||||
av = _("Add Reverse")
|
av = _("Add Reverse")
|
||||||
|
@ -84,7 +85,7 @@ models.append((lambda: _("Basic (optional reversed card)"),
|
||||||
# Cloze
|
# Cloze
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def addClozeModel(col) -> Dict[str, Any]:
|
def addClozeModel(col) -> Model:
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = mm.new(_("Cloze"))
|
m = mm.new(_("Cloze"))
|
||||||
m['type'] = MODEL_CLOZE
|
m['type'] = MODEL_CLOZE
|
||||||
|
|
12
anki/types.py
Normal file
12
anki/types.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
|
# Model attributes are stored in a dict keyed by strings. This type alias
|
||||||
|
# provides more descriptive function signatures than just 'Dict[str, Any]'
|
||||||
|
# for methods that operate on models.
|
||||||
|
# TODO: Use https://www.python.org/dev/peps/pep-0589/ when available in
|
||||||
|
# supported Python versions.
|
||||||
|
Model = Dict[str, Any]
|
||||||
|
|
||||||
|
Field = Dict[str, Any]
|
||||||
|
|
||||||
|
Template = Dict[str, Union[str, int, None]]
|
|
@ -52,7 +52,7 @@ inTimeTable = {
|
||||||
"seconds": lambda n: ngettext("in %s second", "in %s seconds", n),
|
"seconds": lambda n: ngettext("in %s second", "in %s seconds", n),
|
||||||
}
|
}
|
||||||
|
|
||||||
def shortTimeFmt(type: str) -> Any:
|
def shortTimeFmt(type: str) -> str:
|
||||||
return {
|
return {
|
||||||
#T: year is an abbreviation for year. %s is a number of years
|
#T: year is an abbreviation for year. %s is a number of years
|
||||||
"years": _("%sy"),
|
"years": _("%sy"),
|
||||||
|
@ -84,7 +84,7 @@ def fmtTimeSpan(time: Union[int, float], pad: int = 0, point: int = 0, short: bo
|
||||||
timestr = "%%%(a)d.%(b)df" % {'a': pad, 'b': point}
|
timestr = "%%%(a)d.%(b)df" % {'a': pad, 'b': point}
|
||||||
return locale.format_string(fmt % timestr, time)
|
return locale.format_string(fmt % timestr, time)
|
||||||
|
|
||||||
def optimalPeriod(time: Union[int, float], point: int, unit: int) -> Tuple[str, Any]:
|
def optimalPeriod(time: Union[int, float], point: int, unit: int) -> Tuple[str, int]:
|
||||||
if abs(time) < 60 or unit < 1:
|
if abs(time) < 60 or unit < 1:
|
||||||
type = "seconds"
|
type = "seconds"
|
||||||
point -= 1
|
point -= 1
|
||||||
|
@ -152,7 +152,7 @@ def stripHTML(s: str) -> str:
|
||||||
s = entsToTxt(s)
|
s = entsToTxt(s)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def stripHTMLMedia(s: str) -> Any:
|
def stripHTMLMedia(s: str) -> str:
|
||||||
"Strip HTML but keep media filenames"
|
"Strip HTML but keep media filenames"
|
||||||
s = reMedia.sub(" \\1 ", s)
|
s = reMedia.sub(" \\1 ", s)
|
||||||
return stripHTML(s)
|
return stripHTML(s)
|
||||||
|
@ -167,7 +167,7 @@ def minimizeHTML(s) -> str:
|
||||||
'<u>\\1</u>', s)
|
'<u>\\1</u>', s)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def htmlToTextLine(s) -> Any:
|
def htmlToTextLine(s) -> str:
|
||||||
s = s.replace("<br>", " ")
|
s = s.replace("<br>", " ")
|
||||||
s = s.replace("<br />", " ")
|
s = s.replace("<br />", " ")
|
||||||
s = s.replace("<div>", " ")
|
s = s.replace("<div>", " ")
|
||||||
|
|
Loading…
Reference in a new issue