pub fn to_pascal_case(name: &str) -> String {
let mut out = String::new();
let mut upper_next = true;
for ch in name.chars() {
if ch == '-' || ch == '_' {
upper_next = true;
} else if upper_next {
out.extend(ch.to_uppercase());
upper_next = false;
} else {
out.push(ch);
}
}
out
}
pub fn to_lower_camel(name: &str) -> String {
let pascal = to_pascal_case(name);
let mut chars = pascal.chars();
match chars.next() {
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}
pub fn to_screaming_snake(name: &str) -> String {
let mut out = String::new();
for (i, ch) in name.chars().enumerate() {
if ch.is_uppercase() {
if i > 0 {
out.push('_');
}
out.extend(ch.to_uppercase());
} else {
out.extend(ch.to_uppercase());
}
}
out
}
const KOTLIN_HARD_KEYWORDS: &[&str] = &[
"as",
"break",
"class",
"continue",
"do",
"else",
"false",
"for",
"fun",
"if",
"in",
"interface",
"is",
"null",
"object",
"package",
"return",
"super",
"this",
"throw",
"true",
"try",
"typealias",
"typeof",
"val",
"var",
"when",
"while",
];
fn escape_kotlin_keyword(name: &str) -> String {
if KOTLIN_HARD_KEYWORDS.contains(&name) {
format!("`{name}`")
} else {
name.to_string()
}
}
pub fn kotlin_field_name(raw: &str, idx: usize) -> String {
let stripped = raw.trim_start_matches('_');
if stripped.is_empty() || stripped.chars().all(|c| c.is_ascii_digit()) {
return format!("field{idx}");
}
escape_kotlin_keyword(&to_lower_camel(raw))
}
pub fn kotlin_field_name_with_type(
field_name: &str,
field_idx: usize,
field_type_name: Option<&str>,
variant_name: &str,
total_fields: usize,
) -> String {
let stripped = field_name.trim_start_matches('_');
if !stripped.is_empty() && !stripped.chars().all(|c| c.is_ascii_digit()) {
return escape_kotlin_keyword(&to_lower_camel(field_name));
}
if total_fields == 1 {
if let Some(type_name) = field_type_name {
if let Some(remainder) = type_name.strip_prefix(variant_name) {
let derived = to_lower_camel(remainder);
if !derived.is_empty() {
return escape_kotlin_keyword(&derived);
}
}
if is_primitive_or_stdlib_type(type_name) {
return "value".to_string();
}
}
}
if total_fields > 1 {
return format!("value{}", field_idx);
}
"value".to_string()
}
fn is_primitive_or_stdlib_type(type_name: &str) -> bool {
matches!(
type_name,
"String"
| "Byte"
| "Short"
| "Int"
| "Long"
| "Float"
| "Double"
| "Boolean"
| "Unit"
| "Char"
| "Any"
| "Nothing"
)
}