harper_core/
irregular_nouns.rs1use crate::CharStringExt;
2use serde::Deserialize;
3use std::sync::{Arc, LazyLock};
4
5type Noun = (String, String);
6
7#[derive(Debug, Deserialize)]
8pub struct IrregularNouns {
9 nouns: Vec<Noun>,
10}
11
12fn uncached_inner_new() -> Arc<IrregularNouns> {
15 IrregularNouns::from_json_file(include_str!("../irregular_nouns.json"))
16 .map(Arc::new)
17 .unwrap_or_else(|e| panic!("Failed to load irregular noun table: {}", e))
18}
19
20static NOUNS: LazyLock<Arc<IrregularNouns>> = LazyLock::new(uncached_inner_new);
21
22impl IrregularNouns {
23 pub fn new() -> Self {
24 Self { nouns: vec![] }
25 }
26
27 pub fn from_json_file(json: &str) -> Result<Self, serde_json::Error> {
28 let values: Vec<serde_json::Value> =
30 serde_json::from_str(json).expect("Failed to parse irregular nouns JSON");
31
32 let mut nouns = Vec::new();
33
34 for value in values {
35 match value {
36 serde_json::Value::Array(arr) if arr.len() == 2 => {
37 if let (Some(singular), Some(plural)) = (arr[0].as_str(), arr[1].as_str()) {
39 nouns.push((singular.to_string(), plural.to_string()));
40 }
41 }
42 serde_json::Value::String(_) => {}
44 _ => {}
45 }
46 }
47
48 Ok(Self { nouns })
49 }
50
51 pub fn curated() -> Arc<Self> {
52 (*NOUNS).clone()
53 }
54
55 pub fn get_plural_for_singular_chars(&self, singular: &[char]) -> Option<&str> {
56 self.nouns
57 .iter()
58 .find(|(sg, _)| singular.eq_str(sg))
59 .map(|(_, pl)| pl.as_str())
60 }
61
62 pub fn get_plural_for_singular(&self, singular: &str) -> Option<&str> {
63 self.nouns
64 .iter()
65 .find(|(sg, _)| sg.eq_ignore_ascii_case(singular))
66 .map(|(_, pl)| pl.as_str())
67 }
68
69 pub fn get_singular_for_plural(&self, plural: &str) -> Option<&str> {
70 self.nouns
71 .iter()
72 .find(|(_, pl)| pl.eq_ignore_ascii_case(plural))
73 .map(|(sg, _)| sg.as_str())
74 }
75
76 pub fn get_singular_for_plural_chars(&self, plural: &[char]) -> Option<&str> {
77 self.nouns
78 .iter()
79 .find(|(_, pl)| plural.eq_str(pl))
80 .map(|(sg, _)| sg.as_str())
81 }
82}
83
84impl Default for IrregularNouns {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn can_find_irregular_plural_for_singular_lowercase() {
96 assert_eq!(
97 IrregularNouns::curated().get_plural_for_singular("man"),
98 Some("men")
99 );
100 }
101
102 #[test]
103 fn can_find_irregular_plural_for_singular_uppercase() {
104 assert_eq!(
105 IrregularNouns::curated().get_plural_for_singular("WOMAN"),
106 Some("women")
107 );
108 }
109
110 #[test]
111 fn can_find_singular_for_irregular_plural() {
112 assert_eq!(
113 IrregularNouns::curated().get_singular_for_plural("children"),
114 Some("child")
115 );
116 }
117
118 #[test]
119 fn cant_find_regular_plural() {
120 assert_eq!(
121 IrregularNouns::curated().get_plural_for_singular("car"),
122 None
123 );
124 }
125
126 #[test]
127 fn cant_find_non_noun() {
128 assert_eq!(
129 IrregularNouns::curated().get_plural_for_singular("the"),
130 None
131 );
132 }
133}