功能说明
不同语言和配置格式对命名风格有不同约定:Rust 和 Python 常用 snake_case,JavaScript 和 JSON 常用 camelCase。这段代码把标识符拆成单词序列,再按目标风格重新拼接,支持下划线、连字符和空格作为分隔符,也能识别 HTTPServer 这类缩写词边界。
它适合代码生成器、配置键转换、API 字段映射和 CLI 重命名工具。实现只使用标准库,逻辑透明,便于按项目规则调整。
关键代码
split_words 逐字符扫描:遇到 _、- 或空格就结束当前单词;遇到大写字母时检查前一个字符。如果前一个是小写字母或数字,说明进入新单词(userName);如果前一个也是大写且后一个是小写,说明缩写结束(HTTPServer 中的 S)。
to_snake_case 直接用 _ 连接单词。to_camel_case 保留第一个单词的小写形式,其余单词首字母转大写后拼接,因此 order-service-config 变成 orderServiceConfig。
使用注意
大小写转换使用 to_ascii_lowercase 和 to_ascii_uppercase,非 ASCII 字符不会被转换,可能产生混合结果。连续数字不会自动分组,例如 v2beta 与 v2Beta 的结果不同。缩写词全部大写时会被拆成单个单词,ID 会变成 i_d,需要额外规则保留常见缩写。
函数不处理空字符串以外的空白组合,也不去除首尾分隔符产生的空单词(空单词会被跳过)。转换可能产生冲突,例如 a_b 与 aB 都变成 aB,批量重命名时要检测重复。
验证方式
示例输出 HTTPServer -> http_server / httpServer、user_profile_id -> user_profile_id / userProfileId、OrderServiceConfig -> order_service_config / orderServiceConfig、v2Beta -> v2_beta / v2Beta。断言覆盖缩写、分隔符和数字边界。可以再加入 XMLHttpRequest,观察缩写拆分结果是否符合预期。
代码示例
fn split_words(input: &str) -> Vec<String> {
let mut words = Vec::new();
let mut current = String::new();
let characters: Vec<char> = input.chars().collect();
for (index, &character) in characters.iter().enumerate() {
if character == '_' || character == '-' || character == ' ' {
if !current.is_empty() {
words.push(std::mem::take(&mut current));
}
continue;
}
let previous = if index > 0 { Some(characters[index - 1]) } else { None };
let next = characters.get(index + 1).copied();
let boundary = (character.is_uppercase()
&& previous.map_or(false, |value| value.is_lowercase() || value.is_ascii_digit()))
|| (character.is_uppercase()
&& previous.map_or(false, |value| value.is_uppercase())
&& next.map_or(false, |value| value.is_lowercase()));
if boundary && !current.is_empty() {
words.push(std::mem::take(&mut current));
}
current.push(character.to_ascii_lowercase());
}
if !current.is_empty() {
words.push(current);
}
words
}
fn to_snake_case(input: &str) -> String {
split_words(input).join("_")
}
fn to_camel_case(input: &str) -> String {
let mut words = split_words(input);
if words.is_empty() {
return String::new();
}
let mut output = words.remove(0);
for word in words {
let mut characters = word.chars();
if let Some(first) = characters.next() {
output.push(first.to_ascii_uppercase());
output.extend(characters);
}
}
output
}
fn main() {
for sample in ["HTTPServer", "user_profile_id", "OrderServiceConfig", "v2Beta"] {
println!("{sample} -> {} / {}", to_snake_case(sample), to_camel_case(sample));
}
assert_eq!(to_snake_case("HTTPServer"), "http_server");
assert_eq!(to_snake_case("user_profile_id"), "user_profile_id");
assert_eq!(to_camel_case("order-service-config"), "orderServiceConfig");
assert_eq!(to_camel_case("already_snake"), "alreadySnake");
}