diff options
Diffstat (limited to 'crates/cli/src')
| -rw-r--r-- | crates/cli/src/inflection.rs | 51 | ||||
| -rw-r--r-- | crates/cli/src/run.rs | 219 |
2 files changed, 203 insertions, 67 deletions
diff --git a/crates/cli/src/inflection.rs b/crates/cli/src/inflection.rs index c8b8574..5db76d4 100644 --- a/crates/cli/src/inflection.rs +++ b/crates/cli/src/inflection.rs @@ -1,30 +1,7 @@ use mihi::{ - configuration, group_declension_inflections, Category, DeclensionInfo, DeclensionTable, Gender, - Word, + configuration, get_adjective_table, get_inflected_from, get_noun_table, Category, Word, }; -fn get_inflected_from(word: &Word, row: &[DeclensionInfo; 2]) -> String { - if word.is_flag_set("onlysingular") { - row[0].inflected.join("/") - } else if word.is_flag_set("onlyplural") { - row[1].inflected.join("/") - } else { - format!( - "{}, {}", - row[0].inflected.join("/"), - row[1].inflected.join("/") - ) - } -} - -fn get_noun_table(word: &Word) -> Result<DeclensionTable, String> { - let gender = match word.gender { - Gender::MasculineOrFeminine => Gender::Masculine as usize, - _ => word.gender as usize, - }; - group_declension_inflections(word, &word.kind, gender) -} - fn print_noun_inflection(word: &Word) -> Result<(), String> { let table = get_noun_table(word)?; @@ -56,31 +33,6 @@ fn print_noun_inflection(word: &Word) -> Result<(), String> { Ok(()) } -fn get_adjective_table(word: &Word) -> Result<[DeclensionTable; 3], String> { - // Unless the word is a special "unus nauta" variant, force 1/2 declension - // adjectives in the feminine to grab the "a" kind. - let kind_f = if word.kind.as_str() == "unusnauta" { - &word.kind - } else { - match word.declension_id { - Some(1 | 2) => &"a".to_string(), - _ => &word.kind, - } - }; - - let kind_n = if word.kind == "us" { - &"um".to_owned() - } else { - &word.kind - }; - - Ok([ - group_declension_inflections(word, &word.kind, Gender::Masculine as usize)?, - group_declension_inflections(word, kind_f, Gender::Feminine as usize)?, - group_declension_inflections(word, kind_n, Gender::Neuter as usize)?, - ]) -} - fn print_adjective_inflection(word: &Word) -> Result<(), String> { let tables = get_adjective_table(word)?; @@ -168,6 +120,7 @@ pub fn print_full_inflection_for(word: Word) -> Result<(), String> { #[cfg(test)] mod tests { use super::*; + use mihi::DeclensionTable; fn get_word(enunciated: &str) -> Word { let words = mihi::select_enunciated(Some(enunciated.to_string())).unwrap(); diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs index bc62770..31f160e 100644 --- a/crates/cli/src/run.rs +++ b/crates/cli/src/run.rs @@ -1,6 +1,9 @@ use inquire::{Confirm, Editor, Text}; -use mihi::touch_exercise; -use mihi::{select_relevant_words, update_success, Category, Exercise, ExerciseKind, Word}; +use mihi::{configuration, get_inflected_from}; +use mihi::{ + get_noun_table, select_relevant_words, touch_exercise, update_success, Category, Exercise, + ExerciseKind, Word, +}; use std::env; use std::fs; use std::io::Write; @@ -26,12 +29,13 @@ fn help(msg: Option<&str>) { println!(" -e, --exercises\t\tOnly practice with exercises."); println!(" -f, --flag\t\t\tFilter words by a boolean flag. Multiple flags can be provided."); println!(" -h, --help\t\t\tPrint this message."); + println!(" -i, --inflection\t\tOnly practice word inflections (completing enunciates, declensions and conjugations."); println!(" -k, --kind <KIND>\t\tOnly ask for exercises for the given <KIND>."); } // Run the quiz for all the given `words` while expecting answers to be // delivered in the given `locale`. -fn run_words(words: Vec<Word>, locale: &Locale) -> bool { +fn run_words(words: &Vec<Word>, locale: &Locale) -> bool { for word in words { // If the translation cannot be found, skip this word. let Some(translation) = word.translation.get(locale.to_code()) else { @@ -50,14 +54,14 @@ fn run_words(words: Vec<Word>, locale: &Locale) -> bool { if found { if word.steps as usize == MAX_STEPS - 1 { - let _ = update_success(&word, word.succeeded + 1, 0); + let _ = update_success(word, word.succeeded + 1, 0); } else { - let _ = update_success(&word, word.succeeded, word.steps + 1); + let _ = update_success(word, word.succeeded, word.steps + 1); } println!("\x1b[92m✓ {tr}\x1b[0m"); } else { if word.succeeded > 0 { - let _ = update_success(&word, word.succeeded - 1, 0); + let _ = update_success(word, word.succeeded - 1, 0); } println!("\x1b[91m❌{tr}\x1b[0m"); } @@ -66,6 +70,173 @@ fn run_words(words: Vec<Word>, locale: &Locale) -> bool { true } +fn fill_out_enunciated(word: &Word) -> String { + match word.category { + Category::Noun | Category::Adjective => { + let en = word.enunciated.split(',').next().unwrap_or(""); + en.to_string() + } + cat => panic!( + "trying to fill out an enunciated from a non-supported category '{}'", + cat + ), + } +} + +// Returns true if both strings are either more or less the same, or the user +// considers it so. +fn same_answer(given: &String, expected: &String) -> bool { + // If it's literally the same string, then return true. + if given == expected { + return true; + } + + // If it's the same string but just with differences in the white spacing, + // return true as well. + let trimmed_given: String = given.chars().filter(|c| !c.is_whitespace()).collect(); + let trimmed_expected: String = expected.chars().filter(|c| !c.is_whitespace()).collect(); + if trimmed_given == trimmed_expected { + return true; + } + + // It's something else, then let the user to decide. + accepted_diff(given, expected) +} + +fn run_inflect_words(words: &Vec<Word>, locale: &Locale) -> bool { + for word in words { + // If the translation cannot be found, skip this word. + let Some(translation) = word.translation.get(locale.to_code()) else { + continue; + }; + + // Enunciate. + println!("Inflect this {}:", word.category); + println!("Translation: {}.", translation); + + // Complete the enunciate. + let Ok(raw) = Text::new("Enunciated:") + .with_initial_value(&fill_out_enunciated(word)) + .prompt() + else { + return false; + }; + let answer = raw.trim(); + + // Check the answer and update the success rate on the database if + // needed. + if same_answer(&answer.to_string(), &word.enunciated) { + if word.steps as usize == MAX_STEPS - 1 { + let _ = update_success(word, word.succeeded + 1, 0); + } else { + let _ = update_success(word, word.succeeded, word.steps + 1); + } + println!("\x1b[92m✓\x1b[0m\n"); + } else { + if word.succeeded > 0 { + let _ = update_success(word, word.succeeded - 1, 0); + } + println!("\x1b[91m❌\x1b[0m\n"); + } + + let mut initial = format!("== {} ==\n\n", word.enunciated); + let mut expected = format!("== {} ==\n\n", word.enunciated); + + if let Ok(table) = get_noun_table(word) { + for idx in configuration().case_order.to_usizes() { + match idx { + 0 => { + initial.push_str("Nominative: \n"); + expected.push_str( + format!( + "Nominative: {}\n", + get_inflected_from(word, &table.nominative) + ) + .as_str(), + ); + } + 1 => { + initial.push_str("Vocative: \n"); + expected.push_str( + format!("Vocative: {}\n", get_inflected_from(word, &table.vocative)) + .as_str(), + ); + } + 2 => { + initial.push_str("Accusative: \n"); + expected.push_str( + format!( + "Accusative: {}\n", + get_inflected_from(word, &table.accusative) + ) + .as_str(), + ); + } + 3 => { + initial.push_str("Genitive: \n"); + expected.push_str( + format!("Genitive: {}\n", get_inflected_from(word, &table.genitive)) + .as_str(), + ); + } + 4 => { + initial.push_str("Dative: \n"); + expected.push_str( + format!("Dative: {}\n", get_inflected_from(word, &table.dative)) + .as_str(), + ); + } + 5 => { + initial.push_str("Ablative: \n"); + expected.push_str( + format!("Ablative: {}\n", get_inflected_from(word, &table.ablative)) + .as_str(), + ); + } + 6 => { + if word.locative { + initial.push_str("Locative: \n"); + expected.push_str( + format!( + "Locative: {}\n", + get_inflected_from(word, &table.locative) + ) + .as_str(), + ); + } + } + _ => {} + } + } + } + + // Inflection time! + let Ok(solution) = Editor::new("Open the editor to inflect:") + .with_predefined_text(initial.as_str()) + .with_file_extension(".md") + .prompt() + else { + return false; + }; + + if same_answer(&solution, &expected) { + if word.steps as usize == MAX_STEPS - 1 { + let _ = update_success(word, word.succeeded + 1, 0); + } else { + let _ = update_success(word, word.succeeded, word.steps + 1); + } + println!("\x1b[92m✓\x1b[0m\n"); + } else { + if word.succeeded > 0 { + let _ = update_success(word, word.succeeded - 1, 0); + } + println!("\x1b[91m❌\x1b[0m\n"); + } + } + + true +} + // Returns a vector of words which contain a randomized set of words from // different categories. fn select_general_words(flags: &Vec<String>) -> Result<Vec<Word>, String> { @@ -125,7 +296,7 @@ fn diff_tool() -> Option<&'static str> { // and interactively ask the user if things are ok. Returns a boolean depending // on the user's answer to that final question, or false if something went // wrong. -fn accepted_diff(given: String, expected: &String) -> bool { +fn accepted_diff(given: &String, expected: &String) -> bool { // If a diff tool could be fetched, then write into temporary files and call // the diff tool against both temporary files; otherwise just print things // out into the stdout. @@ -196,7 +367,7 @@ fn run_exercises(exercises: Vec<Exercise>) -> bool { // If the exercise is seen as correct by the user, then "touch" // (i.e. refresh the 'updated_at' date). This way, next time we select // exercises to show the user, we can prevent this one showing up first. - if accepted_diff(solution, &exercise.solution) { + if accepted_diff(&solution, &exercise.solution) { let _ = touch_exercise(exercise); } } @@ -209,6 +380,7 @@ pub fn run(args: Vec<String>) { let mut category = None; let mut kind: Option<ExerciseKind> = None; let mut exercises_only = false; + let mut inflection_only = false; let mut endless = false; let mut flags: Vec<String> = vec![]; @@ -248,6 +420,9 @@ pub fn run(args: Vec<String>) { "-e" | "--exercises" => { exercises_only = true; } + "-i" | "--inflection" => { + inflection_only = true; + } "--endless" => { endless = true; } @@ -311,23 +486,31 @@ pub fn run(args: Vec<String>) { let locale = current_locale(); loop { + let words = match category { + Some(cat) => select_relevant_words(cat, &flags, 15), + None => select_general_words(&flags), + }; + if !exercises_only { - let words = match category { - Some(cat) => select_relevant_words(cat, &flags, 15), - None => select_general_words(&flags), - }; if let Ok(list) = words { - if !run_words(list, &locale) { + if !inflection_only && !run_words(&list, &locale) { break; } + if let Ok(words_to_inflect) = mihi::select_words_except(&list, &flags) { + if !run_inflect_words(&words_to_inflect, &locale) { + break; + } + } } } - if let Ok(exercises) = - mihi::select_relevant_exercises(kind, if exercises_only { 5 } else { 1 }) - { - if !run_exercises(exercises) { - break; + if !inflection_only { + if let Ok(exercises) = + mihi::select_relevant_exercises(kind, if exercises_only { 5 } else { 1 }) + { + if !run_exercises(exercises) { + break; + } } } |
