const RUST_KEYWORDS: &[&str] = &[
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for",
"if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
"self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where",
"while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try", "typeof",
"unsized", "virtual", "yield",
];
pub fn escape_keyword(ident: &str) -> String {
if ident
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
return format!("_{}", ident);
}
if RUST_KEYWORDS.contains(&ident) {
format!("r#{}", ident)
} else {
ident.to_string()
}
}
pub fn to_snake_case(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
let mut result = String::new();
for (i, &c) in chars.iter().enumerate() {
if c == '-' || c == ' ' || c == '_' || c == '.' || c == '/' {
if !result.is_empty() && !result.ends_with('_') {
result.push('_');
}
} else if c == '\'' || c == '"' || c == '`' || c == '(' || c == ')' || c == '[' || c == ']'
{
continue;
} else if c.is_uppercase() {
let prev = i.checked_sub(1).map(|j| chars[j]);
let prev_is_lower_or_digit =
prev.is_some_and(|p| p.is_lowercase() || p.is_ascii_digit());
let prev_is_upper = prev.is_some_and(|p| p.is_uppercase());
let next_is_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
if !result.is_empty()
&& !result.ends_with('_')
&& (prev_is_lower_or_digit || (prev_is_upper && next_is_lower))
{
result.push('_');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
let mut final_result = String::new();
let mut last_was_underscore = false;
for c in result.chars() {
if c == '_' {
if !last_was_underscore && !final_result.is_empty() {
final_result.push(c);
last_was_underscore = true;
}
} else {
final_result.push(c);
last_was_underscore = false;
}
}
final_result.trim_matches('_').to_string()
}
pub fn to_pascal_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = true;
for c in s.chars() {
if c == '-' || c == '_' || c == ' ' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
pub fn derive_struct_name(id: &str) -> String {
let pascal = to_pascal_case(id);
if pascal
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
format!("Form{}", pascal)
} else {
pascal
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("PayerName"), "payer_name");
assert_eq!(to_snake_case("payer-name"), "payer_name");
assert_eq!(to_snake_case("payer_name"), "payer_name");
assert_eq!(to_snake_case("PayerTIN"), "payer_tin");
assert_eq!(to_snake_case("Box1Amount"), "box1_amount");
assert_eq!(to_snake_case("already_snake"), "already_snake");
assert_eq!(to_snake_case("XMLParser"), "xml_parser");
}
#[test]
fn test_to_pascal_case() {
assert_eq!(to_pascal_case("payer_name"), "PayerName");
assert_eq!(to_pascal_case("payer-name"), "PayerName");
assert_eq!(to_pascal_case("PayerName"), "PayerName");
assert_eq!(to_pascal_case("form_1099_int"), "Form1099Int");
}
#[test]
fn test_derive_struct_name() {
assert_eq!(derive_struct_name("form_1099_int"), "Form1099Int");
assert_eq!(derive_struct_name("1099-int"), "Form1099Int");
assert_eq!(derive_struct_name("PayeeInfo"), "PayeeInfo");
}
}