cloze generation

This commit is contained in:
Damien Elmes 2020-04-21 13:49:40 +10:00
parent 83bcb084fe
commit 15ff279a96
3 changed files with 81 additions and 12 deletions

View file

@ -110,13 +110,18 @@ pub fn expand_clozes_to_reveal_latex(text: &str) -> String {
} }
pub fn cloze_numbers_in_string(html: &str) -> HashSet<u16> { pub fn cloze_numbers_in_string(html: &str) -> HashSet<u16> {
let mut hash = HashSet::with_capacity(4); let mut set = HashSet::with_capacity(4);
for cap in CLOZE.captures_iter(html) { 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<u16>) {
for cap in CLOZE.captures_iter(field) {
if let Ok(n) = cap[1].parse() { if let Ok(n) = cap[1].parse() {
hash.insert(n); set.insert(n);
} }
} }
hash
} }
fn strip_html_inside_mathjax(text: &str) -> Cow<str> { fn strip_html_inside_mathjax(text: &str) -> Cow<str> {

View file

@ -219,7 +219,24 @@ mod test {
1 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::<Vec<_>>();
ords.sort();
assert_eq!(ords, vec![0, 1, 2, 499]);
Ok(()) Ok(())
} }
} }

View file

@ -4,10 +4,12 @@
use super::NoteType; use super::NoteType;
use crate::{ use crate::{
card::{Card, CardID}, card::{Card, CardID},
cloze::add_cloze_numbers_in_string,
collection::Collection, collection::Collection,
decks::DeckID, decks::DeckID,
err::Result, err::Result,
notes::{Note, NoteID}, notes::{Note, NoteID},
notetype::NoteTypeKind,
template::ParsedTemplate, template::ParsedTemplate,
types::Usn, types::Usn,
}; };
@ -15,6 +17,7 @@ use itertools::Itertools;
use std::collections::HashSet; use std::collections::HashSet;
/// Info about an existing card required when generating new cards /// Info about an existing card required when generating new cards
#[derive(Debug, PartialEq)]
pub(crate) struct AlreadyGeneratedCardInfo { pub(crate) struct AlreadyGeneratedCardInfo {
pub id: CardID, pub id: CardID,
pub nid: NoteID, pub nid: NoteID,
@ -82,16 +85,20 @@ impl CardGenContext<'_> {
note: &Note, note: &Note,
existing: &[AlreadyGeneratedCardInfo], existing: &[AlreadyGeneratedCardInfo],
) -> Vec<CardToGenerate> { ) -> Vec<CardToGenerate> {
let nonempty_fields = note.nonempty_fields(&self.notetype.fields);
let extracted = extract_data_from_existing_cards(existing); 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, &self,
nonempty_fields: &HashSet<&str>, note: &Note,
extracted: &ExtractedCardInfo, extracted: &ExtractedCardInfo,
) -> Vec<CardToGenerate> { ) -> Vec<CardToGenerate> {
let nonempty_fields = note.nonempty_fields(&self.notetype.fields);
self.cards self.cards
.iter() .iter()
.enumerate() .enumerate()
@ -110,6 +117,43 @@ impl CardGenContext<'_> {
}) })
.collect() .collect()
} }
fn new_cards_required_cloze(
&self,
note: &Note,
extracted: &ExtractedCardInfo,
) -> Vec<CardToGenerate> {
// 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 // 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 existing_cards = self.storage.existing_cards_for_notetype(ctx.notetype.id)?;
let by_note = group_generated_cards_by_note(existing_cards); let by_note = group_generated_cards_by_note(existing_cards);
for (nid, existing_cards) in by_note { for (nid, existing_cards) in by_note {
if existing_cards.len() == ctx.notetype.templates.len() { if ctx.notetype.config.kind() == NoteTypeKind::Normal
// nothing to do && 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; continue;
} }
let note = self.storage.get_note(nid)?.unwrap(); let note = self.storage.get_note(nid)?.unwrap();
@ -208,4 +255,4 @@ impl Collection {
} }
// fixme: deal with case where invalid deck pointed to // 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