convert_case/
pattern.rs

1use std::iter;
2
3#[cfg(feature = "random")]
4use rand::prelude::*;
5
6#[derive(Debug, Eq, PartialEq, Clone, Copy)]
7enum WordCase {
8    Lower,
9    Upper,
10    Capital,
11    Toggle,
12}
13
14impl WordCase {
15    fn mutate(&self, word: &str) -> String {
16        use WordCase::*;
17        match self {
18            Lower => word.to_lowercase(),
19            Upper => word.to_uppercase(),
20            Capital => {
21                let mut chars = word.chars();
22                if let Some(c) = chars.next() {
23                    c.to_uppercase()
24                        .chain(chars.as_str().to_lowercase().chars())
25                        .collect()
26                } else {
27                    String::new()
28                }
29            }
30            Toggle => {
31                let mut chars = word.chars();
32                if let Some(c) = chars.next() {
33                    c.to_lowercase()
34                        .chain(chars.as_str().to_uppercase().chars())
35                        .collect()
36                } else {
37                    String::new()
38                }
39            }
40        }
41    }
42}
43
44/// A pattern is how a set of words is mutated before joining with
45/// a delimeter.
46///
47/// The `Random` and `PseudoRandom` patterns are used for their respective cases
48/// and are only available in the "random" feature. 
49#[derive(Debug, Eq, PartialEq, Clone, Copy)]
50pub enum Pattern {
51    /// Lowercase patterns make all words lowercase.
52    /// ```
53    /// use convert_case::Pattern;
54    /// assert_eq!(
55    ///     vec!["case", "conversion", "library"],
56    ///     Pattern::Lowercase.mutate(&["Case", "CONVERSION", "library"])
57    /// );
58    /// ```
59    Lowercase,
60
61    /// Uppercase patterns make all words uppercase.
62    /// ```
63    /// use convert_case::Pattern;
64    /// assert_eq!(
65    ///     vec!["CASE", "CONVERSION", "LIBRARY"],
66    ///     Pattern::Uppercase.mutate(&["Case", "CONVERSION", "library"])
67    /// );
68    /// ```
69    Uppercase,
70
71    /// Capital patterns makes the first letter of each word uppercase
72    /// and the remaining letters of each word lowercase.
73    /// ```
74    /// use convert_case::Pattern;
75    /// assert_eq!(
76    ///     vec!["Case", "Conversion", "Library"],
77    ///     Pattern::Capital.mutate(&["Case", "CONVERSION", "library"])
78    /// );
79    /// ```
80    Capital,
81
82    /// Capital patterns make the first word capitalized and the
83    /// remaining lowercase.
84    /// ```
85    /// use convert_case::Pattern;
86    /// assert_eq!(
87    ///     vec!["Case", "conversion", "library"],
88    ///     Pattern::Sentence.mutate(&["Case", "CONVERSION", "library"])
89    /// );
90    /// ```
91    Sentence,
92
93    /// Camel patterns make the first word lowercase and the remaining
94    /// capitalized.
95    /// ```
96    /// use convert_case::Pattern;
97    /// assert_eq!(
98    ///     vec!["case", "Conversion", "Library"],
99    ///     Pattern::Camel.mutate(&["Case", "CONVERSION", "library"])
100    /// );
101    /// ```
102    Camel,
103
104    /// Alternating patterns make each letter of each word alternate
105    /// between lowercase and uppercase.  They alternate across words,
106    /// which means the last letter of one word and the first letter of the
107    /// next will not be the same letter casing.
108    /// ```
109    /// use convert_case::Pattern;
110    /// assert_eq!(
111    ///     vec!["cAsE", "cOnVeRsIoN", "lIbRaRy"],
112    ///     Pattern::Alternating.mutate(&["Case", "CONVERSION", "library"])
113    /// );
114    /// assert_eq!(
115    ///     vec!["aNoThEr", "ExAmPlE"],
116    ///     Pattern::Alternating.mutate(&["Another", "Example"]),
117    /// );
118    /// ```
119    Alternating,
120
121    /// Toggle patterns have the first letter of each word uppercase
122    /// and the remaining letters of each word uppercase.
123    /// ```
124    /// use convert_case::Pattern;
125    /// assert_eq!(
126    ///     vec!["cASE", "cONVERSION", "lIBRARY"],
127    ///     Pattern::Toggle.mutate(&["Case", "CONVERSION", "library"])
128    /// );
129    /// ```
130    Toggle,
131
132    /// Random patterns will lowercase or uppercase each letter
133    /// uniformly randomly.  This uses the `rand` crate and is only available with the "random"
134    /// feature.  This example will not pass the assertion due to randomness, but it used as an 
135    /// example of what output is possible.
136    /// ```should_panic
137    /// use convert_case::Pattern;
138    /// assert_eq!(
139    ///     vec!["Case", "coNVeRSiOn", "lIBraRY"],
140    ///     Pattern::Random.mutate(&["Case", "CONVERSION", "library"])
141    /// );
142    /// ```
143    #[cfg(feature = "random")]
144    #[cfg(any(doc, feature = "random"))]
145    Random,
146
147    /// PseudoRandom patterns are random-like patterns.  Instead of randomizing
148    /// each letter individually, it mutates each pair of characters
149    /// as either (Lowercase, Uppercase) or (Uppercase, Lowercase).  This generates
150    /// more "random looking" words.  A consequence of this algorithm for randomization
151    /// is that there will never be three consecutive letters that are all lowercase
152    /// or all uppercase.  This uses the `rand` crate and is only available with the "random"
153    /// feature.  This example will not pass the assertion due to randomness, but it used as an 
154    /// example of what output is possible.
155    /// ```should_panic
156    /// use convert_case::Pattern;
157    /// assert_eq!(
158    ///     vec!["cAsE", "cONveRSioN", "lIBrAry"],
159    ///     Pattern::Random.mutate(&["Case", "CONVERSION", "library"]),
160    /// );
161    /// ```
162    #[cfg(any(doc, feature = "random"))]
163    PseudoRandom,
164}
165
166impl Pattern {
167    /// Generates a vector of new `String`s in the right pattern given
168    /// the input strings.
169    /// ```
170    /// use convert_case::Pattern;
171    ///
172    /// assert_eq!(
173    ///     vec!["crack", "the", "skye"],
174    ///     Pattern::Lowercase.mutate(&vec!["CRACK", "the", "Skye"]),
175    /// )
176    /// ```
177    pub fn mutate(&self, words: &[&str]) -> Vec<String> {
178        use Pattern::*;
179        match self {
180            Lowercase => words
181                .iter()
182                .map(|word| WordCase::Lower.mutate(word))
183                .collect(),
184            Uppercase => words
185                .iter()
186                .map(|word| WordCase::Upper.mutate(word))
187                .collect(),
188            Capital => words
189                .iter()
190                .map(|word| WordCase::Capital.mutate(word))
191                .collect(),
192            Toggle => words
193                .iter()
194                .map(|word| WordCase::Toggle.mutate(word))
195                .collect(),
196            Sentence => {
197                let word_cases =
198                    iter::once(WordCase::Capital).chain(iter::once(WordCase::Lower).cycle());
199                words
200                    .iter()
201                    .zip(word_cases)
202                    .map(|(word, word_case)| word_case.mutate(word))
203                    .collect()
204            }
205            Camel => {
206                let word_cases =
207                    iter::once(WordCase::Lower).chain(iter::once(WordCase::Capital).cycle());
208                words
209                    .iter()
210                    .zip(word_cases)
211                    .map(|(word, word_case)| word_case.mutate(word))
212                    .collect()
213            }
214            Alternating => alternating(words),
215            #[cfg(feature = "random")]
216            Random => randomize(words),
217            #[cfg(feature = "random")]
218            PseudoRandom => pseudo_randomize(words),
219        }
220    }
221}
222
223fn alternating(words: &[&str]) -> Vec<String> {
224    let mut upper = false;
225    words
226        .iter()
227        .map(|word| {
228            word.chars()
229                .map(|letter| {
230                    if letter.is_uppercase() || letter.is_lowercase() {
231                        if upper {
232                            upper = false;
233                            letter.to_uppercase().to_string()
234                        } else {
235                            upper = true;
236                            letter.to_lowercase().to_string()
237                        }
238                    } else {
239                        letter.to_string()
240                    }
241                })
242                .collect()
243        })
244        .collect()
245}
246
247/// Randomly picks whether to be upper case or lower case
248#[cfg(feature = "random")]
249fn randomize(words: &[&str]) -> Vec<String> {
250    let mut rng = rand::thread_rng();
251    words
252        .iter()
253        .map(|word| {
254            word.chars()
255                .map(|letter| {
256                    if rng.gen::<f32>() > 0.5 {
257                        letter.to_uppercase().to_string()
258                    } else {
259                        letter.to_lowercase().to_string()
260                    }
261                })
262                .collect()
263        })
264        .collect()
265}
266
267/// Randomly selects patterns: [upper, lower] or [lower, upper]
268/// for a more random feeling pattern.
269#[cfg(feature = "random")]
270fn pseudo_randomize(words: &[&str]) -> Vec<String> {
271    let mut rng = rand::thread_rng();
272
273    // Keeps track of when to alternate
274    let mut alt: Option<bool> = None;
275    words
276        .iter()
277        .map(|word| {
278            word.chars()
279                .map(|letter| {
280                    match alt {
281                        // No existing pattern, start one
282                        None => {
283                            if rng.gen::<f32>() > 0.5 {
284                                alt = Some(false); // Make the next char lower
285                                letter.to_uppercase().to_string()
286                            } else {
287                                alt = Some(true); // Make the next char upper
288                                letter.to_lowercase().to_string()
289                            }
290                        }
291                        // Existing pattern, do what it says
292                        Some(upper) => {
293                            alt = None;
294                            if upper {
295                                letter.to_uppercase().to_string()
296                            } else {
297                                letter.to_lowercase().to_string()
298                            }
299                        }
300                    }
301                })
302                .collect()
303        })
304        .collect()
305}
306
307#[cfg(test)]
308mod test {
309    use super::*;
310
311    #[cfg(feature = "random")]
312    #[test]
313    fn pseudo_no_triples() {
314        let words = vec!["abcdefg", "hijklmnop", "qrstuv", "wxyz"];
315        for _ in 0..5 {
316            let new = pseudo_randomize(&words).join("");
317            let mut iter = new
318                .chars()
319                .zip(new.chars().skip(1))
320                .zip(new.chars().skip(2));
321            assert!(!iter
322                .clone()
323                .any(|((a, b), c)| a.is_lowercase() && b.is_lowercase() && c.is_lowercase()));
324            assert!(
325                !iter.any(|((a, b), c)| a.is_uppercase() && b.is_uppercase() && c.is_uppercase())
326            );
327        }
328    }
329
330    #[cfg(feature = "random")]
331    #[test]
332    fn randoms_are_random() {
333        let words = vec!["abcdefg", "hijklmnop", "qrstuv", "wxyz"];
334
335        for _ in 0..5 {
336            let transformed = pseudo_randomize(&words);
337            assert_ne!(words, transformed);
338            let transformed = randomize(&words);
339            assert_ne!(words, transformed);
340        }
341    }
342
343    #[test]
344    fn mutate_empty_strings() {
345        for wcase in [
346            WordCase::Lower,
347            WordCase::Upper,
348            WordCase::Capital,
349            WordCase::Toggle,
350        ] {
351            assert_eq!(String::new(), wcase.mutate(&String::new()))
352        }
353    }
354}