Skip to main content

component_shape_mcp/
names.rs

1pub fn tool_name(source_module_path: &str, subject_id: &str, fallback_prefix: &str) -> String {
2    #[derive(Clone, Copy, Eq, PartialEq)]
3    enum CharKind {
4        Upper,
5        Lower,
6        Digit,
7    }
8
9    let mut input = source_module_path
10        .chars()
11        .chain(['_'])
12        .chain(subject_id.chars())
13        .peekable();
14    let mut output = String::new();
15    let mut last_was_separator = false;
16    let mut last_kind = None;
17
18    while let Some(ch) = input.next() {
19        let kind = if ch.is_ascii_uppercase() {
20            CharKind::Upper
21        } else if ch.is_ascii_lowercase() {
22            CharKind::Lower
23        } else if ch.is_ascii_digit() {
24            CharKind::Digit
25        } else if !last_was_separator && !output.is_empty() {
26            output.push('_');
27            last_was_separator = true;
28            last_kind = None;
29            continue;
30        } else {
31            last_kind = None;
32            continue;
33        };
34
35        if kind == CharKind::Upper && !last_was_separator && !output.is_empty() {
36            let next_is_lower = input.peek().is_some_and(|next| next.is_ascii_lowercase());
37            if matches!(last_kind, Some(CharKind::Lower | CharKind::Digit))
38                || (matches!(last_kind, Some(CharKind::Upper)) && next_is_lower)
39            {
40                output.push('_');
41            }
42        }
43
44        if ch.is_ascii_alphanumeric() {
45            output.push(ch.to_ascii_lowercase());
46            last_was_separator = false;
47            last_kind = Some(kind);
48        }
49    }
50
51    while output.ends_with('_') {
52        output.pop();
53    }
54
55    if output.is_empty() || output.starts_with(|ch: char| ch.is_ascii_digit()) {
56        output.insert_str(0, fallback_prefix);
57    }
58
59    output
60}