harper_core/
irregular_nouns.rs

1use lazy_static::lazy_static;
2use serde::Deserialize;
3use std::sync::Arc;
4
5type Noun = (String, String);
6
7#[derive(Debug, Deserialize)]
8pub struct IrregularNouns {
9    nouns: Vec<Noun>,
10}
11
12/// The uncached function that is used to produce the original copy of the
13/// irregular noun table.
14fn 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
20lazy_static! {
21    static ref NOUNS: Arc<IrregularNouns> = uncached_inner_new();
22}
23
24impl IrregularNouns {
25    pub fn new() -> Self {
26        Self { nouns: vec![] }
27    }
28
29    pub fn from_json_file(json: &str) -> Result<Self, serde_json::Error> {
30        // Deserialize into Vec<serde_json::Value> to handle mixed types
31        let values: Vec<serde_json::Value> =
32            serde_json::from_str(json).expect("Failed to parse irregular nouns JSON");
33
34        let mut nouns = Vec::new();
35
36        for value in values {
37            match value {
38                serde_json::Value::Array(arr) if arr.len() == 2 => {
39                    // Handle array of 2 strings
40                    if let (Some(singular), Some(plural)) = (arr[0].as_str(), arr[1].as_str()) {
41                        nouns.push((singular.to_string(), plural.to_string()));
42                    }
43                }
44                // Strings are used for comments to guide contributors editing the file
45                serde_json::Value::String(_) => {}
46                _ => {}
47            }
48        }
49
50        Ok(Self { nouns })
51    }
52
53    pub fn curated() -> Arc<Self> {
54        (*NOUNS).clone()
55    }
56
57    pub fn get_plural_for_singular(&self, singular: &str) -> Option<&str> {
58        self.nouns
59            .iter()
60            .find(|(sg, _)| sg.eq_ignore_ascii_case(singular))
61            .map(|(_, pl)| pl.as_str())
62    }
63
64    pub fn get_singular_for_plural(&self, plural: &str) -> Option<&str> {
65        self.nouns
66            .iter()
67            .find(|(_, pl)| pl.eq_ignore_ascii_case(plural))
68            .map(|(sg, _)| sg.as_str())
69    }
70}
71
72impl Default for IrregularNouns {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn can_find_irregular_plural_for_singular_lowercase() {
84        assert_eq!(
85            IrregularNouns::curated().get_plural_for_singular("man"),
86            Some("men")
87        );
88    }
89
90    #[test]
91    fn can_find_irregular_plural_for_singular_uppercase() {
92        assert_eq!(
93            IrregularNouns::curated().get_plural_for_singular("WOMAN"),
94            Some("women")
95        );
96    }
97
98    #[test]
99    fn can_find_singular_for_irregular_plural() {
100        assert_eq!(
101            IrregularNouns::curated().get_singular_for_plural("children"),
102            Some("child")
103        );
104    }
105
106    #[test]
107    fn cant_find_regular_plural() {
108        assert_eq!(
109            IrregularNouns::curated().get_plural_for_singular("car"),
110            None
111        );
112    }
113
114    #[test]
115    fn cant_find_non_noun() {
116        assert_eq!(
117            IrregularNouns::curated().get_plural_for_singular("the"),
118            None
119        );
120    }
121}