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: bool = false;
225 wordsimpl Iterator
226 .iter()
227 .map(|word: &&str| {
228 wordimpl Iterator.chars()
229 .map(|letter: char| {
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}
355