diff options
| -rw-r--r-- | crates/cli/src/words.rs | 273 | ||||
| -rw-r--r-- | lib/mihi/src/lib.rs | 235 |
2 files changed, 305 insertions, 203 deletions
diff --git a/crates/cli/src/words.rs b/crates/cli/src/words.rs index 40f3f26..953760a 100644 --- a/crates/cli/src/words.rs +++ b/crates/cli/src/words.rs @@ -4,7 +4,7 @@ use crate::locale::current_locale; use inquire::{Confirm, Editor, MultiSelect, Select, Text}; use mihi::{ adverb, comparative, joint_related_words, select_related_words, select_tags_for, superlative, - Category, Gender, Language, RelationKind, Word, + Category, Conjugation, Declension, Gender, Language, RelationKind, Word, }; use std::vec::IntoIter; @@ -98,7 +98,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 1].to_string(), Category::Noun, - Some(1), + Some(Declension::First), None, Gender::Feminine, "a".to_string(), @@ -107,7 +107,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 2].to_string(), Category::Noun, - Some(2), + Some(Declension::Second), None, Gender::Masculine, "us".to_string(), @@ -116,7 +116,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 2].to_string(), Category::Noun, - Some(2), + Some(Declension::Second), None, Gender::Neuter, "um".to_string(), @@ -125,7 +125,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 2].to_string(), Category::Noun, - Some(4), + Some(Declension::Fourth), None, Gender::Masculine, "fus".to_string(), @@ -134,7 +134,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 1].to_string(), Category::Noun, - Some(4), + Some(Declension::Fourth), None, Gender::Masculine, "fus".to_string(), @@ -143,7 +143,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 3].to_string(), Category::Noun, - Some(5), + Some(Declension::Fifth), None, Gender::Masculine, "ies".to_string(), @@ -152,7 +152,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( first[0..first.len() - 2].to_string(), Category::Noun, - Some(5), + Some(Declension::Fifth), None, Gender::Masculine, "es".to_string(), @@ -161,7 +161,7 @@ fn get_initial_guess(value: &str) -> Word { return Word::from( second[0..second.len() - 2].to_string(), Category::Noun, - Some(3), + Some(Declension::Third), None, Gender::Masculine, "is".to_string(), @@ -207,6 +207,62 @@ fn get_translated<'a>(word: &'a Word, key: &'a str) -> Result<&'a String, String } } +fn prompt_declension(cat: &Category, declension: Declension) -> Result<Declension, String> { + let options; + let idx; + + match cat { + Category::Noun => { + options = vec![ + Declension::First, + Declension::Second, + Declension::Third, + Declension::Fourth, + Declension::Fifth, + ]; + idx = declension as usize - 1; + } + Category::Adjective => { + options = vec![Declension::First, Declension::Third]; + idx = if matches!(declension, Declension::Third) { + 1 + } else { + 0 + }; + } + _ => panic!("bad parameter"), + } + + let Ok(result) = Select::new("Declension:", options) + .with_starting_cursor(idx) + .prompt() + else { + return Err("abort!".to_string()); + }; + + Ok(result) +} + +fn prompt_conjugation(conjugation: Conjugation) -> Result<Conjugation, String> { + let options = vec![ + Conjugation::First, + Conjugation::Second, + Conjugation::Third, + Conjugation::ThirdIo, + Conjugation::Fourth, + ]; + let idx = conjugation as usize - 1; + + let Ok(result) = Select::new("Conjugation:", options) + .with_starting_cursor(idx) + .prompt() + else { + return Err("abort!".to_string()); + }; + + Ok(result) +} + // Interactively ask the user to provide information for a word by the given // `enunciated`. The default values will be based on the given `word` parameter. fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String> { @@ -257,29 +313,68 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String> _ => Gender::None, }; - let inflection_id = match category { - Category::Noun | Category::Adjective | Category::Verb => { - let Ok(inflection) = Text::new("Inflection:") - .with_initial_value(word.inflection_id().unwrap_or(0).to_string().as_str()) - .prompt() - else { - return Err("abort!".to_string()); - }; - let Ok(inflection_id) = inflection.parse::<isize>() else { - return Err(format!("bad value for inflection ID '{inflection}'")); - }; - Some(inflection_id) + let declension; + let conjugation; + match category { + Category::Noun | Category::Adjective => { + declension = Some(prompt_declension( + &category, + word.declension.clone().unwrap_or(Declension::First), + )?); + conjugation = None; } - _ => None, - }; + Category::Verb => { + declension = None; + conjugation = Some(prompt_conjugation( + word.conjugation.clone().unwrap_or(Conjugation::First), + )?); + } + _ => { + declension = None; + conjugation = None; + } + } - // TODO: refine guess once the inflection is known: select from possible values. let kind = match category { - Category::Noun | Category::Adjective => { - let Ok(kind) = Text::new("Kind:").with_initial_value(&word.kind).prompt() else { - return Err("abort!".to_string()); + Category::Noun => { + let options = match declension { + Some(Declension::First) => vec!["a"], + Some(Declension::Second) => vec!["us", "um", "ius", "er/ir"], + Some(Declension::Third) => vec![ + "is", + "istem", + "pureistem", + "one", + "onenonistem", + "two", + "three", + "visvis", + "sussuis", + "bosbovis", + "iuppiteriovis", + ], + Some(Declension::Fourth) => vec!["fus"], + Some(Declension::Fifth) => vec!["ies", "es"], + _ => panic!("shouldn't be here :D"), }; - kind.trim().to_string() + if options.len() == 1 { + options.first().unwrap().to_string() + } else { + match Select::new("Kind:", options).prompt() { + Ok(kind) => kind.to_string(), + Err(_) => return Err("abort!".to_string()), + } + } + } + Category::Adjective => { + let options = match declension { + Some(Declension::First) => vec!["us", "er/ir"], + _ => vec!["one", "onenonistem", "two", "three"], + }; + match Select::new("Kind:", options).prompt() { + Ok(kind) => kind.to_string(), + Err(_) => return Err("abort!".to_string()), + } } Category::Verb => String::from("verb"), _ => String::from("-"), @@ -316,10 +411,7 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String> return Err("abort!".to_string()); }; let Ok(weight) = raw_weight.parse::<isize>() else { - return Err(format!( - "bad value for inflection ID '{}'", - inflection_id.unwrap_or(0) - )); + return Err("bad value".to_string()); }; if weight > 10 { return Err(format!( @@ -355,13 +447,13 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String> enunciated, particle, language: Language::Latin, - declension_id: if matches!(category, Category::Verb) { + declension: if matches!(category, Category::Verb) { None } else { - inflection_id + declension }, - conjugation_id: if matches!(category, Category::Verb) { - inflection_id + conjugation: if matches!(category, Category::Verb) { + conjugation } else { None }, @@ -762,50 +854,52 @@ fn edit(mut args: IntoIter<String>) -> i32 { } } -// Returns a string with a more human-readable declension kind. -fn humanize_kind(kind: &str) -> &str { +// Returns a string with a more human-readable declension kind. If the kind is +// self-explanatory, then None is returned (e.g. "a" is the only kind for the +// first declension, so it's redundant). +fn humanize_kind(kind: &str) -> Option<&str> { match kind { // Noun - "a" => "-a", - "us" => "-us", - "er/ir" => "-er/-ir", - "um" => "-um", - "ius" => "-ius; like 'fīlius'", - "is" => "-is", - "istem" => "i-stem; '-i-' also in the genitive plural", - "pureistem" => "pure i-stem; '-i-' also in the ablative singular", - "visvis" => "irregular 'vīs, vīs'", - "sussuis" => "irregular 'sūs, suis'", - "bosbovis" => "irregular 'bōs, bovis'", - "iuppiteriovis" => "irregular 'Iuppiter, Iovis'", - "fus" => "-u-", - "domusdomus" => "irregular 'domus, domūs/domī'", - "ies" => "-iēs; like 'diēs, diēī'", - "es" => "-ēs; like 'rēs, reī'", - "indeclinable" => "indeclinable", + "a" => None, + "us" => Some("regular -us"), + "er/ir" => Some("-er/-ir"), + "um" => Some("neuter -um"), + "ius" => Some("-ius; like 'fīlius'"), + "is" => Some("regular -is"), + "istem" => Some("i-stem; '-i-' also in the genitive plural"), + "pureistem" => Some("pure i-stem; '-i-' also in the ablative singular"), + "visvis" => Some("irregular 'vīs, vīs'"), + "sussuis" => Some("irregular 'sūs, suis'"), + "bosbovis" => Some("irregular 'bōs, bovis'"), + "iuppiteriovis" => Some("irregular 'Iuppiter, Iovis'"), + "fus" => None, + "domusdomus" => Some("irregular 'domus, domūs/domī'"), + "ies" => Some("-iēs; like 'diēs, diēī'"), + "es" => Some("-ēs; like 'rēs, reī'"), + "indeclinable" => Some("indeclinable"), // Adjective - "one" => "one termination adjective", - "onenonistem" => "one termination adjective; non i-stem like 'melior, melius'", - "two" => "two termination adjective", - "three" => "three termination adjective", - "unusnauta" => "'ūnus nauta' like 'ūnus, ūna, ūnum'", - "unusnautaer/ir" => "'ūnus nauta' like 'neuter, neutra, neutrum'", - "duo" => "number 'duo, duae, duo'", - "tres" => "number 'trēs, trēs, tria'", - "mille" => "number 'mīlle, mīlle'", + "one" => Some("one termination adjective"), + "onenonistem" => Some("one termination adjective; non i-stem like 'melior, melius'"), + "two" => Some("two termination adjective"), + "three" => Some("three termination adjective"), + "unusnauta" => Some("'ūnus nauta' like 'ūnus, ūna, ūnum'"), + "unusnautaer/ir" => Some("'ūnus nauta' like 'neuter, neutra, neutrum'"), + "duo" => Some("number 'duo, duae, duo'"), + "tres" => Some("number 'trēs, trēs, tria'"), + "mille" => Some("number 'mīlle, mīlle'"), // Others - "egonos" => "'ego, nōs'", - "demonstrative-weak" => "weak demonstrative", - "demonstrative-proximal" => "proximal demonstrative", - "demonstrative-distal" => "distal demonstrative", - "demonstrative-medial" => "medial demonstrative", - "demonstrative-idem" => "'īdem, eadem, idem' demonstrative", - "tuvos" => "'tū, vōs'", - "sesui" => "'sē, suī'", - - _ => kind, + "egonos" => Some("'ego, nōs'"), + "demonstrative-weak" => Some("weak demonstrative"), + "demonstrative-proximal" => Some("proximal demonstrative"), + "demonstrative-distal" => Some("distal demonstrative"), + "demonstrative-medial" => Some("medial demonstrative"), + "demonstrative-idem" => Some("'īdem, eadem, idem' demonstrative"), + "tuvos" => Some("'tū, vōs'"), + "sesui" => Some("'sē, suī'"), + + _ => Some(kind), } } @@ -868,35 +962,22 @@ fn title_for_word(word: &Word) -> String { format!("{}; {})", s, flags) } -fn humanize_conjugation(id: isize) -> String { - match id { - 1 => "1st (ā-stems)", - 2 => "2nd (ē-stems)", - 3 => "3rd (ĕ-stems)", - 4 => "3rd (-iō variants)", - 5 => "4th (ī-stems)", - _ => panic!("bad conjugation id {id}"), - } - .to_string() -} - fn show_info(word: Word) -> Result<(), String> { // Title. println!("Word: {}", title_for_word(&word)); // Conjugation, declension + kind. - match word.conjugation_id { - Some(id) => println!("Conjugation: {}", humanize_conjugation(id)), + match word.conjugation { + Some(ref conjugation) => println!("Conjugation: {}", conjugation), None => { - if let Some(did) = word.declension_id { - if did > 5 { - println!("Declension: {}", humanize_kind(&word.kind)); + if let Some(ref d) = word.declension { + if matches!(d, Declension::Other) { + println!("Declension: {}.", humanize_kind(&word.kind).unwrap_or("-")); } else { - println!( - "Declension: {} ({})", - word.declension_id.unwrap(), - humanize_kind(&word.kind) - ); + match humanize_kind(&word.kind) { + Some(k) => println!("Declension: {}; kind: {}.", d, k), + None => println!("Declension: {}", d), + } } } } diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs index 237fe84..11d269a 100644 --- a/lib/mihi/src/lib.rs +++ b/lib/mihi/src/lib.rs @@ -6,6 +6,8 @@ use std::io::prelude::*; use std::io::{self, BufRead, BufReader, Error}; use std::path::{Path, PathBuf}; +use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; +use rusqlite::Result; use rusqlite::{params, Connection}; mod migrate; @@ -192,6 +194,98 @@ impl std::fmt::Display for Gender { } } +/// Identifies the declension for a given word, and it allows to do SQL to/from +/// conversions. +#[derive(Clone, Debug)] +pub enum Declension { + First = 1, + Second, + Third, + Fourth, + Fifth, + Other, +} + +impl ToSql for Declension { + fn to_sql(&self) -> Result<ToSqlOutput<'_>> { + Ok(ToSqlOutput::from(self.clone() as isize)) + } +} + +impl FromSql for Declension { + fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { + let val = value.as_i64().unwrap_or(0); + + match val { + 1 => Ok(Declension::First), + 2 => Ok(Declension::Second), + 3 => Ok(Declension::Third), + 4 => Ok(Declension::Fourth), + 5 => Ok(Declension::Fifth), + _ => Ok(Declension::Other), + } + } +} + +impl std::fmt::Display for Declension { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Declension::First => write!(f, "1st (-ae)"), + Declension::Second => write!(f, "2nd (-ī)"), + Declension::Third => write!(f, "3rd (-is)"), + Declension::Fourth => write!(f, "4th (-ūs)"), + Declension::Fifth => write!(f, "5th (-eī/-ēī)"), + Declension::Other => write!(f, "other"), + } + } +} + +/// Identifies the conjugation for a given verb, and it allows to do SQL to/from +/// conversions. +#[derive(Clone, Debug)] +pub enum Conjugation { + First = 1, + Second, + Third, + ThirdIo, + Fourth, + Other, +} + +impl ToSql for Conjugation { + fn to_sql(&self) -> Result<ToSqlOutput<'_>> { + Ok(ToSqlOutput::from(self.clone() as isize)) + } +} + +impl FromSql for Conjugation { + fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { + let val = value.as_i64().unwrap_or(0); + + match val { + 1 => Ok(Conjugation::First), + 2 => Ok(Conjugation::Second), + 3 => Ok(Conjugation::Third), + 4 => Ok(Conjugation::ThirdIo), + 5 => Ok(Conjugation::Fourth), + _ => Ok(Conjugation::Other), + } + } +} + +impl std::fmt::Display for Conjugation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Conjugation::First => write!(f, "1st (ā stems)"), + Conjugation::Second => write!(f, "2nd (ē stems)"), + Conjugation::Third => write!(f, "3rd (ĕ stems)"), + Conjugation::ThirdIo => write!(f, "3rd (-iō variants)"), + Conjugation::Fourth => write!(f, "4th (ī stems)"), + Conjugation::Other => write!(f, "other"), + } + } +} + #[derive(Clone, Debug, Default)] pub enum Language { #[default] @@ -380,9 +474,9 @@ pub fn adverb(word: &Word, related: &[Word]) -> String { } let part = word.real_particle(); - match word.declension_id { - Some(1 | 2) => format!("{part}ē"), - Some(3) => format!("{part}iter"), + match word.declension { + Some(Declension::First | Declension::Second) => format!("{part}ē"), + Some(Declension::Third) => format!("{part}iter"), _ => "<unknown>".to_string(), } } @@ -406,8 +500,8 @@ pub struct Word { pub enunciated: String, pub particle: String, pub language: Language, - pub declension_id: Option<isize>, - pub conjugation_id: Option<isize>, + pub declension: Option<Declension>, + pub conjugation: Option<Conjugation>, pub kind: String, pub category: Category, pub regular: bool, @@ -425,8 +519,8 @@ impl Word { pub fn from( particle: String, category: Category, - declension_id: Option<isize>, - conjugation_id: Option<isize>, + declension: Option<Declension>, + conjugation: Option<Conjugation>, gender: Gender, kind: String, ) -> Word { @@ -435,8 +529,8 @@ impl Word { enunciated: "".to_string(), particle, category, - declension_id, - conjugation_id, + declension, + conjugation, kind, regular: true, locative: false, @@ -451,13 +545,6 @@ impl Word { } } - pub fn inflection_id(&self) -> Option<isize> { - if matches!(self.category, Category::Verb) { - return Some(self.conjugation_id.unwrap()); - } - self.declension_id - } - /// Returns whether the given flag is set to true on this word. pub fn is_flag_set(&self, flag: &str) -> bool { match self.flags.get(flag) { @@ -488,43 +575,6 @@ impl Word { } } -const DECLENSIONS_WITH_KINDS: &[&[&str]] = &[ - &["a"], - &["us", "um", "ius", "er/ir"], - &[ - "is", - "istem", - "pureistem", - "one", - "onenonistem", - "two", - "three", - "visvis", - "sussuis", - "bosbovis", - "iuppiteriovis", - ], - &["fus", "domusdomus"], - &["ies", "es"], - &["indeclinable"], -]; - -const ADJECTIVE_KINDS: &[&[&str]] = &[ - &["us", "er/ir"], - &[], - &[ - "one", - "onenonistem", - "two", - "three", - "unusnauta", - "unusnautaer/ir", - "duo", - "tres", - "mille", - ], -]; - /// List of boolean flags supported for words. pub const BOOLEAN_FLAGS: &[&str] = &[ "deponent", @@ -560,55 +610,26 @@ pub fn is_valid_word_flag(flag: &str) -> bool { /// Creates the given word into the database and returns its ID on success. pub fn create_word(word: Word) -> Result<i64, String> { match word.category { - Category::Noun => match word.declension_id { - Some(id @ 1..7) => { - if !DECLENSIONS_WITH_KINDS[id as usize - 1].contains(&word.kind.as_str()) { - return Err(format!("bad kind for declension '{id}'")); - } - } - Some(val) => return Err(format!("the declension ID '{val}' is not valid for nouns")), - None => { + Category::Noun | Category::Adjective => { + if word.declension.is_none() { return Err(String::from( - "you have to provide the declension ID for this noun", - )) + "you have to provide the declension for this verb", + )); } - }, - Category::Adjective => match word.declension_id { - Some(id @ (1 | 3)) => { - if !ADJECTIVE_KINDS[id as usize - 1].contains(&word.kind.as_str()) { - return Err(format!("bad kind for declension '{id}'")); - } - } - Some(val) => { - return Err(format!( - "the declension ID '{val}' is not valid for adjectives" - )) - } - None => { - return Err(String::from( - "you have to provide the declension ID for this adjective", - )) - } - }, - Category::Verb => match word.conjugation_id { - Some(1..6) => { - if word.kind.as_str() != "verb" { - return Err("bad kind for verb".to_string()); - } - } - Some(val) => return Err(format!("the conjugation ID '{val}' is not valid")), - None => { + } + Category::Verb => { + if word.conjugation.is_none() { return Err(String::from( - "you have to provide the conjugation ID for this verb", - )) + "you have to provide the conjugation for this verb", + )); } - }, + } Category::Adverb | Category::Preposition | Category::Conjunction | Category::Interjection | Category::Determiner => { - if word.declension_id.is_some() || word.conjugation_id.is_some() { + if word.declension.is_some() || word.conjugation.is_some() { return Err(format!("no inflection allowed for '{}'", word.category)); } } @@ -632,8 +653,8 @@ pub fn create_word(word: Word) -> Result<i64, String> { word.enunciated.trim(), word.particle.trim(), word.language as isize, - word.declension_id, - word.conjugation_id, + word.declension, + word.conjugation, word.kind.trim(), word.category as isize, word.regular, @@ -671,8 +692,8 @@ pub fn update_word(word: Word) -> Result<(), String> { word.id, word.enunciated, word.particle, - word.declension_id, - word.conjugation_id, + word.declension, + word.conjugation, word.kind, word.category as isize, word.regular, @@ -803,8 +824,8 @@ pub fn select_related_words(word: &Word) -> Result<[Vec<Word>; 5], String> { enunciated: row.get(1).unwrap(), particle: row.get(2).unwrap(), language: row.get::<usize, isize>(3).unwrap().try_into()?, - declension_id: row.get(4).unwrap(), - conjugation_id: row.get(5).unwrap(), + declension: row.get(4).unwrap(), + conjugation: row.get(5).unwrap(), kind: row.get(6).unwrap(), category: row.get::<usize, isize>(7).unwrap().try_into()?, regular: row.get(8).unwrap(), @@ -843,8 +864,8 @@ pub fn find_by(enunciated: &str) -> Result<Word, String> { enunciated: row.get(1).unwrap(), particle: row.get(2).unwrap(), language: row.get::<usize, isize>(3).unwrap().try_into()?, - declension_id: row.get(4).unwrap(), - conjugation_id: row.get(5).unwrap(), + declension: row.get(4).unwrap(), + conjugation: row.get(5).unwrap(), kind: row.get(6).unwrap(), category: row.get::<usize, isize>(7).unwrap().try_into()?, regular: row.get(8).unwrap(), @@ -933,8 +954,8 @@ pub fn select_relevant_words( enunciated: row.get(1).unwrap(), particle: row.get(2).unwrap(), language: row.get::<usize, isize>(3).unwrap().try_into()?, - declension_id: row.get(4).unwrap(), - conjugation_id: row.get(5).unwrap(), + declension: row.get(4).unwrap(), + conjugation: row.get(5).unwrap(), kind: row.get(6).unwrap(), category: row.get::<usize, isize>(7).unwrap().try_into()?, regular: row.get(8).unwrap(), @@ -1020,8 +1041,8 @@ pub fn select_words_except( enunciated: row.get(1).unwrap(), particle: row.get(2).unwrap(), language: row.get::<usize, isize>(3).unwrap().try_into()?, - declension_id: row.get(4).unwrap(), - conjugation_id: row.get(5).unwrap(), + declension: row.get(4).unwrap(), + conjugation: row.get(5).unwrap(), kind: row.get(6).unwrap(), category: row.get::<usize, isize>(7).unwrap().try_into()?, regular: row.get(8).unwrap(), @@ -1717,8 +1738,8 @@ pub fn get_adjective_table(word: &Word) -> Result<[DeclensionTable; 3], String> let kind_f = if word.kind.as_str() == "unusnauta" { &word.kind } else { - match word.declension_id { - Some(1 | 2) => &"a".to_string(), + match word.declension { + Some(Declension::First | Declension::Second) => &"a".to_string(), _ => &word.kind, } }; |
