pub fn snake_case(input: &str) -> String {
let mut out = String::with_capacity(input.len() + 4);
for (i, ch) in input.chars().enumerate() {
if ch.is_uppercase() {
if i != 0 {
out.push('_');
}
for lower in ch.to_lowercase() {
out.push(lower);
}
} else {
out.push(ch);
}
}
out
}
pub fn pluralize(word: &str) -> String {
if word.ends_with('y')
&& !word.ends_with("ay")
&& !word.ends_with("ey")
&& !word.ends_with("oy")
&& !word.ends_with("uy")
{
format!("{}ies", &word[..word.len() - 1])
} else if word.ends_with('s')
|| word.ends_with("x")
|| word.ends_with("ch")
|| word.ends_with("sh")
{
format!("{word}es")
} else {
format!("{word}s")
}
}
pub fn table_name(model: &str) -> String {
pluralize(&snake_case(model))
}
pub fn pascal_case(input: &str) -> String {
let mut out = String::with_capacity(input.len());
for word in input.split('_').filter(|w| !w.is_empty()) {
let mut chars = word.chars();
if let Some(first) = chars.next() {
out.extend(first.to_uppercase());
out.push_str(chars.as_str());
}
}
out
}
pub fn singularize(word: &str) -> String {
if let Some(stem) = word.strip_suffix("ies") {
return format!("{stem}y");
}
if let Some(stem) = word.strip_suffix("es") {
if stem.ends_with('s')
|| stem.ends_with('x')
|| stem.ends_with("ch")
|| stem.ends_with("sh")
{
return stem.to_string();
}
}
if let Some(stem) = word.strip_suffix('s') {
return stem.to_string();
}
word.to_string()
}
pub fn model_name(module: &str) -> String {
pascal_case(&singularize(module))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snake_cases() {
assert_eq!(snake_case("User"), "user");
assert_eq!(snake_case("BlogPost"), "blog_post");
assert_eq!(snake_case("OAuthToken"), "o_auth_token");
}
#[test]
fn pluralizes() {
assert_eq!(pluralize("user"), "users");
assert_eq!(pluralize("category"), "categories");
assert_eq!(pluralize("box"), "boxes");
assert_eq!(pluralize("day"), "days");
}
#[test]
fn builds_table_names() {
assert_eq!(table_name("User"), "users");
assert_eq!(table_name("BlogPost"), "blog_posts");
assert_eq!(table_name("Category"), "categories");
}
#[test]
fn pascal_cases() {
assert_eq!(pascal_case("user"), "User");
assert_eq!(pascal_case("blog_post"), "BlogPost");
assert_eq!(pascal_case("o_auth_token"), "OAuthToken");
}
#[test]
fn singularizes() {
assert_eq!(singularize("users"), "user");
assert_eq!(singularize("categories"), "category");
assert_eq!(singularize("boxes"), "box");
assert_eq!(singularize("dishes"), "dish");
assert_eq!(singularize("days"), "day");
assert_eq!(singularize("posts"), "post");
}
#[test]
fn model_name_inverts_table_name() {
for model in ["User", "BlogPost", "Category", "OAuthToken", "Product"] {
assert_eq!(model_name(&table_name(model)), model, "round-trip {model}");
}
}
}