From 4bca26161b3023c6a38a58851ed2c5cf735cb25e Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 8 Jan 2020 12:07:44 +1000 Subject: [PATCH] clean up _renderQA(), and split rendering part out --- pylib/anki/collection.py | 83 ++++++++++++++++++++++------------------ pylib/anki/template2.py | 33 ++++++++++++++++ 2 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 pylib/anki/template2.py diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 1ac8fd917..8634e9230 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -29,8 +29,8 @@ from anki.notes import Note from anki.rsbackend import RustBackend from anki.sched import Scheduler as V1Scheduler from anki.schedv2 import Scheduler as V2Scheduler -from anki.sound import stripSounds from anki.tags import TagManager +from anki.template2 import renderFromFieldMap from anki.types import NoteType, QAData, Template from anki.utils import ( devMode, @@ -629,51 +629,58 @@ where c.nid = n.id and c.id in %s group by nid""" raise Exception() return [self._renderQA(*row) for row in self._qaData(where)] - def _renderQA(self, data: QAData, qfmt: None = None, afmt: None = None) -> Dict: + # data is [cid, nid, mid, did, ord, tags, flds, cardFlags] + def _renderQA( + self, data: QAData, qfmt: Optional[str] = None, afmt: Optional[str] = None + ) -> Dict[str, Union[str, int]]: "Returns hash of id, question, answer." - # data is [cid, nid, mid, did, ord, tags, flds, cardFlags] - # unpack fields and create dict - flist = splitFields(data[6]) - fields = {} + # extract info from data + split_fields = splitFields(data[6]) + card_ord = data[4] model = self.models.get(data[2]) - assert model - for (name, (idx, conf)) in list(self.models.fieldMap(model).items()): - fields[name] = flist[idx] - fields["Tags"] = data[5].strip() - fields["Type"] = model["name"] - fields["Deck"] = self.decks.name(data[3]) - fields["Subdeck"] = fields["Deck"].split("::")[-1] - fields["CardFlag"] = self._flagNameFromCardFlags(data[7]) if model["type"] == MODEL_STD: template = model["tmpls"][data[4]] else: template = model["tmpls"][0] - fields["Card"] = template["name"] - fields["c%d" % (data[4] + 1)] = "1" - # render q & a - d: Dict[str, Any] = dict(id=data[0]) + flag = data[7] + deck_id = data[3] + card_id = data[0] + tags = data[5] qfmt = qfmt or template["qfmt"] afmt = afmt or template["afmt"] - for (type, format) in (("q", qfmt), ("a", afmt)): - if type == "q": - format = re.sub( - "{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4] + 1), format - ) - format = format.replace("<%cloze:", "<%%cq:%d:" % (data[4] + 1)) - else: - format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4] + 1), format) - format = format.replace("<%cloze:", "<%%ca:%d:" % (data[4] + 1)) - fields["FrontSide"] = stripSounds(d["q"]) - fields = runFilter("mungeFields", fields, model, data, self) - html = anki.template.render(format, fields) - d[type] = runFilter("mungeQA", html, type, fields, model, data, self) - # empty cloze? - if type == "q" and model["type"] == MODEL_CLOZE: - if not self.models._availClozeOrds(model, data[6], False): - d["q"] += "

" + _( - "Please edit this note and add some cloze deletions. (%s)" - ) % ("%s" % (HELP_SITE, _("help"))) - return d + + # create map of field names -> field content + fields: Dict[str, str] = {} + for (name, (idx, conf)) in list(self.models.fieldMap(model).items()): + fields[name] = split_fields[idx] + + # add special fields + fields["Tags"] = tags.strip() + fields["Type"] = model["name"] + fields["Deck"] = self.decks.name(deck_id) + fields["Subdeck"] = fields["Deck"].split("::")[-1] + fields["Card"] = template["name"] + fields["CardFlag"] = self._flagNameFromCardFlags(flag) + fields["c%d" % (card_ord + 1)] = "1" + + fields = runFilter("mungeFields", fields, model, data, self) + + # render fields + qatext = renderFromFieldMap(qfmt, afmt, fields, card_ord) + ret: Dict[str, Any] = dict(q=qatext[0], a=qatext[1], id=card_id) + + # allow add-ons to modify the generated result + for type in "q", "a": + ret[type] = runFilter("mungeQA", ret[type], type, fields, model, data, self) + + # empty cloze? + if type == "q" and model["type"] == MODEL_CLOZE: + if not self.models._availClozeOrds(model, data[6], False): + ret["q"] += "

" + _( + "Please edit this note and add some cloze deletions. (%s)" + ) % ("%s" % (HELP_SITE, _("help"))) + + return ret def _qaData(self, where="") -> Any: "Return [cid, nid, mid, did, ord, tags, flds, cardFlags] db query" diff --git a/pylib/anki/template2.py b/pylib/anki/template2.py new file mode 100644 index 000000000..16e6333f6 --- /dev/null +++ b/pylib/anki/template2.py @@ -0,0 +1,33 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +""" +This file contains some code related to templates that is not directly +connected to pystache. It may be renamed in the future. +""" + +from __future__ import annotations + +import re +from typing import Dict, Tuple + +import anki +from anki.sound import stripSounds + + +def renderFromFieldMap( + qfmt: str, afmt: str, fields: Dict[str, str], card_ord: int +) -> Tuple[str, str]: + "Renders the provided templates, returning rendered q & a text." + # question + format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (card_ord + 1), qfmt) + format = format.replace("<%cloze:", "<%%cq:%d:" % (card_ord + 1)) + qtext = anki.template.render(format, fields) + + # answer + format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (card_ord + 1), afmt) + format = format.replace("<%cloze:", "<%%ca:%d:" % (card_ord + 1)) + fields["FrontSide"] = stripSounds(qtext) + atext = anki.template.render(format, fields) + + return qtext, atext