Skip to main content

graphify_core/
id.rs

1/// Generate a deterministic ID from string parts.
2///
3/// Matches the Python `_make_id` implementation exactly:
4/// ```python
5/// def _make_id(*parts: str) -> str:
6///     combined = "_".join(p.strip("_.") for p in parts if p)
7///     cleaned = re.sub(r"[^a-zA-Z0-9]+", "_", combined)
8///     return cleaned.strip("_").lower()
9/// ```
10pub fn make_id(parts: &[&str]) -> String {
11    // Filter out empty strings, strip leading/trailing '_' and '.' from each part
12    let combined = parts
13        .iter()
14        .filter(|p| !p.is_empty())
15        .map(|p| p.trim_matches(&['_', '.'][..]))
16        .collect::<Vec<_>>()
17        .join("_");
18
19    // Replace runs of non-alphanumeric chars with a single '_'
20    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    // Strip leading/trailing '_' and lowercase
33    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        // Python: _make_id("__init__", "MyClass") -> "init_myclass"
88        assert_eq!(make_id(&["__init__", "MyClass"]), "init_myclass");
89    }
90}