1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum RenameRule {
5 PascalCase,
7 CamelCase,
9 SnakeCase,
11 ScreamingSnakeCase,
13 KebabCase,
15 ScreamingKebabCase,
17 Lowercase,
19 Uppercase,
21}
22
23impl RenameRule {
24 pub fn parse(rule: &str) -> Option<Self> {
26 match rule {
27 "PascalCase" => Some(RenameRule::PascalCase),
28 "camelCase" => Some(RenameRule::CamelCase),
29 "snake_case" => Some(RenameRule::SnakeCase),
30 "SCREAMING_SNAKE_CASE" => Some(RenameRule::ScreamingSnakeCase),
31 "kebab-case" => Some(RenameRule::KebabCase),
32 "SCREAMING-KEBAB-CASE" => Some(RenameRule::ScreamingKebabCase),
33 "lowercase" => Some(RenameRule::Lowercase),
34 "UPPERCASE" => Some(RenameRule::Uppercase),
35 _ => None,
36 }
37 }
38
39 pub fn apply(self, input: &str) -> String {
41 match self {
42 RenameRule::PascalCase => to_pascal_case(input),
43 RenameRule::CamelCase => to_camel_case(input),
44 RenameRule::SnakeCase => to_snake_case(input),
45 RenameRule::ScreamingSnakeCase => to_screaming_snake_case(input),
46 RenameRule::KebabCase => to_kebab_case(input),
47 RenameRule::ScreamingKebabCase => to_screaming_kebab_case(input),
48 RenameRule::Lowercase => to_lowercase(input),
49 RenameRule::Uppercase => to_uppercase(input),
50 }
51 }
52}
53
54fn to_pascal_case(input: &str) -> String {
56 split_into_words(input)
57 .iter()
58 .map(|word| {
59 let mut chars = word.chars();
60 match chars.next() {
61 None => String::new(),
62 Some(c) => {
63 c.to_uppercase().collect::<String>() + &chars.collect::<String>().to_lowercase()
64 }
65 }
66 })
67 .collect()
68}
69
70fn to_camel_case(input: &str) -> String {
72 let pascal = to_pascal_case(input);
73 if pascal.is_empty() {
74 return String::new();
75 }
76
77 let mut result = String::new();
78 let mut chars = pascal.chars();
79 if let Some(first_char) = chars.next() {
80 result.push(first_char.to_lowercase().next().unwrap());
81 }
82 result.extend(chars);
83 result
84}
85
86fn to_snake_case(input: &str) -> String {
88 let words = split_into_words(input);
89 words
90 .iter()
91 .map(|word| word.to_lowercase())
92 .collect::<Vec<_>>()
93 .join("_")
94}
95
96fn to_screaming_snake_case(input: &str) -> String {
98 let words = split_into_words(input);
99 words
100 .iter()
101 .map(|word| word.to_uppercase())
102 .collect::<Vec<_>>()
103 .join("_")
104}
105
106fn to_kebab_case(input: &str) -> String {
108 let words = split_into_words(input);
109 words
110 .iter()
111 .map(|word| word.to_lowercase())
112 .collect::<Vec<_>>()
113 .join("-")
114}
115
116fn to_screaming_kebab_case(input: &str) -> String {
118 let words = split_into_words(input);
119 words
120 .iter()
121 .map(|word| word.to_uppercase())
122 .collect::<Vec<_>>()
123 .join("-")
124}
125
126fn to_lowercase(input: &str) -> String {
128 let words = split_into_words(input);
129 words
130 .iter()
131 .map(|word| word.to_lowercase())
132 .collect::<Vec<_>>()
133 .join("")
134}
135
136fn to_uppercase(input: &str) -> String {
138 let words = split_into_words(input);
139 words
140 .iter()
141 .map(|word| word.to_uppercase())
142 .collect::<Vec<_>>()
143 .join("")
144}
145
146fn split_into_words(input: &str) -> Vec<String> {
156 if input.is_empty() {
157 return vec![];
158 }
159
160 let mut words = Vec::new();
161 let mut current_word = String::new();
162 let mut chars = input.chars().peekable();
163
164 while let Some(c) = chars.next() {
165 if c == '_' || c == '-' || c.is_whitespace() {
167 if !current_word.is_empty() {
168 words.push(std::mem::take(&mut current_word));
169 }
170 continue;
171 }
172
173 let next = chars.peek().copied();
175
176 if c.is_uppercase() {
177 if !current_word.is_empty() {
178 let prev = current_word.chars().last().unwrap();
179 if prev.is_lowercase()
184 || prev.is_ascii_digit()
185 || (prev.is_uppercase() && next.map(|n| n.is_lowercase()).unwrap_or(false))
186 {
187 words.push(std::mem::take(&mut current_word));
188 }
189 }
190 current_word.push(c);
191 } else {
192 current_word.push(c);
195 }
196 }
197
198 if !current_word.is_empty() {
199 words.push(current_word);
200 }
201
202 words.into_iter().filter(|s| !s.is_empty()).collect()
203}
204
205#[cfg(test)]
206mod tests {
207 use super::split_into_words;
208
209 #[test]
210 fn test_split_into_words_simple_snake_case() {
211 assert_eq!(split_into_words("foo_bar_baz"), vec!["foo", "bar", "baz"]);
212 }
213
214 #[test]
215 fn test_split_into_words_single_word() {
216 assert_eq!(split_into_words("foo"), vec!["foo"]);
217 assert_eq!(split_into_words("Foo"), vec!["Foo"]);
218 }
219
220 #[test]
221 fn test_split_into_words_empty_string() {
222 assert_eq!(split_into_words(""), Vec::<String>::new());
223 }
224
225 #[test]
226 fn test_split_into_words_multiple_underscores() {
227 assert_eq!(split_into_words("foo__bar"), vec!["foo", "bar"]);
228 assert_eq!(split_into_words("_foo_bar_"), vec!["foo", "bar"]);
229 }
230
231 #[test]
232 fn test_split_into_words_kebab_case() {
233 assert_eq!(split_into_words("foo-bar-baz"), vec!["foo", "bar", "baz"]);
234 }
235
236 #[test]
237 fn test_split_into_words_mixed_separators_and_space() {
238 assert_eq!(split_into_words("foo_ bar-baz"), vec!["foo", "bar", "baz"]);
239 assert_eq!(split_into_words("a_b-c d"), vec!["a", "b", "c", "d"]);
240 }
241
242 #[test]
243 fn test_split_into_words_camel_case() {
244 assert_eq!(split_into_words("fooBarBaz"), vec!["foo", "Bar", "Baz"]);
245 assert_eq!(split_into_words("fooBar"), vec!["foo", "Bar"]);
246 assert_eq!(
247 split_into_words("fooBar_BazQuux"),
248 vec!["foo", "Bar", "Baz", "Quux"]
249 );
250 }
251
252 #[test]
253 fn test_split_into_words_pascal_case() {
254 assert_eq!(split_into_words("FooBarBaz"), vec!["Foo", "Bar", "Baz"]);
255 assert_eq!(split_into_words("FooBar"), vec!["Foo", "Bar"]);
256 }
257
258 #[test]
259 fn test_split_into_words_http_server() {
260 assert_eq!(split_into_words("HTTPServer"), vec!["HTTP", "Server"]);
261 assert_eq!(
262 split_into_words("theHTTPServer"),
263 vec!["the", "HTTP", "Server"]
264 );
265 }
266
267 #[test]
268 fn test_split_into_words_consecutive_uppercase_at_end() {
269 assert_eq!(split_into_words("FooBAR"), vec!["Foo", "BAR"]);
270 assert_eq!(split_into_words("FooBARBaz"), vec!["Foo", "BAR", "Baz"]);
271 }
272
273 #[test]
274 fn test_split_into_words_separators_and_case_boundaries() {
275 assert_eq!(split_into_words("foo_barBaz"), vec!["foo", "bar", "Baz"]);
276 assert_eq!(
277 split_into_words("fooBar_bazQux"),
278 vec!["foo", "Bar", "baz", "Qux"]
279 );
280 }
281
282 #[test]
283 fn test_rename_rule_snake_case() {
284 use super::RenameRule;
285 assert_eq!(RenameRule::SnakeCase.apply("foo_bar_baz"), "foo_bar_baz");
287 assert_eq!(RenameRule::SnakeCase.apply("fooBarBaz"), "foo_bar_baz");
289 assert_eq!(RenameRule::SnakeCase.apply("FooBarBaz"), "foo_bar_baz");
291 assert_eq!(RenameRule::SnakeCase.apply("FOO_BAR_BAZ"), "foo_bar_baz");
293 assert_eq!(RenameRule::SnakeCase.apply("foo-bar-baz"), "foo_bar_baz");
295 assert_eq!(
296 RenameRule::SnakeCase.apply("Foo_Bar-Baz quux"),
297 "foo_bar_baz_quux"
298 );
299 assert_eq!(
301 RenameRule::SnakeCase.apply("theHTTPServer"),
302 "the_http_server"
303 );
304 assert_eq!(RenameRule::SnakeCase.apply("FooBARBaz"), "foo_bar_baz");
305 assert_eq!(RenameRule::SnakeCase.apply(""), "");
307 }
308
309 #[test]
310 fn test_rename_rule_lowercase() {
311 use super::RenameRule;
312 assert_eq!(RenameRule::Lowercase.apply("foo_bar_baz"), "foobarbaz");
314 assert_eq!(RenameRule::Lowercase.apply("fooBarBaz"), "foobarbaz");
316 assert_eq!(RenameRule::Lowercase.apply("FooBarBaz"), "foobarbaz");
318 assert_eq!(RenameRule::Lowercase.apply("FOO_BAR_BAZ"), "foobarbaz");
320 assert_eq!(RenameRule::Lowercase.apply("foo-bar-baz"), "foobarbaz");
322 assert_eq!(
324 RenameRule::Lowercase.apply("theHTTPServer"),
325 "thehttpserver"
326 );
327 assert_eq!(RenameRule::Lowercase.apply(""), "");
329 }
330
331 #[test]
332 fn test_rename_rule_uppercase() {
333 use super::RenameRule;
334 assert_eq!(RenameRule::Uppercase.apply("foo_bar_baz"), "FOOBARBAZ");
336 assert_eq!(RenameRule::Uppercase.apply("fooBarBaz"), "FOOBARBAZ");
338 assert_eq!(RenameRule::Uppercase.apply("FooBarBaz"), "FOOBARBAZ");
340 assert_eq!(RenameRule::Uppercase.apply("FOO_BAR_BAZ"), "FOOBARBAZ");
342 assert_eq!(RenameRule::Uppercase.apply("foo-bar-baz"), "FOOBARBAZ");
344 assert_eq!(
346 RenameRule::Uppercase.apply("theHTTPServer"),
347 "THEHTTPSERVER"
348 );
349 assert_eq!(RenameRule::Uppercase.apply("max_size"), "MAXSIZE");
351 assert_eq!(RenameRule::Uppercase.apply("min_value"), "MINVALUE");
352 assert_eq!(RenameRule::Uppercase.apply(""), "");
354 }
355}