1 | #[cfg (test)] |
2 | use strum::EnumIter; |
3 | |
4 | use crate::boundary::Boundary; |
5 | use crate::pattern::Pattern; |
6 | |
7 | /// Defines the type of casing a string can be. |
8 | /// |
9 | /// ``` |
10 | /// use convert_case::{Case, Casing}; |
11 | /// |
12 | /// let super_mario_title: String = "super_mario_64" .to_case(Case::Title); |
13 | /// assert_eq!("Super Mario 64" , super_mario_title); |
14 | /// ``` |
15 | /// |
16 | /// A case is the pair of a [pattern](enum.Pattern.html) and a delimeter (a string). Given |
17 | /// a list of words, a pattern describes how to mutate the words and a delimeter is how the mutated |
18 | /// words are joined together. These inherantly are the properties of what makes a "multiword |
19 | /// identifier case", or simply "case". |
20 | /// |
21 | /// This crate provides the ability to convert "from" a case. This introduces a different feature |
22 | /// of cases which are the [word boundaries](Boundary) that segment the identifier into words. For example, a |
23 | /// snake case identifier `my_var_name` can be split on underscores `_` to segment into words. A |
24 | /// camel case identifier `myVarName` is split where a lowercase letter is followed by an |
25 | /// uppercase letter. Each case is also associated with a list of boundaries that are used when |
26 | /// converting "from" a particular case. |
27 | #[cfg_attr (test, derive(EnumIter))] |
28 | #[derive (Eq, PartialEq, Hash, Clone, Copy, Debug)] |
29 | pub enum Case { |
30 | /// Uppercase strings are delimited by spaces and all characters are uppercase. |
31 | /// * Boundaries: [Space](`Boundary::SPACE`) |
32 | /// * Pattern: [Uppercase](`Pattern::Uppercase`) |
33 | /// * Delimeter: Space |
34 | /// |
35 | /// ``` |
36 | /// use convert_case::{Case, Casing}; |
37 | /// assert_eq!("MY VARIABLE NAME" , "My variable NAME" .to_case(Case::Upper)) |
38 | /// ``` |
39 | Upper, |
40 | |
41 | /// Lowercase strings are delimited by spaces and all characters are lowercase. |
42 | /// * Boundaries: [Space](`Boundary::SPACE`) |
43 | /// * Pattern: [Lowercase](`Pattern::Lowercase`) |
44 | /// * Delimeter: Space |
45 | /// |
46 | /// ``` |
47 | /// use convert_case::{Case, Casing}; |
48 | /// assert_eq!("my variable name" , "My variable NAME" .to_case(Case::Lower)) |
49 | /// ``` |
50 | Lower, |
51 | |
52 | /// Title case strings are delimited by spaces. Only the leading character of |
53 | /// each word is uppercase. No inferences are made about language, so words |
54 | /// like "as", "to", and "for" will still be capitalized. |
55 | /// * Boundaries: [Space](`Boundary::SPACE`) |
56 | /// * Pattern: [Capital](`Pattern::Capital`) |
57 | /// * Delimeter: Space |
58 | /// |
59 | /// ``` |
60 | /// use convert_case::{Case, Casing}; |
61 | /// assert_eq!("My Variable Name" , "My variable NAME" .to_case(Case::Title)) |
62 | /// ``` |
63 | Title, |
64 | |
65 | /// Sentence case strings are delimited by spaces. Only the leading character of |
66 | /// the first word is uppercase. |
67 | /// * Boundaries: [Space](`Boundary::SPACE`) |
68 | /// * Pattern: [Capital](`Pattern::Sentence`) |
69 | /// * Delimeter: Space |
70 | /// |
71 | /// ``` |
72 | /// use convert_case::{Case, Casing}; |
73 | /// assert_eq!("My variable name" , "My variable NAME" .to_case(Case::Sentence)) |
74 | /// ``` |
75 | Sentence, |
76 | |
77 | /// Toggle case strings are delimited by spaces. All characters are uppercase except |
78 | /// for the leading character of each word, which is lowercase. |
79 | /// * Boundaries: [Space](`Boundary::SPACE`) |
80 | /// * Pattern: [Toggle](`Pattern::Toggle`) |
81 | /// * Delimeter: Space |
82 | /// |
83 | /// ``` |
84 | /// use convert_case::{Case, Casing}; |
85 | /// assert_eq!("mY vARIABLE nAME" , "My variable NAME" .to_case(Case::Toggle)) |
86 | /// ``` |
87 | Toggle, |
88 | |
89 | /// Camel case strings are lowercase, but for every word _except the first_ the |
90 | /// first letter is capitalized. |
91 | /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER), |
92 | /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER), |
93 | /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM) |
94 | /// * Pattern: [Camel](`Pattern::Camel`) |
95 | /// * Delimeter: No delimeter |
96 | /// |
97 | /// ``` |
98 | /// use convert_case::{Case, Casing}; |
99 | /// assert_eq!("myVariableName" , "My variable NAME" .to_case(Case::Camel)) |
100 | /// ``` |
101 | Camel, |
102 | |
103 | /// Pascal case strings are lowercase, but for every word the |
104 | /// first letter is capitalized. |
105 | /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER), |
106 | /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER), |
107 | /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM) |
108 | /// * Pattern: [Capital](`Pattern::Capital`) |
109 | /// * Delimeter: No delimeter |
110 | /// |
111 | /// ``` |
112 | /// use convert_case::{Case, Casing}; |
113 | /// assert_eq!("MyVariableName" , "My variable NAME" .to_case(Case::Pascal)) |
114 | /// ``` |
115 | Pascal, |
116 | |
117 | /// Upper camel case is an alternative name for [Pascal case](Case::Pascal). |
118 | UpperCamel, |
119 | |
120 | /// Snake case strings are delimited by underscores `_` and are all lowercase. |
121 | /// * Boundaries: [Underscore](Boundary::UNDERSCORE) |
122 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
123 | /// * Delimeter: Underscore `_` |
124 | /// |
125 | /// ``` |
126 | /// use convert_case::{Case, Casing}; |
127 | /// assert_eq!("my_variable_name" , "My variable NAME" .to_case(Case::Snake)) |
128 | /// ``` |
129 | Snake, |
130 | |
131 | /// Constant case strings are delimited by underscores `_` and are all uppercase. |
132 | /// * Boundaries: [Underscore](Boundary::UNDERSCORE) |
133 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
134 | /// * Delimeter: Underscore `_` |
135 | /// |
136 | /// ``` |
137 | /// use convert_case::{Case, Casing}; |
138 | /// assert_eq!("MY_VARIABLE_NAME" , "My variable NAME" .to_case(Case::Constant)) |
139 | /// ``` |
140 | Constant, |
141 | |
142 | /// Upper snake case is an alternative name for [constant case](Case::Constant). |
143 | UpperSnake, |
144 | |
145 | /// Kebab case strings are delimited by hyphens `-` and are all lowercase. |
146 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
147 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
148 | /// * Delimeter: Hyphen `-` |
149 | /// |
150 | /// ``` |
151 | /// use convert_case::{Case, Casing}; |
152 | /// assert_eq!("my-variable-name" , "My variable NAME" .to_case(Case::Kebab)) |
153 | /// ``` |
154 | Kebab, |
155 | |
156 | /// Cobol case strings are delimited by hyphens `-` and are all uppercase. |
157 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
158 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
159 | /// * Delimeter: Hyphen `-` |
160 | /// |
161 | /// ``` |
162 | /// use convert_case::{Case, Casing}; |
163 | /// assert_eq!("MY-VARIABLE-NAME" , "My variable NAME" .to_case(Case::Cobol)) |
164 | /// ``` |
165 | Cobol, |
166 | |
167 | /// Upper kebab case is an alternative name for [Cobol case](Case::Cobol). |
168 | UpperKebab, |
169 | |
170 | /// Train case strings are delimited by hyphens `-`. All characters are lowercase |
171 | /// except for the leading character of each word. |
172 | /// * Boundaries: [Hyphen](Boundary::HYPHEN) |
173 | /// * Pattern: [Capital](Pattern::Capital) |
174 | /// * Delimeter: Hyphen `-` |
175 | /// |
176 | /// ``` |
177 | /// use convert_case::{Case, Casing}; |
178 | /// assert_eq!("My-Variable-Name" , "My variable NAME" .to_case(Case::Train)) |
179 | /// ``` |
180 | Train, |
181 | |
182 | /// Flat case strings are all lowercase, with no delimiter. Note that word boundaries are lost. |
183 | /// * Boundaries: No boundaries |
184 | /// * Pattern: [Lowercase](Pattern::Lowercase) |
185 | /// * Delimeter: No delimeter |
186 | /// |
187 | /// ``` |
188 | /// use convert_case::{Case, Casing}; |
189 | /// assert_eq!("myvariablename" , "My variable NAME" .to_case(Case::Flat)) |
190 | /// ``` |
191 | Flat, |
192 | |
193 | /// Upper flat case strings are all uppercase, with no delimiter. Note that word boundaries are lost. |
194 | /// * Boundaries: No boundaries |
195 | /// * Pattern: [Uppercase](Pattern::Uppercase) |
196 | /// * Delimeter: No delimeter |
197 | /// |
198 | /// ``` |
199 | /// use convert_case::{Case, Casing}; |
200 | /// assert_eq!("MYVARIABLENAME" , "My variable NAME" .to_case(Case::UpperFlat)) |
201 | /// ``` |
202 | UpperFlat, |
203 | |
204 | /// Alternating case strings are delimited by spaces. Characters alternate between uppercase |
205 | /// and lowercase. |
206 | /// * Boundaries: [Space](Boundary::SPACE) |
207 | /// * Pattern: [Alternating](Pattern::Alternating) |
208 | /// * Delimeter: Space |
209 | /// |
210 | /// ``` |
211 | /// use convert_case::{Case, Casing}; |
212 | /// assert_eq!("mY vArIaBlE nAmE" , "My variable NAME" .to_case(Case::Alternating)); |
213 | /// ``` |
214 | Alternating, |
215 | |
216 | /// Random case strings are delimited by spaces and characters are |
217 | /// randomly upper case or lower case. This uses the `rand` crate |
218 | /// and is only available with the "random" feature. |
219 | /// * Boundaries: [Space](Boundary::SPACE) |
220 | /// * Pattern: [Random](Pattern::Random) |
221 | /// * Delimeter: Space |
222 | /// |
223 | /// ``` |
224 | /// use convert_case::{Case, Casing}; |
225 | /// # #[cfg(any(doc, feature = "random"))] |
226 | /// let new = "My variable NAME".to_case(Case::Random); |
227 | /// ``` |
228 | /// String `new` could be "My vaRIAbLE nAme" for example. |
229 | #[cfg (any(doc, feature = "random" ))] |
230 | #[cfg (feature = "random" )] |
231 | Random, |
232 | |
233 | /// Pseudo-random case strings are delimited by spaces and characters are randomly |
234 | /// upper case or lower case, but there will never more than two consecutive lower |
235 | /// case or upper case letters in a row. This uses the `rand` crate and is |
236 | /// only available with the "random" feature. |
237 | /// * Boundaries: [Space](Boundary::SPACE) |
238 | /// * Pattern: [PseudoRandom](Pattern::PseudoRandom) |
239 | /// * Delimeter: Space |
240 | /// |
241 | /// ``` |
242 | /// use convert_case::{Case, Casing}; |
243 | /// # #[cfg(any(doc, feature = "random"))] |
244 | /// let new = "My variable NAME".to_case(Case::Random); |
245 | /// ``` |
246 | /// String `new` could be "mY vArIAblE NamE" for example. |
247 | #[cfg (any(doc, feature = "random" ))] |
248 | #[cfg (feature = "random" )] |
249 | PseudoRandom, |
250 | } |
251 | |
252 | impl Case { |
253 | /// Returns the delimiter used in the corresponding case. The following |
254 | /// table outlines which cases use which delimeter. |
255 | /// |
256 | /// | Cases | Delimeter | |
257 | /// | --- | --- | |
258 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
259 | /// | Snake, Constant, UpperSnake | Underscore `_` | |
260 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
261 | /// | UpperFlat, Flat, Camel, UpperCamel, Pascal | Empty string, no delimeter | |
262 | pub const fn delim(&self) -> &'static str { |
263 | use Case::*; |
264 | match self { |
265 | Upper | Lower | Title | Sentence | Toggle | Alternating => " " , |
266 | Snake | Constant | UpperSnake => "_" , |
267 | Kebab | Cobol | UpperKebab | Train => "-" , |
268 | |
269 | #[cfg (feature = "random" )] |
270 | Random | PseudoRandom => " " , |
271 | |
272 | UpperFlat | Flat | Camel | UpperCamel | Pascal => "" , |
273 | } |
274 | } |
275 | |
276 | /// Returns the pattern used in the corresponding case. The following |
277 | /// table outlines which cases use which pattern. |
278 | /// |
279 | /// | Cases | Pattern | |
280 | /// | --- | --- | |
281 | /// | Upper, Constant, UpperSnake, UpperFlat, Cobol, UpperKebab | Uppercase | |
282 | /// | Lower, Snake, Kebab, Flat | Lowercase | |
283 | /// | Title, Pascal, UpperCamel, Train | Capital | |
284 | /// | Camel | Camel | |
285 | /// | Alternating | Alternating | |
286 | /// | Random | Random | |
287 | /// | PseudoRandom | PseudoRandom | |
288 | pub const fn pattern(&self) -> Pattern { |
289 | use Case::*; |
290 | match self { |
291 | Upper | Constant | UpperSnake | UpperFlat | Cobol | UpperKebab => Pattern::Uppercase, |
292 | Lower | Snake | Kebab | Flat => Pattern::Lowercase, |
293 | Title | Pascal | UpperCamel | Train => Pattern::Capital, |
294 | Camel => Pattern::Camel, |
295 | Toggle => Pattern::Toggle, |
296 | Alternating => Pattern::Alternating, |
297 | Sentence => Pattern::Sentence, |
298 | |
299 | #[cfg (feature = "random" )] |
300 | Random => Pattern::Random, |
301 | #[cfg (feature = "random" )] |
302 | PseudoRandom => Pattern::PseudoRandom, |
303 | } |
304 | } |
305 | |
306 | /// Returns the boundaries used in the corresponding case. That is, where can word boundaries |
307 | /// be distinguished in a string of the given case. The table outlines which cases use which |
308 | /// set of boundaries. |
309 | /// |
310 | /// | Cases | Boundaries | |
311 | /// | --- | --- | |
312 | /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space | |
313 | /// | Snake, Constant, UpperSnake | Underscore `_` | |
314 | /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` | |
315 | /// | Camel, UpperCamel, Pascal | LowerUpper, LowerDigit, UpperDigit, DigitLower, DigitUpper, Acronym | |
316 | /// | UpperFlat, Flat | No boundaries | |
317 | pub fn boundaries(&self) -> Vec<Boundary> { |
318 | use Case::*; |
319 | match self { |
320 | Upper | Lower | Title | Sentence | Toggle | Alternating => vec![Boundary::SPACE], |
321 | Snake | Constant | UpperSnake => vec![Boundary::UNDERSCORE], |
322 | Kebab | Cobol | UpperKebab | Train => vec![Boundary::HYPHEN], |
323 | |
324 | #[cfg (feature = "random" )] |
325 | Random | PseudoRandom => vec![Boundary::SPACE], |
326 | |
327 | UpperFlat | Flat => vec![], |
328 | Camel | UpperCamel | Pascal => vec![ |
329 | Boundary::LOWER_UPPER, |
330 | Boundary::ACRONYM, |
331 | Boundary::LOWER_DIGIT, |
332 | Boundary::UPPER_DIGIT, |
333 | Boundary::DIGIT_LOWER, |
334 | Boundary::DIGIT_UPPER, |
335 | ], |
336 | } |
337 | } |
338 | |
339 | // Created to avoid using the EnumIter trait from strum in |
340 | // final library. A test confirms that all cases are listed here. |
341 | // Why is this needed? If it's only for ccase then I don't see why it's here. |
342 | /// Returns a vector with all case enum variants in no particular order. |
343 | pub fn all_cases() -> Vec<Case> { |
344 | use Case::*; |
345 | vec![ |
346 | Upper, |
347 | Lower, |
348 | Title, |
349 | Sentence, |
350 | Toggle, |
351 | Camel, |
352 | Pascal, |
353 | UpperCamel, |
354 | Snake, |
355 | Constant, |
356 | UpperSnake, |
357 | Kebab, |
358 | Cobol, |
359 | UpperKebab, |
360 | Train, |
361 | Flat, |
362 | UpperFlat, |
363 | Alternating, |
364 | #[cfg (feature = "random" )] |
365 | Random, |
366 | #[cfg (feature = "random" )] |
367 | PseudoRandom, |
368 | ] |
369 | } |
370 | |
371 | /// Returns a vector with the two "random" feature cases `Random` and `PseudoRandom`. Only |
372 | /// defined in the "random" feature. |
373 | #[cfg (feature = "random" )] |
374 | pub fn random_cases() -> Vec<Case> { |
375 | use Case::*; |
376 | vec![Random, PseudoRandom] |
377 | } |
378 | |
379 | /// Returns a vector with all the cases that do not depend on randomness. This is all |
380 | /// the cases not in the "random" feature. |
381 | pub fn deterministic_cases() -> Vec<Case> { |
382 | use Case::*; |
383 | vec![ |
384 | Upper, |
385 | Lower, |
386 | Title, |
387 | Sentence, |
388 | Toggle, |
389 | Camel, |
390 | Pascal, |
391 | UpperCamel, |
392 | Snake, |
393 | Constant, |
394 | UpperSnake, |
395 | Kebab, |
396 | Cobol, |
397 | UpperKebab, |
398 | Train, |
399 | Flat, |
400 | UpperFlat, |
401 | Alternating, |
402 | ] |
403 | } |
404 | } |
405 | |
406 | #[cfg (test)] |
407 | mod test { |
408 | |
409 | use super::*; |
410 | use strum::IntoEnumIterator; |
411 | |
412 | #[test ] |
413 | fn all_cases_in_iter() { |
414 | let all = Case::all_cases(); |
415 | for case in Case::iter() { |
416 | assert!(all.contains(&case)); |
417 | } |
418 | } |
419 | } |
420 | |