aboutsummaryrefslogtreecommitdiff
path: root/crates/cli
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-01-13 15:27:05 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-01-13 15:27:05 +0100
commit43632164dcacb3424ccb9ceba76ddfd7586cb59c (patch)
tree5c8d42f793326fecef757a7e60eb94ad45c13511 /crates/cli
parentbb3bbcfd018816a80077a6c1f96f842d2bf5f3a4 (diff)
parent472d7fb9d37ec838fc69692cf24c8cb4a68af47a (diff)
downloadmihi-43632164dcacb3424ccb9ceba76ddfd7586cb59c.tar.gz
mihi-43632164dcacb3424ccb9ceba76ddfd7586cb59c.zip
Merge branch 'inflection'
Diffstat (limited to 'crates/cli')
-rw-r--r--crates/cli/src/inflection.rs336
-rw-r--r--crates/cli/src/locale.rs38
-rw-r--r--crates/cli/src/main.rs2
-rw-r--r--crates/cli/src/run.rs39
-rw-r--r--crates/cli/src/words.rs216
5 files changed, 569 insertions, 62 deletions
diff --git a/crates/cli/src/inflection.rs b/crates/cli/src/inflection.rs
new file mode 100644
index 0000000..f42d1e8
--- /dev/null
+++ b/crates/cli/src/inflection.rs
@@ -0,0 +1,336 @@
+use mihi::{group_declension_inflections, Category, DeclensionInfo, DeclensionTable, Gender, 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)?;
+
+ println!("\n== Inflection ==\n");
+
+ println!(
+ "Nominative:\t{}",
+ get_inflected_from(&word, &table.nominative)
+ );
+ println!("Vocative:\t{}", get_inflected_from(&word, &table.vocative));
+ println!(
+ "Accusative:\t{}",
+ get_inflected_from(&word, &table.accusative)
+ );
+ println!("Genitive:\t{}", get_inflected_from(&word, &table.genitive));
+ println!("Dative:\t\t{}", get_inflected_from(&word, &table.dative));
+ println!("Ablative:\t{}", get_inflected_from(&word, &table.ablative));
+ if word.locative {
+ println!("Locative:\t{}", get_inflected_from(&word, &table.locative));
+ }
+
+ 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)?;
+
+ println!("\n== Inflection ==\n");
+
+ println!(
+ "Nominative:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].nominative),
+ get_inflected_from(&word, &tables[1].nominative),
+ get_inflected_from(&word, &tables[2].nominative)
+ );
+ println!(
+ "Vocative:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].vocative),
+ get_inflected_from(&word, &tables[1].vocative),
+ get_inflected_from(&word, &tables[2].vocative)
+ );
+ println!(
+ "Accusative:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].accusative),
+ get_inflected_from(&word, &tables[1].accusative),
+ get_inflected_from(&word, &tables[2].accusative)
+ );
+ println!(
+ "Genitive:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].genitive),
+ get_inflected_from(&word, &tables[1].genitive),
+ get_inflected_from(&word, &tables[2].genitive)
+ );
+ println!(
+ "Dative:\t\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].dative),
+ get_inflected_from(&word, &tables[1].dative),
+ get_inflected_from(&word, &tables[2].dative)
+ );
+ println!(
+ "Ablative:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].ablative),
+ get_inflected_from(&word, &tables[1].ablative),
+ get_inflected_from(&word, &tables[2].ablative)
+ );
+ if word.locative {
+ println!(
+ "Locative:\t{} | {} | {}",
+ get_inflected_from(&word, &tables[0].locative),
+ get_inflected_from(&word, &tables[1].locative),
+ get_inflected_from(&word, &tables[2].locative)
+ );
+ }
+
+ Ok(())
+}
+
+pub fn print_full_inflection_for(word: Word) -> Result<(), String> {
+ if word.is_flag_set("indeclinable") {
+ return Ok(());
+ }
+
+ match word.category {
+ Category::Noun => print_noun_inflection(&word)?,
+ Category::Adjective => print_adjective_inflection(&word)?,
+ Category::Verb => {} // TODO
+ Category::Pronoun => {} // TODO
+ Category::Adverb
+ | Category::Preposition
+ | Category::Conjunction
+ | Category::Interjection
+ | Category::Determiner
+ | Category::Unknown => {
+ // Nothing to do.
+ }
+ }
+ // TODO: on the 'extra' info.
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn get_word(enunciated: &str) -> Word {
+ let words = mihi::select_enunciated(Some(enunciated.to_string())).unwrap();
+
+ assert_eq!(words.len(), 1);
+
+ mihi::find_by(words.first().unwrap().as_str()).unwrap()
+ }
+
+ fn stringify_with(word: &Word, table: &DeclensionTable) -> String {
+ let mut res = get_inflected_from(&word, &table.nominative);
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.vocative).as_str());
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.accusative).as_str());
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.genitive).as_str());
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.dative).as_str());
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.ablative).as_str());
+ if word.locative {
+ res.push_str(" | ");
+ res.push_str(get_inflected_from(&word, &table.locative).as_str());
+ }
+
+ res
+ }
+
+ fn assert_noun_table(enunciated: &str, expected: &str) {
+ let word = get_word(enunciated);
+ let table = get_noun_table(&word).unwrap();
+
+ let res = stringify_with(&word, &table);
+
+ assert_eq!(res, expected);
+ }
+
+ fn assert_adjective_table(enunciated: &str, masculine: &str, feminine: &str, neuter: &str) {
+ let word = get_word(enunciated);
+ let tables = get_adjective_table(&word).unwrap();
+
+ let res = stringify_with(&word, &tables[0]);
+ assert_eq!(res, masculine);
+
+ let res = stringify_with(&word, &tables[1]);
+ assert_eq!(res, feminine);
+
+ let res = stringify_with(&word, &tables[2]);
+ assert_eq!(res, neuter);
+ }
+
+ #[test]
+ fn test_nouns() {
+ assert_noun_table(
+ "rosa, rosae",
+ "rosa, rosae | rosa, rosae | rosam, rosās | rosae, rosārum | rosae, rosīs | rosā, rosīs",
+ );
+ assert_noun_table(
+ "fīlia, fīliae",
+ "fīlia, fīliae | fīlia, fīliae | fīliam, fīliās | fīliae, fīliārum | fīliae, fīliīs/fīliābus | fīliā, fīliīs/fīliābus",
+ );
+ assert_noun_table(
+ "dea, deae",
+ "dea, deae | dea, deae | deam, deās | deae, deārum | deae, deābus | deā, deābus",
+ );
+ assert_noun_table(
+ "Rōma, Rōmae",
+ "Rōma | Rōma | Rōmam | Rōmae | Rōmae | Rōmā | Rōmae",
+ );
+ assert_noun_table(
+ "lupus, lupī",
+ "lupus, lupī | lupe, lupī | lupum, lupōs | lupī, lupōrum | lupō, lupīs | lupō, lupīs",
+ );
+ assert_noun_table(
+ "templum, templī",
+ "templum, templa | templum, templa | templum, templa | templī, templōrum | templō, templīs | templō, templīs",
+ );
+ assert_noun_table(
+ "vir, virī",
+ "vir, virī | vir, virī | virum, virōs | virī, virōrum | virō, virīs | virō, virīs",
+ );
+ assert_noun_table(
+ "liber, librī",
+ "liber, librī | liber, librī | librum, librōs | librī, librōrum | librō, librīs | librō, librīs",
+ );
+ assert_noun_table(
+ "fīlius, fīliī",
+ "fīlius, fīliī | fīlī, fīliī | fīlium, fīliōs | fīlī/fīliī, fīliōrum | fīliō, fīliīs | fīliō, fīliīs",
+ );
+ assert_noun_table(
+ "leō, leōnis",
+ "leō, leōnēs | leō, leōnēs | leōnem, leōnēs | leōnis, leōnum | leōnī, leōnibus | leōne, leōnibus",
+ );
+ assert_noun_table(
+ "ovis, ovis",
+ "ovis, ovēs | ovis, ovēs | ovem, ovēs | ovis, ovium | ovī, ovibus | ove, ovibus",
+ );
+ assert_noun_table(
+ "mare, maris",
+ "mare, maria | mare, maria | mare, maria | maris, marium/marum | marī, maribus | marī/mare, maribus",
+ );
+ assert_noun_table(
+ "Iuppiter, Iovis",
+ "Iuppiter | Iuppiter | Iovem | Iovis | Iovī | Iove",
+ );
+ assert_noun_table(
+ "portus, portūs",
+ "portus, portūs | portus, portūs | portum, portūs | portūs, portuum | portuī, portibus | portū, portibus",
+ );
+ assert_noun_table(
+ "cornū, cornūs",
+ "cornū, cornua | cornū, cornua | cornū, cornua | cornūs, cornuum | cornuī, cornibus | cornū, cornibus",
+ );
+ // TODO: domus
+ assert_noun_table(
+ "diēs, diēī",
+ "diēs, diēs | diēs, diēs | diem, diēs | diēī, diērum | diēī, diēbus | diē, diēbus",
+ );
+ assert_noun_table(
+ "rēs, reī",
+ "rēs, rēs | rēs, rēs | rem, rēs | reī, rērum | reī, rēbus | rē, rēbus",
+ );
+ }
+
+ #[test]
+ fn test_adjectives() {
+ assert_adjective_table(
+ "novus, nova, novum",
+ "novus, novī | nove, novī | novum, novōs | novī, novōrum | novō, novīs | novō, novīs",
+ "nova, novae | nova, novae | novam, novās | novae, novārum | novae, novīs | novā, novīs",
+ "novum, nova | novum, nova | novum, nova | novī, novōrum | novō, novīs | novō, novīs",
+ );
+ assert_adjective_table(
+ "pulcher, pulchra, pulchrum",
+ "pulcher, pulchrī | pulcher, pulchrī | pulchrum, pulchrōs | pulchrī, pulchrōrum | pulchrō, pulchrīs | pulchrō, pulchrīs",
+ "pulchra, pulchrae | pulchra, pulchrae | pulchram, pulchrās | pulchrae, pulchrārum | pulchrae, pulchrīs | pulchrā, pulchrīs",
+ "pulcher, pulchra | pulcher, pulchra | pulcher, pulchra | pulchrī, pulchrōrum | pulchrō, pulchrīs | pulchrō, pulchrīs",
+ );
+ assert_adjective_table(
+ "ūnus, ūna, ūnum",
+ "ūnus, ūnī | ūne, ūnī | ūnum, ūnōs | ūnīus, ūnōrum | ūnī, ūnīs | ūnō, ūnīs",
+ "ūna, ūnae | ūna, ūnae | ūnam, ūnās | ūnīus, ūnārum | ūnī, ūnīs | ūnā, ūnīs",
+ "ūnum, ūna | ūnum, ūna | ūnum, ūna | ūnīus, ūnōrum | ūnī, ūnīs | ūnō, ūnīs",
+ );
+ assert_adjective_table(
+ "ferōx, ferōx",
+ "ferōx, ferōcēs | ferōx, ferōcēs | ferōcem, ferōcēs | ferōcis, ferōcium | ferōcī, ferōcibus | ferōcī, ferōcibus",
+ "ferōx, ferōcēs | ferōx, ferōcēs | ferōcem, ferōcēs | ferōcis, ferōcium | ferōcī, ferōcibus | ferōcī, ferōcibus",
+ "ferōx, ferōcia | ferōx, ferōcia | ferōx, ferōcia | ferōcis, ferōcium | ferōcī, ferōcibus | ferōcī, ferōcibus",
+ );
+ assert_adjective_table(
+ "gravis, grave",
+ "gravis, gravēs | gravis, gravēs | gravem/gravīs, gravēs | gravis, gravium | gravī, gravibus | gravī, gravibus",
+ "gravis, gravēs | gravis, gravēs | gravem/gravīs, gravēs | gravis, gravium | gravī, gravibus | gravī, gravibus",
+ "grave, gravia | grave, gravia | grave, gravia | gravis, gravium | gravī, gravibus | gravī, gravibus",
+ );
+ assert_adjective_table(
+ "celer, celeris, celere",
+ "celer, celerēs | celer, celerēs | celerem, celerēs | celeris, celerium | celerī, celeribus | celerī, celeribus",
+ "celeris, celerēs | celeris, celerēs | celerem, celerēs | celeris, celerium | celerī, celeribus | celerī, celeribus",
+ "celere, celeria | celere, celeria | celere, celeria | celeris, celerium | celerī, celeribus | celerī, celeribus"
+ );
+ assert_adjective_table(
+ "duo, duae, duo",
+ "duo | duo | duo/duōs | duōrum | duōbus | duōbus",
+ "duae | duae | duās | duārum | duābus | duābus",
+ "duo | duo | duo | duōrum | duōbus | duōbus",
+ );
+ assert_adjective_table(
+ "trēs, trēs, tria",
+ "trēs | trēs | trēs/trīs | trium | tribus | tribus",
+ "trēs | trēs | trēs/trīs | trium | tribus | tribus",
+ "tria | tria | tria | trium | tribus | tribus",
+ );
+ assert_adjective_table(
+ "mīlle, mīlle",
+ "mīlle, mīlia | mīlle, mīlia | mīlle, mīlia | mīlle, mīlium | mīlle, mīlibus | mīlle, mīlibus",
+ "mīlle, mīlia | mīlle, mīlia | mīlle, mīlia | mīlle, mīlium | mīlle, mīlibus | mīlle, mīlibus",
+ "mīlle, mīlia | mīlle, mīlia | mīlle, mīlia | mīlle, mīlium | mīlle, mīlibus | mīlle, mīlibus"
+ );
+ }
+}
diff --git a/crates/cli/src/locale.rs b/crates/cli/src/locale.rs
new file mode 100644
index 0000000..f9d42b6
--- /dev/null
+++ b/crates/cli/src/locale.rs
@@ -0,0 +1,38 @@
+// Locale represents the locales accepted for delivering answers on this
+// tool. That is, it's not about i18n on the strings for this application. but
+// rather the different translations accepted in places like
+// `Word.translations`.
+pub enum Locale {
+ English,
+ Catalan,
+}
+
+impl Locale {
+ // Returns the string representation for the locale's code.
+ pub fn to_code(&self) -> &str {
+ match self {
+ Self::English => "en",
+ Self::Catalan => "ca",
+ }
+ }
+}
+
+impl std::fmt::Display for Locale {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::English => write!(f, "english"),
+ Self::Catalan => write!(f, "català"),
+ }
+ }
+}
+
+/// Fetches the Locale object that is suitable for the current environment.
+pub fn current_locale() -> Locale {
+ let raw_locale = std::env::var("LC_ALL").unwrap_or("en".to_string());
+
+ if raw_locale.starts_with("ca") {
+ Locale::Catalan
+ } else {
+ Locale::English
+ }
+}
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index ffbcdae..62ffd45 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -1,5 +1,7 @@
mod exercises;
+mod inflection;
mod init;
+mod locale;
mod nuke;
mod run;
mod words;
diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs
index 0046d47..10e0721 100644
--- a/crates/cli/src/run.rs
+++ b/crates/cli/src/run.rs
@@ -6,6 +6,8 @@ use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
+use crate::locale::{current_locale, Locale};
+
// Maximum number of times a word has to be run in order to increase the number
// of successful runs.
const MAX_STEPS: usize = 5;
@@ -21,39 +23,11 @@ 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!(" -f, --flag\t\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>.");
}
-// Locale represents the locales accepted for delivering answers on this
-// tool. That is, it's not about i18n on the strings for this application. but
-// rather the different translations accepted in places like
-// `Word.translations`.
-enum Locale {
- English,
- Catalan,
-}
-
-impl Locale {
- // Returns the string representation for the locale's code.
- fn to_code(&self) -> &str {
- match self {
- Self::English => "en",
- Self::Catalan => "ca",
- }
- }
-}
-
-impl std::fmt::Display for Locale {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- Self::English => write!(f, "english"),
- Self::Catalan => write!(f, "català"),
- }
- }
-}
-
// 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) -> i32 {
@@ -323,12 +297,7 @@ pub fn run(args: Vec<String>) {
}
}
- let raw_locale = std::env::var("LC_ALL").unwrap_or("en".to_string());
- let locale = if raw_locale.starts_with("ca") {
- Locale::Catalan
- } else {
- Locale::English
- };
+ let locale = current_locale();
let words = match category {
Some(cat) => select_relevant_words(cat, &flags, 15),
diff --git a/crates/cli/src/words.rs b/crates/cli/src/words.rs
index 7db84a2..23a3946 100644
--- a/crates/cli/src/words.rs
+++ b/crates/cli/src/words.rs
@@ -1,3 +1,6 @@
+use crate::inflection::print_full_inflection_for;
+use crate::locale::current_locale;
+
use inquire::{Confirm, Editor, Select, Text};
use mihi::{Category, Gender, Language, Word};
use std::vec::IntoIter;
@@ -148,15 +151,22 @@ fn get_initial_guess(value: &str) -> Word {
return Word::from(
second[0..second.len() - 2].to_string(),
Category::Noun,
- Some(5),
+ Some(3),
None,
Gender::Masculine,
- "es".to_string(),
+ "is".to_string(),
);
}
}
- Word::default()
+ Word::from(
+ value.to_string(),
+ Category::Unknown,
+ None,
+ None,
+ Gender::None,
+ String::from("-"),
+ )
}
// Remove comments from the "flags" text that was provided.
@@ -196,6 +206,7 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String>
else {
return Err("abort!".to_string());
};
+ let particle = particle.trim().to_string();
let categories = vec![
Category::Unknown,
@@ -236,28 +247,56 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String>
_ => Gender::None,
};
- let Ok(kind) = Text::new("Kind:").with_initial_value(&word.kind).prompt() else {
- return Err("abort!".to_string());
+ 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::<usize>() else {
+ return Err(format!("bad value for inflection ID '{inflection}'"));
+ };
+ Some(inflection_id)
+ }
+ _ => None,
};
- let Ok(inflection) = Text::new("Inflection:")
- .with_initial_value(word.inflection_id().to_string().as_str())
- .prompt()
- else {
- return Err("abort!".to_string());
- };
- let Ok(inflection_id) = inflection.parse::<usize>() else {
- return Err(format!("bad value for inflection ID '{inflection}'"));
+ // 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());
+ };
+ kind.trim().to_string()
+ }
+ Category::Verb => String::from("verb"),
+ _ => String::from("-"),
};
- let Ok(regular) = Confirm::new("Regular:").with_default(word.regular).prompt() else {
- return Err("abort!".to_string());
+ let regular = if matches!(
+ category,
+ Category::Noun | Category::Adjective | Category::Verb
+ ) {
+ let Ok(regular) = Confirm::new("Regular:").with_default(word.regular).prompt() else {
+ return Err("abort!".to_string());
+ };
+ regular
+ } else {
+ true
};
- let Ok(locative) = Confirm::new("Locative:")
- .with_default(word.locative)
- .prompt()
- else {
- return Err("abort!".to_string());
+
+ let locative = if matches!(category, Category::Noun) {
+ let Ok(locative) = Confirm::new("Locative:")
+ .with_default(word.locative)
+ .prompt()
+ else {
+ return Err("abort!".to_string());
+ };
+ locative
+ } else {
+ false
};
let Ok(raw_weight) = Text::new("Weight:")
@@ -267,7 +306,10 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String>
return Err("abort!".to_string());
};
let Ok(weight) = raw_weight.parse::<usize>() else {
- return Err(format!("bad value for inflection ID '{inflection}'"));
+ return Err(format!(
+ "bad value for inflection ID '{}'",
+ inflection_id.unwrap_or(0)
+ ));
};
if weight > 10 {
return Err(format!(
@@ -306,10 +348,10 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String>
declension_id: if matches!(category, Category::Verb) {
None
} else {
- Some(inflection_id)
+ inflection_id
},
conjugation_id: if matches!(category, Category::Verb) {
- Some(inflection_id)
+ inflection_id
} else {
None
},
@@ -339,7 +381,7 @@ fn ask_for_word_based_on(enunciated: String, word: Word) -> Result<Word, String>
// from it, and insert it into the database.
fn do_create(enunciated: String) -> Result<(), String> {
let mut guess = get_initial_guess(enunciated.as_str());
- guess.enunciated = enunciated.clone();
+ guess.enunciated = enunciated.trim().to_string();
let word = ask_for_word_based_on(enunciated.clone(), guess)?;
match mihi::create_word(word) {
@@ -409,7 +451,6 @@ fn create(args: IntoIter<String>) -> i32 {
}
}
-// TODO: accept a --raw flag, which is implied on pipe
fn ls(mut args: IntoIter<String>) -> i32 {
if args.len() > 1 {
help(Some("error: words: too many filters"));
@@ -509,8 +550,129 @@ fn edit(mut args: IntoIter<String>) -> i32 {
}
}
-fn show(mut _args: IntoIter<String>) -> i32 {
- todo!()
+// Returns a string with a more human-readable declension kind.
+fn humanize_kind(kind: &str) -> &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",
+
+ // 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'",
+
+ // 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,
+ }
+}
+
+fn show_info(word: Word) -> Result<(), String> {
+ // Title.
+ match word.gender {
+ Gender::None => println!("Word: {} ({})", word.enunciated, word.category),
+ _ => println!(
+ "Word: {} ({} {})",
+ word.enunciated,
+ word.gender.abbrev(),
+ word.category
+ ),
+ }
+
+ // Conjugation, declension + kind.
+ // TODO: to_human
+ match word.conjugation_id {
+ Some(id) => println!("Conjugation: {}", id),
+ None => match word.declension_id {
+ Some(did) => {
+ if did > 5 {
+ println!("Declension: {}", humanize_kind(&word.kind));
+ } else {
+ println!(
+ "Declension: {} ({})",
+ word.declension_id.unwrap(),
+ humanize_kind(&word.kind)
+ );
+ }
+ }
+ None => {}
+ },
+ };
+
+ // Show translation if available.
+ let locale = current_locale();
+ if let Some(translation) = word.translation.get(locale.to_code()) {
+ let s = translation.as_str().unwrap_or("");
+ if !s.is_empty() {
+ println!("Translation ({}): {}.", locale.to_code(), s);
+ }
+ }
+
+ print_full_inflection_for(word)?;
+
+ Ok(())
+}
+
+fn show(mut args: IntoIter<String>) -> i32 {
+ if args.len() > 1 {
+ help(Some(
+ "error: words: only one argument. If it's an enunciate, wrap it in double quotes",
+ ));
+ return 1;
+ }
+
+ let enunciated = match select_single_word(args.next()) {
+ Ok(word) => word,
+ Err(e) => {
+ println!("error: words: {e}.");
+ return 1;
+ }
+ };
+
+ let word = match mihi::find_by(enunciated.as_str()) {
+ Ok(word) => word,
+ Err(e) => {
+ println!("error: words: {e}.");
+ return 1;
+ }
+ };
+
+ if let Err(e) = show_info(word) {
+ println!("error: words: {e}.");
+ return 1;
+ }
+
+ 0
}
fn rm(mut args: IntoIter<String>) -> i32 {