mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
cloze generation
This commit is contained in:
parent
83bcb084fe
commit
15ff279a96
3 changed files with 81 additions and 12 deletions
|
@ -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> {
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue