convert_case/
case.rs

1#[cfg(test)]
2use strum_macros::EnumIter;
3
4use crate::pattern::Pattern;
5use crate::Boundary;
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    /// Toggle case strings are delimited by spaces.  All characters are uppercase except
66    /// for the leading character of each word, which is lowercase.
67    /// * Boundaries: [Space](`Boundary::Space`)
68    /// * Pattern: [Toggle](`Pattern::Toggle`)
69    /// * Delimeter: Space
70    ///
71    /// ```
72    /// use convert_case::{Case, Casing};
73    /// assert_eq!("mY vARIABLE nAME", "My variable NAME".to_case(Case::Toggle))
74    /// ```
75    Toggle,
76
77    /// Camel case strings are lowercase, but for every word _except the first_ the
78    /// first letter is capitalized.
79    /// * Boundaries: [LowerUpper](Boundary::LowerUpper), [DigitUpper](Boundary::DigitUpper),
80    /// [UpperDigit](Boundary::UpperDigit), [DigitLower](Boundary::DigitLower),
81    /// [LowerDigit](Boundary::LowerDigit), [Acronym](Boundary::Acronym)
82    /// * Pattern: [Camel](`Pattern::Camel`)
83    /// * Delimeter: No delimeter
84    ///
85    /// ```
86    /// use convert_case::{Case, Casing};
87    /// assert_eq!("myVariableName", "My variable NAME".to_case(Case::Camel))
88    /// ```
89    Camel,
90
91    /// Pascal case strings are lowercase, but for every word the
92    /// first letter is capitalized.
93    /// * Boundaries: [LowerUpper](Boundary::LowerUpper), [DigitUpper](Boundary::DigitUpper),
94    /// [UpperDigit](Boundary::UpperDigit), [DigitLower](Boundary::DigitLower),
95    /// [LowerDigit](Boundary::LowerDigit), [Acronym](Boundary::Acronym)
96    /// * Pattern: [Capital](`Pattern::Capital`)
97    /// * Delimeter: No delimeter
98    ///
99    /// ```
100    /// use convert_case::{Case, Casing};
101    /// assert_eq!("MyVariableName", "My variable NAME".to_case(Case::Pascal))
102    /// ```
103    Pascal,
104
105    /// Upper camel case is an alternative name for [Pascal case](Case::Pascal).
106    UpperCamel,
107
108    /// Snake case strings are delimited by underscores `_` and are all lowercase.
109    /// * Boundaries: [Underscore](Boundary::Underscore)
110    /// * Pattern: [Lowercase](Pattern::Lowercase)
111    /// * Delimeter: Underscore `_`
112    ///
113    /// ```
114    /// use convert_case::{Case, Casing};
115    /// assert_eq!("my_variable_name", "My variable NAME".to_case(Case::Snake))
116    /// ```
117    Snake,
118
119    /// Upper snake case strings are delimited by underscores `_` and are all uppercase.
120    /// * Boundaries: [Underscore](Boundary::Underscore)
121    /// * Pattern: [Uppercase](Pattern::Uppercase)
122    /// * Delimeter: Underscore `_`
123    ///
124    /// ```
125    /// use convert_case::{Case, Casing};
126    /// assert_eq!("MY_VARIABLE_NAME", "My variable NAME".to_case(Case::UpperSnake))
127    /// ```
128    UpperSnake,
129
130    /// Screaming snake case is an alternative name for [upper snake case](Case::UpperSnake).
131    ScreamingSnake,
132
133    /// Kebab case strings are delimited by hyphens `-` and are all lowercase.
134    /// * Boundaries: [Hyphen](Boundary::Hyphen)
135    /// * Pattern: [Lowercase](Pattern::Lowercase)
136    /// * Delimeter: Hyphen `-`
137    ///
138    /// ```
139    /// use convert_case::{Case, Casing};
140    /// assert_eq!("my-variable-name", "My variable NAME".to_case(Case::Kebab))
141    /// ```
142    Kebab,
143
144    /// Cobol case strings are delimited by hyphens `-` and are all uppercase.
145    /// * Boundaries: [Hyphen](Boundary::Hyphen)
146    /// * Pattern: [Uppercase](Pattern::Uppercase)
147    /// * Delimeter: Hyphen `-`
148    ///
149    /// ```
150    /// use convert_case::{Case, Casing};
151    /// assert_eq!("MY-VARIABLE-NAME", "My variable NAME".to_case(Case::Cobol))
152    /// ```
153    Cobol,
154
155    /// Upper kebab case is an alternative name for [Cobol case](Case::Cobol).
156    UpperKebab,
157
158    /// Train case strings are delimited by hyphens `-`.  All characters are lowercase
159    /// except for the leading character of each word.
160    /// * Boundaries: [Hyphen](Boundary::Hyphen)
161    /// * Pattern: [Capital](Pattern::Capital)
162    /// * Delimeter: Hyphen `-`
163    ///
164    /// ```
165    /// use convert_case::{Case, Casing};
166    /// assert_eq!("My-Variable-Name", "My variable NAME".to_case(Case::Train))
167    /// ```
168    Train,
169
170    /// Flat case strings are all lowercase, with no delimiter. Note that word boundaries are lost.
171    /// * Boundaries: No boundaries
172    /// * Pattern: [Lowercase](Pattern::Lowercase)
173    /// * Delimeter: No delimeter
174    ///
175    /// ```
176    /// use convert_case::{Case, Casing};
177    /// assert_eq!("myvariablename", "My variable NAME".to_case(Case::Flat))
178    /// ```
179    Flat,
180
181    /// Upper flat case strings are all uppercase, with no delimiter. Note that word boundaries are lost.
182    /// * Boundaries: No boundaries
183    /// * Pattern: [Uppercase](Pattern::Uppercase)
184    /// * Delimeter: No delimeter
185    ///
186    /// ```
187    /// use convert_case::{Case, Casing};
188    /// assert_eq!("MYVARIABLENAME", "My variable NAME".to_case(Case::UpperFlat))
189    /// ```
190    UpperFlat,
191
192    /// Alternating case strings are delimited by spaces.  Characters alternate between uppercase
193    /// and lowercase.
194    /// * Boundaries: [Space](Boundary::Space)
195    /// * Pattern: [Alternating](Pattern::Alternating)
196    /// * Delimeter: Space
197    ///
198    /// ```
199    /// use convert_case::{Case, Casing};
200    /// assert_eq!("mY vArIaBlE nAmE", "My variable NAME".to_case(Case::Alternating));
201    /// ```
202    Alternating,
203
204    /// Random case strings are delimited by spaces and characters are
205    /// randomly upper case or lower case.  This uses the `rand` crate
206    /// and is only available with the "random" feature.
207    /// * Boundaries: [Space](Boundary::Space)
208    /// * Pattern: [Random](Pattern::Random)
209    /// * Delimeter: Space
210    ///
211    /// ```
212    /// use convert_case::{Case, Casing};
213    /// let new = "My variable NAME".to_case(Case::Random);
214    /// ```
215    /// String `new` could be "My vaRIAbLE nAme" for example.
216    #[cfg(any(doc, feature = "random"))]
217    Random,
218
219    /// Pseudo-random case strings are delimited by spaces and characters are randomly
220    /// upper case or lower case, but there will never more than two consecutive lower
221    /// case or upper case letters in a row.  This uses the `rand` crate and is
222    /// only available with the "random" feature.
223    /// * Boundaries: [Space](Boundary::Space)
224    /// * Pattern: [PseudoRandom](Pattern::PseudoRandom)
225    /// * Delimeter: Space
226    ///
227    /// ```
228    /// use convert_case::{Case, Casing};
229    /// let new = "My variable NAME".to_case(Case::Random);
230    /// ```
231    /// String `new` could be "mY vArIAblE NamE" for example.
232    #[cfg(any(doc, feature = "random"))]
233    PseudoRandom,
234}
235
236impl Case {
237    /// Returns the delimiter used in the corresponding case.  The following
238    /// table outlines which cases use which delimeter.
239    ///
240    /// | Cases | Delimeter |
241    /// | --- | --- |
242    /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space |
243    /// | Snake, UpperSnake, ScreamingSnake | Underscore `_` |
244    /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` |
245    /// | UpperFlat, Flat, Camel, UpperCamel, Pascal | Empty string, no delimeter |
246    pub const fn delim(&self) -> &'static str {
247        use Case::*;
248        match self {
249            Upper | Lower | Title | Toggle | Alternating => " ",
250            Snake | UpperSnake | ScreamingSnake => "_",
251            Kebab | Cobol | UpperKebab | Train => "-",
252
253            #[cfg(feature = "random")]
254            Random | PseudoRandom => " ",
255
256            UpperFlat | Flat | Camel | UpperCamel | Pascal => "",
257        }
258    }
259
260    /// Returns the pattern used in the corresponding case.  The following
261    /// table outlines which cases use which pattern.
262    ///
263    /// | Cases | Pattern |
264    /// | --- | --- |
265    /// | Upper, UpperSnake, ScreamingSnake, UpperFlat, Cobol, UpperKebab | Uppercase |
266    /// | Lower, Snake, Kebab, Flat | Lowercase |
267    /// | Title, Pascal, UpperCamel, Train | Capital |
268    /// | Camel | Camel |
269    /// | Alternating | Alternating |
270    /// | Random | Random |
271    /// | PseudoRandom | PseudoRandom |
272    pub const fn pattern(&self) -> Pattern {
273        use Case::*;
274        match self {
275            Upper | UpperSnake | ScreamingSnake | UpperFlat | Cobol | UpperKebab => {
276                Pattern::Uppercase
277            }
278            Lower | Snake | Kebab | Flat => Pattern::Lowercase,
279            Title | Pascal | UpperCamel | Train => Pattern::Capital,
280            Camel => Pattern::Camel,
281            Toggle => Pattern::Toggle,
282            Alternating => Pattern::Alternating,
283
284            #[cfg(feature = "random")]
285            Random => Pattern::Random,
286            #[cfg(feature = "random")]
287            PseudoRandom => Pattern::PseudoRandom,
288        }
289    }
290
291    /// Returns the boundaries used in the corresponding case.  That is, where can word boundaries
292    /// be distinguished in a string of the given case.  The table outlines which cases use which
293    /// set of boundaries.
294    ///
295    /// | Cases | Boundaries |
296    /// | --- | --- |
297    /// | Upper, Lower, Title, Toggle, Alternating, Random, PseudoRandom | Space |
298    /// | Snake, UpperSnake, ScreamingSnake | Underscore `_` |
299    /// | Kebab, Cobol, UpperKebab, Train | Hyphen `-` |
300    /// | Camel, UpperCamel, Pascal | LowerUpper, LowerDigit, UpperDigit, DigitLower, DigitUpper, Acronym |
301    /// | UpperFlat, Flat | No boundaries |
302    pub fn boundaries(&self) -> Vec<Boundary> {
303        use Boundary::*;
304        use Case::*;
305        match self {
306            Upper | Lower | Title | Toggle | Alternating => vec![Space],
307            Snake | UpperSnake | ScreamingSnake => vec![Underscore],
308            Kebab | Cobol | UpperKebab | Train => vec![Hyphen],
309
310            #[cfg(feature = "random")]
311            Random | PseudoRandom => vec![Space],
312
313            UpperFlat | Flat => vec![],
314            Camel | UpperCamel | Pascal => vec![
315                LowerUpper, Acronym, LowerDigit, UpperDigit, DigitLower, DigitUpper,
316            ],
317        }
318    }
319
320    // Created to avoid using the EnumIter trait from strum in
321    // final library.  A test confirms that all cases are listed here.
322    /// Returns a vector with all case enum variants in no particular order.
323    pub fn all_cases() -> Vec<Case> {
324        use Case::*;
325        vec![
326            Upper,
327            Lower,
328            Title,
329            Toggle,
330            Camel,
331            Pascal,
332            UpperCamel,
333            Snake,
334            UpperSnake,
335            ScreamingSnake,
336            Kebab,
337            Cobol,
338            UpperKebab,
339            Train,
340            Flat,
341            UpperFlat,
342            Alternating,
343            #[cfg(feature = "random")]
344            Random,
345            #[cfg(feature = "random")]
346            PseudoRandom,
347        ]
348    }
349
350    /// Returns a vector with the two "random" feature cases `Random` and `PseudoRandom`.  Only
351    /// defined in the "random" feature.
352    #[cfg(feature = "random")]
353    pub fn random_cases() -> Vec<Case> {
354        use Case::*;
355        vec![Random, PseudoRandom]
356    }
357
358    /// Returns a vector with all the cases that do not depend on randomness.  This is all
359    /// the cases not in the "random" feature.
360    pub fn deterministic_cases() -> Vec<Case> {
361        use Case::*;
362        vec![
363            Upper,
364            Lower,
365            Title,
366            Toggle,
367            Camel,
368            Pascal,
369            UpperCamel,
370            Snake,
371            UpperSnake,
372            ScreamingSnake,
373            Kebab,
374            Cobol,
375            UpperKebab,
376            Train,
377            Flat,
378            UpperFlat,
379            Alternating,
380        ]
381    }
382}
383
384#[cfg(test)]
385mod test {
386
387    use super::*;
388    use strum::IntoEnumIterator;
389
390    #[test]
391    fn all_cases_in_iter() {
392        let all = Case::all_cases();
393        for case in Case::iter() {
394            assert!(all.contains(&case));
395        }
396    }
397}