typeshare_engine/
rename.rs

1// Based off Serde implementation: https://github.com/serde-rs/serde/blob/7950f3cdc52d4898aa4195b853cbec12d65bb091/serde_derive/src/internals/case.rs
2
3/// Trait to rename a string using common case or character separators.
4pub trait RenameExt {
5    /// Convert to camelCase.
6    fn to_camel_case(&self) -> String;
7    /// Convert to PascalCase.
8    fn to_pascal_case(&self) -> String;
9    /// Convert to snake_case.
10    fn to_snake_case(&self) -> String;
11    /// Convert to SCREAMING_SNAKE_CASE.
12    fn to_screaming_snake_case(&self) -> String;
13    /// Convert to kebab-case.
14    fn to_kebab_case(&self) -> String;
15    /// Convert to SCREAMING-KEBAB-CASE.
16    fn to_screaming_kebab_case(&self) -> String;
17}
18
19impl RenameExt for String {
20    fn to_camel_case(&self) -> String {
21        let pascal = self.to_pascal_case();
22        pascal[..1].to_ascii_lowercase() + &pascal[1..]
23    }
24
25    fn to_pascal_case(&self) -> String {
26        let mut pascal = Self::new();
27        let mut capitalize = true;
28        let to_lowercase = {
29            // Check if string is all uppercase, such as "URL" or "TOTP". In that case, we don't want
30            // to preserve the cases.
31            self.to_ascii_uppercase() == *self
32        };
33
34        for ch in self.chars() {
35            if ch == '_' {
36                capitalize = true;
37            } else if capitalize {
38                pascal.push(ch.to_ascii_uppercase());
39                capitalize = false;
40            } else {
41                pascal.push(if to_lowercase {
42                    ch.to_ascii_lowercase()
43                } else {
44                    ch
45                });
46            }
47        }
48        pascal
49    }
50
51    fn to_snake_case(&self) -> String {
52        let mut snake = Self::new();
53        let is_uppercase = self.to_ascii_uppercase() == *self;
54        for (i, ch) in self.char_indices() {
55            if i > 0 && ch.is_uppercase() && !is_uppercase {
56                snake.push('_');
57            }
58            snake.push(ch.to_ascii_lowercase());
59        }
60        snake
61    }
62
63    fn to_screaming_snake_case(&self) -> String {
64        self.to_snake_case().to_ascii_uppercase()
65    }
66
67    fn to_kebab_case(&self) -> String {
68        self.to_snake_case().replace('_', "-")
69    }
70
71    fn to_screaming_kebab_case(&self) -> String {
72        self.to_kebab_case().to_ascii_uppercase()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    struct Test {
81        original: String,
82        camel_case: String,
83        pascal_case: String,
84        snake_case: String,
85        screaming_snake_case: String,
86        kebab_case: String,
87        screaming_kebab_case: String,
88    }
89
90    #[test]
91    fn test_rename() {
92        let tests = &[
93            Test {
94                original: "FooBar".to_string(),
95                camel_case: "fooBar".to_string(),
96                pascal_case: "FooBar".to_string(),
97                snake_case: "foo_bar".to_string(),
98                screaming_snake_case: "FOO_BAR".to_string(),
99                kebab_case: "foo-bar".to_string(),
100                screaming_kebab_case: "FOO-BAR".to_string(),
101            },
102            Test {
103                original: "foo_bar".to_string(),
104                camel_case: "fooBar".to_string(),
105                pascal_case: "FooBar".to_string(),
106                snake_case: "foo_bar".to_string(),
107                screaming_snake_case: "FOO_BAR".to_string(),
108                kebab_case: "foo-bar".to_string(),
109                screaming_kebab_case: "FOO-BAR".to_string(),
110            },
111            Test {
112                original: "Hello".to_string(),
113                camel_case: "hello".to_string(),
114                pascal_case: "Hello".to_string(),
115                snake_case: "hello".to_string(),
116                screaming_snake_case: "HELLO".to_string(),
117                kebab_case: "hello".to_string(),
118                screaming_kebab_case: "HELLO".to_string(),
119            },
120            Test {
121                original: "Number1".to_string(),
122                camel_case: "number1".to_string(),
123                pascal_case: "Number1".to_string(),
124                snake_case: "number1".to_string(),
125                screaming_snake_case: "NUMBER1".to_string(),
126                kebab_case: "number1".to_string(),
127                screaming_kebab_case: "NUMBER1".to_string(),
128            },
129            Test {
130                original: "AddressLine1".to_string(),
131                camel_case: "addressLine1".to_string(),
132                pascal_case: "AddressLine1".to_string(),
133                snake_case: "address_line1".to_string(),
134                screaming_snake_case: "ADDRESS_LINE1".to_string(),
135                kebab_case: "address-line1".to_string(),
136                screaming_kebab_case: "ADDRESS-LINE1".to_string(),
137            },
138            Test {
139                original: "URL".to_string(),
140                camel_case: "url".to_string(),
141                pascal_case: "Url".to_string(),
142                snake_case: "url".to_string(),
143                screaming_snake_case: "URL".to_string(),
144                kebab_case: "url".to_string(),
145                screaming_kebab_case: "URL".to_string(),
146            },
147        ];
148
149        for test in tests {
150            assert_eq!(test.original.to_camel_case(), test.camel_case);
151            assert_eq!(test.original.to_pascal_case(), test.pascal_case);
152            assert_eq!(test.original.to_snake_case(), test.snake_case);
153            assert_eq!(
154                test.original.to_screaming_snake_case(),
155                test.screaming_snake_case
156            );
157            assert_eq!(test.original.to_kebab_case(), test.kebab_case);
158            assert_eq!(
159                test.original.to_screaming_kebab_case(),
160                test.screaming_kebab_case
161            );
162        }
163    }
164}