convert_case/
case.rs

1#[cfg(test)]
2use strum::EnumIter;
3
4use crate::boundary::Boundary;
5use 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)]
29pub 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
252impl 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)]
407mod 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}