aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/cli/src/words.rs87
-rw-r--r--lib/mihi/src/lib.rs169
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