pub fn singularize_and_capitalize(s: &str) -> String {
let singular = if s.ends_with("ies") && s.len() > 3 {
format!("{}y", &s[..s.len() - 3])
} else if s.ends_with("es") && s.len() > 2 {
let base = &s[..s.len() - 2];
if base.ends_with("ss")
|| base.ends_with("sh")
|| base.ends_with("ch")
|| base.ends_with('x')
{
base.to_string()
} else {
s[..s.len() - 1].to_string()
}
} else if s.ends_with('s') && s.len() > 1 {
s[..s.len() - 1].to_string()
} else {
s.to_string()
};
singular
.split('_')
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_plurals() {
assert_eq!(singularize_and_capitalize("users"), "User");
assert_eq!(singularize_and_capitalize("posts"), "Post");
assert_eq!(singularize_and_capitalize("items"), "Item");
}
#[test]
fn test_ies_plurals() {
assert_eq!(singularize_and_capitalize("categories"), "Category");
assert_eq!(singularize_and_capitalize("entries"), "Entry");
assert_eq!(singularize_and_capitalize("queries"), "Query");
}
#[test]
fn test_es_plurals() {
assert_eq!(singularize_and_capitalize("boxes"), "Box");
assert_eq!(singularize_and_capitalize("classes"), "Class");
assert_eq!(singularize_and_capitalize("matches"), "Match");
assert_eq!(singularize_and_capitalize("bushes"), "Bush");
}
#[test]
fn test_snake_case() {
assert_eq!(singularize_and_capitalize("user_posts"), "UserPost");
assert_eq!(singularize_and_capitalize("alias_contexts"), "AliasContext");
assert_eq!(singularize_and_capitalize("my_items"), "MyItem");
}
#[test]
fn test_already_singular() {
assert_eq!(singularize_and_capitalize("user"), "User");
assert_eq!(singularize_and_capitalize("data"), "Data");
assert_eq!(singularize_and_capitalize("info"), "Info");
}
#[test]
fn test_edge_cases() {
assert_eq!(singularize_and_capitalize("s"), "S");
assert_eq!(singularize_and_capitalize(""), "");
assert_eq!(singularize_and_capitalize("_"), "");
assert_eq!(singularize_and_capitalize("__users__"), "Users");
}
#[test]
fn test_already_pascal_case() {
assert_eq!(singularize_and_capitalize("User"), "User");
assert_eq!(singularize_and_capitalize("UserPost"), "UserPost");
}
#[test]
fn test_single_letter() {
assert_eq!(singularize_and_capitalize("a"), "A");
assert_eq!(singularize_and_capitalize("x"), "X");
}
#[test]
fn test_two_letters() {
assert_eq!(singularize_and_capitalize("us"), "U");
assert_eq!(singularize_and_capitalize("ax"), "Ax");
}
#[test]
fn test_words_ending_in_ss() {
assert_eq!(singularize_and_capitalize("class"), "Clas");
assert_eq!(singularize_and_capitalize("pass"), "Pas");
assert_eq!(singularize_and_capitalize("boss"), "Bos");
assert_eq!(singularize_and_capitalize("classes"), "Class");
}
#[test]
fn test_words_ending_in_us() {
assert_eq!(singularize_and_capitalize("status"), "Statu");
assert_eq!(singularize_and_capitalize("bonus"), "Bonu");
}
#[test]
fn test_words_ending_in_is() {
assert_eq!(singularize_and_capitalize("basis"), "Basi");
assert_eq!(singularize_and_capitalize("analysis"), "Analysi");
}
#[test]
fn test_foxes_type_words() {
assert_eq!(singularize_and_capitalize("foxes"), "Fox");
assert_eq!(singularize_and_capitalize("taxes"), "Tax");
assert_eq!(singularize_and_capitalize("indexes"), "Index");
}
#[test]
fn test_wishes_type_words() {
assert_eq!(singularize_and_capitalize("wishes"), "Wish");
assert_eq!(singularize_and_capitalize("dishes"), "Dish");
}
#[test]
fn test_watches_type_words() {
assert_eq!(singularize_and_capitalize("watches"), "Watch");
assert_eq!(singularize_and_capitalize("catches"), "Catch");
}
#[test]
fn test_types_word() {
assert_eq!(singularize_and_capitalize("types"), "Type");
assert_eq!(singularize_and_capitalize("files"), "File");
assert_eq!(singularize_and_capitalize("names"), "Name");
}
#[test]
fn test_deep_snake_case() {
assert_eq!(singularize_and_capitalize("a_b_c_d_e"), "ABCDE");
}
#[test]
fn test_snake_case_with_numbers() {
assert_eq!(singularize_and_capitalize("user_v2"), "UserV2");
assert_eq!(singularize_and_capitalize("api_v1_users"), "ApiV1User");
}
#[test]
fn test_leading_underscore_snake() {
assert_eq!(singularize_and_capitalize("_items"), "Item");
}
#[test]
fn test_trailing_underscore_snake() {
assert_eq!(singularize_and_capitalize("items_"), "Items");
}
#[test]
fn test_multiple_underscores() {
assert_eq!(singularize_and_capitalize("some__items"), "SomeItem");
assert_eq!(singularize_and_capitalize("many___things"), "ManyThing");
}
#[test]
fn test_mixed_case_snake() {
assert_eq!(singularize_and_capitalize("user_IDs"), "UserID");
assert_eq!(singularize_and_capitalize("xml_PARSERS"), "XmlPARSERS");
}
#[test]
fn test_ies_short_word() {
assert_eq!(singularize_and_capitalize("flies"), "Fly");
assert_eq!(singularize_and_capitalize("ties"), "Ty");
}
#[test]
fn test_ies_exact_length() {
assert_eq!(singularize_and_capitalize("ies"), "Ie");
}
#[test]
fn test_es_on_short_words() {
assert_eq!(singularize_and_capitalize("es"), "E");
}
#[test]
fn test_es_various() {
assert_eq!(singularize_and_capitalize("heroes"), "Heroe");
assert_eq!(singularize_and_capitalize("potatoes"), "Potatoe");
assert_eq!(singularize_and_capitalize("tomatoes"), "Tomatoe");
}
#[test]
fn test_snake_plus_ies() {
assert_eq!(
singularize_and_capitalize("user_categories"),
"UserCategory"
);
assert_eq!(singularize_and_capitalize("post_entries"), "PostEntry");
}
#[test]
fn test_snake_plus_es() {
assert_eq!(singularize_and_capitalize("data_boxes"), "DataBox");
assert_eq!(singularize_and_capitalize("file_matches"), "FileMatch");
}
#[test]
fn test_very_long_snake_case() {
assert_eq!(
singularize_and_capitalize("this_is_a_very_long_identifier_with_many_parts"),
"ThisIsAVeryLongIdentifierWithManyPart"
);
}
#[test]
fn test_unicode_preserved() {
assert_eq!(singularize_and_capitalize("日本語s"), "日本語");
assert_eq!(singularize_and_capitalize("émojis"), "Émoji");
}
#[test]
fn test_all_caps() {
assert_eq!(singularize_and_capitalize("USERS"), "USERS");
assert_eq!(singularize_and_capitalize("ITEMS"), "ITEMS");
assert_eq!(singularize_and_capitalize("users"), "User");
}
#[test]
fn test_mixed_caps() {
assert_eq!(singularize_and_capitalize("myItems"), "MyItem");
assert_eq!(singularize_and_capitalize("SomeThings"), "SomeThing");
}
}