diff options
| -rw-r--r-- | crates/cli/src/words.rs | 87 | ||||
| -rw-r--r-- | lib/mihi/src/lib.rs | 169 |
2 files changed, 255 insertions, 1 deletions
diff --git a/crates/cli/src/words.rs b/crates/cli/src/words.rs index 9ed16d3..181ea81 100644 --- a/crates/cli/src/words.rs +++ b/crates/cli/src/words.rs @@ -2,7 +2,10 @@ 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 mihi::{ + adverb, comparative, joint_related_words, select_related_words, superlative, Category, Gender, + Language, RelationKind, Word, +}; use std::vec::IntoIter; static NEW_MESSAGE: &str = "New word"; @@ -628,6 +631,43 @@ fn show_info(word: Word) -> Result<(), String> { } }; + // Show relationships with other words. + + let related = select_related_words(&word)?; + + if matches!(word.category, Category::Adjective) { + print!( + "Comparative: {} || ", + comparative(&word, &related[RelationKind::Comparative as usize - 1]) + ); + print!( + "Superlative: {} || ", + superlative(&word, &related[RelationKind::Superlative as usize - 1]) + ); + println!( + "Adverb: {}", + adverb(&word, &related[RelationKind::Adverb as usize - 1]) + ); + } + + 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)), + } + let gendered = &related[RelationKind::Gendered as usize - 1]; + let g = if matches!(word.gender, Gender::Masculine) { + "Feminine" + } else { + "Masculine" + }; + match gendered.len() { + 0 => {} + 1 => println!("{g} alternative: {}", joint_related_words(&gendered)), + _ => println!("{g} alternatives: {}", joint_related_words(&gendered)), + } + // Show translation if available. let locale = current_locale(); if let Some(translation) = word.translation.get(locale.to_code()) { @@ -759,3 +799,48 @@ pub fn run(args: Vec<String>) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + // Returns a string with the format "{comparative form}-{superlative + // form}-{adverbial form}-{alternatives}-{gendered alternatives}". + fn related_for(enunciated: &str) -> String { + let word = mihi::find_by(enunciated).unwrap(); + let related = select_related_words(&word).unwrap(); + let alternatives = &related[RelationKind::Alternative as usize - 1]; + let gendered = &related[RelationKind::Gendered as usize - 1]; + + let first = if matches!(word.category, Category::Adjective) { + format!( + "{}-{}-{}", + comparative(&word, &related[RelationKind::Comparative as usize - 1]), + superlative(&word, &related[RelationKind::Superlative as usize - 1]), + adverb(&word, &related[RelationKind::Adverb as usize - 1]) + ) + } else { + "--".to_string() + }; + + format!( + "{}-{}-{}", + first, + mihi::joint_related_words(&alternatives), + mihi::joint_related_words(&gendered) + ) + } + + #[test] + fn related() { + assert_eq!( + related_for("parvus, parva, parvum"), + "minor, minus-minimus, minima, minimum-parvē--" + ); + assert_eq!( + related_for("versō, versāre, versāvī, versātum"), + "---vorsō, vorsāre, vorsāvī, vorsātum-" + ); + assert_eq!(related_for("victor, victōris"), "----victrīx, victrīcis"); + } +} diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs index 8153b87..7555a0f 100644 --- a/lib/mihi/src/lib.rs +++ b/lib/mihi/src/lib.rs @@ -1,4 +1,5 @@ use serde_json::Value; +use std::convert::TryFrom; use std::fs; use std::fs::File; use std::io::prelude::*; @@ -257,6 +258,107 @@ pub fn init_database() -> Result<(), String> { } } +/// Defines in which way two words are related. +#[derive(Clone, Debug)] +pub enum RelationKind { + /// The destination word is the comparative of the source (e.g. 'magnus, + /// magna, magnum' -> has irregular comparative -> 'māior, māius'). + Comparative = 1, + + /// The destination word is the superlative of the source (e.g. 'magnus, + /// magna, magnum' -> has irregular superlative -> 'maximus, maxima, + /// maximum'). + Superlative, + + /// The destination word is the adverb of the other (e.g. 'magnus, magna, + /// magnum' -> has an adverb -> 'magnē'). + Adverb, + + /// Two given words are the alternative of the other because of their root + /// or because of some sort of historical contraction (e.g. 'nihil' <-> + /// 'nīl', or the root on 'versō' <-> 'vōrsō'). + Alternative, + + /// One is the gendered alternative of the other (e.g. 'victor' <-> + /// 'victrix'). + Gendered, +} + +impl TryFrom<isize> for RelationKind { + type Error = String; + + fn try_from(v: isize) -> Result<Self, Self::Error> { + match v { + 1 => Ok(RelationKind::Comparative), + 2 => Ok(RelationKind::Superlative), + 3 => Ok(RelationKind::Adverb), + 4 => Ok(RelationKind::Alternative), + 5 => Ok(RelationKind::Gendered), + _ => Err(format!("unknown relation kind value '{}'", v)), + } + } +} + +/// Join by enunciate the given words. +pub fn joint_related_words(related: &Vec<Word>) -> String { + return related + .iter() + .map(|w| w.enunciated.clone()) + .collect::<Vec<String>>() + .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 { + if !related.is_empty() { + return joint_related_words(related); + } + if word.is_flag_set("compsup_prefix") { + return format!("magis {}", word.singular_nominative()); + } + + let part = word.real_particle(); + format!("{part}ior, {part}ius") +} + +/// 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 { + if !related.is_empty() { + return joint_related_words(related); + } + if word.is_flag_set("compsup_prefix") { + return format!("maximē {}", word.singular_nominative()); + } + + let part = &word.particle; + if word.is_flag_set("irregularsup") { + return format!("{part}limus, {part}lima, {part}limum"); + } else if word.is_flag_set("contracted_root") { + return format!("{part}rimus, {part}rima, {part}rimum"); + } + format!("{part}issimus, {part}issima, {part}issimum") +} + +/// 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 { + if !related.is_empty() { + return joint_related_words(related); + } + + let part = word.real_particle(); + match word.declension_id { + Some(1 | 2) => format!("{part}ē"), + Some(3) => format!("{part}iter"), + _ => "<unknown>".to_string(), + } +} + #[derive(Clone, Debug)] pub struct Word { pub id: i32, @@ -322,6 +424,27 @@ impl Word { None => false, } } + + /// Returns the nominative version of the enunciate. + pub fn singular_nominative(&self) -> String { + self.enunciated + .split(',') + .nth(0) + .unwrap_or("") + .trim() + .to_string() + } + + pub fn real_particle(&self) -> String { + if self.is_flag_set("contracted_root") { + return format!( + "{}{}", + self.particle[0..(self.particle.len() - 2)].to_string(), + self.particle.chars().last().unwrap_or(' '), + ); + } + self.particle.clone() + } } const DECLENSIONS_WITH_KINDS: &[&[&str]] = &[ @@ -551,6 +674,52 @@ pub fn select_enunciated(filter: Option<String>) -> Result<Vec<String>, String> Ok(res) } +/// Returns all words that are related to the given `word` in one way or +/// another. The result is given as an array where each element is indexed by +/// RelationKind, and has a vector of words following that relationship. +pub fn select_related_words(word: &Word) -> Result<[Vec<Word>; 5], String> { + let mut res = [vec![], vec![], vec![], vec![], vec![]]; + + let conn = get_connection()?; + let mut stmt = conn + .prepare( + "SELECT w.id, w.enunciated, w.particle, w.language_id, w.declension_id, w.conjugation_id, \ + w.kind as wkind, w.category, w.regular, w.locative, w.gender, w.suffix, w.translation, \ + w.succeeded, w.steps, w.flags, w.weight, r.kind as rkind \ + FROM words w \ + JOIN word_relations r ON w.id = r.destination_id + WHERE r.source_id = ?1", + ) + .unwrap(); + let mut it = stmt.query([word.id]).unwrap(); + + while let Some(row) = it.next().unwrap() { + let relation: RelationKind = row.get::<usize, isize>(17).unwrap().try_into()?; + + res[relation as usize - 1].push(Word { + id: row.get(0).unwrap(), + 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(), + kind: row.get(6).unwrap(), + category: row.get::<usize, isize>(7).unwrap().try_into()?, + regular: row.get(8).unwrap(), + locative: row.get(9).unwrap(), + gender: row.get::<usize, isize>(10).unwrap().try_into()?, + suffix: row.get(11).unwrap(), + translation: serde_json::from_str(&row.get::<usize, String>(12).unwrap()).unwrap(), + succeeded: row.get(13).unwrap(), + steps: row.get(14).unwrap(), + flags: serde_json::from_str(&row.get::<usize, String>(15).unwrap()).unwrap(), + weight: row.get(16).unwrap(), + }); + } + + Ok(res) +} + pub fn find_by(enunciated: &str) -> Result<Word, String> { let conn = get_connection()?; let mut stmt = conn |
