aboutsummaryrefslogtreecommitdiff
path: root/lib
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 /lib
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 'lib')
-rw-r--r--lib/mihi/src/lib.rs235
1 files changed, 128 insertions, 107 deletions
diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs
index 237fe84..11d269a 100644
--- a/lib/mihi/src/lib.rs
+++ b/lib/mihi/src/lib.rs
@@ -6,6 +6,8 @@ use std::io::prelude::*;
use std::io::{self, BufRead, BufReader, Error};
use std::path::{Path, PathBuf};
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::Result;
use rusqlite::{params, Connection};
mod migrate;
@@ -192,6 +194,98 @@ impl std::fmt::Display for Gender {
}
}
+/// Identifies the declension for a given word, and it allows to do SQL to/from
+/// conversions.
+#[derive(Clone, Debug)]
+pub enum Declension {
+ First = 1,
+ Second,
+ Third,
+ Fourth,
+ Fifth,
+ Other,
+}
+
+impl ToSql for Declension {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.clone() as isize))
+ }
+}
+
+impl FromSql for Declension {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let val = value.as_i64().unwrap_or(0);
+
+ match val {
+ 1 => Ok(Declension::First),
+ 2 => Ok(Declension::Second),
+ 3 => Ok(Declension::Third),
+ 4 => Ok(Declension::Fourth),
+ 5 => Ok(Declension::Fifth),
+ _ => Ok(Declension::Other),
+ }
+ }
+}
+
+impl std::fmt::Display for Declension {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Declension::First => write!(f, "1st (-ae)"),
+ Declension::Second => write!(f, "2nd (-ī)"),
+ Declension::Third => write!(f, "3rd (-is)"),
+ Declension::Fourth => write!(f, "4th (-ūs)"),
+ Declension::Fifth => write!(f, "5th (-eī/-ēī)"),
+ Declension::Other => write!(f, "other"),
+ }
+ }
+}
+
+/// Identifies the conjugation for a given verb, and it allows to do SQL to/from
+/// conversions.
+#[derive(Clone, Debug)]
+pub enum Conjugation {
+ First = 1,
+ Second,
+ Third,
+ ThirdIo,
+ Fourth,
+ Other,
+}
+
+impl ToSql for Conjugation {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.clone() as isize))
+ }
+}
+
+impl FromSql for Conjugation {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let val = value.as_i64().unwrap_or(0);
+
+ match val {
+ 1 => Ok(Conjugation::First),
+ 2 => Ok(Conjugation::Second),
+ 3 => Ok(Conjugation::Third),
+ 4 => Ok(Conjugation::ThirdIo),
+ 5 => Ok(Conjugation::Fourth),
+ _ => Ok(Conjugation::Other),
+ }
+ }
+}
+
+impl std::fmt::Display for Conjugation {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Conjugation::First => write!(f, "1st (ā stems)"),
+ Conjugation::Second => write!(f, "2nd (ē stems)"),
+ Conjugation::Third => write!(f, "3rd (ĕ stems)"),
+ Conjugation::ThirdIo => write!(f, "3rd (-iō variants)"),
+ Conjugation::Fourth => write!(f, "4th (ī stems)"),
+ Conjugation::Other => write!(f, "other"),
+ }
+ }
+}
+
#[derive(Clone, Debug, Default)]
pub enum Language {
#[default]
@@ -380,9 +474,9 @@ pub fn adverb(word: &Word, related: &[Word]) -> String {
}
let part = word.real_particle();
- match word.declension_id {
- Some(1 | 2) => format!("{part}ē"),
- Some(3) => format!("{part}iter"),
+ match word.declension {
+ Some(Declension::First | Declension::Second) => format!("{part}ē"),
+ Some(Declension::Third) => format!("{part}iter"),
_ => "<unknown>".to_string(),
}
}
@@ -406,8 +500,8 @@ pub struct Word {
pub enunciated: String,
pub particle: String,
pub language: Language,
- pub declension_id: Option<isize>,
- pub conjugation_id: Option<isize>,
+ pub declension: Option<Declension>,
+ pub conjugation: Option<Conjugation>,
pub kind: String,
pub category: Category,
pub regular: bool,
@@ -425,8 +519,8 @@ impl Word {
pub fn from(
particle: String,
category: Category,
- declension_id: Option<isize>,
- conjugation_id: Option<isize>,
+ declension: Option<Declension>,
+ conjugation: Option<Conjugation>,
gender: Gender,
kind: String,
) -> Word {
@@ -435,8 +529,8 @@ impl Word {
enunciated: "".to_string(),
particle,
category,
- declension_id,
- conjugation_id,
+ declension,
+ conjugation,
kind,
regular: true,
locative: false,
@@ -451,13 +545,6 @@ impl Word {
}
}
- pub fn inflection_id(&self) -> Option<isize> {
- if matches!(self.category, Category::Verb) {
- return Some(self.conjugation_id.unwrap());
- }
- self.declension_id
- }
-
/// Returns whether the given flag is set to true on this word.
pub fn is_flag_set(&self, flag: &str) -> bool {
match self.flags.get(flag) {
@@ -488,43 +575,6 @@ impl Word {
}
}
-const DECLENSIONS_WITH_KINDS: &[&[&str]] = &[
- &["a"],
- &["us", "um", "ius", "er/ir"],
- &[
- "is",
- "istem",
- "pureistem",
- "one",
- "onenonistem",
- "two",
- "three",
- "visvis",
- "sussuis",
- "bosbovis",
- "iuppiteriovis",
- ],
- &["fus", "domusdomus"],
- &["ies", "es"],
- &["indeclinable"],
-];
-
-const ADJECTIVE_KINDS: &[&[&str]] = &[
- &["us", "er/ir"],
- &[],
- &[
- "one",
- "onenonistem",
- "two",
- "three",
- "unusnauta",
- "unusnautaer/ir",
- "duo",
- "tres",
- "mille",
- ],
-];
-
/// List of boolean flags supported for words.
pub const BOOLEAN_FLAGS: &[&str] = &[
"deponent",
@@ -560,55 +610,26 @@ pub fn is_valid_word_flag(flag: &str) -> bool {
/// Creates the given word into the database and returns its ID on success.
pub fn create_word(word: Word) -> Result<i64, String> {
match word.category {
- Category::Noun => match word.declension_id {
- Some(id @ 1..7) => {
- if !DECLENSIONS_WITH_KINDS[id as usize - 1].contains(&word.kind.as_str()) {
- return Err(format!("bad kind for declension '{id}'"));
- }
- }
- Some(val) => return Err(format!("the declension ID '{val}' is not valid for nouns")),
- None => {
+ Category::Noun | Category::Adjective => {
+ if word.declension.is_none() {
return Err(String::from(
- "you have to provide the declension ID for this noun",
- ))
+ "you have to provide the declension for this verb",
+ ));
}
- },
- Category::Adjective => match word.declension_id {
- Some(id @ (1 | 3)) => {
- if !ADJECTIVE_KINDS[id as usize - 1].contains(&word.kind.as_str()) {
- return Err(format!("bad kind for declension '{id}'"));
- }
- }
- Some(val) => {
- return Err(format!(
- "the declension ID '{val}' is not valid for adjectives"
- ))
- }
- None => {
- return Err(String::from(
- "you have to provide the declension ID for this adjective",
- ))
- }
- },
- Category::Verb => match word.conjugation_id {
- Some(1..6) => {
- if word.kind.as_str() != "verb" {
- return Err("bad kind for verb".to_string());
- }
- }
- Some(val) => return Err(format!("the conjugation ID '{val}' is not valid")),
- None => {
+ }
+ Category::Verb => {
+ if word.conjugation.is_none() {
return Err(String::from(
- "you have to provide the conjugation ID for this verb",
- ))
+ "you have to provide the conjugation for this verb",
+ ));
}
- },
+ }
Category::Adverb
| Category::Preposition
| Category::Conjunction
| Category::Interjection
| Category::Determiner => {
- if word.declension_id.is_some() || word.conjugation_id.is_some() {
+ if word.declension.is_some() || word.conjugation.is_some() {
return Err(format!("no inflection allowed for '{}'", word.category));
}
}
@@ -632,8 +653,8 @@ pub fn create_word(word: Word) -> Result<i64, String> {
word.enunciated.trim(),
word.particle.trim(),
word.language as isize,
- word.declension_id,
- word.conjugation_id,
+ word.declension,
+ word.conjugation,
word.kind.trim(),
word.category as isize,
word.regular,
@@ -671,8 +692,8 @@ pub fn update_word(word: Word) -> Result<(), String> {
word.id,
word.enunciated,
word.particle,
- word.declension_id,
- word.conjugation_id,
+ word.declension,
+ word.conjugation,
word.kind,
word.category as isize,
word.regular,
@@ -803,8 +824,8 @@ pub fn select_related_words(word: &Word) -> Result<[Vec<Word>; 5], String> {
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(),
+ declension: row.get(4).unwrap(),
+ conjugation: row.get(5).unwrap(),
kind: row.get(6).unwrap(),
category: row.get::<usize, isize>(7).unwrap().try_into()?,
regular: row.get(8).unwrap(),
@@ -843,8 +864,8 @@ pub fn find_by(enunciated: &str) -> Result<Word, String> {
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(),
+ declension: row.get(4).unwrap(),
+ conjugation: row.get(5).unwrap(),
kind: row.get(6).unwrap(),
category: row.get::<usize, isize>(7).unwrap().try_into()?,
regular: row.get(8).unwrap(),
@@ -933,8 +954,8 @@ pub fn select_relevant_words(
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(),
+ declension: row.get(4).unwrap(),
+ conjugation: row.get(5).unwrap(),
kind: row.get(6).unwrap(),
category: row.get::<usize, isize>(7).unwrap().try_into()?,
regular: row.get(8).unwrap(),
@@ -1020,8 +1041,8 @@ pub fn select_words_except(
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(),
+ declension: row.get(4).unwrap(),
+ conjugation: row.get(5).unwrap(),
kind: row.get(6).unwrap(),
category: row.get::<usize, isize>(7).unwrap().try_into()?,
regular: row.get(8).unwrap(),
@@ -1717,8 +1738,8 @@ pub fn get_adjective_table(word: &Word) -> Result<[DeclensionTable; 3], String>
let kind_f = if word.kind.as_str() == "unusnauta" {
&word.kind
} else {
- match word.declension_id {
- Some(1 | 2) => &"a".to_string(),
+ match word.declension {
+ Some(Declension::First | Declension::Second) => &"a".to_string(),
_ => &word.kind,
}
};