1use crate::types::RenameAll;
27
28impl RenameAll {
29 pub fn apply_to_field(self, field: &str) -> String {
40 match self {
41 Self::Lowercase | Self::SnakeCase => field.to_owned(),
46 Self::Uppercase => field.to_ascii_uppercase(),
47 Self::PascalCase => snake_to_pascal(field),
48 Self::CamelCase => {
49 let pascal = snake_to_pascal(field);
50 lowercase_first_char(&pascal)
51 }
52 Self::ScreamingSnakeCase => field.to_ascii_uppercase(),
53 Self::KebabCase => field.replace('_', "-"),
54 Self::ScreamingKebabCase => field.to_ascii_uppercase().replace('_', "-"),
55 }
56 }
57
58 pub fn apply_to_variant(self, variant: &str) -> String {
69 match self {
70 Self::PascalCase => variant.to_owned(),
72 Self::Lowercase => variant.to_ascii_lowercase(),
73 Self::Uppercase => variant.to_ascii_uppercase(),
74 Self::CamelCase => lowercase_first_char(variant),
76 Self::SnakeCase => pascal_to_snake(variant),
77 Self::ScreamingSnakeCase => pascal_to_snake(variant).to_ascii_uppercase(),
78 Self::KebabCase => pascal_to_snake(variant).replace('_', "-"),
79 Self::ScreamingKebabCase => pascal_to_snake(variant).to_ascii_uppercase().replace('_', "-"),
80 }
81 }
82}
83
84fn snake_to_pascal(snake: &str) -> String {
89 let mut out = String::with_capacity(snake.len());
90 let mut capitalize_next = true;
91 for ch in snake.chars() {
92 if ch == '_' {
93 capitalize_next = true;
94 } else if capitalize_next {
95 out.push(ch.to_ascii_uppercase());
96 capitalize_next = false;
97 } else {
98 out.push(ch);
99 }
100 }
101 out
102}
103
104fn pascal_to_snake(pascal: &str) -> String {
109 let mut out = String::with_capacity(pascal.len() + 4);
110 for (i, ch) in pascal.char_indices() {
111 if i > 0 && ch.is_ascii_uppercase() {
112 out.push('_');
113 }
114 out.push(ch.to_ascii_lowercase());
115 }
116 out
117}
118
119fn lowercase_first_char(s: &str) -> String {
121 let mut chars = s.chars();
122 match chars.next() {
123 Some(first) => {
124 let mut out = String::with_capacity(s.len());
125 out.push(first.to_ascii_lowercase());
126 out.extend(chars);
127 out
128 }
129 None => String::new(),
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::types::RenameAll;
136
137 #[test]
140 fn field_lowercase_is_identity() {
141 assert_eq!(RenameAll::Lowercase.apply_to_field("parse_url_v2"), "parse_url_v2");
142 }
143
144 #[test]
145 fn field_snake_case_is_identity() {
146 assert_eq!(RenameAll::SnakeCase.apply_to_field("parse_url_v2"), "parse_url_v2");
147 }
148
149 #[test]
150 fn field_uppercase() {
151 assert_eq!(RenameAll::Uppercase.apply_to_field("parse_url_v2"), "PARSE_URL_V2");
152 }
153
154 #[test]
155 fn field_pascal_case() {
156 assert_eq!(RenameAll::PascalCase.apply_to_field("parse_url_v2"), "ParseUrlV2");
157 assert_eq!(RenameAll::PascalCase.apply_to_field("html_parser"), "HtmlParser");
158 assert_eq!(RenameAll::PascalCase.apply_to_field("single"), "Single");
159 }
160
161 #[test]
162 fn field_camel_case() {
163 assert_eq!(RenameAll::CamelCase.apply_to_field("parse_url_v2"), "parseUrlV2");
164 assert_eq!(RenameAll::CamelCase.apply_to_field("html_parser"), "htmlParser");
165 assert_eq!(RenameAll::CamelCase.apply_to_field("single"), "single");
166 }
167
168 #[test]
169 fn field_screaming_snake_case() {
170 assert_eq!(RenameAll::ScreamingSnakeCase.apply_to_field("parse_url_v2"), "PARSE_URL_V2");
171 }
172
173 #[test]
174 fn field_kebab_case() {
175 assert_eq!(RenameAll::KebabCase.apply_to_field("parse_url_v2"), "parse-url-v2");
176 }
177
178 #[test]
179 fn field_screaming_kebab_case() {
180 assert_eq!(RenameAll::ScreamingKebabCase.apply_to_field("parse_url_v2"), "PARSE-URL-V2");
181 }
182
183 #[test]
186 fn variant_pascal_case_is_identity() {
187 assert_eq!(RenameAll::PascalCase.apply_to_variant("ApiClient"), "ApiClient");
188 }
189
190 #[test]
191 fn variant_lowercase() {
192 assert_eq!(RenameAll::Lowercase.apply_to_variant("ApiClient"), "apiclient");
193 }
194
195 #[test]
196 fn variant_uppercase() {
197 assert_eq!(RenameAll::Uppercase.apply_to_variant("ApiClient"), "APICLIENT");
198 }
199
200 #[test]
201 fn variant_camel_case() {
202 assert_eq!(RenameAll::CamelCase.apply_to_variant("ApiClient"), "apiClient");
204 assert_eq!(RenameAll::CamelCase.apply_to_variant("HTMLParser"), "hTMLParser");
205 }
206
207 #[test]
208 fn variant_snake_case() {
209 assert_eq!(RenameAll::SnakeCase.apply_to_variant("ApiClient"), "api_client");
210 assert_eq!(RenameAll::SnakeCase.apply_to_variant("HTMLParser"), "h_t_m_l_parser");
212 assert_eq!(RenameAll::SnakeCase.apply_to_variant("Single"), "single");
213 }
214
215 #[test]
216 fn variant_screaming_snake_case() {
217 assert_eq!(RenameAll::ScreamingSnakeCase.apply_to_variant("ApiClient"), "API_CLIENT");
218 assert_eq!(RenameAll::ScreamingSnakeCase.apply_to_variant("HTMLParser"), "H_T_M_L_PARSER");
219 }
220
221 #[test]
222 fn variant_kebab_case() {
223 assert_eq!(RenameAll::KebabCase.apply_to_variant("ApiClient"), "api-client");
224 }
225
226 #[test]
227 fn variant_screaming_kebab_case() {
228 assert_eq!(RenameAll::ScreamingKebabCase.apply_to_variant("ApiClient"), "API-CLIENT");
229 }
230
231 #[test]
234 fn empty_input_pass_through() {
235 for mode in [
237 RenameAll::Lowercase,
238 RenameAll::Uppercase,
239 RenameAll::PascalCase,
240 RenameAll::CamelCase,
241 RenameAll::SnakeCase,
242 RenameAll::ScreamingSnakeCase,
243 RenameAll::KebabCase,
244 RenameAll::ScreamingKebabCase,
245 ] {
246 assert_eq!(mode.apply_to_field(""), "");
247 assert_eq!(mode.apply_to_variant(""), "");
248 }
249 }
250
251 #[test]
252 fn field_with_digit_segment_camel_case() {
253 assert_eq!(RenameAll::PascalCase.apply_to_field("parse_v2"), "ParseV2");
257 assert_eq!(RenameAll::CamelCase.apply_to_field("parse_v2"), "parseV2");
258 }
259}