sqlx-record/sqlx-record-derive/src/string_utils.rs

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)
}