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