plotnik_core/
utils.rs

1/// Convert snake_case or kebab-case to PascalCase.
2///
3/// Normalizes words separated by `_`, `-`, or `.`. If the input is already
4/// PascalCase (starts uppercase, no separators), it is returned unchanged.
5///
6/// # Examples
7/// ```
8/// use plotnik_core::utils::to_pascal_case;
9/// assert_eq!(to_pascal_case("foo_bar"), "FooBar");
10/// assert_eq!(to_pascal_case("FOO_BAR"), "FooBar");
11/// assert_eq!(to_pascal_case("FooBar"), "FooBar");  // idempotent
12/// ```
13pub fn to_pascal_case(s: &str) -> String {
14    fn is_separator(c: char) -> bool {
15        matches!(c, '_' | '-' | '.')
16    }
17
18    let has_separator = s.chars().any(is_separator);
19    let has_lowercase = s.chars().any(|c| c.is_ascii_lowercase());
20    let starts_uppercase = s.chars().next().is_some_and(|c| c.is_ascii_uppercase());
21
22    // Already PascalCase: starts uppercase, has lowercase, no separators
23    if starts_uppercase && has_lowercase && !has_separator {
24        return s.to_string();
25    }
26
27    let mut result = String::with_capacity(s.len());
28    let mut capitalize_next = true;
29    for c in s.chars() {
30        if is_separator(c) {
31            capitalize_next = true;
32            continue;
33        }
34        if capitalize_next {
35            result.push(c.to_ascii_uppercase());
36            capitalize_next = false;
37        } else {
38            result.push(c.to_ascii_lowercase());
39        }
40    }
41    result
42}
43
44/// Convert PascalCase or camelCase to snake_case.
45///
46/// # Examples
47/// ```
48/// use plotnik_core::utils::to_snake_case;
49/// assert_eq!(to_snake_case("FooBar"), "foo_bar");
50/// assert_eq!(to_snake_case("fooBar"), "foo_bar");
51/// ```
52pub fn to_snake_case(s: &str) -> String {
53    let mut result = String::new();
54    for (i, c) in s.chars().enumerate() {
55        if c.is_ascii_uppercase() {
56            if i > 0 && !result.ends_with('_') {
57                result.push('_');
58            }
59            result.push(c.to_ascii_lowercase());
60        } else {
61            result.push(c);
62        }
63    }
64    result
65}