diff options
| -rw-r--r-- | crates/cli/src/run.rs | 60 | ||||
| -rw-r--r-- | lib/mihi/src/lib.rs | 71 |
2 files changed, 110 insertions, 21 deletions
diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs index 4e6e707..0046d47 100644 --- a/crates/cli/src/run.rs +++ b/crates/cli/src/run.rs @@ -1,5 +1,5 @@ use inquire::{Confirm, Editor, Text}; -use mihi::{select_random_words, update_success, Category, Exercise, ExerciseKind, Word}; +use mihi::{select_relevant_words, update_success, Category, Exercise, ExerciseKind, Word}; use std::env; use std::fs; use std::io::Write; @@ -21,6 +21,7 @@ fn help(msg: Option<&str>) { println!("Options:"); println!(" -c, --category <CATEGORY>\tOnly ask for words on the given <CATEGORY>."); println!(" -e, --exercises\t\tOnly practice with exercises."); + println!(" -f, --flag\t\tFilter words by a boolean flag. Multiple flags can be provided."); println!(" -h, --help\t\t\tPrint this message."); println!(" -k, --kind <KIND>\t\tOnly ask for exercises for the given <KIND>."); } @@ -95,14 +96,14 @@ fn run_words(words: Vec<Word>, locale: &Locale) -> i32 { // Returns a vector of words which contain a randomized set of words from // different categories. -fn select_general_words() -> Result<Vec<Word>, String> { - let mut res = select_random_words(Category::Noun, 4)?; - res.append(&mut select_random_words(Category::Adjective, 2)?); - res.append(&mut select_random_words(Category::Verb, 4)?); - res.append(&mut select_random_words(Category::Pronoun, 1)?); - res.append(&mut select_random_words(Category::Adverb, 2)?); - res.append(&mut select_random_words(Category::Preposition, 1)?); - res.append(&mut select_random_words(Category::Conjunction, 1)?); +fn select_general_words(flags: &Vec<String>) -> Result<Vec<Word>, String> { + let mut res = select_relevant_words(Category::Noun, flags, 4)?; + res.append(&mut select_relevant_words(Category::Adjective, flags, 2)?); + res.append(&mut select_relevant_words(Category::Verb, flags, 4)?); + res.append(&mut select_relevant_words(Category::Pronoun, flags, 1)?); + res.append(&mut select_relevant_words(Category::Adverb, flags, 2)?); + res.append(&mut select_relevant_words(Category::Preposition, flags, 1)?); + res.append(&mut select_relevant_words(Category::Conjunction, flags, 1)?); Ok(res) } @@ -143,7 +144,9 @@ fn is_executable(bin: &str) -> bool { // Returns the string for the name of the command that should be used to show // diffs. fn diff_tool() -> Option<&'static str> { - ["difft", "vimdiff", "diff"].into_iter().find(|&cmd| is_executable(cmd)) + ["difft", "vimdiff", "diff"] + .into_iter() + .find(|&cmd| is_executable(cmd)) } // Perform a diff with the `given` and the `expected` answers for an exercise @@ -225,6 +228,7 @@ pub fn run(args: Vec<String>) { let mut category = None; let mut kind: Option<ExerciseKind> = None; let mut exercises_only = false; + let mut flags: Vec<String> = vec![]; while let Some(first) = it.next() { match first.as_str() { @@ -262,6 +266,34 @@ pub fn run(args: Vec<String>) { "-e" | "--exercises" => { exercises_only = true; } + "-f" | "--flag" => match it.next() { + Some(flag) => { + if mihi::is_valid_word_flag(flag.as_str()) { + if flags.iter().any(|s| s.as_str() == flag) { + println!( + "warning: practice: flag '{flag}' was provided multiple times" + ); + } else { + flags.push(flag); + } + } else { + let supported = mihi::BOOLEAN_FLAGS.join(", "); + help(Some( + format!( + "error: practice: unknown flag value '{flag}'. You have to pick between: {supported}" + ) + .as_str(), + )); + std::process::exit(1); + } + } + None => { + help(Some( + "error: practice: you have to provide a value for the flag", + )); + std::process::exit(1); + } + }, "-k" | "--kind" => { if kind.is_some() { help(Some( @@ -273,9 +305,7 @@ pub fn run(args: Vec<String>) { Some(k) => { kind = match k.trim().to_lowercase().as_str().try_into() { Ok(k) => Some(k), - Err(e) => { - return help(Some(format!("error: practice: {e}").as_str())) - } + Err(e) => return help(Some(format!("error: practice: {e}").as_str())), }; } None => { @@ -301,8 +331,8 @@ pub fn run(args: Vec<String>) { }; let words = match category { - Some(cat) => select_random_words(cat, 15), - None => select_general_words(), + Some(cat) => select_relevant_words(cat, &flags, 15), + None => select_general_words(&flags), }; let exercises = match mihi::select_relevant_exercises(kind, if exercises_only { 5 } else { 1 }) diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs index 7d13226..439e92e 100644 --- a/lib/mihi/src/lib.rs +++ b/lib/mihi/src/lib.rs @@ -282,6 +282,38 @@ const ADJECTIVE_KINDS: &[&[&str]] = &[ ], ]; +/// List of boolean flags supported for words. +pub const BOOLEAN_FLAGS: &[&str] = &[ + "deponent", + "onlysingular", + "onlyplural", + "contracted_root", + "nonpositive", + "compsup_prefix", + "indeclinable", + "irregularsup", + "nopassive", + "nosupine", + "noperfect", + "nogerundive", + "impersonal", + "impersonalpassive", + "noimperative", + "noinfinitive", + "shortimperative", + "onlythirdpassive", + "enclitic", + "notcomparable", + "onlyperfect", + "semideponent", + "contracted_vocative", +]; + +/// Returns true if the given flag is supported by this application. +pub fn is_valid_word_flag(flag: &str) -> bool { + BOOLEAN_FLAGS.contains(&flag) +} + /// Creates the given word into the database. pub fn create_word(word: Word) -> Result<(), String> { match word.category { @@ -458,17 +490,44 @@ pub fn find_by(enunciated: &str) -> Result<Word, String> { } } -pub fn select_random_words(category: Category, number: usize) -> Result<Vec<Word>, String> { +// Builds up a chain of OR clauses that check whether either of the given +// `flags` are set for a row. If no flags are given, then an empty string is +// returned. Otherwise the string is prepended by an "AND" clause, meaning that +// it expects the caller to have other clauses before this one. +fn flags_clause(flags: &Vec<String>) -> String { + if flags.is_empty() { + return "".to_string(); + } + + let mut clauses: Vec<String> = vec![]; + for flag in flags { + clauses.push(format!("json_extract(flags, '$.{flag}') = 1")); + } + + "AND (".to_owned() + &clauses.join(" OR ") + ")" +} + +// Select a maximum of `number` words which match a given word `category` and +// have set one of the given boolean `flags`. +pub fn select_relevant_words( + category: Category, + flags: &Vec<String>, + number: usize, +) -> Result<Vec<Word>, String> { let conn = get_connection()?; let mut stmt = conn .prepare( - "SELECT id, enunciated, particle, language_id, declension_id, conjugation_id, \ + format!( + "SELECT id, enunciated, particle, language_id, declension_id, conjugation_id, \ kind, category, regular, locative, gender, suffix, translation, \ succeeded, steps, flags, weight \ - FROM words \ - WHERE category = ?1 AND translation != '{}' \ - ORDER BY weight DESC, succeeded ASC, updated_at DESC - LIMIT ?2", + FROM words \ + WHERE category = ?1 AND translation != '{{}}' {} \ + ORDER BY weight DESC, succeeded ASC, updated_at DESC + LIMIT ?2", + flags_clause(flags) + ) + .as_str(), ) .unwrap(); let mut it = stmt.query([category as usize, number]).unwrap(); |
