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}