pub fn make_id(parts: &[&str]) -> String {
let combined = parts
.iter()
.filter(|p| !p.is_empty())
.map(|p| p.trim_matches(&['_', '.'][..]))
.collect::<Vec<_>>()
.join("_");
let mut cleaned = String::with_capacity(combined.len());
let mut prev_was_sep = false;
for ch in combined.chars() {
if ch.is_ascii_alphanumeric() {
cleaned.push(ch);
prev_was_sep = false;
} else if !prev_was_sep {
cleaned.push('_');
prev_was_sep = true;
}
}
cleaned.trim_matches('_').to_ascii_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_parts() {
assert_eq!(make_id(&["Hello", "World"]), "hello_world");
}
#[test]
fn strips_dots_and_underscores() {
assert_eq!(make_id(&["__foo__", "..bar.."]), "foo_bar");
}
#[test]
fn replaces_special_chars() {
assert_eq!(make_id(&["my-class", "method()"]), "my_class_method");
}
#[test]
fn filters_empty_parts() {
assert_eq!(make_id(&["a", "", "b", ""]), "a_b");
}
#[test]
fn single_part() {
assert_eq!(make_id(&["SomeClass"]), "someclass");
}
#[test]
fn all_empty() {
assert_eq!(make_id(&["", ""]), "");
}
#[test]
fn special_only() {
assert_eq!(make_id(&["---"]), "");
}
#[test]
fn mixed_unicode_and_ascii() {
assert_eq!(make_id(&["foo::bar"]), "foo_bar");
}
#[test]
fn consecutive_separators_collapsed() {
assert_eq!(make_id(&["a!!!b"]), "a_b");
}
#[test]
fn python_compat_complex() {
assert_eq!(make_id(&["__init__", "MyClass"]), "init_myclass");
}
}