inflector/string/singularize/
mod.rs

1use regex::Regex;
2use string::constants::UNACCONTABLE_WORDS;
3
4macro_rules! special_cases{
5    ($s:ident, $($singular: expr => $plural:expr), *) => {
6        match &$s[..] {
7            $(
8                $singular => {
9                    return $plural.to_owned();
10                },
11            )*
12            _ => ()
13        }
14    }
15}
16
17
18/// Converts a `&str` to singularized `String`
19///
20/// ```
21///     use inflector::string::singularize::to_singular;
22///     let mock_string: &str = "foo_bars";
23///     let expected_string: String = "foo_bar".to_owned();
24///     let asserted_string: String = to_singular(mock_string);
25///     assert!(asserted_string == expected_string);
26///
27/// ```
28/// ```
29///     use inflector::string::singularize::to_singular;
30///     let mock_string: &str = "oxen";
31///     let expected_string: String = "ox".to_owned();
32///     let asserted_string: String = to_singular(mock_string);
33///     assert!(asserted_string == expected_string);
34///
35/// ```
36/// ```
37///     use inflector::string::singularize::to_singular;
38///     let mock_string: &str = "crates";
39///     let expected_string: String = "crate".to_owned();
40///     let asserted_string: String = to_singular(mock_string);
41///     assert!(asserted_string == expected_string);
42///
43/// ```
44/// ```
45///     use inflector::string::singularize::to_singular;
46///     let mock_string: &str = "oxen";
47///     let expected_string: String = "ox".to_owned();
48///     let asserted_string: String = to_singular(mock_string);
49///     assert!(asserted_string == expected_string);
50///
51/// ```
52/// ```
53///     use inflector::string::singularize::to_singular;
54///     let mock_string: &str = "boxes";
55///     let expected_string: String = "box".to_owned();
56///     let asserted_string: String = to_singular(mock_string);
57///     assert!(asserted_string == expected_string);
58///
59/// ```
60/// ```
61///     use inflector::string::singularize::to_singular;
62///     let mock_string: &str = "vengeance";
63///     let expected_string: String = "vengeance".to_owned();
64///     let asserted_string: String = to_singular(mock_string);
65///     assert!(asserted_string == expected_string);
66///
67/// ```
68/// ```
69///     use inflector::string::singularize::to_singular;
70///     let mock_string: &str = "yoga";
71///     let expected_string: String = "yoga".to_owned();
72///     let asserted_string: String = to_singular(mock_string);
73///     assert!(asserted_string == expected_string);
74///
75/// ```
76///
77pub fn to_singular(non_singular_string: &str) -> String {
78    if UNACCONTABLE_WORDS.contains(&non_singular_string.as_ref()) {
79        non_singular_string.to_owned()
80    } else {
81        special_cases![non_singular_string,
82            "oxen" => "ox",
83            "boxes" => "box",
84            "men" => "man",
85            "women" => "woman",
86            "dice" => "die",
87            "yeses" => "yes",
88            "feet" => "foot",
89            "eaves" => "eave",
90            "geese" => "goose",
91            "teeth" => "tooth",
92            "quizzes" => "quiz"
93        ];
94        for &(ref rule, replace) in RULES.iter().rev() {
95            if let Some(captures) = rule.captures(&non_singular_string) {
96                if let Some(c) = captures.get(1) {
97                    let mut buf = String::new();
98                    captures.expand(&format!("{}{}", c.as_str(), replace), &mut buf);
99                    return buf;
100                }
101            }
102        }
103
104        format!("{}", non_singular_string)
105    }
106}
107
108macro_rules! add_rule{
109    ($r:ident, $rule:expr => $replace:expr) => {
110        $r.push((Regex::new($rule).unwrap(), $replace));
111    }
112}
113
114macro_rules! rules{
115    ($r:ident; $($rule:expr => $replace:expr), *) => {
116        $(
117            add_rule!{$r, $rule => $replace}
118        )*
119    }
120}
121
122
123lazy_static!{
124    static ref RULES: Vec<(Regex, &'static str)> = {
125    let mut r = Vec::with_capacity(27);
126    rules![r;
127     r"(\w*)s$" => "",
128     r"(\w*)(ss)$" => "$2",
129     r"(n)ews$" => "ews",
130     r"(\w*)(o)es$" => "",
131     r"(\w*)([ti])a$" => "um",
132     r"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$" => "sis",
133     r"(^analy)(sis|ses)$" => "sis",
134     r"(\w*)([^f])ves$" => "fe",
135     r"(\w*)(hive)s$" => "",
136     r"(\w*)(tive)s$" => "",
137     r"(\w*)([lr])ves$" => "f",
138     r"(\w*([^aeiouy]|qu))ies$" => "y",
139     r"(s)eries$" => "eries",
140     r"(m)ovies$" => "ovie",
141     r"(\w*)(x|ch|ss|sh)es$" => "$2",
142     r"(m|l)ice$" => "ouse",
143     r"(bus)(es)?$" => "",
144     r"(shoe)s$" => "",
145     r"(cris|test)(is|es)$" => "is",
146     r"^(a)x[ie]s$" => "xis",
147     r"(octop|vir)(us|i)$" => "us",
148     r"(alias|status)(es)?$" => "",
149     r"^(ox)en" => "",
150     r"(vert|ind)ices$" => "ex",
151     r"(matr)ices$" => "ix",
152     r"(quiz)zes$" => "",
153     r"(database)s$" => ""
154         ];
155     r
156    };
157}
158
159#[test]
160fn singularize_ies_suffix() {
161    assert_eq!("reply", to_singular("replies"));
162    assert_eq!("lady", to_singular("ladies"));
163    assert_eq!("soliloquy", to_singular("soliloquies"));
164}
165
166#[test]
167fn singularize_ss_suffix() {
168    assert_eq!("glass", to_singular("glass"));
169    assert_eq!("access", to_singular("access"));
170    assert_eq!("glass", to_singular("glasses"));
171    assert_eq!("witch", to_singular("witches"));
172    assert_eq!("dish", to_singular("dishes"));
173}
174
175#[test]
176fn singularize_string_if_a_regex_will_match() {
177    let expected_string: String = "ox".to_owned();
178    let asserted_string: String = to_singular("oxen");
179    assert!(expected_string == asserted_string);
180
181}
182
183#[test]
184fn singularize_string_returns_none_option_if_no_match() {
185    let expected_string: String = "bacon".to_owned();
186    let asserted_string: String = to_singular("bacon");
187
188    assert!(expected_string == asserted_string);
189}