Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
/// Irregular rules
pub(crate) const IRREGULAR_RULES: &'static [(&str, &str)] = &[
// Pronouns.
("I", "we"),
("me", "us"),
("he", "they"),
("she", "they"),
("them", "them"),
("myself", "ourselves"),
("yourself", "yourselves"),
("itself", "themselves"),
("herself", "themselves"),
("himself", "themselves"),
("themself", "themselves"),
("is", "are"),
("was", "were"),
("has", "have"),
("this", "these"),
("that", "those"),
("my", "our"),
("its", "their"),
("his", "their"),
("her", "their"),
// Words ending in with a consonant and `o`.
("echo", "echoes"),
("dingo", "dingoes"),
("volcano", "volcanoes"),
("tornado", "tornadoes"),
("torpedo", "torpedoes"),
// Ends with `us`.
("genus", "genera"),
("viscus", "viscera"),
// Ends with `ma`.
("stigma", "stigmata"),
("stoma", "stomata"),
("dogma", "dogmata"),
("lemma", "lemmata"),
("schema", "schemata"),
("anathema", "anathemata"),
// Other irregular rules.
("ox", "oxen"),
("axe", "axes"),
("die", "dice"),
("yes", "yeses"),
("foot", "feet"),
("eave", "eaves"),
("goose", "geese"),
("tooth", "teeth"),
("quiz", "quizzes"),
("human", "humans"),
("proof", "proofs"),
("carve", "carves"),
("valve", "valves"),
("looey", "looies"),
("thief", "thieves"),
("groove", "grooves"),
("pickaxe", "pickaxes"),
("passerby", "passersby"),
("canvas", "canvases"),
];
/// Pluralization rules
pub(crate) const PLURAL_RULES: &'static [(&str, &str)] = &[
("(?i)s?$", "s"),
(r"(?i)[^\u0000-\u007F]$", "$0"),
(r"(?i)([^aeiou]ese)$", "$1"),
(r"(?i)(ax|test)is$", "$1es"),
(r"(?i)(alias|[^aou]us|t[lm]as|gas|ris)$", "$1es"),
(r"(?i)(e[mn]u)s?$", "$1s"),
(r"(?i)([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$", "$1"),
(
r"(?i)(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$",
"$1i",
),
(r"(?i)(alumn|alg|vertebr)(?:a|ae)$", "$1ae"),
(r"(?i)(seraph|cherub)(?:im)?$", "$1im"),
(r"(?i)(her|at|gr)o$", "$1oes"),
(
r"(?i)(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$",
"$1a",
),
(
r"(?i)(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$",
"$1a",
),
(r"(?i)sis$", "ses"),
(r"(?i)(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$", "$1$2ves"),
(r"(?i)([^aeiouy]|qu)y$", "$1ies"),
(r"(?i)([^ch][ieo][ln])ey$", "$1ies"),
(r"(?i)(x|ch|ss|sh|zz)$", "$1es"),
(
r"(?i)(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$",
"$1ices",
),
(r"(?i)\b((?:tit)?m|l)(?:ice|ouse)$", "$1ice"),
(r"(?i)(pe)(?:rson|ople)$", "$1ople"),
(r"(?i)(child)(?:ren)?$", "$1ren"),
(r"(?i)eaux$", "$0"),
(r"(?i)m[ae]n$", "men"),
(r"(?i)^thou$", "you"),
];
/// Singularization rules
pub(crate) const SINGULAR_RULES: &'static [(&str, &str)] = &[
("(?i)s$", ""),
("(?i)(ss)$", "$1"),
("(?i)(wi|kni|(?:after|half|high|low|mid|non|night|[^\\w]|^)li)ves$", "$1fe"),
("(?i)(ar|(?:wo|[ae])l|[eo][ao])ves$", "$1f"),
("(?i)ies$", "y"),
("(?i)(dg|ss|ois|lk|ok|wn|mb|th|ch|ec|oal|is|ck|ix|sser|ts|wb)ies$", "$1ie"),
("(?i)\\b(l|(?:neck|cross|hog|aun)?t|coll|faer|food|gen|goon|group|hipp|junk|vegg|(?:pork)?p|charl|calor|cut)ies$", "$1ie"),
("(?i)\\b(mon|smil)ies$", "$1ey"),
("(?i)\\b((?:tit)?m|l)ice$", "$1ouse"),
("(?i)(seraph|cherub)im$", "$1"),
("(?i)(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$", "$1"),
("(?i)(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$", "$1sis"),
("(?i)(movie|twelve|abuse|e[mn]u)s$", "$1"),
("(?i)(test)(?:is|es)$", "$1is"),
("(?i)(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$", "$1us"),
("(?i)(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$", "$1um"),
("(?i)(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$", "$1on"),
("(?i)(alumn|alg|vertebr)ae$", "$1a"),
("(?i)(cod|mur|sil|vert|ind)ices$", "$1ex"),
("(?i)(matr|append)ices$", "$1ix"),
("(?i)(pe)(rson|ople)$", "$1rson"),
("(?i)(child)ren$", "$1"),
("(?i)(eau)x?$", "$1"),
("(?i)men$", "man"),
];
pub(crate) const UNCOUNTABLE_RULES: &'static [&str] = &[
// Singular words with no plurals.
"adulthood",
"advice",
"agenda",
"aid",
"aircraft",
"alcohol",
"ammo",
"analytics",
"anime",
"athletics",
"audio",
"bison",
"blood",
"bream",
"buffalo",
"butter",
"carp",
"cash",
"chassis",
"chess",
"clothing",
"cod",
"commerce",
"cooperation",
"corps",
"debris",
"diabetes",
"digestion",
"elk",
"energy",
"equipment",
"excretion",
"expertise",
"firmware",
"flounder",
"fun",
"gallows",
"garbage",
"graffiti",
"hardware",
"headquarters",
"health",
"herpes",
"highjinks",
"homework",
"housework",
"information",
"jeans",
"justice",
"kudos",
"labour",
"literature",
"machinery",
"mackerel",
"mail",
"media",
"mews",
"moose",
"music",
"mud",
"manga",
"news",
"only",
"personnel",
"pike",
"plankton",
"pliers",
"police",
"pollution",
"premises",
"rain",
"research",
"rice",
"salmon",
"scissors",
"series",
"sewage",
"shambles",
"shrimp",
"software",
"staff",
"swine",
"tennis",
"traffic",
"transportation",
"trout",
"tuna",
"wealth",
"welfare",
"whiting",
"wildebeest",
"wildlife",
"you",
];
pub(crate) const UNCOUNTABLE_REGEX_RULES: &'static [&str] = &[
"(?i)pok[eé]mon$",
"(?i)[^aeiou]ese$", // "chinese", "japanese"
"(?i)deer$", // "deer", "reindeer"
"(?i)fish$", // "fish", "blowfish", "angelfish"
"(?i)measles$",
"(?i)o[iu]s$", // "carnivorous"
"(?i)pox$", // "chickpox", "smallpox"
"(?i)sheep$",
];
+354
View File
@@ -0,0 +1,354 @@
// Copyright 2025 pluralizer Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
/*!
Rust package to pluralize or singularize any word based on a count inspired on pluralize NPM package.
It will keep plurals are plurals if the count given is not 1, either way, it is going to keep the singular form if the count given is 1
# Example
```rust
use pluralizer::pluralize;
fn main() {
// It can convert to plural
println!("{}", pluralize("House", 2, true)); // 2 Houses
// But also can convert to singular
println!("{}", pluralize("Houses", 1, true)); // 1 House
// And keep singularization if needed
println!("{}", pluralize("House", 1, false)); // House
// Or keep pluralization
println!("{}", pluralize("Houses", 2, false)); // Houses
}
```
*/
pub(crate) mod constants;
#[cfg(test)]
mod test;
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;
use std::sync::RwLock;
#[derive(Debug, Clone)]
struct WordRule {
rule: Regex,
placement: String,
}
// Macros to load data
macro_rules! load_regex_vec {
($rules: expr, $uncountable: expr) => {{
let mut vec = $rules
.iter()
.map(|(k, v)| WordRule {
rule: Regex::new(k).expect("Invalid regular expression"),
placement: v.to_string(),
})
.collect::<Vec<WordRule>>();
vec.append(
&mut $uncountable
.iter()
.map(|s| WordRule {
rule: Regex::new(s).expect("Invalid regular expression"),
placement: "$0".to_string(),
})
.collect::<Vec<WordRule>>(),
);
vec
}};
}
macro_rules! load_irregular_map {
($rules: expr, $map: expr) => {
$rules.iter().map($map).collect()
};
}
// Static references with RwLock
static IRREGULAR_SINGLES: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| {
RwLock::new(load_irregular_map!(constants::IRREGULAR_RULES, |(k, v)| (
k.to_string(),
v.to_string()
)))
});
static IRREGULAR_PLURALS: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| {
RwLock::new(load_irregular_map!(constants::IRREGULAR_RULES, |(k, v)| (
v.to_string(),
k.to_string()
)))
});
static PLURAL_RULES: Lazy<RwLock<Vec<WordRule>>> = Lazy::new(|| {
RwLock::new(load_regex_vec!(
constants::PLURAL_RULES,
constants::UNCOUNTABLE_REGEX_RULES
))
});
static SINGULAR_RULES: Lazy<RwLock<Vec<WordRule>>> = Lazy::new(|| {
RwLock::new(load_regex_vec!(
constants::SINGULAR_RULES,
constants::UNCOUNTABLE_REGEX_RULES
))
});
static UNCOUNTABLE_RULES: Lazy<RwLock<Vec<String>>> = Lazy::new(|| {
RwLock::new(
constants::UNCOUNTABLE_RULES
.iter()
.map(|s| s.to_string())
.collect(),
)
});
/// Add an irregular word definition.
///
/// # Examples
/// ```
/// pluralizer::add_irregular_rule("I".to_string(), "we".to_string());
///
/// let result = pluralizer::pluralize("I", 2, false); // we
/// ```
pub fn add_irregular_rule(singular: String, plural: String) {
{
let mut singles = IRREGULAR_SINGLES.write().unwrap();
singles.insert(singular.clone(), plural.clone());
}
{
let mut plurals = IRREGULAR_PLURALS.write().unwrap();
plurals.insert(plural, singular);
}
}
/// Add a pluralization rule to the collection.
///
/// The rule argument must be a regular expression string.
///
/// # Examples
/// ```
/// use regex::Regex;
///
/// pluralizer::add_plural_rule(Regex::new("(?i)(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$").unwrap(), "$1ices".to_string());
///
/// let result = pluralizer::pluralize("Vertex", 2, false); // Vertices
/// ```
pub fn add_plural_rule(rule: Regex, placement: String) {
let mut plural_rules = PLURAL_RULES.write().unwrap();
plural_rules.push(WordRule { rule, placement });
}
/// Add a singularization rule to the collection.
///
/// The rule argument must be a regular expression string.
///
/// # Examples
/// ```
/// use regex::Regex;
///
/// pluralizer::add_singular_rule(Regex::new("(?i)(matr|append)ices$").unwrap(), "$1ix".to_string());
///
/// let result = pluralizer::pluralize("Matrices", 1, false); // Matrix
/// ```
pub fn add_singular_rule(rule: Regex, placement: String) {
let mut singular_rules = SINGULAR_RULES.write().unwrap();
singular_rules.push(WordRule { rule, placement });
}
/// Uncountable rule struct
///
/// It's given as a parameter of [add_uncountable_rule](add_uncountable_rule) method
pub enum UncountableRule {
Regex(Regex),
String(String),
}
/// Add an uncountable word rule.
///
/// The rule can be either a word or a RegEx using [the rule struct](UncountableRule)
///
/// # Examples
/// ```
/// use pluralizer::UncountableRule;
///
/// pluralizer::add_uncountable_rule(UncountableRule::String("cash".to_string()));
///
/// let result = pluralizer::pluralize("Cash", 2, false); // Cash
/// ```
pub fn add_uncountable_rule(rule: UncountableRule) {
match rule {
UncountableRule::Regex(regex_rule) => {
// We add it as both plural and singular rules with the same placement
add_plural_rule(regex_rule.clone(), "$0".to_string());
add_singular_rule(regex_rule, "$0".to_string());
}
UncountableRule::String(word) => {
let mut uncountable = UNCOUNTABLE_RULES.write().unwrap();
uncountable.push(word.to_lowercase());
}
}
}
/// Pluralize or singularize a word based on the passed in count.
///
/// # Examples
/// ```
/// pluralizer::pluralize("House", 2, true); // 2 Houses
/// pluralizer::pluralize("Houses", 1, true); // 1 House
/// pluralizer::pluralize("House", 1, false); // House
/// pluralizer::pluralize("Houses", 2, false); // Houses
/// ```
pub fn pluralize(word: &str, count: isize, include_count: bool) -> String {
let pluralized = if count == 1 {
to_singular(word)
} else {
to_plural(word)
};
if include_count {
format!("{} {}", count, pluralized)
} else {
pluralized
}
}
fn to_singular(word: &str) -> String {
let irregular_plurals = IRREGULAR_PLURALS.read().unwrap();
let irregular_singles = IRREGULAR_SINGLES.read().unwrap();
let singular_rules = SINGULAR_RULES.read().unwrap();
let uncountable = UNCOUNTABLE_RULES.read().unwrap();
replace_word(
&irregular_plurals,
&irregular_singles,
&singular_rules,
&uncountable,
word,
)
}
fn to_plural(word: &str) -> String {
let irregular_singles = IRREGULAR_SINGLES.read().unwrap();
let irregular_plurals = IRREGULAR_PLURALS.read().unwrap();
let plural_rules = PLURAL_RULES.read().unwrap();
let uncountable = UNCOUNTABLE_RULES.read().unwrap();
replace_word(
&irregular_singles,
&irregular_plurals,
&plural_rules,
&uncountable,
word,
)
}
// This function tries to replace the given word by looking at:
// 1. The "replace_map" (e.g., known irregular singular->plural or vice versa)
// 2. The "keep_map" (the inverse map, e.g., known irregular plural->singular)
// 3. The set of regex-based rules
// 4. The list of uncountable words
fn replace_word(
replace_map: &HashMap<String, String>,
keep_map: &HashMap<String, String>,
rules: &[WordRule],
uncountable: &[String],
word: &str,
) -> String {
let token = word.to_lowercase();
// Check against the keep map
if keep_map.contains_key(&token) {
return restore_case(word, &token);
}
// Check if there's a direct replacement in the replace_map
if let Some(replacement) = replace_map.get(&token) {
return restore_case(word, replacement);
}
// Finally, check rules or see if it's uncountable
sanitize_word(&token, word, rules, uncountable)
}
// This performs the main logic for applying regex-based transformations,
// taking into account "uncountable" words
fn sanitize_word(token: &str, word: &str, rules: &[WordRule], uncountable: &[String]) -> String {
// If empty or uncountable, return as-is
if token.is_empty() || uncountable.contains(&token.to_owned()) {
return word.to_string();
}
// Iterate rules from last to first
for word_rule in rules.iter().rev() {
if word_rule.rule.is_match(word) {
let replaced = word_rule.rule.replace(word, |caps: &regex::Captures| {
let mut out = restore_case(word, &word_rule.placement);
for (i, m) in caps.iter().filter_map(|m| m).enumerate() {
out = out.replace(&format!("${}", i), &restore_case(word, m.as_str()));
}
out
});
// Remove leftover `$` if any
return remove_dollar_escapes(&replaced);
}
}
word.to_string()
}
// If the replaced string has `$` leftover, remove them
fn remove_dollar_escapes(s: &str) -> String {
let mut skip = false;
let mut result = String::new();
for c in s.chars() {
if skip {
skip = false;
continue;
}
if c == '$' {
skip = true;
continue;
}
result.push(c);
}
result
}
// Restores the case from `word` into `token`
fn restore_case(word: &str, token: &str) -> String {
if word == token {
return token.to_string();
}
if word == word.to_lowercase() {
return token.to_lowercase();
}
if word == word.to_uppercase() {
return token.to_uppercase();
}
if let Some(first) = word.chars().next() {
if first.is_uppercase() {
if let Some(token_first) = token.chars().next() {
let remainder = if token.len() > 1 { &token[1..] } else { "" };
return format!("{}{}", token_first, remainder);
}
}
}
token.to_lowercase()
}
+723
View File
@@ -0,0 +1,723 @@
const BASIC_TESTS: &'static [[&str; 2]] = &[
// Uncountables.
["firmware", "firmware"],
["fish", "fish"],
["media", "media"],
["moose", "moose"],
["police", "police"],
["sheep", "sheep"],
["series", "series"],
["agenda", "agenda"],
["news", "news"],
["reindeer", "reindeer"],
["starfish", "starfish"],
["smallpox", "smallpox"],
["tennis", "tennis"],
["chickenpox", "chickenpox"],
["shambles", "shambles"],
["garbage", "garbage"],
["you", "you"],
["wildlife", "wildlife"],
["Staff", "Staff"],
["STAFF", "STAFF"],
["turquois", "turquois"],
["carnivorous", "carnivorous"],
["only", "only"],
["aircraft", "aircraft"],
// Latin.
["veniam", "veniam"],
// Pluralization.
["this", "these"],
["that", "those"],
["is", "are"],
["man", "men"],
["superman", "supermen"],
["ox", "oxen"],
["bus", "buses"],
["airbus", "airbuses"],
["railbus", "railbuses"],
["wife", "wives"],
["guest", "guests"],
["thing", "things"],
["mess", "messes"],
["guess", "guesses"],
["person", "people"],
["meteor", "meteors"],
["chateau", "chateaus"],
["lap", "laps"],
["cough", "coughs"],
["death", "deaths"],
["coach", "coaches"],
["boy", "boys"],
["toy", "toys"],
["guy", "guys"],
["girl", "girls"],
["chair", "chairs"],
["toe", "toes"],
["tiptoe", "tiptoes"],
["tomato", "tomatoes"],
["potato", "potatoes"],
["tornado", "tornadoes"],
["torpedo", "torpedoes"],
["hero", "heroes"],
["superhero", "superheroes"],
["volcano", "volcanoes"],
["canto", "cantos"],
["hetero", "heteros"],
["photo", "photos"],
["portico", "porticos"],
["quarto", "quartos"],
["kimono", "kimonos"],
["albino", "albinos"],
["cherry", "cherries"],
["piano", "pianos"],
["pro", "pros"],
["combo", "combos"],
["turbo", "turbos"],
["bar", "bars"],
["crowbar", "crowbars"],
["van", "vans"],
["tobacco", "tobaccos"],
["afficionado", "afficionados"],
["monkey", "monkeys"],
["neutrino", "neutrinos"],
["rhino", "rhinos"],
["steno", "stenos"],
["latino", "latinos"],
["casino", "casinos"],
["avocado", "avocados"],
["commando", "commandos"],
["tuxedo", "tuxedos"],
["speedo", "speedos"],
["dingo", "dingoes"],
["echo", "echoes"],
["nacho", "nachos"],
["motto", "mottos"],
["psycho", "psychos"],
["poncho", "ponchos"],
["pass", "passes"],
["ghetto", "ghettos"],
["mango", "mangos"],
["lady", "ladies"],
["bath", "baths"],
["professional", "professionals"],
["dwarf", "dwarves"], // Proper spelling is "dwarfs".
["encyclopedia", "encyclopedias"],
["louse", "lice"],
["roof", "roofs"],
["woman", "women"],
["formula", "formulas"],
["polyhedron", "polyhedra"],
["index", "indices"], // Maybe "indexes".
["matrix", "matrices"],
["vertex", "vertices"],
["axe", "axes"], // Could also be plural of "ax".
["pickaxe", "pickaxes"],
["crisis", "crises"],
["criterion", "criteria"],
["phenomenon", "phenomena"],
["addendum", "addenda"],
["datum", "data"],
["forum", "forums"],
["millennium", "millennia"],
["alumnus", "alumni"],
["medium", "mediums"],
["census", "censuses"],
["genus", "genera"],
["dogma", "dogmata"],
["life", "lives"],
["hive", "hives"],
["kiss", "kisses"],
["dish", "dishes"],
["human", "humans"],
["knife", "knives"],
["phase", "phases"],
["judge", "judges"],
["class", "classes"],
["witch", "witches"],
["church", "churches"],
["massage", "massages"],
["prospectus", "prospectuses"],
["syllabus", "syllabi"],
["viscus", "viscera"],
["cactus", "cacti"],
["hippopotamus", "hippopotamuses"],
["octopus", "octopuses"],
["platypus", "platypuses"],
["kangaroo", "kangaroos"],
["atlas", "atlases"],
["stigma", "stigmata"],
["schema", "schemata"],
["phenomenon", "phenomena"],
["diagnosis", "diagnoses"],
["mongoose", "mongooses"],
["mouse", "mice"],
["liturgist", "liturgists"],
["box", "boxes"],
["gas", "gases"],
["self", "selves"],
["chief", "chiefs"],
["quiz", "quizzes"],
["child", "children"],
["shelf", "shelves"],
["fizz", "fizzes"],
["tooth", "teeth"],
["thief", "thieves"],
["day", "days"],
["loaf", "loaves"],
["fix", "fixes"],
["spy", "spies"],
["vertebra", "vertebrae"],
["clock", "clocks"],
["lap", "laps"],
["cuff", "cuffs"],
["leaf", "leaves"],
["calf", "calves"],
["moth", "moths"],
["mouth", "mouths"],
["house", "houses"],
["proof", "proofs"],
["hoof", "hooves"],
["elf", "elves"],
["turf", "turfs"],
["craft", "crafts"],
["die", "dice"],
["penny", "pennies"],
["campus", "campuses"],
["virus", "viri"],
["iris", "irises"],
["bureau", "bureaus"],
["kiwi", "kiwis"],
["wiki", "wikis"],
["igloo", "igloos"],
["ninja", "ninjas"],
["pizza", "pizzas"],
["kayak", "kayaks"],
["canoe", "canoes"],
["tiding", "tidings"],
["pea", "peas"],
["drive", "drives"],
["nose", "noses"],
["movie", "movies"],
["status", "statuses"],
["alias", "aliases"],
["memorandum", "memorandums"],
["language", "languages"],
["plural", "plurals"],
["word", "words"],
["multiple", "multiples"],
["reward", "rewards"],
["sandwich", "sandwiches"],
["subway", "subways"],
["direction", "directions"],
["land", "lands"],
["row", "rows"],
["grow", "grows"],
["flow", "flows"],
["rose", "roses"],
["raise", "raises"],
["friend", "friends"],
["follower", "followers"],
["male", "males"],
["nail", "nails"],
["sex", "sexes"],
["tape", "tapes"],
["ruler", "rulers"],
["king", "kings"],
["queen", "queens"],
["zero", "zeros"],
["quest", "quests"],
["goose", "geese"],
["foot", "feet"],
["ex", "exes"],
["reflex", "reflexes"],
["heat", "heats"],
["train", "trains"],
["test", "tests"],
["pie", "pies"],
["fly", "flies"],
["eye", "eyes"],
["lie", "lies"],
["node", "nodes"],
["trade", "trades"],
["chinese", "chinese"],
["please", "pleases"],
["japanese", "japanese"],
["regex", "regexes"],
["license", "licenses"],
["zebra", "zebras"],
["general", "generals"],
["corps", "corps"],
["pliers", "pliers"],
["flyer", "flyers"],
["scissors", "scissors"],
["fireman", "firemen"],
["chirp", "chirps"],
["harp", "harps"],
["corpse", "corpses"],
["dye", "dyes"],
["move", "moves"],
["zombie", "zombies"],
["variety", "varieties"],
["talkie", "talkies"],
["walkie-talkie", "walkie-talkies"],
["groupie", "groupies"],
["goonie", "goonies"],
["lassie", "lassies"],
["genie", "genies"],
["foodie", "foodies"],
["faerie", "faeries"],
["collie", "collies"],
["obloquy", "obloquies"],
["looey", "looies"],
["osprey", "ospreys"],
["cover", "covers"],
["tie", "ties"],
["groove", "grooves"],
["bee", "bees"],
["ave", "aves"],
["wave", "waves"],
["wolf", "wolves"],
["airwave", "airwaves"],
["archive", "archives"],
["arch", "arches"],
["dive", "dives"],
["aftershave", "aftershaves"],
["cave", "caves"],
["grave", "graves"],
["gift", "gifts"],
["nerve", "nerves"],
["nerd", "nerds"],
["carve", "carves"],
["rave", "raves"],
["scarf", "scarves"],
["sale", "sales"],
["sail", "sails"],
["swerve", "swerves"],
["love", "loves"],
["dove", "doves"],
["glove", "gloves"],
["wharf", "wharves"],
["valve", "valves"],
["werewolf", "werewolves"],
["view", "views"],
["emu", "emus"],
["menu", "menus"],
["wax", "waxes"],
["fax", "faxes"],
["nut", "nuts"],
["crust", "crusts"],
["lemma", "lemmata"],
["anathema", "anathemata"],
["analysis", "analyses"],
["locus", "loci"],
["uterus", "uteri"],
["curriculum", "curricula"],
["quorum", "quora"],
["genius", "geniuses"],
["flower", "flowers"],
["crash", "crashes"],
["soul", "souls"],
["career", "careers"],
["planet", "planets"],
["son", "sons"],
["sun", "suns"],
["drink", "drinks"],
["diploma", "diplomas"],
["dilemma", "dilemmas"],
["grandma", "grandmas"],
["no", "nos"],
["yes", "yeses"],
["employ", "employs"],
["employee", "employees"],
["history", "histories"],
["story", "stories"],
["purchase", "purchases"],
["order", "orders"],
["key", "keys"],
["bomb", "bombs"],
["city", "cities"],
["sanity", "sanities"],
["ability", "abilities"],
["activity", "activities"],
["cutie", "cuties"],
["validation", "validations"],
["floaty", "floaties"],
["nicety", "niceties"],
["goalie", "goalies"],
["crawly", "crawlies"],
["duty", "duties"],
["scrutiny", "scrutinies"],
["deputy", "deputies"],
["beauty", "beauties"],
["bank", "banks"],
["family", "families"],
["tally", "tallies"],
["ally", "allies"],
["alley", "alleys"],
["valley", "valleys"],
["medley", "medleys"],
["melody", "melodies"],
["trolly", "trollies"],
["thunk", "thunks"],
["koala", "koalas"],
["special", "specials"],
["book", "books"],
["knob", "knobs"],
["crab", "crabs"],
["plough", "ploughs"],
["high", "highs"],
["low", "lows"],
["hiccup", "hiccups"],
["bonus", "bonuses"],
["circus", "circuses"],
["abacus", "abacuses"],
["phobia", "phobias"],
["case", "cases"],
["lace", "laces"],
["trace", "traces"],
["mage", "mages"],
["lotus", "lotuses"],
["motorbus", "motorbuses"],
["cutlas", "cutlases"],
["tequila", "tequilas"],
["liar", "liars"],
["delta", "deltas"],
["visa", "visas"],
["flea", "fleas"],
["favela", "favelas"],
["cobra", "cobras"],
["finish", "finishes"],
["gorilla", "gorillas"],
["mass", "masses"],
["face", "faces"],
["rabbit", "rabbits"],
["adventure", "adventures"],
["breeze", "breezes"],
["brew", "brews"],
["canopy", "canopies"],
["copy", "copies"],
["spy", "spies"],
["cave", "caves"],
["charge", "charges"],
["cinema", "cinemas"],
["coffee", "coffees"],
["favourite", "favourites"],
["themself", "themselves"],
["country", "countries"],
["issue", "issues"],
["authority", "authorities"],
["force", "forces"],
["objective", "objectives"],
["present", "presents"],
["industry", "industries"],
["believe", "believes"],
["century", "centuries"],
["category", "categories"],
["eve", "eves"],
["fee", "fees"],
["gene", "genes"],
["try", "tries"],
["currency", "currencies"],
["pose", "poses"],
["cheese", "cheeses"],
["clue", "clues"],
["cheer", "cheers"],
["litre", "litres"],
["money", "monies"],
["attorney", "attorneys"],
["balcony", "balconies"],
["cockney", "cockneys"],
["donkey", "donkeys"],
["honey", "honeys"],
["smiley", "smilies"],
["survey", "surveys"],
["whiskey", "whiskeys"],
["whisky", "whiskies"],
["volley", "volleys"],
["tongue", "tongues"],
["suit", "suits"],
["suite", "suites"],
["cruise", "cruises"],
["eave", "eaves"],
["consultancy", "consultancies"],
["pouch", "pouches"],
["wallaby", "wallabies"],
["abyss", "abysses"],
["weekly", "weeklies"],
["whistle", "whistles"],
["utilise", "utilises"],
["utilize", "utilizes"],
["mercy", "mercies"],
["mercenary", "mercenaries"],
["take", "takes"],
["flush", "flushes"],
["gate", "gates"],
["evolve", "evolves"],
["slave", "slaves"],
["native", "natives"],
["revolve", "revolves"],
["twelve", "twelves"],
["sleeve", "sleeves"],
["subjective", "subjectives"],
["stream", "streams"],
["beam", "beams"],
["foam", "foams"],
["callus", "calluses"],
["use", "uses"],
["beau", "beaus"],
["gateau", "gateaus"],
["fetus", "fetuses"],
["luau", "luaus"],
["pilau", "pilaus"],
["shoe", "shoes"],
["sandshoe", "sandshoes"],
["zeus", "zeuses"],
["nucleus", "nuclei"],
["sky", "skies"],
["beach", "beaches"],
["brush", "brushes"],
["hoax", "hoaxes"],
["scratch", "scratches"],
["nanny", "nannies"],
["negro", "negroes"],
["taco", "tacos"],
["cafe", "cafes"],
["cave", "caves"],
["giraffe", "giraffes"],
["goodwife", "goodwives"],
["housewife", "housewives"],
["safe", "safes"],
["save", "saves"],
["pocketknife", "pocketknives"],
["tartufe", "tartufes"],
["tartuffe", "tartuffes"],
["truffle", "truffles"],
["jefe", "jefes"],
["agrafe", "agrafes"],
["agraffe", "agraffes"],
["bouffe", "bouffes"],
["carafe", "carafes"],
["chafe", "chafes"],
["pouffe", "pouffes"],
["pouf", "poufs"],
["piaffe", "piaffes"],
["gaffe", "gaffes"],
["executive", "executives"],
["cove", "coves"],
["dove", "doves"],
["fave", "faves"],
["positive", "positives"],
["solve", "solves"],
["trove", "troves"],
["treasure", "treasures"],
["suave", "suaves"],
["bluff", "bluffs"],
["half", "halves"],
["knockoff", "knockoffs"],
["handkerchief", "handkerchiefs"],
["reed", "reeds"],
["reef", "reefs"],
["yourself", "yourselves"],
["sunroof", "sunroofs"],
["plateau", "plateaus"],
["radius", "radii"],
["stratum", "strata"],
["stratus", "strati"],
["focus", "foci"],
["fungus", "fungi"],
["appendix", "appendices"],
["seraph", "seraphim"],
["cherub", "cherubim"],
["memo", "memos"],
["cello", "cellos"],
["automaton", "automata"],
["button", "buttons"],
["crayon", "crayons"],
["captive", "captives"],
["abrasive", "abrasives"],
["archive", "archives"],
["additive", "additives"],
["hive", "hives"],
["beehive", "beehives"],
["olive", "olives"],
["black olive", "black olives"],
["chive", "chives"],
["adjective", "adjectives"],
["cattle drive", "cattle drives"],
["explosive", "explosives"],
["executive", "executives"],
["negative", "negatives"],
["fugitive", "fugitives"],
["progressive", "progressives"],
["laxative", "laxatives"],
["incentive", "incentives"],
["genesis", "geneses"],
["surprise", "surprises"],
["enterprise", "enterprises"],
["relative", "relatives"],
["positive", "positives"],
["perspective", "perspectives"],
["superlative", "superlatives"],
["afterlife", "afterlives"],
["native", "natives"],
["detective", "detectives"],
["collective", "collectives"],
["lowlife", "lowlives"],
["low-life", "low-lives"],
["strife", "strifes"],
["pony", "ponies"],
["phony", "phonies"],
["felony", "felonies"],
["colony", "colonies"],
["symphony", "symphonies"],
["semicolony", "semicolonies"],
["radiotelephony", "radiotelephonies"],
["company", "companies"],
["ceremony", "ceremonies"],
["carnivore", "carnivores"],
["emphasis", "emphases"],
["abuse", "abuses"],
["ass", "asses"],
["mile", "miles"],
["consensus", "consensuses"],
["coatdress", "coatdresses"],
["courthouse", "courthouses"],
["playhouse", "playhouses"],
["crispness", "crispnesses"],
["racehorse", "racehorses"],
["greatness", "greatnesses"],
["demon", "demons"],
["lemon", "lemons"],
["pokemon", "pokemon"],
["pokémon", "pokémon"],
["christmas", "christmases"],
["zymase", "zymases"],
["accomplice", "accomplices"],
["amice", "amices"],
["titmouse", "titmice"],
["slice", "slices"],
["base", "bases"],
["database", "databases"],
["rise", "rises"],
["uprise", "uprises"],
["size", "sizes"],
["prize", "prizes"],
["booby", "boobies"],
["hobby", "hobbies"],
["baby", "babies"],
["cookie", "cookies"],
["budgie", "budgies"],
["calorie", "calories"],
["brownie", "brownies"],
["lolly", "lollies"],
["hippie", "hippies"],
["smoothie", "smoothies"],
["techie", "techies"],
["specie", "species"],
["quickie", "quickies"],
["pixie", "pixies"],
["rotisserie", "rotisseries"],
["porkpie", "porkpies"],
["newbie", "newbies"],
["veggie", "veggies"],
["bourgeoisie", "bourgeoisies"],
["party", "parties"],
["apology", "apologies"],
["ancestry", "ancestries"],
["anomaly", "anomalies"],
["anniversary", "anniversaries"],
["battery", "batteries"],
["nappy", "nappies"],
["hanky", "hankies"],
["junkie", "junkies"],
["hogtie", "hogties"],
["footsie", "footsies"],
["curry", "curries"],
["fantasy", "fantasies"],
["housefly", "houseflies"],
["falsy", "falsies"],
["doggy", "doggies"],
["carny", "carnies"],
["cabby", "cabbies"],
["charlie", "charlies"],
["bookie", "bookies"],
["auntie", "aunties"],
// Prototype inheritance.
["constructor", "constructors"],
// Non-standard case.
["randomWord", "randomWords"],
["camelCase", "camelCases"],
["PascalCase", "PascalCases"],
["Alumnus", "Alumni"],
["CHICKEN", "CHICKENS"],
["日本語", "日本語"],
["한국", "한국"],
["中文", "中文"],
["اللغة العربية", "اللغة العربية"],
["四 chicken", "四 chickens"],
["Order2", "Order2s"],
["Work Order2", "Work Order2s"],
["SoundFX2", "SoundFX2s"],
["oDonald", "oDonalds"],
];
const SINGULAR_TESTS: &'static [[&str; 2]] = &[
["dingo", "dingos"],
["mango", "mangoes"],
["echo", "echos"],
["ghetto", "ghettoes"],
["nucleus", "nucleuses"],
["bureau", "bureaux"],
["seraph", "seraphs"],
];
const PLURAL_TESTS: &'static [[&str; 2]] = &[
["plateaux", "plateaux"],
["axis", "axes"],
["basis", "bases"],
["automatum", "automata"],
["thou", "you"],
["axiS", "axes"],
["passerby", "passersby"],
];
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
fn can_convert_to_plural() {
let mut tests = BASIC_TESTS.to_vec();
tests.extend_from_slice(PLURAL_TESTS);
for [singular, plural] in tests {
// Make sure the word stays pluralized.
assert_eq!(pluralize(plural, 5, false), plural);
// Make sure the word becomes a plural.
if singular != plural {
assert_eq!(pluralize(singular, 5, false), plural);
}
}
}
#[test]
fn can_convert_to_singular() {
let mut tests = BASIC_TESTS.to_vec();
tests.extend_from_slice(SINGULAR_TESTS);
for [singular, plural] in tests {
// Make sure the word stays singular.
assert_eq!(pluralize(singular, 1, false), singular);
// Make sure the word becomes singular.
if singular != plural {
assert_eq!(pluralize(plural, 1, false), singular);
}
}
}
}