fn replace_invalid_identifier_chars(s: &str) -> String {
s.strip_prefix('$')
.unwrap_or(s)
.replace(|c: char| !c.is_alphanumeric() && c != '_', "_")
}
fn replace_numeric_start(s: &str) -> String {
if s.chars().next().is_some_and(char::is_numeric) {
format!("_{s}")
} else {
s.to_string()
}
}
fn remove_excess_underscores(s: &str) -> String {
let mut result = String::new();
let mut char_iter = s.chars().peekable();
while let Some(c) = char_iter.next() {
let next_c = char_iter.peek();
if c != '_' || !matches!(next_c, Some('_')) {
result.push(c);
}
}
result
}
fn capitalize(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().chain(c).collect(),
}
}
pub fn str_to_enum_variant(s: &str) -> syn::Ident {
let parts: Vec<_> = s.split([' ', '_', '-']).map(capitalize).collect();
parse_str(&parts.join(""))
}
pub fn parse_str(s: &str) -> syn::Ident {
if s.is_empty() {
return quote::format_ident!("empty_");
}
if s.chars().all(|c| c == '_') {
return quote::format_ident!("underscore_");
}
let s = replace_invalid_identifier_chars(s);
let s = replace_numeric_start(&s);
let s = remove_excess_underscores(&s);
if s.is_empty() {
return quote::format_ident!("invalid_");
}
let keywords = [
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use",
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
"typeof", "unsized", "virtual", "yield", "async", "await", "try",
];
if keywords.iter().any(|&keyword| keyword == s) {
return quote::format_ident!("{}_", s);
}
quote::format_ident!("{}", s)
}