#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenameRule {
PascalCase,
CamelCase,
SnakeCase,
ScreamingSnakeCase,
KebabCase,
ScreamingKebabCase,
Lowercase,
Uppercase,
}
impl RenameRule {
pub fn parse(rule: &str) -> Option<Self> {
match rule {
"PascalCase" => Some(RenameRule::PascalCase),
"camelCase" => Some(RenameRule::CamelCase),
"snake_case" => Some(RenameRule::SnakeCase),
"SCREAMING_SNAKE_CASE" => Some(RenameRule::ScreamingSnakeCase),
"kebab-case" => Some(RenameRule::KebabCase),
"SCREAMING-KEBAB-CASE" => Some(RenameRule::ScreamingKebabCase),
"lowercase" => Some(RenameRule::Lowercase),
"UPPERCASE" => Some(RenameRule::Uppercase),
_ => None,
}
}
pub fn apply(self, input: &str) -> String {
match self {
RenameRule::PascalCase => to_pascal_case(input),
RenameRule::CamelCase => to_camel_case(input),
RenameRule::SnakeCase => to_snake_case(input),
RenameRule::ScreamingSnakeCase => to_screaming_snake_case(input),
RenameRule::KebabCase => to_kebab_case(input),
RenameRule::ScreamingKebabCase => to_screaming_kebab_case(input),
RenameRule::Lowercase => to_lowercase(input),
RenameRule::Uppercase => to_uppercase(input),
}
}
}
fn to_pascal_case(input: &str) -> String {
split_into_words(input)
.iter()
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(c) => {
c.to_uppercase().collect::<String>() + &chars.collect::<String>().to_lowercase()
}
}
})
.collect()
}
fn to_camel_case(input: &str) -> String {
let pascal = to_pascal_case(input);
if pascal.is_empty() {
return String::new();
}
let mut result = String::new();
let mut chars = pascal.chars();
if let Some(first_char) = chars.next() {
result.push(first_char.to_lowercase().next().unwrap());
}
result.extend(chars);
result
}
fn to_snake_case(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_lowercase())
.collect::<Vec<_>>()
.join("_")
}
fn to_screaming_snake_case(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_uppercase())
.collect::<Vec<_>>()
.join("_")
}
fn to_kebab_case(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_lowercase())
.collect::<Vec<_>>()
.join("-")
}
fn to_screaming_kebab_case(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_uppercase())
.collect::<Vec<_>>()
.join("-")
}
fn to_lowercase(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_lowercase())
.collect::<Vec<_>>()
.join("")
}
fn to_uppercase(input: &str) -> String {
let words = split_into_words(input);
words
.iter()
.map(|word| word.to_uppercase())
.collect::<Vec<_>>()
.join("")
}
fn split_into_words(input: &str) -> Vec<String> {
if input.is_empty() {
return vec![];
}
let mut words = Vec::new();
let mut current_word = String::new();
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '_' || c == '-' || c.is_whitespace() {
if !current_word.is_empty() {
words.push(std::mem::take(&mut current_word));
}
continue;
}
let next = chars.peek().copied();
if c.is_uppercase() {
if !current_word.is_empty() {
let prev = current_word.chars().last().unwrap();
if prev.is_lowercase()
|| prev.is_ascii_digit()
|| (prev.is_uppercase() && next.map(|n| n.is_lowercase()).unwrap_or(false))
{
words.push(std::mem::take(&mut current_word));
}
}
current_word.push(c);
} else {
current_word.push(c);
}
}
if !current_word.is_empty() {
words.push(current_word);
}
words.into_iter().filter(|s| !s.is_empty()).collect()
}
#[cfg(test)]
mod tests {
use super::split_into_words;
#[test]
fn test_split_into_words_simple_snake_case() {
assert_eq!(split_into_words("foo_bar_baz"), vec!["foo", "bar", "baz"]);
}
#[test]
fn test_split_into_words_single_word() {
assert_eq!(split_into_words("foo"), vec!["foo"]);
assert_eq!(split_into_words("Foo"), vec!["Foo"]);
}
#[test]
fn test_split_into_words_empty_string() {
assert_eq!(split_into_words(""), Vec::<String>::new());
}
#[test]
fn test_split_into_words_multiple_underscores() {
assert_eq!(split_into_words("foo__bar"), vec!["foo", "bar"]);
assert_eq!(split_into_words("_foo_bar_"), vec!["foo", "bar"]);
}
#[test]
fn test_split_into_words_kebab_case() {
assert_eq!(split_into_words("foo-bar-baz"), vec!["foo", "bar", "baz"]);
}
#[test]
fn test_split_into_words_mixed_separators_and_space() {
assert_eq!(split_into_words("foo_ bar-baz"), vec!["foo", "bar", "baz"]);
assert_eq!(split_into_words("a_b-c d"), vec!["a", "b", "c", "d"]);
}
#[test]
fn test_split_into_words_camel_case() {
assert_eq!(split_into_words("fooBarBaz"), vec!["foo", "Bar", "Baz"]);
assert_eq!(split_into_words("fooBar"), vec!["foo", "Bar"]);
assert_eq!(
split_into_words("fooBar_BazQuux"),
vec!["foo", "Bar", "Baz", "Quux"]
);
}
#[test]
fn test_split_into_words_pascal_case() {
assert_eq!(split_into_words("FooBarBaz"), vec!["Foo", "Bar", "Baz"]);
assert_eq!(split_into_words("FooBar"), vec!["Foo", "Bar"]);
}
#[test]
fn test_split_into_words_http_server() {
assert_eq!(split_into_words("HTTPServer"), vec!["HTTP", "Server"]);
assert_eq!(
split_into_words("theHTTPServer"),
vec!["the", "HTTP", "Server"]
);
}
#[test]
fn test_split_into_words_consecutive_uppercase_at_end() {
assert_eq!(split_into_words("FooBAR"), vec!["Foo", "BAR"]);
assert_eq!(split_into_words("FooBARBaz"), vec!["Foo", "BAR", "Baz"]);
}
#[test]
fn test_split_into_words_separators_and_case_boundaries() {
assert_eq!(split_into_words("foo_barBaz"), vec!["foo", "bar", "Baz"]);
assert_eq!(
split_into_words("fooBar_bazQux"),
vec!["foo", "Bar", "baz", "Qux"]
);
}
#[test]
fn test_rename_rule_snake_case() {
use super::RenameRule;
assert_eq!(RenameRule::SnakeCase.apply("foo_bar_baz"), "foo_bar_baz");
assert_eq!(RenameRule::SnakeCase.apply("fooBarBaz"), "foo_bar_baz");
assert_eq!(RenameRule::SnakeCase.apply("FooBarBaz"), "foo_bar_baz");
assert_eq!(RenameRule::SnakeCase.apply("FOO_BAR_BAZ"), "foo_bar_baz");
assert_eq!(RenameRule::SnakeCase.apply("foo-bar-baz"), "foo_bar_baz");
assert_eq!(
RenameRule::SnakeCase.apply("Foo_Bar-Baz quux"),
"foo_bar_baz_quux"
);
assert_eq!(
RenameRule::SnakeCase.apply("theHTTPServer"),
"the_http_server"
);
assert_eq!(RenameRule::SnakeCase.apply("FooBARBaz"), "foo_bar_baz");
assert_eq!(RenameRule::SnakeCase.apply(""), "");
}
#[test]
fn test_rename_rule_lowercase() {
use super::RenameRule;
assert_eq!(RenameRule::Lowercase.apply("foo_bar_baz"), "foobarbaz");
assert_eq!(RenameRule::Lowercase.apply("fooBarBaz"), "foobarbaz");
assert_eq!(RenameRule::Lowercase.apply("FooBarBaz"), "foobarbaz");
assert_eq!(RenameRule::Lowercase.apply("FOO_BAR_BAZ"), "foobarbaz");
assert_eq!(RenameRule::Lowercase.apply("foo-bar-baz"), "foobarbaz");
assert_eq!(
RenameRule::Lowercase.apply("theHTTPServer"),
"thehttpserver"
);
assert_eq!(RenameRule::Lowercase.apply(""), "");
}
#[test]
fn test_rename_rule_uppercase() {
use super::RenameRule;
assert_eq!(RenameRule::Uppercase.apply("foo_bar_baz"), "FOOBARBAZ");
assert_eq!(RenameRule::Uppercase.apply("fooBarBaz"), "FOOBARBAZ");
assert_eq!(RenameRule::Uppercase.apply("FooBarBaz"), "FOOBARBAZ");
assert_eq!(RenameRule::Uppercase.apply("FOO_BAR_BAZ"), "FOOBARBAZ");
assert_eq!(RenameRule::Uppercase.apply("foo-bar-baz"), "FOOBARBAZ");
assert_eq!(
RenameRule::Uppercase.apply("theHTTPServer"),
"THEHTTPSERVER"
);
assert_eq!(RenameRule::Uppercase.apply("max_size"), "MAXSIZE");
assert_eq!(RenameRule::Uppercase.apply("min_value"), "MINVALUE");
assert_eq!(RenameRule::Uppercase.apply(""), "");
}
}