#![allow(dead_code)]
pub fn snake(input: &str) -> String {
let mut out = String::with_capacity(input.len() + 4);
let chars: Vec<char> = input.chars().collect();
for (index, ch) in chars.iter().enumerate() {
if *ch == '-' || *ch == ' ' || *ch == '.' {
out.push('_');
continue;
}
if ch.is_uppercase() {
let previous_is_lower = index > 0 && chars[index - 1].is_lowercase();
let next_is_lower = chars.get(index + 1).is_some_and(|c| c.is_lowercase());
let previous_is_upper = index > 0 && chars[index - 1].is_uppercase();
if previous_is_lower || (previous_is_upper && next_is_lower) {
out.push('_');
}
out.extend(ch.to_lowercase());
continue;
}
out.push(*ch);
}
out.trim_matches('_').to_string()
}
pub fn pascal(input: &str) -> String {
snake(input)
.split('_')
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
})
.collect()
}
pub fn camel(input: &str) -> String {
let pascal = pascal(input);
let mut chars = pascal.chars();
match chars.next() {
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}
pub fn kebab(input: &str) -> String {
snake(input).replace('_', "-")
}
pub fn plural(input: &str) -> String {
let lower = input.to_lowercase();
const IRREGULAR: &[(&str, &str)] = &[
("person", "people"),
("child", "children"),
("man", "men"),
("woman", "women"),
("tooth", "teeth"),
("foot", "feet"),
("mouse", "mice"),
("goose", "geese"),
];
for (singular, plural) in IRREGULAR {
if lower.ends_with(singular) {
return format!("{}{plural}", &input[..input.len() - singular.len()]);
}
}
if lower.ends_with("s") && !lower.ends_with("us") && !lower.ends_with("ss") {
return input.to_string();
}
if let Some(stem) = lower.strip_suffix('y')
&& !stem.ends_with(['a', 'e', 'i', 'o', 'u']) {
return format!("{}ies", &input[..input.len() - 1]);
}
if lower.ends_with(['s', 'x', 'z']) || lower.ends_with("ch") || lower.ends_with("sh") {
return format!("{input}es");
}
format!("{input}s")
}
pub fn table_name(model: &str) -> String {
plural(&snake(model))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_between_cases() {
assert_eq!(snake("UserProfile"), "user_profile");
assert_eq!(snake("user-profile"), "user_profile");
assert_eq!(snake("HTTPServer"), "http_server");
assert_eq!(snake("already_snake"), "already_snake");
assert_eq!(pascal("user_profile"), "UserProfile");
assert_eq!(pascal("user-profile"), "UserProfile");
assert_eq!(camel("user_profile"), "userProfile");
assert_eq!(kebab("UserProfile"), "user-profile");
}
#[test]
fn pluralizes_the_common_shapes() {
assert_eq!(plural("post"), "posts");
assert_eq!(plural("category"), "categories");
assert_eq!(plural("day"), "days");
assert_eq!(plural("box"), "boxes");
assert_eq!(plural("dish"), "dishes");
assert_eq!(plural("person"), "people");
assert_eq!(plural("status"), "statuses");
assert_eq!(plural("posts"), "posts");
}
#[test]
fn derives_table_names_from_models() {
assert_eq!(table_name("UserProfile"), "user_profiles");
assert_eq!(table_name("Category"), "categories");
}
}