1pub fn make_id(parts: &[&str]) -> String {
11 let combined = parts
13 .iter()
14 .filter(|p| !p.is_empty())
15 .map(|p| p.trim_matches(&['_', '.'][..]))
16 .collect::<Vec<_>>()
17 .join("_");
18
19 let mut cleaned = String::with_capacity(combined.len());
21 let mut prev_was_sep = false;
22 for ch in combined.chars() {
23 if ch.is_ascii_alphanumeric() {
24 cleaned.push(ch);
25 prev_was_sep = false;
26 } else if !prev_was_sep {
27 cleaned.push('_');
28 prev_was_sep = true;
29 }
30 }
31
32 cleaned.trim_matches('_').to_ascii_lowercase()
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn basic_parts() {
42 assert_eq!(make_id(&["Hello", "World"]), "hello_world");
43 }
44
45 #[test]
46 fn strips_dots_and_underscores() {
47 assert_eq!(make_id(&["__foo__", "..bar.."]), "foo_bar");
48 }
49
50 #[test]
51 fn replaces_special_chars() {
52 assert_eq!(make_id(&["my-class", "method()"]), "my_class_method");
53 }
54
55 #[test]
56 fn filters_empty_parts() {
57 assert_eq!(make_id(&["a", "", "b", ""]), "a_b");
58 }
59
60 #[test]
61 fn single_part() {
62 assert_eq!(make_id(&["SomeClass"]), "someclass");
63 }
64
65 #[test]
66 fn all_empty() {
67 assert_eq!(make_id(&["", ""]), "");
68 }
69
70 #[test]
71 fn special_only() {
72 assert_eq!(make_id(&["---"]), "");
73 }
74
75 #[test]
76 fn mixed_unicode_and_ascii() {
77 assert_eq!(make_id(&["foo::bar"]), "foo_bar");
78 }
79
80 #[test]
81 fn consecutive_separators_collapsed() {
82 assert_eq!(make_id(&["a!!!b"]), "a_b");
83 }
84
85 #[test]
86 fn python_compat_complex() {
87 assert_eq!(make_id(&["__init__", "MyClass"]), "init_myclass");
89 }
90}