158 lines
4.9 KiB
Rust
158 lines
4.9 KiB
Rust
pub(crate) fn to_snake_case(name: &str) -> String {
|
|
let mut result = String::with_capacity(name.len() + 4);
|
|
let mut chars = name.chars().peekable();
|
|
|
|
while let Some(current) = chars.next() {
|
|
if let Some(_next) = chars.peek() {
|
|
if current.is_uppercase() {
|
|
if !result.is_empty() {
|
|
result.push('_');
|
|
}
|
|
result.push(current.to_lowercase().next().unwrap());
|
|
} else {
|
|
result.push(current);
|
|
}
|
|
} else {
|
|
// Last character
|
|
result.push(current.to_lowercase().next().unwrap());
|
|
}
|
|
}
|
|
|
|
pluralize(&result)
|
|
}
|
|
pub(crate) fn pluralize(word: &str) -> String {
|
|
if word.is_empty() {
|
|
return String::new();
|
|
}
|
|
|
|
// Handle possessives and existing plurals
|
|
if word.ends_with("'s") || word.ends_with("'") ||
|
|
word.ends_with("s's") || word.ends_with("s'") {
|
|
return word.to_string();
|
|
}
|
|
|
|
// Compound words (handle last word)
|
|
if word.contains(" of ") {
|
|
let parts: Vec<&str> = word.split(" of ").collect();
|
|
return format!("{} of {}", pluralize(parts[0]), parts[1]);
|
|
}
|
|
|
|
// Compound words with hyphens
|
|
if word.contains('-') {
|
|
let parts: Vec<&str> = word.split('-').collect();
|
|
return format!("{}-{}",
|
|
pluralize(parts[0]),
|
|
parts[1..].join("-")
|
|
);
|
|
}
|
|
|
|
// Invariant words (same singular and plural)
|
|
match word.to_lowercase().as_str() {
|
|
"sheep" | "deer" | "moose" | "swine" | "buffalo" | "fish" | "trout" |
|
|
"salmon" | "pike" | "aircraft" | "series" | "species" | "means" |
|
|
"crossroads" | "swiss" | "portuguese" | "vietnamese" | "japanese" |
|
|
"chinese" | "chassis" | "corps" | "headquarters" | "diabetes" |
|
|
"news" | "odds" | "innings" => return word.to_string(),
|
|
_ => {}
|
|
}
|
|
|
|
// Irregular plurals
|
|
match word.to_lowercase().as_str() {
|
|
// People
|
|
"person" => "people",
|
|
"man" => "men",
|
|
"woman" => "women",
|
|
"child" => "children",
|
|
"tooth" => "teeth",
|
|
"foot" => "feet",
|
|
"mouse" => "mice",
|
|
"louse" => "lice",
|
|
"goose" => "geese",
|
|
"ox" => "oxen",
|
|
|
|
// Latin/Greek endings
|
|
"alumnus" => "alumni",
|
|
"alga" => "algae",
|
|
"larva" => "larvae",
|
|
"vertex" => "vertices",
|
|
"index" => "indices",
|
|
"matrix" => "matrices",
|
|
"criterion" => "criteria",
|
|
"phenomenon" => "phenomena",
|
|
"datum" => "data",
|
|
"medium" => "media",
|
|
"analysis" => "analyses",
|
|
"thesis" => "theses",
|
|
"crisis" => "crises",
|
|
"appendix" => "appendices",
|
|
"stimulus" => "stimuli",
|
|
"radius" => "radii",
|
|
"axis" => "axes",
|
|
"hypothesis" => "hypotheses",
|
|
"basis" => "bases",
|
|
"diagnosis" => "diagnoses",
|
|
"formula" => "formulae",
|
|
"fungus" => "fungi",
|
|
"nucleus" => "nuclei",
|
|
"syllabus" => "syllabi",
|
|
"focus" => "foci",
|
|
"cactus" => "cacti",
|
|
"bacterium" => "bacteria",
|
|
"curriculum" => "curricula",
|
|
"memorandum" => "memoranda",
|
|
"millennium" => "millennia",
|
|
|
|
_ => return apply_general_rules(word),
|
|
}.to_string()
|
|
}
|
|
|
|
fn apply_general_rules(word: &str) -> String {
|
|
// Words ending in -o
|
|
if word.ends_with('o') {
|
|
match word.to_lowercase().as_str() {
|
|
// -o → -oes
|
|
w if matches!(w, "hero" | "potato" | "tomato" | "echo" |
|
|
"tornado" | "torpedo" | "veto" | "mosquito" |
|
|
"volcano" | "buffalo" | "domino" | "embargo") => {
|
|
return format!("{}es", word);
|
|
}
|
|
// -o → -os
|
|
_ => return format!("{}s", word),
|
|
}
|
|
}
|
|
|
|
// Words ending in -f or -fe
|
|
if word.ends_with('f') {
|
|
match word.to_lowercase().as_str() {
|
|
w if matches!(w, "roof" | "belief" | "chief" | "reef") => {
|
|
return format!("{}s", word);
|
|
}
|
|
_ => return format!("{}ves", &word[..word.len() - 1]),
|
|
}
|
|
}
|
|
if word.ends_with("fe") {
|
|
return format!("{}ves", &word[..word.len() - 2]);
|
|
}
|
|
|
|
// Words ending in -y
|
|
if word.ends_with('y') {
|
|
let chars: Vec<char> = word.chars().collect();
|
|
if chars.len() > 1 {
|
|
let before_y = chars[chars.len() - 2];
|
|
if !matches!(before_y, 'a' | 'e' | 'i' | 'o' | 'u') {
|
|
return format!("{}ies", &word[..word.len() - 1]);
|
|
}
|
|
}
|
|
return format!("{}s", word);
|
|
}
|
|
|
|
// Words ending in sibilants (-s, -ss, -sh, -ch, -x, -z)
|
|
if word.ends_with('s') || word.ends_with("ss") ||
|
|
word.ends_with("sh") || word.ends_with("ch") ||
|
|
word.ends_with('x') || word.ends_with('z') {
|
|
return format!("{}es", word);
|
|
}
|
|
|
|
// Default case: add 's'
|
|
format!("{}s", word)
|
|
} |