use std::{
collections::HashSet,
sync::{LazyLock, OnceLock},
};
static DEFAULT_ACRONYMS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
[
"s3", "ec2", "ipv4", "ipv6", "oauth1", "oauth2", "sha1", "sha256", "sha512", "md5",
"base64", "utf8", "p256", "p384",
]
.into_iter()
.collect()
});
static RUNTIME_ACRONYMS: OnceLock<HashSet<String>> = OnceLock::new();
pub fn set_runtime_acronyms(extra: &[String]) {
let mut set: HashSet<String> = DEFAULT_ACRONYMS.iter().map(|s| (*s).to_string()).collect();
for term in extra {
let term = term.trim().to_ascii_lowercase();
if !term.is_empty() {
set.insert(term);
}
}
let _ = RUNTIME_ACRONYMS.set(set);
}
fn is_registered_acronym(candidate: &str) -> bool {
match RUNTIME_ACRONYMS.get() {
Some(set) => set.contains(candidate),
None => DEFAULT_ACRONYMS.contains(candidate),
}
}
fn acronym_spans_digit(chars: &[char], word_start: usize, digit_start: usize) -> bool {
let mut end = digit_start;
while end < chars.len() && chars[end].is_ascii_digit() {
end += 1;
}
let candidate: String = chars[word_start..end].iter().collect::<String>().to_ascii_lowercase();
is_registered_acronym(&candidate)
}
#[must_use]
pub fn to_snake_case(name: &str) -> String {
let chars: Vec<char> = name.chars().collect();
let mut result = String::with_capacity(name.len() + 5);
let mut prev_was_upper = false;
let mut prev_was_lower = false;
let mut prev_was_digit = false;
let mut lower_run_start = 0usize;
for (i, &c) in chars.iter().enumerate() {
if c.is_uppercase() {
if i > 0 {
let next_is_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase());
if prev_was_lower || prev_was_digit || (prev_was_upper && next_is_lower) {
result.push('_');
}
}
result.push(c.to_ascii_lowercase());
prev_was_upper = true;
prev_was_lower = false;
prev_was_digit = false;
} else if c.is_ascii_digit() {
if prev_was_lower && !acronym_spans_digit(&chars, lower_run_start, i) {
result.push('_');
}
result.push(c);
prev_was_upper = false;
prev_was_lower = false;
prev_was_digit = true;
} else {
if c.is_lowercase() {
if !prev_was_lower {
lower_run_start = i;
}
prev_was_lower = true;
} else {
prev_was_lower = false;
}
result.push(c);
prev_was_upper = false;
prev_was_digit = false;
}
}
result
}
#[cfg(test)]
mod tests;