From 15ff279a96f02ca3156ff0fed42efd8f3dab416d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 21 Apr 2020 13:49:40 +1000 Subject: [PATCH] cloze generation --- rslib/src/cloze.rs | 13 +++++--- rslib/src/notes.rs | 19 ++++++++++- rslib/src/notetype/cardgen.rs | 61 +++++++++++++++++++++++++++++++---- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/rslib/src/cloze.rs b/rslib/src/cloze.rs index 425690680..4fa51401a 100644 --- a/rslib/src/cloze.rs +++ b/rslib/src/cloze.rs @@ -110,13 +110,18 @@ pub fn expand_clozes_to_reveal_latex(text: &str) -> String { } pub fn cloze_numbers_in_string(html: &str) -> HashSet { - let mut hash = HashSet::with_capacity(4); - for cap in CLOZE.captures_iter(html) { + let mut set = HashSet::with_capacity(4); + add_cloze_numbers_in_string(html, &mut set); + set +} + +#[allow(clippy::implicit_hasher)] +pub fn add_cloze_numbers_in_string(field: &str, set: &mut HashSet) { + for cap in CLOZE.captures_iter(field) { if let Ok(n) = cap[1].parse() { - hash.insert(n); + set.insert(n); } } - hash } fn strip_html_inside_mathjax(text: &str) -> Cow { diff --git a/rslib/src/notes.rs b/rslib/src/notes.rs index 7cb3325aa..52b8529f8 100644 --- a/rslib/src/notes.rs +++ b/rslib/src/notes.rs @@ -219,7 +219,24 @@ mod test { 1 ); - // fixme: add nt cache, refcount + let nt = col.get_notetype_by_name("cloze")?.unwrap(); + let mut note = nt.new_note(); + // cloze cards without any cloze deletions are allowed + col.add_note(&mut note).unwrap(); + let existing = col.storage.existing_cards_for_note(note.id)?; + assert_eq!(existing.len(), 1); + assert_eq!(existing[0].ord, 0); + + // fixme + // assert_eq!(existing[0].original_deck_id, DeckID(1)); + + note.fields[0] = "{{c1::foo}} {{c2::bar}} {{c3::baz}} {{c0::quux}} {{c501::over}}".into(); + col.update_note(&mut note)?; + let existing = col.storage.existing_cards_for_note(note.id)?; + let mut ords = existing.iter().map(|a| a.ord).collect::>(); + ords.sort(); + assert_eq!(ords, vec![0, 1, 2, 499]); + Ok(()) } } diff --git a/rslib/src/notetype/cardgen.rs b/rslib/src/notetype/cardgen.rs index 960472f30..af9169472 100644 --- a/rslib/src/notetype/cardgen.rs +++ b/rslib/src/notetype/cardgen.rs @@ -4,10 +4,12 @@ use super::NoteType; use crate::{ card::{Card, CardID}, + cloze::add_cloze_numbers_in_string, collection::Collection, decks::DeckID, err::Result, notes::{Note, NoteID}, + notetype::NoteTypeKind, template::ParsedTemplate, types::Usn, }; @@ -15,6 +17,7 @@ use itertools::Itertools; use std::collections::HashSet; /// Info about an existing card required when generating new cards +#[derive(Debug, PartialEq)] pub(crate) struct AlreadyGeneratedCardInfo { pub id: CardID, pub nid: NoteID, @@ -82,16 +85,20 @@ impl CardGenContext<'_> { note: &Note, existing: &[AlreadyGeneratedCardInfo], ) -> Vec { - let nonempty_fields = note.nonempty_fields(&self.notetype.fields); let extracted = extract_data_from_existing_cards(existing); - self.new_cards_required_inner(&nonempty_fields, &extracted) + match self.notetype.config.kind() { + NoteTypeKind::Normal => self.new_cards_required_normal(note, &extracted), + NoteTypeKind::Cloze => self.new_cards_required_cloze(note, &extracted), + } } - fn new_cards_required_inner( + fn new_cards_required_normal( &self, - nonempty_fields: &HashSet<&str>, + note: &Note, extracted: &ExtractedCardInfo, ) -> Vec { + let nonempty_fields = note.nonempty_fields(&self.notetype.fields); + self.cards .iter() .enumerate() @@ -110,6 +117,43 @@ impl CardGenContext<'_> { }) .collect() } + + fn new_cards_required_cloze( + &self, + note: &Note, + extracted: &ExtractedCardInfo, + ) -> Vec { + // gather all cloze numbers + let mut set = HashSet::with_capacity(4); + for field in note.fields() { + add_cloze_numbers_in_string(field, &mut set); + } + let cards: Vec<_> = set + .into_iter() + .filter_map(|cloze_ord| { + let card_ord = cloze_ord.saturating_sub(1).min(499); + if extracted.existing_ords.contains(&(card_ord as u32)) { + None + } else { + Some(CardToGenerate { + ord: card_ord as u32, + did: extracted.deck_id, + due: extracted.due, + }) + } + }) + .collect(); + if cards.is_empty() && extracted.existing_ords.is_empty() { + // if no cloze deletions are found, we add a card with ord 0 + vec![CardToGenerate { + ord: 0, + did: extracted.deck_id, + due: extracted.due, + }] + } else { + cards + } + } } // this could be reworked in the future to avoid the extra vec allocation @@ -173,8 +217,11 @@ impl Collection { let existing_cards = self.storage.existing_cards_for_notetype(ctx.notetype.id)?; let by_note = group_generated_cards_by_note(existing_cards); for (nid, existing_cards) in by_note { - if existing_cards.len() == ctx.notetype.templates.len() { - // nothing to do + if ctx.notetype.config.kind() == NoteTypeKind::Normal + && existing_cards.len() == ctx.notetype.templates.len() + { + // in a normal note type, if card count matches template count, we don't need + // to load the note contents to know if all cards have been generated continue; } let note = self.storage.get_note(nid)?.unwrap(); @@ -208,4 +255,4 @@ impl Collection { } // fixme: deal with case where invalid deck pointed to -// fixme: cloze cards, & avoid template count comparison for cloze +// fixme: make sure we don't orphan notes