ruby_inflector/string/
pluralize.rs

1use crate::string::constants::UNACCONTABLE_WORDS;
2use once_cell::sync::Lazy;
3use regex::Regex;
4
5static RULES: Lazy<Vec<(Regex, &'static str)>> = Lazy::new(|| {
6    vec![(r"(\w*)s$", "s"),
7           (r"(\w*([^aeiou]ese))$", ""),
8           (r"(\w*(ax|test))is$", "es"),
9           (r"(\w*(alias|[^aou]us|tlas|gas|ris))$", "es"),
10           (r"(\w*(e[mn]u))s?$", "s"),
11           (r"(\w*([^l]ias|[aeiou]las|[emjzr]as|[iu]am))$", ""),
12           (r"(\w*(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat))(?:us|i)$", "i"),
13           (r"(\w*(alumn|alg|vertebr))(?:a|ae)$", "ae"),
14           (r"(\w*(seraph|cherub))(?:im)?$", "im"),
15           (r"(\w*(her|at|gr))o$", "oes"),
16           (r"(\w*(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor))(?:a|um)$", "a"),
17           (r"(\w*(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat))(?:a|on)$", "a"),
18           (r"(\w*)sis$", "ses"),
19           (r"(\w*(kni|wi|li))fe$", "ves"),
20           (r"(\w*(ar|l|ea|eo|oa|hoo))f$", "ves"),
21           (r"(\w*([^aeiouy]|qu))y$", "ies"),
22           (r"(\w*([^ch][ieo][ln]))ey$", "ies"),
23           (r"(\w*(x|ch|ss|sh|zz)es)$", ""),
24           (r"(\w*(x|ch|ss|sh|zz))$", "es"),
25           (r"(\w*(matr|cod|mur|sil|vert|ind|append))(?:ix|ex)$", "ices"),
26           (r"(\w*(m|l))(?:ice|ouse)$", "ice"),
27           (r"(\w*(pe))(?:rson|ople)$", "ople"),
28           (r"(\w*(child))(?:ren)?$", "ren"),
29           (r"(\w*eaux)$", "")].into_iter().map(|(rule, replace)| {(Regex::new(rule).unwrap(), replace)}).collect()
30});
31
32macro_rules! special_cases{
33    ($s:ident, $($singular: expr => $plural:expr), *) => {
34        match &$s[..] {
35            $(
36                $singular => {
37                    return $plural.to_owned();
38                },
39            )*
40            _ => ()
41        }
42    }
43}
44
45/// Converts a `&str` to pluralized `String`
46///
47/// ```
48/// use ruby_inflector::string::pluralize::to_plural;
49/// let mock_string: &str = "foo_bar";
50/// let expected_string: String = "foo_bars".to_owned();
51/// let asserted_string: String = to_plural(mock_string);
52/// assert_eq!(asserted_string, expected_string);
53///
54/// ```
55/// ```
56/// use ruby_inflector::string::pluralize::to_plural;
57/// let mock_string: &str = "ox";
58/// let expected_string: String = "oxen".to_owned();
59/// let asserted_string: String = to_plural(mock_string);
60/// assert_eq!(asserted_string, expected_string);
61///
62/// ```
63/// ```
64/// use ruby_inflector::string::pluralize::to_plural;
65/// let mock_string: &str = "crate";
66/// let expected_string: String = "crates".to_owned();
67/// let asserted_string: String = to_plural(mock_string);
68/// assert_eq!(asserted_string, expected_string);
69///
70/// ```
71/// ```
72/// use ruby_inflector::string::pluralize::to_plural;
73/// let mock_string: &str = "boxes";
74/// let expected_string: String = "boxes".to_owned();
75/// let asserted_string: String = to_plural(mock_string);
76/// assert_eq!(asserted_string, expected_string);
77///
78/// ```
79/// ```
80/// use ruby_inflector::string::pluralize::to_plural;
81/// let mock_string: &str = "vengeance";
82/// let expected_string: String = "vengeance".to_owned();
83/// let asserted_string: String = to_plural(mock_string);
84/// assert_eq!(asserted_string, expected_string);
85///
86/// ```
87/// ```
88/// use ruby_inflector::string::pluralize::to_plural;
89/// let mock_string: &str = "yoga";
90/// let expected_string: String = "yoga".to_owned();
91/// let asserted_string: String = to_plural(mock_string);
92/// assert_eq!(asserted_string, expected_string);
93///
94/// ```
95/// ```
96/// use ruby_inflector::string::pluralize::to_plural;
97/// let mock_string: &str = "geometry";
98/// let expected_string: String = "geometries".to_owned();
99/// let asserted_string: String = to_plural(mock_string);
100/// assert_eq!(asserted_string, expected_string);
101///
102/// ```
103///
104pub fn to_plural(non_plural_string: &str) -> String {
105    if UNACCONTABLE_WORDS.contains(&non_plural_string) {
106        non_plural_string.to_owned()
107    } else {
108        special_cases![non_plural_string,
109            "ox" => "oxen",
110            "man" => "men",
111            "woman" => "women",
112            "die" => "dice",
113            "yes" => "yeses",
114            "foot" => "feet",
115            "eave" => "eaves",
116            "goose" => "geese",
117            "tooth" => "teeth",
118            "quiz" => "quizzes"
119        ];
120        for &(ref rule, replace) in RULES.iter().rev() {
121            if let Some(c) = rule.captures(non_plural_string) {
122                if let Some(c) = c.get(1) {
123                    return format!("{}{}", c.as_str(), replace);
124                }
125            }
126        }
127
128        format!("{}s", non_plural_string)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134
135    macro_rules! as_item {
136        ($i:item) => {
137            $i
138        };
139    }
140
141    macro_rules! make_tests{
142        ($($singular:ident => $plural:ident); *) =>{
143            $(
144                   as_item! {
145                       #[test]
146                       fn $singular(){
147                           assert_eq!(
148                               stringify!($plural),
149                               super::to_plural(stringify!($singular))
150                               );
151                       }
152                   }
153            )*
154        }
155    }
156
157    #[test]
158    fn boxes() {
159        assert_eq!("boxes", super::to_plural("box"));
160    }
161
162    make_tests! {
163        geometry => geometries;
164        ox => oxen;
165        woman => women;
166        test => tests;
167        axis => axes;
168        knife => knives;
169        agendum => agenda;
170        elf => elves;
171        zoology => zoology;
172        mice => mice;
173        people => people
174    }
175}