1#![forbid(unsafe_code)]
2#![allow(dead_code)]
3
4use std::collections::HashSet;
5use regex::Regex;
6use lazy_static::lazy_static;
7
8macro_rules! case_insensitive {
9 ($str:expr) => {{
10 $str
11 .chars()
12 .map(|c| format!("[{}{}]", c.to_string(), c.to_uppercase().to_string()))
13 .collect::<String>()
14 }};
15}
16
17type Upsc = (HashSet<String>, Vec<(Regex, String)>, Vec<(Regex, String)>, Vec<Regex>);
18lazy_static! {
19 static ref UPS: Upsc = {
20 let mut plurals: Vec<(String, String)> = vec![
21 (r"(?i)(?P<a>\w+)s?-in-law$".to_string(), "${a}s-in-law".to_string()),
22 (r"(?i)(?P<a>quiz)$".to_string(), "${a}zes".to_string()),
23 (r"(?i)^(?P<a>oxen)$".to_string(), "${a}".to_string()),
24 (r"(?i)^(?P<a>ox)$".to_string(), "${a}en".to_string()),
25 (r"(?i)(?P<a>m|l)ice$".to_string(), "${a}ice".to_string()),
26 (r"(?i)(?P<a>m|l)ouse$".to_string(), "${a}ice".to_string()),
27 (r"(?i)(?P<a>passer)s?by$".to_string(), "${a}sby".to_string()),
28 (
29 r"(?i)(?P<a>matr|vert|ind)(?:ix|ex)$".to_string(),
30 "${a}ices".to_string(),
31 ),
32 (r"(?i)(?P<a>x|ch|ss|sh)$".to_string(), "${a}es".to_string()),
33 (
34 r"(?i)(?P<a>[^aeiouy]|qu)y$".to_string(),
35 "${a}ies".to_string(),
36 ),
37 (r"(?i)(?P<a>hive)$".to_string(), "${a}s".to_string()),
38 (r"(?i)(?P<a>[lr])f$".to_string(), "${a}ves".to_string()),
39 (r"(?i)(?P<a>[^f])fe$".to_string(), "${a}ves".to_string()),
40 (r"(?i)sis$".to_string(), "ses".to_string()),
41 (r"(?i)(?P<a>[ti])a$".to_string(), "${a}a".to_string()),
42 (r"(?i)(?P<a>[ti])um$".to_string(), "${a}a".to_string()),
43 (
44 r"(?i)(?P<a>buffal|potat|tomat|her)o$".to_string(),
45 "${a}oes".to_string(),
46 ),
47 (r"(?i)(?P<a>bu)s$".to_string(), "${a}ses".to_string()),
48 (
49 r"(?i)(?P<a>alias|status)$".to_string(),
50 "${a}es".to_string(),
51 ),
52 (r"(?i)(?P<a>octop|vir|radi)i$".to_string(), "${a}i".to_string()),
53 (r"(?i)(?P<a>octop|vir|radi)us$".to_string(), "${a}i".to_string()),
54 (r"(?i)^(?P<a>ax|test)is$".to_string(), "${a}es".to_string()),
55 (r"(?i)s$".to_string(), "s".to_string()),
56 (r"$".to_string(), "s".to_string()),
57 ];
58
59 let mut singulars: Vec<(String, String)> = vec![
60 (r"(?i)(?P<a>\w+)s-in-law$".to_string(), "${a}-in-law".to_string()),
61 (r"(?i)(?P<a>database)s$".to_string(), "${a}".to_string()),
62 (r"(?i)(?P<a>quiz)zes$".to_string(), "${a}".to_string()),
63 (r"(?i)(?P<a>matr)ices$".to_string(), "${a}ix".to_string()),
64 (
65 r"(?i)(?P<a>vert|ind)ices$".to_string(),
66 "${a}ex".to_string(),
67 ),
68 (r"(?i)(?P<a>passer)sby$".to_string(), "${a}by".to_string()),
69 (r"(?i)^(?P<a>ox)en".to_string(), "${a}".to_string()),
70 (
71 r"(?i)(?P<a>alias|status)(es)?$".to_string(),
72 "${a}".to_string(),
73 ),
74 (
75 r"(?i)(?P<a>octop|vir|radi)(us|i)$".to_string(),
76 "${a}us".to_string(),
77 ),
78 (r"(?i)^(?P<a>a)x[ie]s$".to_string(), "${a}xis".to_string()),
79 (
80 r"(?i)(?P<a>cris|test)(is|es)$".to_string(),
81 "${a}is".to_string(),
82 ),
83 (r"(?i)(?P<a>shoe)s$".to_string(), "${a}".to_string()),
84 (r"(?i)(?P<a>o)es$".to_string(), "${a}".to_string()),
85 (r"(?i)(?P<a>bus)(es)?$".to_string(), "${a}".to_string()),
86 (r"(?i)(?P<a>m|l)ice$".to_string(), "${a}ouse".to_string()),
87 (r"(?i)(?P<a>x|ch|ss|sh)es$".to_string(), "${a}".to_string()),
88 (r"(?i)(?P<a>m)ovies$".to_string(), "${a}ovie".to_string()),
89 (r"(?i)(?P<a>s)eries$".to_string(), "${a}eries".to_string()),
90 (
91 r"(?i)(?P<a>[^aeiouy]|qu)ies$".to_string(),
92 "${a}y".to_string(),
93 ),
94 (r"(?i)(?P<a>[lr])ves$".to_string(), "${a}f".to_string()),
95 (r"(?i)(?P<a>tive)s$".to_string(), "${a}".to_string()),
96 (r"(?i)(?P<a>hive)s$".to_string(), "${a}".to_string()),
97 (r"(?i)(?P<a>[^f])ves$".to_string(), "${a}fe".to_string()),
98 (
99 r"(?i)(?P<a>t)he(sis|ses)$".to_string(),
100 "${a}hesis".to_string(),
101 ),
102 (
103 r"(?i)(?P<a>s)ynop(sis|ses)$".to_string(),
104 "${a}ynopsis".to_string(),
105 ),
106 (
107 r"(?i)(?P<a>p)rogno(sis|ses)$".to_string(),
108 "${a}rognosis".to_string(),
109 ),
110 (
111 r"(?i)(?P<a>p)arenthe(sis|ses)$".to_string(),
112 "${a}arenthesis".to_string(),
113 ),
114 (
115 r"(?i)(?P<a>d)iagno(sis|ses)$".to_string(),
116 "${a}iagnosis".to_string(),
117 ),
118 (
119 r"(?i)(?P<a>b)a(sis|ses)$".to_string(),
120 "${a}asis".to_string(),
121 ),
122 (
123 r"(?i)(?P<a>a)naly(sis|ses)$".to_string(),
124 "${a}nalysis".to_string(),
125 ),
126 (r"(?i)(?P<a>[ti])a$".to_string(), "${a}um".to_string()),
127 (r"(?i)(?P<a>n)ews$".to_string(), "${a}ews".to_string()),
128 (r"(?i)(?P<a>ss)$".to_string(), "${a}".to_string()),
129 (r"(?i)s$".to_string(), "".to_string()),
130 ];
131
132 let uncountable = HashSet::<String>::from([
133 "equipment".to_string(),
134 "fish".to_string(),
135 "information".to_string(),
136 "jeans".to_string(),
137 "money".to_string(),
138 "rice".to_string(),
139 "series".to_string(),
140 "sheep".to_string(),
141 "species".to_string(),
142 ]);
143
144 let uncountable_progs: Vec<Regex> = uncountable
145 .clone()
146 .into_iter()
147 .map(|x| {
148 Regex::new(&format!(r"(?i)\b({})\z", x)).unwrap()
149 })
150 .collect();
151
152 let add_irregular = |
153 plurals: &mut Vec<(String, String)>,
154 singulars: &mut Vec<(String, String)>,
155 singular: String,
156 plural: String
157 | {
158 let singular_first_char = &singular[..1];
159 let plural_first_char = &plural[..1];
160
161 let plural_stem = &plural[1..];
162 let singular_stem = &singular[1..];
163
164 if singular_first_char.to_uppercase() == plural_first_char.to_uppercase() {
165 plurals.insert(
167 0,
168 (
169 format!(r"(?i)(?P<a>{}){}$", singular_first_char, singular_stem),
170 format!("{}{}", r"${a}".to_owned(), plural_stem),
171 ),
172 );
173 plurals.insert(
174 0,
175 (
176 format!(r"(?i)(?P<a>{}){}$", plural_first_char, plural_stem),
177 format!("{}{}", r"${a}".to_owned(), plural_stem),
178 ),
179 );
180
181 singulars.insert(
182 0,
183 (
184 format!(r"(?i)(?P<a>{}){}$", plural_first_char, plural_stem),
185 format!("{}{}", r"${a}".to_owned(), singular_stem),
186 ),
187 );
188 } else {
189 let plural_copy_upper1 =
190 format!("{}{}", plural_first_char.to_uppercase(), plural_stem);
191
192 let plural_copy_lower1 =
193 format!("{}{}", plural_first_char.to_lowercase(), plural_stem);
194
195 let plural_copy_upper2 =
196 format!("{}{}", plural_first_char.to_uppercase(), plural_stem);
197
198 let plural_copy_lower2 =
199 format!("{}{}", plural_first_char.to_lowercase(), plural_stem);
200
201 let singular_copy_upper1 =
202 format!("{}{}", singular_first_char.to_uppercase(), singular_stem);
203
204 let singular_copy_lower1 =
205 format!("{}{}", singular_first_char.to_lowercase(), singular_stem);
206
207 plurals.insert(
208 0,
209 (
210 format!(
211 r"{}{}$",
212 singular_first_char.to_uppercase(),
213 case_insensitive!(singular_stem)
214 ),
215 plural_copy_upper1,
216 ),
217 );
218 plurals.insert(
219 0,
220 (
221 format!(
222 r"{}{}$",
223 singular_first_char.to_lowercase(),
224 case_insensitive!(singular_stem)
225 ),
226 plural_copy_lower1,
227 ),
228 );
229 plurals.insert(
230 0,
231 (
232 format!(
233 r"{}{}$",
234 plural_first_char.to_uppercase(),
235 case_insensitive!(plural_stem)
236 ),
237 plural_copy_upper2,
238 ),
239 );
240 plurals.insert(
241 0,
242 (
243 format!(
244 r"{}{}$",
245 plural_first_char.to_lowercase(),
246 case_insensitive!(plural_stem)
247 ),
248 plural_copy_lower2,
249 ),
250 );
251 singulars.insert(
252 0,
253 (
254 format!(
255 r"{}{}$",
256 plural_first_char.to_uppercase(),
257 case_insensitive!(plural_stem)
258 ),
259 singular_copy_upper1,
260 ),
261 );
262 singulars.insert(
263 0,
264 (
265 format!(
266 r"{}{}$",
267 plural_first_char.to_lowercase(),
268 case_insensitive!(plural_stem)
269 ),
270 singular_copy_lower1,
271 ),
272 );
273 }
274 };
275
276 add_irregular(&mut plurals, &mut singulars, "person".to_string(), "people".to_string());
277 add_irregular(&mut plurals, &mut singulars, "man".to_string(), "men".to_string());
278 add_irregular(&mut plurals, &mut singulars, "human".to_string(), "humans".to_string());
279 add_irregular(&mut plurals, &mut singulars, "child".to_string(), "children".to_string());
280 add_irregular(&mut plurals, &mut singulars, "sex".to_string(), "sexes".to_string());
281 add_irregular(&mut plurals, &mut singulars, "move".to_string(), "moves".to_string());
282 add_irregular(&mut plurals, &mut singulars, "cow".to_string(), "kine".to_string());
283 add_irregular(&mut plurals, &mut singulars, "zombie".to_string(), "zombies".to_string());
284 add_irregular(&mut plurals, &mut singulars, "slave".to_string(), "slaves".to_string());
285 add_irregular(&mut plurals, &mut singulars, "this".to_string(), "this".to_string());
286 add_irregular(&mut plurals, &mut singulars, "flour".to_string(), "flour".to_string());
287 add_irregular(&mut plurals, &mut singulars, "milk".to_string(), "milk".to_string());
288 add_irregular(&mut plurals, &mut singulars, "water".to_string(), "water".to_string());
289 add_irregular(&mut plurals, &mut singulars, "reserve".to_string(), "reserves".to_string());
290 add_irregular(&mut plurals, &mut singulars, "gas".to_string(), "gasses".to_string());
291 add_irregular(&mut plurals, &mut singulars, "bias".to_string(), "biases".to_string());
292 add_irregular(&mut plurals, &mut singulars, "atlas".to_string(), "atlases".to_string());
293 add_irregular(&mut plurals, &mut singulars, "goose".to_string(), "geese".to_string());
294 add_irregular(&mut plurals, &mut singulars, "pasta".to_string(), "pastas".to_string());
295 add_irregular(&mut plurals, &mut singulars, "slice".to_string(), "slices".to_string());
296 add_irregular(&mut plurals, &mut singulars, "cactus".to_string(), "cacti".to_string());
297 add_irregular(&mut plurals, &mut singulars, "buzz".to_string(), "buzzes".to_string());
298
299 let plurals: Vec<(Regex, String)> = plurals
300 .into_iter()
301 .map(|(rule, repl)| {
302 (Regex::new(&rule).unwrap(), repl)
303 })
304 .collect();
305
306 let singulars: Vec<(Regex, String)> = singulars
307 .into_iter()
308 .map(|(rule, repl)| {
309 (Regex::new(&rule).unwrap(), repl)
310 })
311 .collect();
312
313 (uncountable, plurals, singulars, uncountable_progs)
314 };
315}
316
317#[doc = include_str ! ("./../README.md")]
318pub mod inflection {
319 use std::collections::HashSet;
320 use regex::Regex;
321 use lazy_static::lazy_static;
322
323 use crate::UPS;
324
325 #[inline]
326 fn get_uncountable() -> &'static HashSet<String> {
327 &UPS.0
328 }
329
330 #[inline]
331 fn get_uncountable_compiled() -> &'static Vec<Regex> {
332 &UPS.3
333 }
334
335 #[inline]
336 fn get_plurals() -> &'static Vec<(Regex, String)> {
337 &UPS.1
338 }
339
340 #[inline]
341 fn get_singulars() -> &'static Vec<(Regex, String)> {
342 &UPS.2
343 }
344
345 macro_rules! create_ordinal_function {
346 ($func_name:ident, $abs:expr, $param_type:ty) => {
347 pub fn $func_name(number: $param_type) -> String {
348 let n = $abs(number);
349 match n % 100 {
350 11 | 12 | 13 => "th".to_string(),
351 _ => match n % 10 {
352 1 => "st".to_string(),
353 2 => "nd".to_string(),
354 3 => "rd".to_string(),
355 _ => "th".to_string(),
356 },
357 }
358 }
359 };
360 }
361
362 macro_rules! create_ordinalize_function {
363 ($func_name:ident, $ordinal_function:ident, $param_type:ty) => {
364 pub fn $func_name(number: $param_type) -> String {
365 format!("{}{}", number, $ordinal_function(number))
366 }
367 };
368 }
369
370 create_ordinal_function!(ordinal_i8, |x: i8| x.abs(), i8);
371 create_ordinal_function!(ordinal_i16, |x: i16| x.abs(), i16);
372 create_ordinal_function!(ordinal_i32, |x: i32| x.abs(), i32);
373 create_ordinal_function!(ordinal_i64, |x: i64| x.abs(), i64);
374 create_ordinal_function!(ordinal_i128, |x: i128| x.abs(), i128);
375 create_ordinal_function!(ordinal_u8, |x: u8| x, u8);
376 create_ordinal_function!(ordinal_u16, |x: u16| x, u16);
377 create_ordinal_function!(ordinal_u32, |x: u32| x, u32);
378 create_ordinal_function!(ordinal_u64, |x: u64| x, u64);
379 create_ordinal_function!(ordinal_u128, |x: u128| x, u128);
380 create_ordinal_function!(ordinal_usize, |x: usize| x, usize);
381
382 create_ordinalize_function!(ordinalize_i8, ordinal_i8, i8);
383 create_ordinalize_function!(ordinalize_i16, ordinal_i16, i16);
384 create_ordinalize_function!(ordinalize_i32, ordinal_i32, i32);
385 create_ordinalize_function!(ordinalize_i64, ordinal_i64, i64);
386 create_ordinalize_function!(ordinalize_i128, ordinal_i128, i128);
387 create_ordinalize_function!(ordinalize_u8, ordinal_u8, u8);
388 create_ordinalize_function!(ordinalize_u16, ordinal_u16, u16);
389 create_ordinalize_function!(ordinalize_u32, ordinal_u32, u32);
390 create_ordinalize_function!(ordinalize_u64, ordinal_u64, u64);
391 create_ordinalize_function!(ordinalize_u128, ordinal_u128, u128);
392 create_ordinalize_function!(ordinalize_usize, ordinal_usize, usize);
393
394 pub fn camelize<S: AsRef<str>>(string: S) -> String {
395 camelize_upper(string, true)
396 }
397
398 pub fn camelize_upper<S: AsRef<str>>(string: S, uppercase_first_letter: bool) -> String {
399 let input_string = string.as_ref().to_owned();
400
401 if input_string.is_empty() {
402 return input_string;
403 }
404
405 if uppercase_first_letter {
406 lazy_static! {
407 static ref CU_RE: Regex = Regex::new(r"(?:^|_)(.)").unwrap();
408 }
409 let mut result: String = input_string.to_owned();
410
411 for cap in CU_RE.find_iter(&input_string) {
412 let replace_with = &cap
413 .as_str()
414 .chars()
415 .last()
416 .unwrap_or(' ')
417 .to_uppercase()
418 .to_string();
419 result.replace_range(cap.range(), replace_with);
420 }
421 return result;
422 }
423
424 let input_string = camelize_upper(input_string, true);
425 let mut result = string
426 .as_ref()
427 .to_string()
428 .chars()
429 .next()
430 .unwrap_or(' ')
431 .to_lowercase()
432 .to_string();
433 result.push_str(&input_string[1..]);
434 result
435 }
436
437 pub fn dasherize<S: AsRef<str>>(word: S) -> String {
438 word.as_ref().to_string().replace('_', "-")
439 }
440
441 pub fn humanize<S: AsRef<str>>(word: S) -> String {
442 lazy_static! {
443 static ref H_ID_PROG: Regex = Regex::new(r"_id$").unwrap();
444 static ref H_STEM_PROG: Regex = Regex::new(r"(?i)([a-z\d]*)").unwrap();
445 static ref H_WORD_PROG: Regex = Regex::new(r"^\w").unwrap();
446 }
447
448 let mut result: String = H_ID_PROG.replace_all(word.as_ref(), "").to_string();
449 result = result.replace('_', " ");
450
451 if result.is_empty() {
452 return result;
453 }
454
455 let updated_result = result.to_owned();
456 for cap in H_STEM_PROG.find_iter(&updated_result) {
457 let replace_with = cap.as_str().to_lowercase().to_string();
458 result.replace_range(cap.range(), &replace_with);
459 }
460
461 let updated_result = result.to_owned();
462 for cap in H_WORD_PROG.find_iter(&updated_result) {
463 let mut replace_with = cap
464 .as_str()
465 .chars()
466 .next()
467 .unwrap_or(' ')
468 .to_uppercase()
469 .to_string();
470 let last_part = &cap.as_str()[1..];
471 replace_with.push_str(last_part);
472 result.replace_range(cap.range(), &replace_with);
473 }
474 result
475 }
476
477 pub fn underscore<S: AsRef<str>>(string: S) -> String {
478 lazy_static! {
479 static ref U_PROG1: Regex = Regex::new(r"(?P<a>[A-Z]+)(?P<b>[A-Z][a-z])").unwrap();
480 static ref U_PROG2: Regex = Regex::new(r"(?P<a>[a-z\d])(?P<b>[A-Z])").unwrap();
481 }
482 let stand_in = "$a-$b";
483 let mut word = string.as_ref().to_string();
484 word = U_PROG1.replace_all(&word, stand_in).to_string();
485 word = U_PROG2.replace_all(&word, stand_in).to_string();
486 word = word.replace('-', "_");
487 word.to_lowercase()
488 }
489
490 pub fn transliterate<S: AsRef<str>>(string: S) -> String {
491 deunicode::deunicode(string.as_ref())
492 }
493
494 pub fn parameterize_with_sep<S: AsRef<str>>(string: S, sep: String) -> String {
495 let transliterated0 = transliterate(string);
496 let transliterated = transliterated0.as_str();
497
498 let is_sep_empty = sep.is_empty();
499 let sep_copy = sep.to_owned();
500
501 lazy_static! {
502 static ref PWS_CLEAN_PROG: Regex = Regex::new(r"(?i)[^a-z0-9\-_]+").unwrap();
503 }
504 let cleaned0 = PWS_CLEAN_PROG.replace_all(transliterated, sep);
505 let cleaned = cleaned0.as_ref();
506 if !is_sep_empty {
507 let re_sep = regex::escape(&sep_copy);
508 let sep_prog = Regex::new(&format!(r"{}{}", re_sep, re_sep)).unwrap();
509 let leading_sep_prog = Regex::new(&format!(r"(?i)^{}|{}$", re_sep, re_sep)).unwrap();
510
511 let rm_sep = sep_prog.replace_all(cleaned, sep_copy);
512 let rm_sep = leading_sep_prog.replace_all(&rm_sep, "");
513
514 return rm_sep.as_ref().to_lowercase();
515 }
516
517 cleaned.to_lowercase()
518 }
519
520 pub fn parameterize<S: AsRef<str>>(string: S) -> String {
521 parameterize_with_sep::<S>(string, "-".to_string())
522 }
523
524 pub fn pluralize<S: AsRef<str>>(string: S) -> String {
525 let word: &str = string.as_ref();
526 let word_is_empty = word.is_empty();
527 let word_is_in_uncountable: bool =
528 get_uncountable().contains(word.to_lowercase().as_str());
529
530 if word_is_empty || word_is_in_uncountable {
531 return word.to_string();
532 }
533
534 for (rule, repl) in get_plurals().iter() {
535 if rule.is_match(word) {
537 return rule.replace_all(word, repl).to_string();
538 }
539 }
540
541 word.to_string()
542 }
543
544 pub fn singularize<S: AsRef<str>>(string: S) -> String {
545 let word = string.as_ref();
546
547 for re in get_uncountable_compiled().iter() {
548 if re.is_match(word) {
551 return word.to_string();
552 }
553 }
554
555 for (rule, repl) in get_singulars().iter() {
556 if rule.is_match(word) {
558 return rule.replace_all(word, repl).to_string();
559 }
560 }
561
562 word.to_string()
563 }
564
565 pub fn tableize<S: AsRef<str>>(string: S) -> String {
566 let underscore = underscore(string);
567 pluralize(underscore)
568 }
569
570 fn capitalize<S: AsRef<str>>(s: S) -> String {
571 let mut c = s.as_ref().chars();
572 match c.next() {
573 None => String::new(),
574 Some(f) => f.to_uppercase().chain(c).collect(),
575 }
576 }
577
578 pub fn titleize<S: AsRef<str>>(string: S) -> String {
579 let input_string = string.as_ref();
580 let mut result: String = underscore(&string);
581 result = humanize(result);
582 lazy_static! {
583 static ref H_FIRST_PROG: Regex = Regex::new(r"\b((\s+)?'?\w)").unwrap();
584 }
585 for cap in H_FIRST_PROG.find_iter(input_string) {
586 result.replace_range(cap.range(), cap.as_str());
587 }
588 result = result
589 .split(char::is_whitespace)
590 .map(|word| format!(" {}", capitalize(word)))
591 .collect::<String>()
592 .trim()
593 .to_string();
594 result
595 }
596
597 pub fn normalize_spaces<S: AsRef<str>>(string: S) -> String {
598 lazy_static! {
599 static ref NS_RE: Regex = Regex::new(r"\s+").unwrap();
600 }
601 let text = string.as_ref();
602 return NS_RE.replace_all(text, " ").trim().to_string();
603 }
604
605 fn _only_alpha<S: AsRef<str>>(
606 string: S,
607 check_fn: fn(c: &char) -> bool,
608 repl: Option<char>,
609 ) -> String {
610 let chars = string.as_ref().chars();
611
612 chars
613 .filter_map(|c| if !check_fn(&c) { repl } else { Some(c) })
614 .collect()
615 }
616
617 pub fn only_alpha<S: AsRef<str>>(string: S, repl: Option<char>) -> String {
618 let check_fn = |c: &char| c.is_alphabetic();
619 _only_alpha(string.as_ref(), check_fn, repl)
620 }
621
622 pub fn only_alphanum<S: AsRef<str>>(string: S, repl: Option<char>) -> String {
623 let check_fn = |c: &char| c.is_alphanumeric();
624 _only_alpha(string.as_ref(), check_fn, repl)
625 }
626
627 pub fn only_alpha_ascii<S: AsRef<str>>(string: S, repl: Option<char>) -> String {
628 let check_fn = |c: &char| c.is_ascii_alphabetic();
629 _only_alpha(string.as_ref(), check_fn, repl)
630 }
631
632 pub fn only_alphanum_ascii<S: AsRef<str>>(string: S, repl: Option<char>) -> String {
633 let check_fn = |c: &char| c.is_ascii_alphanumeric();
634 _only_alpha(string.as_ref(), check_fn, repl)
635 }
636
637 pub fn keyify<S: AsRef<str>>(string: S) -> String {
638 let result = only_alphanum_ascii(string, Some(' '));
639 let result = normalize_spaces(result);
640 let result = titleize(result);
641 let result = parameterize_with_sep(result, "_".to_string());
642 underscore(result.trim())
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use crate::inflection;
649
650 const SINGULAR_TO_PLURAL: [(&str, &str); 90] = [
651 ("search", "searches"),
652 ("switch", "switches"),
653 ("fix", "fixes"),
654 ("box", "boxes"),
655 ("process", "processes"),
656 ("address", "addresses"),
657 ("case", "cases"),
658 ("stack", "stacks"),
659 ("wish", "wishes"),
660 ("fish", "fish"),
661 ("jeans", "jeans"),
662 ("funky jeans", "funky jeans"),
663 ("category", "categories"),
664 ("query", "queries"),
665 ("ability", "abilities"),
666 ("agency", "agencies"),
667 ("movie", "movies"),
668 ("archive", "archives"),
669 ("index", "indices"),
670 ("wife", "wives"),
671 ("safe", "saves"),
672 ("half", "halves"),
673 ("move", "moves"),
674 ("salesperson", "salespeople"),
675 ("person", "people"),
676 ("spokesman", "spokesmen"),
677 ("man", "men"),
678 ("woman", "women"),
679 ("basis", "bases"),
680 ("diagnosis", "diagnoses"),
681 ("diagnosis_a", "diagnosis_as"),
682 ("datum", "data"),
683 ("medium", "media"),
684 ("stadium", "stadia"),
685 ("analysis", "analyses"),
686 ("node_child", "node_children"),
687 ("child", "children"),
688 ("experience", "experiences"),
689 ("day", "days"),
690 ("comment", "comments"),
691 ("foobar", "foobars"),
692 ("newsletter", "newsletters"),
693 ("old_news", "old_news"),
694 ("news", "news"),
695 ("series", "series"),
696 ("species", "species"),
697 ("quiz", "quizzes"),
698 ("perspective", "perspectives"),
699 ("ox", "oxen"),
700 ("passerby", "passersby"),
701 ("photo", "photos"),
702 ("buffalo", "buffaloes"),
703 ("tomato", "tomatoes"),
704 ("potato", "potatoes"),
705 ("dwarf", "dwarves"),
706 ("elf", "elves"),
707 ("information", "information"),
708 ("equipment", "equipment"),
709 ("bus", "buses"),
710 ("status", "statuses"),
711 ("status_code", "status_codes"),
712 ("mouse", "mice"),
713 ("louse", "lice"),
714 ("house", "houses"),
715 ("octopus", "octopi"),
716 ("virus", "viri"),
717 ("alias", "aliases"),
718 ("portfolio", "portfolios"),
719 ("vertex", "vertices"),
720 ("matrix", "matrices"),
721 ("matrix_fu", "matrix_fus"),
722 ("axis", "axes"),
723 ("testis", "testes"),
724 ("crisis", "crises"),
725 ("rice", "rice"),
726 ("shoe", "shoes"),
727 ("horse", "horses"),
728 ("prize", "prizes"),
729 ("edge", "edges"),
730 ("cow", "kine"),
731 ("database", "databases"),
732 ("human", "humans"),
733 ("flour", "flour"),
734 ("water", "water"),
735 ("slave", "slaves"),
736 ("milk", "milk"),
737 ("reserve", "reserves"),
738 ("gas", "gasses"),
739 ("bias", "biases"),
740 ("atlas", "atlases"),
741 ];
742
743 const CAMEL_TO_UNDERSCORE: [(&str, &str); 4] = [
744 ("Product", "product"),
745 ("SpecialGuest", "special_guest"),
746 ("ApplicationController", "application_controller"),
747 ("Area51Controller", "area51_controller"),
748 ];
749
750 const CAMEL_TO_UNDERSCORE_WITHOUT_REVERSE: [(&str, &str); 4] = [
751 ("HTMLTidy", "html_tidy"),
752 ("HTMLTidyGenerator", "html_tidy_generator"),
753 ("FreeBSD", "free_bsd"),
754 ("HTML", "html"),
755 ];
756
757 const STRING_TO_PARAMETERIZED: [(&str, &str); 8] = [
758 (r"Donald E. Knuth", "donald-e-knuth"),
759 (
760 r"Random text with *(bad)* characters",
761 "random-text-with-bad-characters",
762 ),
763 (r"Allow_Under_Scores", "allow_under_scores"),
764 (r"Trailing bad characters!@#", "trailing-bad-characters"),
765 (r"!@#Leading bad characters", "leading-bad-characters"),
766 (r"Squeeze separators", "squeeze-separators"),
767 (r"Test with + sign", "test-with-sign"),
768 (
769 r"Test with malformed utf8 \251",
770 "test-with-malformed-utf8-251",
771 ),
772 ];
773
774 const STRING_TO_PARAMETERIZE_WITH_NO_SEPARATOR: [(&str, &str); 8] = [
775 (r"Donald E. Knuth", "donaldeknuth"),
776 (r"With-some-dashes", "with-some-dashes"),
777 (
778 r"Random text with *(bad)* characters",
779 "randomtextwithbadcharacters",
780 ),
781 (r"Trailing bad characters!@#", "trailingbadcharacters"),
782 (r"!@#Leading bad characters", "leadingbadcharacters"),
783 (r"Squeeze separators", "squeezeseparators"),
784 (r"Test with + sign", "testwithsign"),
785 (r"Test with malformed utf8 \251", "testwithmalformedutf8251"),
786 ];
787
788 const STRING_TO_PARAMETERIZE_WITH_UNDERSCORE: [(&str, &str); 9] = [
789 (r"Donald E. Knuth", "donald_e_knuth"),
790 (
791 r"Random text with *(bad)* characters",
792 "random_text_with_bad_characters",
793 ),
794 (r"With-some-dashes", "with-some-dashes"),
795 (r"Retain_underscore", "retain_underscore"),
796 (r"Trailing bad characters!@#", "trailing_bad_characters"),
797 (r"!@#Leading bad characters", "leading_bad_characters"),
798 (r"Squeeze separators", "squeeze_separators"),
799 (r"Test with + sign", "test_with_sign"),
800 (
801 r"Test with malformed utf8 \251",
802 "test_with_malformed_utf8_251",
803 ),
804 ];
805
806 const KEYIFY_BULK: [(&str, &str); 18] = [
807 (r"Donald E. Knuth", "donald_e_knuth"),
808 (
809 r"Random text with *(bad)* characters",
810 "random_text_with_bad_characters",
811 ),
812 (r"With-some-dashes", "with_some_dashes"),
813 (r"Retain_underscore", "retain_underscore"),
814 (r"Trailing bad characters!@#", "trailing_bad_characters"),
815 (r"!@#Leading bad characters", "leading_bad_characters"),
816 (r"Squeeze separators", "squeeze_separators"),
817 (r"Test with + sign", "test_with_sign"),
818 (
819 r"Test with malformed utf8 \251",
820 "test_with_malformed_utf8_251",
821 ),
822 (" --== some strange_key", "some_strange_key"),
823 (" --== some otherKey_", "some_other_key"),
824 (" --== some other-key_", "some_other_key"),
825 ("Some Other Key", "some_other_key"),
826 ("Some-Other-Key", "some_other_key"),
827 ("some_other_key", "some_other_key"),
828 (" ", ""),
829 (" -----", ""),
830 ("========", ""),
831 ];
832
833 const STRING_TO_PARAMETERIZED_AND_NORMALIZED: [(&str, &str); 6] = [
834 (r"Malmö", "malmo"),
835 (r"Garçons", "garcons"),
836 (r"Ops\331", "ops-331"),
837 (r"Ærøskøbing", "aeroskobing"),
838 (r"Aßlar", "asslar"),
839 (r"日本語", "ri-ben-yu"),
840 ];
841
842 const UNDERSCORE_TO_HUMAN: [(&str, &str); 3] = [
843 ("employee_salary", "Employee salary"),
844 ("employee_id", "Employee"),
845 ("underground", "Underground"),
846 ];
847
848 const MIXTURE_TO_TITLEIZED: [(&str, &str); 12] = [
849 ("active_record", "Active Record"),
850 ("ActiveRecord", "Active Record"),
851 ("action web service", "Action Web Service"),
852 ("Action Web Service", "Action Web Service"),
853 ("Action web service", "Action Web Service"),
854 ("actionwebservice", "Actionwebservice"),
855 ("Actionwebservice", "Actionwebservice"),
856 ("david's code", "David's Code"),
857 ("David's code", "David's Code"),
858 ("david's Code", "David's Code"),
859 ("ana índia", "Ana Índia"),
860 ("Ana Índia", "Ana Índia"),
861 ];
862
863 const UNDERSCORES_TO_DASHES: [(&str, &str); 3] = [
864 ("street", "street"),
865 ("street_address", "street-address"),
866 ("person_street_address", "person-street-address"),
867 ];
868
869 const STRING_TO_TABLEIZE: [(&str, &str); 4] = [
870 ("person", "people"),
871 ("Country", "countries"),
872 ("ChildToy", "child_toys"),
873 ("_RecipeIngredient", "_recipe_ingredients"),
874 ];
875
876 #[test]
877 fn substring() {
878 assert_eq!(&"1Hello"[1..], "Hello");
879 }
880
881 #[test]
882 fn camelize_bulk() {
883 for (expected, input) in CAMEL_TO_UNDERSCORE {
884 assert_eq!(inflection::camelize(input), expected);
885 }
886 }
887
888 #[test]
889 fn pluralize_bulk() {
890 for (input, expected) in SINGULAR_TO_PLURAL {
891 assert_eq!(inflection::pluralize(input), expected);
892 }
893 }
894
895 #[test]
896 fn singularize_bulk() {
897 for (expected, input) in SINGULAR_TO_PLURAL {
898 assert_eq!(inflection::singularize(input), expected);
899 }
900 }
901
902 #[test]
903 fn underscore_bulk() {
904 for (expected, input) in UNDERSCORES_TO_DASHES {
905 assert_eq!(inflection::underscore(input), expected);
906 }
907
908 for (input, expected) in CAMEL_TO_UNDERSCORE_WITHOUT_REVERSE {
909 assert_eq!(inflection::underscore(input), expected);
910 }
911 }
912
913 #[test]
914 fn dasherize_bulk() {
915 for (input, expected) in UNDERSCORES_TO_DASHES {
916 assert_eq!(inflection::dasherize(input), expected);
917 }
918 }
919
920 #[test]
921 fn tableize_bulk() {
922 for (input, expected) in STRING_TO_TABLEIZE {
923 assert_eq!(inflection::tableize(input), expected);
924 }
925 }
926
927 #[test]
928 fn humanize_bulk() {
929 for (input, expected) in UNDERSCORE_TO_HUMAN {
930 assert_eq!(inflection::humanize(input), expected);
931 }
932 }
933
934 #[test]
935 fn titleize_bulk() {
936 for (input, expected) in MIXTURE_TO_TITLEIZED {
937 assert_eq!(inflection::titleize(input), expected);
938 }
939 }
940
941 #[test]
942 fn keyify_test() {
943 for (input, expected) in KEYIFY_BULK {
944 assert_eq!(inflection::keyify(input), expected);
945 }
946 }
947
948 #[test]
949 fn parameterize_bulk() {
950 for (input, expected) in STRING_TO_PARAMETERIZED {
951 assert_eq!(inflection::parameterize(input), expected);
952 }
953
954 for (input, expected) in STRING_TO_PARAMETERIZED_AND_NORMALIZED {
955 assert_eq!(inflection::parameterize(input), expected);
956 }
957
958 for (input, expected) in STRING_TO_PARAMETERIZE_WITH_UNDERSCORE {
959 assert_eq!(
960 inflection::parameterize_with_sep(input, "_".to_string()),
961 expected
962 );
963 }
964
965 for (input, expected) in STRING_TO_PARAMETERIZE_WITH_NO_SEPARATOR {
966 assert_eq!(
967 inflection::parameterize_with_sep(input, "".to_string()),
968 expected
969 );
970 }
971 }
972
973 macro_rules! test_ordinal {
974 ($ordinal:ident, $ordinalize:ident, $ordinalize_bulk:ident, $param_type:ty) => {
975 #[test]
976 fn $ordinal() {
977 assert_eq!(inflection::$ordinal(1), "st");
978 assert_eq!(inflection::$ordinal(2), "nd");
979 assert_eq!(inflection::$ordinal(3), "rd");
980 assert_eq!(inflection::$ordinal(4), "th");
981 assert_eq!(inflection::$ordinal(10), "th");
982
983 assert_eq!(inflection::$ordinal(1002), "nd");
984 assert_eq!(inflection::$ordinal(1003), "rd");
985 }
986
987 #[test]
988 fn $ordinalize() {
989 assert_eq!(inflection::$ordinalize(1), "1st");
990 assert_eq!(inflection::$ordinalize(2), "2nd");
991 assert_eq!(inflection::$ordinalize(3), "3rd");
992 assert_eq!(inflection::$ordinalize(4), "4th");
993 assert_eq!(inflection::$ordinalize(10), "10th");
994 assert_eq!(inflection::$ordinalize(1002), "1002nd");
995 assert_eq!(inflection::$ordinalize(1003), "1003rd");
996 }
997
998 #[test]
999 fn $ordinalize_bulk() {
1000 let ordinal_numbers: [($param_type, &str); 31] = [
1001 (0, "0th"),
1002 (1, "1st"),
1003 (2, "2nd"),
1004 (3, "3rd"),
1005 (4, "4th"),
1006 (5, "5th"),
1007 (6, "6th"),
1008 (7, "7th"),
1009 (8, "8th"),
1010 (9, "9th"),
1011 (10, "10th"),
1012 (11, "11th"),
1013 (12, "12th"),
1014 (13, "13th"),
1015 (14, "14th"),
1016 (20, "20th"),
1017 (21, "21st"),
1018 (22, "22nd"),
1019 (23, "23rd"),
1020 (24, "24th"),
1021 (100, "100th"),
1022 (101, "101st"),
1023 (102, "102nd"),
1024 (103, "103rd"),
1025 (104, "104th"),
1026 (110, "110th"),
1027 (111, "111th"),
1028 (112, "112th"),
1029 (113, "113th"),
1030 (1000, "1000th"),
1031 (1001, "1001st"),
1032 ];
1033
1034 for (input, expected) in ordinal_numbers {
1035 assert_eq!(inflection::$ordinalize(input), expected);
1036 }
1037 }
1038 };
1039 }
1040
1041 test_ordinal!(ordinal_u16, ordinalize_u16, oridinalize_u16_bulk, u16);
1042 test_ordinal!(ordinal_u32, ordinalize_u32, oridinalize_u32_bulk, u32);
1043 test_ordinal!(ordinal_u64, ordinalize_u64, oridinalize_u64_bulk, u64);
1044 test_ordinal!(ordinal_u128, ordinalize_u128, oridinalize_u128_bulk, u128);
1045
1046 test_ordinal!(ordinal_i16, ordinalize_i16, oridinalize_i16_bulk, i16);
1047 test_ordinal!(ordinal_i32, ordinalize_i32, oridinalize_i32_bulk, i32);
1048 test_ordinal!(ordinal_i64, ordinalize_i64, oridinalize_i64_bulk, i64);
1049 test_ordinal!(ordinal_i128, ordinalize_i128, oridinalize_i128_bulk, i128);
1050}