const KEYWORDS: &[&str] = &[
"as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false",
"fn", "for", "gen", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
"ref", "return", "static", "struct", "trait", "true", "type", "union", "unsafe", "use",
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
"try", "typeof", "unsized", "virtual", "yield",
];
const RESERVED: &[&str] = &["crate", "self", "Self", "super"];
pub fn snake_case(name: &str) -> String {
let mut out = String::with_capacity(name.len() + 4);
let mut chars = name.chars().peekable();
let mut previous: Option<char> = None;
while let Some(c) = chars.next() {
if c == '-' || c == '.' || c == ' ' {
if !out.ends_with('_') && !out.is_empty() {
out.push('_');
}
previous = None;
continue;
}
if c.is_ascii_uppercase() {
let starts_word = match previous {
Some(p) if p.is_ascii_lowercase() || p.is_ascii_digit() => true,
Some(p) if p.is_ascii_uppercase() => {
chars.peek().is_some_and(|n| n.is_ascii_lowercase())
}
_ => false,
};
if starts_word && !out.is_empty() && !out.ends_with('_') {
out.push('_');
}
out.extend(c.to_lowercase());
} else {
out.push(c);
}
previous = Some(c);
}
escape(out)
}
fn escape(name: String) -> String {
if name.is_empty() {
return String::from("_");
}
if RESERVED.contains(&name.as_str()) {
return format!("{name}_");
}
if KEYWORDS.contains(&name.as_str()) {
return format!("r#{name}");
}
if name.starts_with(|c: char| c.is_ascii_digit()) {
return format!("_{name}");
}
name
}
pub fn pascal_case(name: &str) -> String {
let last = name.rsplit('.').next().unwrap_or(name);
let mut out = String::with_capacity(last.len());
let mut capitalize = true;
for c in last.chars() {
if c == '-' || c == '_' || c == ' ' {
capitalize = true;
continue;
}
if capitalize {
out.extend(c.to_uppercase());
capitalize = false;
} else {
out.push(c);
}
}
if out.is_empty() {
out.push('_');
}
out
}
pub fn module_name(interface: &str) -> String {
snake_case(interface.rsplit('.').next().unwrap_or(interface))
}
pub fn argument_name(name: Option<&str>, index: usize) -> String {
match name {
Some(name) if !name.is_empty() => {
let name = snake_case(name);
if name.starts_with("__") {
format!("arg_{name}")
} else {
name
}
}
_ => format!("arg{index}"),
}
}