From f23eb350e4f608ad0ddbbcd6c99d050cb6a5af86 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 14 May 2020 12:14:00 +1000 Subject: [PATCH] drop availOrds(), and use backend for getting cloze numbers --- proto/backend.proto | 6 ++++ pylib/anki/models.py | 58 +++----------------------------------- pylib/anki/notes.py | 3 ++ pylib/anki/rsbackend.py | 2 ++ pylib/tests/test_models.py | 47 ------------------------------ qt/aqt/clayout.py | 2 +- rslib/src/backend/mod.rs | 16 ++++++++++- 7 files changed, 31 insertions(+), 103 deletions(-) diff --git a/proto/backend.proto b/proto/backend.proto index 43dcea93e..a150281db 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -98,6 +98,7 @@ message BackendInput { Empty get_preferences = 84; Preferences set_preferences = 85; RenderExistingCardIn render_existing_card = 86; + Note cloze_numbers_in_note = 87; } } @@ -114,6 +115,7 @@ message BackendOutput { string congrats_learn_msg = 33; Empty abort_media_sync = 46; AllStockNotetypesOut all_stock_notetypes = 60; + ClozeNumbersInNoteOut cloze_numbers_in_note = 87; // fallible commands SchedTimingTodayOut sched_timing_today = 17; @@ -781,3 +783,7 @@ message CollectionSchedulingSettings { message Preferences { CollectionSchedulingSettings sched = 1; } + +message ClozeNumbersInNoteOut { + repeated uint32 numbers = 1; +} diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 2739ee9cd..0969e8f8d 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -521,63 +521,13 @@ class ModelManager: s += t["name"] return checksum(s) - # Required field/text cache + # Cloze ########################################################################## - # fixme: clayout, importing - def availOrds(self, m: NoteType, flds: str) -> List: - "Given a joined field string, return available template ordinals." - if m["type"] == MODEL_CLOZE: - return self._availClozeOrds(m, flds) - fields = {} - for c, f in enumerate(splitFields(flds)): - fields[c] = f.strip() - avail = [] - for ord, type, req in m["req"]: - # unsatisfiable template - if type == "none": - continue - # AND requirement? - elif type == "all": - ok = True - for idx in req: - if not fields[idx]: - # missing and was required - ok = False - break - if not ok: - continue - # OR requirement? - elif type == "any": - ok = False - for idx in req: - if fields[idx]: - ok = True - break - if not ok: - continue - avail.append(ord) - return avail - def _availClozeOrds(self, m: NoteType, flds: str, allowEmpty: bool = True) -> List: - sflds = splitFields(flds) - map = self.fieldMap(m) - ords = set() - matches = re.findall("{{[^}]*?cloze:(?:[^}]?:)*(.+?)}}", m["tmpls"][0]["qfmt"]) - matches += re.findall("<%cloze:(.+?)%>", m["tmpls"][0]["qfmt"]) - for fname in matches: - if fname not in map: - continue - ord = map[fname][0] - ords.update( - [int(m) - 1 for m in re.findall(r"(?s){{c(\d+)::.+?}}", sflds[ord])] - ) - if -1 in ords: - ords.remove(-1) - if not ords and allowEmpty: - # empty clozes use first ord - return [0] - return list(ords) + print("_availClozeOrds() is deprecated") + note = anki.rsbackend.BackendNote(fields=[flds]) + return self.col.backend.cloze_numbers_in_note(note) # Sync handling ########################################################################## diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 203172fc9..5c774b998 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -82,6 +82,9 @@ class Note: _model = property(model) + def cloze_numbers_in_fields(self) -> List[int]: + return self.col.backend.cloze_numbers_in_note(self.to_backend_note()) + # Dict interface ################################################## diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index de3287105..44ceef559 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -808,6 +808,8 @@ class RustBackend: def set_preferences(self, prefs: pb.Preferences) -> None: self._run_command(pb.BackendInput(set_preferences=prefs)) + def cloze_numbers_in_note(self, note: pb.Note) -> List[int]: + return list(self._run_command(pb.BackendInput(cloze_numbers_in_note=note)).cloze_numbers_in_note.numbers) def translate_string_in( key: TR, **kwargs: Union[str, int, float] diff --git a/pylib/tests/test_models.py b/pylib/tests/test_models.py index 7414b4cd4..e79efb9f5 100644 --- a/pylib/tests/test_models.py +++ b/pylib/tests/test_models.py @@ -355,37 +355,6 @@ def test_modelChange(): assert deck.db.scalar("select count() from cards where nid = ?", f.id) == 1 -def test_availOrds(): - d = getEmptyCol() - m = d.models.current() - mm = d.models - t = m["tmpls"][0] - f = d.newNote() - f["Front"] = "1" - # simple templates - assert mm.availOrds(m, joinFields(f.fields)) == [0] - t["qfmt"] = "{{Back}}" - mm.save(m, templates=True) - assert not mm.availOrds(m, joinFields(f.fields)) - # AND - t = m["tmpls"][0] - t["qfmt"] = "{{#Front}}{{#Back}}{{Front}}{{/Back}}{{/Front}}" - mm.save(m, templates=True) - assert not mm.availOrds(m, joinFields(f.fields)) - t = m["tmpls"][0] - t["qfmt"] = "{{#Front}}\n{{#Back}}\n{{Front}}\n{{/Back}}\n{{/Front}}" - mm.save(m, templates=True) - assert not mm.availOrds(m, joinFields(f.fields)) - # OR - t = m["tmpls"][0] - t["qfmt"] = "{{Front}}\n{{Back}}" - mm.save(m, templates=True) - assert mm.availOrds(m, joinFields(f.fields)) == [0] - t["Front"] = "" - t["Back"] = "1" - assert mm.availOrds(m, joinFields(f.fields)) == [0] - - def test_req(): def reqSize(model): if model["type"] == MODEL_CLOZE: @@ -421,19 +390,3 @@ def test_req(): r = opt["req"][0] assert r[1] in ("any", "all") assert r[2] == [0, 1] - - -# def test_updatereqs_performance(): -# import time -# d = getEmptyCol() -# mm = d.models -# m = mm.byName("Basic") -# for i in range(100): -# fld = mm.newField(f"field{i}") -# mm.addField(m, fld) -# tmpl = mm.newTemplate(f"template{i}") -# tmpl['qfmt'] = "{{field%s}}" % i -# mm.addTemplate(m, tmpl) -# t = time.time() -# mm.save(m, templates=True) -# print("took", (time.time()-t)*100) diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index db445a4dd..31b844e87 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -241,7 +241,7 @@ class CardLayout(QDialog): def updateMainArea(self): if self._isCloze(): - cnt = len(self.mm.availOrds(self.model, joinFields(self.note.fields))) + cnt = len(self.note.cloze_numbers_in_fields()) for g in self.pform.groupBox, self.pform.groupBox_2: g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1)) diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index a32f1164e..383ac018a 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -8,6 +8,7 @@ use crate::{ backend_proto::{AddOrUpdateDeckConfigIn, Empty, RenderedTemplateReplacement, SyncMediaIn}, card::{Card, CardID}, card::{CardQueue, CardType}, + cloze::add_cloze_numbers_in_string, collection::{open_collection, Collection}, config::SortKind, deckconf::{DeckConf, DeckConfID}, @@ -39,7 +40,7 @@ use log::error; use pb::backend_input::Value; use prost::Message; use serde_json::Value as JsonValue; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::path::PathBuf; use std::sync::{Arc, Mutex}; @@ -381,6 +382,9 @@ impl Backend { Value::RenderUncommittedCard(input) => { OValue::RenderUncommittedCard(self.render_uncommitted_card(input)?) } + Value::ClozeNumbersInNote(note) => { + OValue::ClozeNumbersInNote(self.cloze_numbers_in_note(note)) + } }) } @@ -1136,6 +1140,16 @@ impl Backend { fn set_preferences(&self, prefs: pb::Preferences) -> Result<()> { self.with_col(|col| col.transact(None, |col| col.set_preferences(prefs))) } + + fn cloze_numbers_in_note(&self, note: pb::Note) -> pb::ClozeNumbersInNoteOut { + let mut set = HashSet::with_capacity(4); + for field in ¬e.fields { + add_cloze_numbers_in_string(field, &mut set); + } + pb::ClozeNumbersInNoteOut { + numbers: set.into_iter().map(|n| n as u32).collect(), + } + } } fn to_nids(ids: Vec) -> Vec {