convert_case/case.rs
1use crate::boundary::{self, Boundary};
2use crate::pattern;
3
4use alloc::string::String;
5use alloc::vec::Vec;
6
7/// Defines the case of an identifier.
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](pattern::Pattern) 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/// | pattern | underscore `_` | hyphen `-` | empty string | space |
22/// | ---: | --- | --- | --- | --- |
23/// | [lowercase](pattern::lowercase) | [snake_case](Case::Snake) | [kebab-case](Case::Kebab) | [flatcase](Case::Flat) | [lower case](Case::Lower) |
24/// | [uppercase](pattern::uppercase) | [CONSTANT_CASE](Case::Constant) | [COBOL-CASE](Case::Cobol) | [UPPERFLATCASE](Case::UpperFlat) | [UPPER CASE](Case::Upper) |
25/// | [capital](pattern::capital) | [Ada_Case](Case::Ada) | [Train-Case](Case::Train) | [PascalCase](Case::Pascal) | [Title Case](Case::Title) |
26/// | [camel](pattern::camel) | | | [camelCase](Case::Camel) |
27///
28/// There are other less common cases, such as [`Case::Sentence`], [`Case::Alternating`], and [`Case::Toggle`].
29///
30/// Then there are two random cases [`Case::Random`] and [`Case::PseudoRandom`] from the `random` feature.
31///
32/// This crate provides the ability to convert "from" a case. This introduces a different feature
33/// of cases which are the [word boundaries](Boundary) that segment the identifier into words. For example, a
34/// snake case identifier `my_var_name` can be split on underscores `_` to segment into words. A
35/// camel case identifier `myVarName` is split where a lowercase letter is followed by an
36/// uppercase letter. Each case is also associated with a list of boundaries that are used when
37/// converting "from" a particular case.
38#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
39pub enum Case<'a> {
40 /// Custom cases can be delimited by any static string slice and mutate words
41 /// using any pattern. Further, they can use any list of boundaries for
42 /// splitting identifiers into words.
43 ///
44 /// This flexibility can create cases not present as another variant of the
45 /// Case enum. For instance, you could create a "dot case" like so.
46 /// ```
47 /// use convert_case::{Case, Casing, Boundary, pattern};
48 /// let dot_case = Case::Custom {
49 /// boundaries: &[Boundary::from_delim(".")],
50 /// pattern: pattern::lowercase,
51 /// delim: ".",
52 /// };
53 ///
54 /// assert_eq!(
55 /// "my.new.case",
56 /// "myNewCase".to_case(dot_case),
57 /// );
58 /// assert_eq!(
59 /// "My New Case",
60 /// "my.new.case".from_case(dot_case).to_case(Case::Title),
61 /// );
62 /// ```
63 Custom {
64 boundaries: &'a [Boundary],
65 pattern: fn(&[&str]) -> Vec<String>,
66 delim: &'static str,
67 },
68
69 /// Snake case strings are delimited by underscores `_` and are all lowercase.
70 /// * Boundaries: [Underscore](Boundary::UNDERSCORE)
71 /// * Pattern: [lowercase](pattern::lowercase)
72 /// * Delimeter: Underscore `"_"`
73 ///
74 /// ```
75 /// use convert_case::{Case, Casing};
76 /// assert_eq!("my_variable_name", "My variable NAME".to_case(Case::Snake))
77 /// ```
78 Snake,
79
80 /// Constant case strings are delimited by underscores `_` and are all uppercase.
81 /// * Boundaries: [Underscore](Boundary::UNDERSCORE)
82 /// * Pattern: [uppercase](pattern::uppercase)
83 /// * Delimeter: Underscore `"_"`
84 ///
85 /// ```
86 /// use convert_case::{Case, Casing};
87 /// assert_eq!("MY_VARIABLE_NAME", "My variable NAME".to_case(Case::Constant))
88 /// ```
89 Constant,
90
91 /// Upper snake case is an alternative name for [constant case](Case::Constant).
92 UpperSnake,
93
94 /// Ada case strings are delimited by underscores `_`. The leading letter of
95 /// each word is uppercase, while the rest is lowercase.
96 /// * Boundaries: [Underscore](Boundary::UNDERSCORE)
97 /// * Pattern: [capital](pattern::capital)
98 /// * Delimeter: Underscore `"_"`
99 ///
100 /// ```
101 /// use convert_case::{Case, Casing};
102 /// assert_eq!("My_Variable_Name", "My variable NAME".to_case(Case::Ada))
103 /// ```
104 Ada,
105
106 /// Kebab case strings are delimited by hyphens `-` and are all lowercase.
107 /// * Boundaries: [Hyphen](Boundary::HYPHEN)
108 /// * Pattern: [lowercase](pattern::lowercase)
109 /// * Delimeter: Hyphen `"-"`
110 ///
111 /// ```
112 /// use convert_case::{Case, Casing};
113 /// assert_eq!("my-variable-name", "My variable NAME".to_case(Case::Kebab))
114 /// ```
115 Kebab,
116
117 /// Cobol case strings are delimited by hyphens `-` and are all uppercase.
118 /// * Boundaries: [Hyphen](Boundary::HYPHEN)
119 /// * Pattern: [uppercase](pattern::uppercase)
120 /// * Delimeter: Hyphen `"-"`
121 ///
122 /// ```
123 /// use convert_case::{Case, Casing};
124 /// assert_eq!("MY-VARIABLE-NAME", "My variable NAME".to_case(Case::Cobol))
125 /// ```
126 Cobol,
127
128 /// Upper kebab case is an alternative name for [Cobol case](Case::Cobol).
129 UpperKebab,
130
131 /// Train case strings are delimited by hyphens `-`. All characters are lowercase
132 /// except for the leading character of each word.
133 /// * Boundaries: [Hyphen](Boundary::HYPHEN)
134 /// * Pattern: [capital](pattern::capital)
135 /// * Delimeter: Hyphen `"-"`
136 ///
137 /// ```
138 /// use convert_case::{Case, Casing};
139 /// assert_eq!("My-Variable-Name", "My variable NAME".to_case(Case::Train))
140 /// ```
141 Train,
142
143 /// Flat case strings are all lowercase, with no delimiter. Note that word boundaries are lost.
144 /// * Boundaries: No boundaries
145 /// * Pattern: [lowercase](pattern::lowercase)
146 /// * Delimeter: Empty string `""`
147 ///
148 /// ```
149 /// use convert_case::{Case, Casing};
150 /// assert_eq!("myvariablename", "My variable NAME".to_case(Case::Flat))
151 /// ```
152 Flat,
153
154 /// Upper flat case strings are all uppercase, with no delimiter. Note that word boundaries are lost.
155 /// * Boundaries: No boundaries
156 /// * Pattern: [uppercase](pattern::uppercase)
157 /// * Delimeter: Empty string `""`
158 ///
159 /// ```
160 /// use convert_case::{Case, Casing};
161 /// assert_eq!("MYVARIABLENAME", "My variable NAME".to_case(Case::UpperFlat))
162 /// ```
163 UpperFlat,
164
165 /// Pascal case strings are lowercase, but for every word the
166 /// first letter is capitalized.
167 /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER),
168 /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER),
169 /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM)
170 /// * Pattern: [capital](`pattern::capital`)
171 /// * Delimeter: Empty string `""`
172 ///
173 /// ```
174 /// use convert_case::{Case, Casing};
175 /// assert_eq!("MyVariableName", "My variable NAME".to_case(Case::Pascal))
176 /// ```
177 Pascal,
178
179 /// Upper camel case is an alternative name for [Pascal case](Case::Pascal).
180 UpperCamel,
181
182 /// Camel case strings are lowercase, but for every word _except the first_ the
183 /// first letter is capitalized.
184 /// * Boundaries: [LowerUpper](Boundary::LOWER_UPPER), [DigitUpper](Boundary::DIGIT_UPPER),
185 /// [UpperDigit](Boundary::UPPER_DIGIT), [DigitLower](Boundary::DIGIT_LOWER),
186 /// [LowerDigit](Boundary::LOWER_DIGIT), [Acronym](Boundary::ACRONYM)
187 /// * Pattern: [camel](`pattern::camel`)
188 /// * Delimeter: Empty string `""`
189 ///
190 /// ```
191 /// use convert_case::{Case, Casing};
192 /// assert_eq!("myVariableName", "My variable NAME".to_case(Case::Camel))
193 /// ```
194 Camel,
195
196 /// Lowercase strings are delimited by spaces and all characters are lowercase.
197 /// * Boundaries: [Space](`Boundary::SPACE`)
198 /// * Pattern: [lowercase](`pattern::lowercase`)
199 /// * Delimeter: Space `" "`
200 ///
201 /// ```
202 /// use convert_case::{Case, Casing};
203 /// assert_eq!("my variable name", "My variable NAME".to_case(Case::Lower))
204 /// ```
205 Lower,
206
207 /// Lowercase strings are delimited by spaces and all characters are lowercase.
208 /// * Boundaries: [Space](`Boundary::SPACE`)
209 /// * Pattern: [uppercase](`pattern::uppercase`)
210 /// * Delimeter: Space `" "`
211 ///
212 /// ```
213 /// use convert_case::{Case, Casing};
214 /// assert_eq!("MY VARIABLE NAME", "My variable NAME".to_case(Case::Upper))
215 /// ```
216 Upper,
217
218 /// Title case strings are delimited by spaces. Only the leading character of
219 /// each word is uppercase. No inferences are made about language, so words
220 /// like "as", "to", and "for" will still be capitalized.
221 /// * Boundaries: [Space](`Boundary::SPACE`)
222 /// * Pattern: [capital](`pattern::capital`)
223 /// * Delimeter: Space `" "`
224 ///
225 /// ```
226 /// use convert_case::{Case, Casing};
227 /// assert_eq!("My Variable Name", "My variable NAME".to_case(Case::Title))
228 /// ```
229 Title,
230
231 /// Sentence case strings are delimited by spaces. Only the leading character of
232 /// the first word is uppercase.
233 /// * Boundaries: [Space](`Boundary::SPACE`)
234 /// * Pattern: [sentence](`pattern::sentence`)
235 /// * Delimeter: Space `" "`
236 ///
237 /// ```
238 /// use convert_case::{Case, Casing};
239 /// assert_eq!("My variable name", "My variable NAME".to_case(Case::Sentence))
240 /// ```
241 Sentence,
242
243 /// Alternating case strings are delimited by spaces. Characters alternate between uppercase
244 /// and lowercase.
245 /// * Boundaries: [Space](Boundary::SPACE)
246 /// * Pattern: [alternating](pattern::alternating)
247 /// * Delimeter: Space `" "`
248 ///
249 /// ```
250 /// use convert_case::{Case, Casing};
251 /// assert_eq!("mY vArIaBlE nAmE", "My variable NAME".to_case(Case::Alternating));
252 /// ```
253 Alternating,
254
255 /// Toggle case strings are delimited by spaces. All characters are uppercase except
256 /// for the leading character of each word, which is lowercase.
257 /// * Boundaries: [Space](`Boundary::SPACE`)
258 /// * Pattern: [toggle](`pattern::toggle`)
259 /// * Delimeter: Space `" "`
260 ///
261 /// ```
262 /// use convert_case::{Case, Casing};
263 /// assert_eq!("mY vARIABLE nAME", "My variable NAME".to_case(Case::Toggle))
264 /// ```
265 Toggle,
266
267 /// Random case strings are delimited by spaces and characters are
268 /// randomly upper case or lower case.
269 ///
270 /// This uses the `rand` crate
271 /// and is only available with the "random" feature.
272 /// * Boundaries: [Space](Boundary::SPACE)
273 /// * Pattern: [random](pattern::random)
274 /// * Delimeter: Space `" "`
275 ///
276 /// ```
277 /// use convert_case::{Case, Casing};
278 /// # #[cfg(any(doc, feature = "random"))]
279 /// let new = "My variable NAME".to_case(Case::Random);
280 /// ```
281 /// String `new` could be "My vaRIAbLE nAme" for example.
282 #[cfg(any(doc, feature = "random"))]
283 #[cfg(feature = "random")]
284 Random,
285
286 /// Pseudo-random case strings are delimited by spaces and characters are randomly
287 /// upper case or lower case, but there will never more than two consecutive lower
288 /// case or upper case letters in a row.
289 ///
290 /// This uses the `rand` crate and is
291 /// only available with the "random" feature.
292 /// * Boundaries: [Space](Boundary::SPACE)
293 /// * Pattern: [pseudo_random](pattern::pseudo_random)
294 /// * Delimeter: Space `" "`
295 ///
296 /// ```
297 /// use convert_case::{Case, Casing};
298 /// # #[cfg(any(doc, feature = "random"))]
299 /// let new = "My variable NAME".to_case(Case::Random);
300 /// ```
301 /// String `new` could be "mY vArIAblE NamE" for example.
302 #[cfg(any(doc, feature = "random"))]
303 #[cfg(feature = "random")]
304 PseudoRandom,
305}
306
307impl Case<'_> {
308 /// Returns the boundaries used in the corresponding case. That is, where can word boundaries
309 /// be distinguished in a string of the given case. The table outlines which cases use which
310 /// set of boundaries.
311 ///
312 /// | Cases | Boundaries |
313 /// | --- | --- |
314 /// | Snake, Constant, UpperSnake, Ada | [UNDERSCORE](Boundary::UNDERSCORE) |
315 /// | Kebab, Cobol, UpperKebab, Train | [HYPHEN](Boundary::HYPHEN) |
316 /// | Lower, Upper, Title, Alternating, Toggle, Random, PseudoRandom | [SPACE](Boundary::Space) |
317 /// | Pascal, UpperCamel, Camel | [LOWER_UPPER](Boundary::LOWER_UPPER), [LOWER_DIGIT](Boundary::LOWER_DIGIT), [UPPER_DIGIT](Boundary::UPPER_DIGIT), [DIGIT_LOWER](Boundary::DIGIT_LOWER), [DIGIT_UPPER](Boundary::DIGIT_UPPER), [ACRONYM](Boundary::ACRONYM) |
318 /// | Flat, UpperFlat | No boundaries |
319 pub fn boundaries(&self) -> &[Boundary] {
320 use Case::*;
321 match self {
322 Snake | Constant | UpperSnake | Ada => &[Boundary::UNDERSCORE],
323 Kebab | Cobol | UpperKebab | Train => &[Boundary::HYPHEN],
324 Upper | Lower | Title | Sentence | Toggle | Alternating => &[Boundary::SPACE],
325 Camel | UpperCamel | Pascal => &[
326 Boundary::LOWER_UPPER,
327 Boundary::ACRONYM,
328 Boundary::LOWER_DIGIT,
329 Boundary::UPPER_DIGIT,
330 Boundary::DIGIT_LOWER,
331 Boundary::DIGIT_UPPER,
332 ],
333 UpperFlat | Flat => &[],
334 Custom { boundaries, .. } => boundaries,
335
336 #[cfg(feature = "random")]
337 Random | PseudoRandom => &[Boundary::SPACE],
338 }
339 }
340
341 /// Returns the delimiter used in the corresponding case. The following
342 /// table outlines which cases use which delimeter.
343 ///
344 /// | Cases | Delimeter |
345 /// | --- | --- |
346 /// | Snake, Constant, UpperSnake, Ada | Underscore `"_"` |
347 /// | Kebab, Cobol, UpperKebab, Train | Hyphen `"-"` |
348 /// | Upper, Lower, Title, Sentence, Alternating, Toggle, Random, PseudoRandom | Space `" "` |
349 /// | Flat, UpperFlat, Pascal, UpperCamel, Camel | Empty string `""` |
350 pub const fn delim(&self) -> &'static str {
351 use Case::*;
352 match self {
353 Snake | Constant | UpperSnake | Ada => "_",
354 Kebab | Cobol | UpperKebab | Train => "-",
355 Upper | Lower | Title | Sentence | Alternating | Toggle => " ",
356 Flat | UpperFlat | Pascal | UpperCamel | Camel => "",
357 Custom { delim, .. } => delim,
358
359 #[cfg(feature = "random")]
360 Random | PseudoRandom => " ",
361 }
362 }
363
364 /// Returns the pattern used in the corresponding case. The following
365 /// table outlines which cases use which pattern.
366 ///
367 /// | Cases | Pattern |
368 /// | --- | --- |
369 /// | Constant, UpperSnake, Cobol, UpperKebab, UpperFlat, Upper | [uppercase](pattern::uppercase) |
370 /// | Snake, Kebab, Flat, Lower | [lowercase](pattern::lowercase) |
371 /// | Ada, Train, Pascal, UpperCamel, Title | [capital](pattern::capital) |
372 /// | Camel | [camel](pattern::camel) |
373 /// | Alternating | [alternating](pattern::alternating) |
374 /// | Random | [random](pattern::random) |
375 /// | PseudoRandom | [pseudo_random](pattern::pseudo_random) |
376 pub const fn pattern(&self) -> pattern::Pattern {
377 use Case::*;
378 match self {
379 Constant | UpperSnake | Cobol | UpperKebab | UpperFlat | Upper => pattern::uppercase,
380 Snake | Kebab | Flat | Lower => pattern::lowercase,
381 Ada | Train | Pascal | UpperCamel | Title => pattern::capital,
382 Camel => pattern::camel,
383 Toggle => pattern::toggle,
384 Alternating => pattern::alternating,
385 Sentence => pattern::sentence,
386 Custom { pattern, .. } => *pattern,
387
388 #[cfg(feature = "random")]
389 Random => pattern::random,
390 #[cfg(feature = "random")]
391 PseudoRandom => pattern::pseudo_random,
392 }
393 }
394
395 /// Split an identifier into words based on the boundaries of this case.
396 /// ```
397 /// use convert_case::Case;
398 /// assert_eq!(
399 /// vec!["get", "Total", "Length"],
400 /// Case::Pascal.split(&"getTotalLength"),
401 /// );
402 /// ```
403 pub fn split<T>(self, s: &T) -> Vec<&str>
404 where
405 T: AsRef<str>,
406 {
407 boundary::split(s, self.boundaries())
408 }
409
410 /// Mutate a list of words based on the pattern of this case.
411 /// ```
412 /// use convert_case::Case;
413 /// assert_eq!(
414 /// vec!["get", "total", "length"],
415 /// Case::Snake.mutate(&["get", "Total", "Length"]),
416 /// );
417 /// ```
418 pub fn mutate(self, words: &[&str]) -> Vec<String> {
419 (self.pattern())(words)
420 }
421
422 /// Join a list of words into a single identifier using the delimiter of this case.
423 /// ```
424 /// use convert_case::Case;
425 /// assert_eq!(
426 /// String::from("get_total_length"),
427 /// Case::Snake.join(&[
428 /// String::from("get"),
429 /// String::from("total"),
430 /// String::from("length")
431 /// ]),
432 /// );
433 /// ```
434 pub fn join(self, words: &[String]) -> String {
435 words.join(self.delim())
436 }
437
438 /// Array of all non-custom case enum variants. Does not include aliases.
439 pub fn all_cases() -> &'static [Case<'static>] {
440 use Case::*;
441 &[
442 Snake,
443 Constant,
444 Ada,
445 Kebab,
446 Cobol,
447 Train,
448 Flat,
449 UpperFlat,
450 Pascal,
451 Camel,
452 Upper,
453 Lower,
454 Title,
455 Sentence,
456 Alternating,
457 Toggle,
458 #[cfg(feature = "random")]
459 Random,
460 #[cfg(feature = "random")]
461 PseudoRandom,
462 ]
463 }
464
465 /// Array with the two "random" feature cases `Random` and `PseudoRandom`. Only
466 /// defined in the "random" feature.
467 #[cfg(feature = "random")]
468 pub fn random_cases() -> &'static [Case<'static>] {
469 use Case::*;
470 &[Random, PseudoRandom]
471 }
472
473 /// Array of all the cases that do not depend on randomness. This is all
474 /// the cases not in the "random" feature. Does not include aliases.
475 pub fn deterministic_cases() -> &'static [Case<'static>] {
476 use Case::*;
477 &[
478 Snake,
479 Constant,
480 Ada,
481 Kebab,
482 Cobol,
483 Train,
484 Flat,
485 UpperFlat,
486 Pascal,
487 Camel,
488 Upper,
489 Lower,
490 Title,
491 Sentence,
492 Alternating,
493 Toggle,
494 ]
495 }
496}