aboutsummaryrefslogtreecommitdiff
path: root/crates/cli
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-01-28 21:42:00 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-01-28 21:42:00 +0100
commit765b569282c0494b2c7bac54d895a63b0577072a (patch)
tree84aa78943136ed2ef41f92d4b9161c60ae640ef5 /crates/cli
parent3aaddf6759e6865d4a9ca3dd24f1ef7d2906a9d1 (diff)
downloadmihi-765b569282c0494b2c7bac54d895a63b0577072a.tar.gz
mihi-765b569282c0494b2c7bac54d895a63b0577072a.zip
Move declension/conjugation IDs into enums
There were quite a lot of hacks that came out of my laziness on refusing to give inflections their own proper enums. This commit introduces this, while also providing implementations for the traits involving SQL. This way, moving data in and out from these enums feels more seamless. One advantage from this move is that a lot of redundant checks are gone, as the validation checks are performed when fetching the data. This is one of those times in which type masturbation as encouraged by the Rust programming language actually makes sense. Last but not least, this conversion also allows us to perform a change on the creation/edit of words, as suggestions can be done for conjugations/declensions and their kinds. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'crates/cli')
-rw-r--r--crates/cli/src/words.rs273
1 files changed, 177 insertions, 96 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),
+ }
}
}
}