component_shape_mcp/
names.rs1pub 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}