aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-01-19 21:48:32 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-01-19 22:01:36 +0100
commite1e7a94d0c781fa3c8a3c82a74c671ee1e92af92 (patch)
tree656137442ca8a1b6d1daf3c11296fd403779c088
parent19d7c1eab58d9f02e41f831eda121ef587487858 (diff)
downloadmihi-e1e7a94d0c781fa3c8a3c82a74c671ee1e92af92.tar.gz
mihi-e1e7a94d0c781fa3c8a3c82a74c671ee1e92af92.zip
run: ask for alternatives as well
This commit also fixes the need to ask for a random gender when asking for adjectives. This is somewhat unrelated to the main subject, but untying this now was a bit too much of a hassle. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
-rw-r--r--Cargo.lock59
-rw-r--r--crates/cli/Cargo.toml1
-rw-r--r--crates/cli/src/run.rs96
-rw-r--r--crates/cli/src/words.rs12
-rw-r--r--lib/mihi/src/lib.rs14
5 files changed, 165 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a33bf5a..0fee4e6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -48,6 +48,7 @@ version = "0.1.0"
dependencies = [
"inquire",
"mihi",
+ "rand",
"serde",
"serde_json",
"tempfile",
@@ -316,6 +317,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -340,6 +350,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -734,6 +773,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
+name = "zerocopy"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "zmij"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index cd2a8c9..5cfe588 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -14,3 +14,4 @@ inquire = { version = "0.7.5", features = ["editor"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tempfile = "3.20"
+rand = "0.9"
diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs
index 21a92c2..45149ac 100644
--- a/crates/cli/src/run.rs
+++ b/crates/cli/src/run.rs
@@ -1,9 +1,11 @@
+extern crate rand;
use inquire::{Confirm, Editor, Text};
-use mihi::{configuration, get_adjective_table, get_inflected_from};
+use mihi::{configuration, get_adjective_table, get_inflected_from, select_related_words};
use mihi::{
get_noun_table, select_relevant_words, touch_exercise, update_success, Category,
- DeclensionTable, Exercise, ExerciseKind, Word,
+ DeclensionTable, Exercise, ExerciseKind, RelationKind, Word,
};
+use rand::prelude::*;
use std::env;
use std::fs;
use std::io::Write;
@@ -182,16 +184,102 @@ fn ask_for_table(word: &Word, table: &DeclensionTable, id: Option<&str>) -> bool
same_answer(&solution, &expected)
}
+// Ask for alternative forms (gendered or otherwise) about a given word.
+fn ask_for_alternatives(related: &[Vec<Word>; 5]) -> bool {
+ let alternatives = &related[RelationKind::Alternative as usize - 1];
+ if !alternatives.is_empty() {
+ let Ok(raw) =
+ Text::new("Do you know of any alternative (not asking about a gendered one)?").prompt()
+ else {
+ return false;
+ };
+ let expected = mihi::joint_related_words(alternatives);
+ if !same_answer(&raw, &expected) {
+ return false;
+ }
+ }
+
+ let gendered = &related[RelationKind::Gendered as usize - 1];
+ if !gendered.is_empty() {
+ let Ok(raw) = Text::new("Do you know of the same word but on the other gender?").prompt()
+ else {
+ return false;
+ };
+ let expected = mihi::joint_related_words(gendered);
+ if !same_answer(&raw, &expected) {
+ return false;
+ }
+ }
+
+ true
+}
+
+// Ask for other forms for the given word (i.e. comparative, superlative,
+// adverbial).
+//
+// NOTE: this word _has_ to be an adjective.
+fn ask_for_others(word: &Word, related: &[Vec<Word>; 5]) -> bool {
+ assert!(matches!(word.category, Category::Adjective));
+
+ let comparative = mihi::comparative(word, &related[RelationKind::Comparative as usize - 1]);
+ let Ok(raw) = Text::new("Comparative:").prompt() else {
+ return false;
+ };
+ if !same_answer(&raw, &comparative) {
+ return false;
+ }
+
+ let superlative = mihi::superlative(word, &related[RelationKind::Superlative as usize - 1]);
+ let Ok(raw) = Text::new("Superlative:").prompt() else {
+ return false;
+ };
+ if !same_answer(&raw, &superlative) {
+ return false;
+ }
+
+ let adverbial = mihi::adverb(word, &related[RelationKind::Adverb as usize - 1]);
+ let Ok(raw) = Text::new("Adverb:").prompt() else {
+ return false;
+ };
+ if !same_answer(&raw, &adverbial) {
+ return false;
+ }
+
+ true
+}
+
fn good_noun_inflection(word: &Word) -> bool {
if let Ok(table) = get_noun_table(word) {
- return ask_for_table(word, &table, None);
+ if !ask_for_table(word, &table, None) {
+ return false;
+ }
+ if let Ok(related) = select_related_words(word) {
+ return ask_for_alternatives(&related);
+ }
}
true
}
fn good_adjective_inflection(word: &Word) -> bool {
if let Ok(tables) = get_adjective_table(word) {
- ask_for_table(word, &tables[0], Some("in the masculine"));
+ // Pick which gender from the adjective table to ask.
+ let mut rng = rand::rng();
+ let gender = rng.random_range(0..=2);
+ let suffix = match gender {
+ 1 => Some("in the feminine"),
+ 2 => Some("in the neuter"),
+ _ => Some("in the masculine"),
+ };
+
+ if !ask_for_table(word, &tables[gender], suffix) {
+ return false;
+ }
+ if let Ok(related) = select_related_words(word) {
+ if !ask_for_others(word, &related) {
+ return false;
+ }
+ return ask_for_alternatives(&related);
+ }
}
true
}
diff --git a/crates/cli/src/words.rs b/crates/cli/src/words.rs
index 181ea81..413d193 100644
--- a/crates/cli/src/words.rs
+++ b/crates/cli/src/words.rs
@@ -653,8 +653,8 @@ fn show_info(word: Word) -> Result<(), String> {
let alternatives = &related[RelationKind::Alternative as usize - 1];
match alternatives.len() {
0 => {}
- 1 => println!("Alternative: {}", joint_related_words(&alternatives)),
- _ => println!("Alternatives: {}", joint_related_words(&alternatives)),
+ 1 => println!("Alternative: {}", joint_related_words(alternatives)),
+ _ => println!("Alternatives: {}", joint_related_words(alternatives)),
}
let gendered = &related[RelationKind::Gendered as usize - 1];
let g = if matches!(word.gender, Gender::Masculine) {
@@ -664,8 +664,8 @@ fn show_info(word: Word) -> Result<(), String> {
};
match gendered.len() {
0 => {}
- 1 => println!("{g} alternative: {}", joint_related_words(&gendered)),
- _ => println!("{g} alternatives: {}", joint_related_words(&gendered)),
+ 1 => println!("{g} alternative: {}", joint_related_words(gendered)),
+ _ => println!("{g} alternatives: {}", joint_related_words(gendered)),
}
// Show translation if available.
@@ -826,8 +826,8 @@ mod tests {
format!(
"{}-{}-{}",
first,
- mihi::joint_related_words(&alternatives),
- mihi::joint_related_words(&gendered)
+ mihi::joint_related_words(alternatives),
+ mihi::joint_related_words(gendered)
)
}
diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs
index 7555a0f..d75965d 100644
--- a/lib/mihi/src/lib.rs
+++ b/lib/mihi/src/lib.rs
@@ -300,18 +300,18 @@ impl TryFrom<isize> for RelationKind {
}
/// Join by enunciate the given words.
-pub fn joint_related_words(related: &Vec<Word>) -> String {
- return related
+pub fn joint_related_words(related: &[Word]) -> String {
+ related
.iter()
.map(|w| w.enunciated.clone())
.collect::<Vec<String>>()
- .join("; ");
+ .join("; ")
}
/// Returns a string with the enunciate of the comparative form of the given
/// `word`. This function assumes that it really does, or at least it's
/// contained in the `related` vector.
-pub fn comparative(word: &Word, related: &Vec<Word>) -> String {
+pub fn comparative(word: &Word, related: &[Word]) -> String {
if !related.is_empty() {
return joint_related_words(related);
}
@@ -326,7 +326,7 @@ pub fn comparative(word: &Word, related: &Vec<Word>) -> String {
/// Returns a string with the enunciate of the superlative form of the given
/// `word`. This function assumes that it really does, or at least it's
/// contained in the `related` vector.
-pub fn superlative(word: &Word, related: &Vec<Word>) -> String {
+pub fn superlative(word: &Word, related: &[Word]) -> String {
if !related.is_empty() {
return joint_related_words(related);
}
@@ -346,7 +346,7 @@ pub fn superlative(word: &Word, related: &Vec<Word>) -> String {
/// Returns a string with the enunciate of the adverbial form of the given
/// `word`. This function assumes that it really does, or at least it's
/// contained in the `related` vector.
-pub fn adverb(word: &Word, related: &Vec<Word>) -> String {
+pub fn adverb(word: &Word, related: &[Word]) -> String {
if !related.is_empty() {
return joint_related_words(related);
}
@@ -439,7 +439,7 @@ impl Word {
if self.is_flag_set("contracted_root") {
return format!(
"{}{}",
- self.particle[0..(self.particle.len() - 2)].to_string(),
+ &self.particle[0..(self.particle.len() - 2)],
self.particle.chars().last().unwrap_or(' '),
);
}