Skip to main content

radiate_utils/
fmt.rs

1#[inline]
2pub fn short_type_name<T: ?Sized>() -> String {
3    let full = std::any::type_name::<T>();
4    let mut out = String::with_capacity(full.len());
5    let mut segment_start = 0;
6    for (i, c) in full.char_indices() {
7        if matches!(c, '<' | '>' | ',' | ' ') {
8            out.push_str(strip_path(&full[segment_start..i]));
9            out.push(c);
10            segment_start = i + c.len_utf8();
11        }
12    }
13
14    if segment_start < full.len() {
15        out.push_str(strip_path(&full[segment_start..]));
16    }
17
18    out
19}
20
21fn strip_path(segment: &str) -> &str {
22    match segment.rfind("::") {
23        Some(idx) => &segment[idx + 2..],
24        None => segment,
25    }
26}
27
28pub trait ToSnakeCase<O> {
29    fn to_snake_case(&self) -> O;
30}
31
32impl ToSnakeCase<String> for &'_ str {
33    fn to_snake_case(&self) -> String {
34        if self
35            .chars()
36            .all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '_')
37        {
38            return self.to_string();
39        }
40
41        let mut snake_case = String::new();
42
43        for (i, c) in self.chars().enumerate() {
44            if c.is_uppercase() {
45                if i != 0 {
46                    snake_case.push('_');
47                }
48                for lower_c in c.to_lowercase() {
49                    snake_case.push(lower_c);
50                }
51            } else {
52                snake_case.push(c);
53            }
54        }
55        snake_case
56    }
57}
58
59impl ToSnakeCase<String> for String {
60    fn to_snake_case(&self) -> String {
61        if self
62            .chars()
63            .all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '_')
64        {
65            return self.to_string();
66        }
67
68        let mut snake_case = String::new();
69
70        for (i, c) in self.chars().enumerate() {
71            if c.is_uppercase() {
72                if i != 0 {
73                    snake_case.push('_');
74                }
75                for lower_c in c.to_lowercase() {
76                    snake_case.push(lower_c);
77                }
78            } else {
79                snake_case.push(c);
80            }
81        }
82        snake_case
83    }
84}
85
86pub fn generate_metric_key<T: ?Sized>(category: &str) -> String {
87    let struct_type_name = std::any::type_name::<T>();
88    let head = struct_type_name
89        .split('<')
90        .next()
91        .unwrap_or(struct_type_name);
92    let base = head.rsplit("::").next().unwrap_or(head).trim();
93
94    let mut parts = words(base);
95    parts.retain(|w| w != category);
96
97    let mut result = vec![category.to_string()];
98    result.extend(parts);
99    result.join(".")
100}
101
102fn words(s: &str) -> Vec<String> {
103    let chars: Vec<char> = s.chars().collect();
104    let mut words = Vec::new();
105    let mut current = String::new();
106
107    for (i, &c) in chars.iter().enumerate() {
108        if c == '_' || c == '-' || c.is_whitespace() {
109            if !current.is_empty() {
110                words.push(std::mem::take(&mut current));
111            }
112            continue;
113        }
114        if c.is_uppercase() {
115            let prev_lower_or_digit =
116                i > 0 && (chars[i - 1].is_lowercase() || chars[i - 1].is_ascii_digit());
117            let prev_upper = i > 0 && chars[i - 1].is_uppercase();
118            let next_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
119            let boundary = prev_lower_or_digit || (prev_upper && next_lower);
120            if boundary && !current.is_empty() {
121                words.push(std::mem::take(&mut current));
122            }
123        }
124        current.push(c.to_ascii_lowercase());
125    }
126    if !current.is_empty() {
127        words.push(current);
128    }
129    words
130}