#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;
use std::fmt::{self, Debug, Display};
use proc_macro2::Ident;
#[cfg(test)]
use proc_macro2::Span;
use self::RenameRule::*;
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub(crate) enum RenameRule {
#[default]
None,
LowerCase,
UpperCase,
PascalCase,
CamelCase,
SnakeCase,
ScreamingSnakeCase,
KebabCase,
ScreamingKebabCase,
}
const RENAME_RULES: &[(&str, RenameRule)] = &[
("\"lowercase\"", LowerCase),
("\"UPPERCASE\"", UpperCase),
("\"PascalCase\"", PascalCase),
("\"camelCase\"", CamelCase),
("\"snake_case\"", SnakeCase),
("\"SCREAMING_SNAKE_CASE\"", ScreamingSnakeCase),
("\"kebab-case\"", KebabCase),
("\"SCREAMING-KEBAB-CASE\"", ScreamingKebabCase),
];
impl RenameRule {
pub(crate) fn from_str(rename_all_str: &str) -> Result<Self, ParseError<'_>> {
for (name, rule) in RENAME_RULES {
if rename_all_str == *name {
return Ok(*rule);
}
}
Err(ParseError {
unknown: rename_all_str,
})
}
pub(crate) fn apply_to_variant(&self, ident: &Ident) -> String {
let variant = ident.to_string();
match *self {
None | PascalCase => variant,
LowerCase => variant.to_ascii_lowercase(),
UpperCase => variant.to_ascii_uppercase(),
CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
SnakeCase => {
let mut snake = String::new();
for (i, ch) in variant.char_indices() {
if i > 0 && ch.is_uppercase() {
snake.push('_');
}
snake.push(ch.to_ascii_lowercase());
}
snake
}
ScreamingSnakeCase => SnakeCase.apply_to_variant(ident).to_ascii_uppercase(),
KebabCase => SnakeCase.apply_to_variant(ident).replace('_', "-"),
ScreamingKebabCase => ScreamingSnakeCase.apply_to_variant(ident).replace('_', "-"),
}
}
pub(crate) fn apply_to_field(&self, ident: &Ident) -> String {
let mut field = ident.to_string();
if field.starts_with("r#") {
field = field[2..].to_string();
}
match *self {
None | LowerCase | SnakeCase => field,
UpperCase => field.to_ascii_uppercase(),
PascalCase => {
let mut pascal = String::new();
let mut capitalize = true;
for ch in field.chars() {
if ch == '_' {
capitalize = true;
} else if capitalize {
pascal.push(ch.to_ascii_uppercase());
capitalize = false;
} else {
pascal.push(ch);
}
}
pascal
}
CamelCase => {
let pascal = PascalCase.apply_to_field(ident);
pascal[..1].to_ascii_lowercase() + &pascal[1..]
}
ScreamingSnakeCase => field.to_ascii_uppercase(),
KebabCase => field.replace('_', "-"),
ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(ident).replace('_', "-"),
}
}
}
pub(crate) struct ParseError<'a> {
unknown: &'a str,
}
impl Display for ParseError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown rename rule `rename_all = ")?;
Debug::fmt(self.unknown, f)?;
f.write_str("`, expected one of ")?;
for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
Debug::fmt(name, f)?;
}
Ok(())
}
}
#[test]
fn rename_variants() {
for &(name, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
(
"Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
),
(
"VeryTasty",
"verytasty",
"VERYTASTY",
"veryTasty",
"very_tasty",
"VERY_TASTY",
"very-tasty",
"VERY-TASTY",
),
("A", "a", "A", "a", "a", "A", "a", "A"),
("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
] {
let original = &Ident::new(name, Span::call_site());
assert_eq!(None.apply_to_variant(original), name);
assert_eq!(LowerCase.apply_to_variant(original), lower);
assert_eq!(UpperCase.apply_to_variant(original), upper);
assert_eq!(PascalCase.apply_to_variant(original), name);
assert_eq!(CamelCase.apply_to_variant(original), camel);
assert_eq!(SnakeCase.apply_to_variant(original), snake);
assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
assert_eq!(KebabCase.apply_to_variant(original), kebab);
assert_eq!(
ScreamingKebabCase.apply_to_variant(original),
screaming_kebab
);
}
}
#[test]
fn rename_fields() {
for &(name, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
(
"outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
),
(
"very_tasty",
"VERY_TASTY",
"VeryTasty",
"veryTasty",
"VERY_TASTY",
"very-tasty",
"VERY-TASTY",
),
("a", "A", "A", "a", "A", "a", "A"),
("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
] {
let original = &Ident::new(name, Span::call_site());
assert_eq!(None.apply_to_field(original), name);
assert_eq!(UpperCase.apply_to_field(original), upper);
assert_eq!(PascalCase.apply_to_field(original), pascal);
assert_eq!(CamelCase.apply_to_field(original), camel);
assert_eq!(SnakeCase.apply_to_field(original), name);
assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
assert_eq!(KebabCase.apply_to_field(original), kebab);
assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
}
}