recase/
lib.rs

1//! # ReCase
2//!
3//! `recase` is a text processing utility that changes the input text into desired convention cases.
4
5use crate::utils::WordSplit;
6
7mod utils;
8
9/// An instance that holds the text to be re-cased.
10/// # Example
11/// ```
12/// let recase = recase::ReCase::new("Example String");
13/// assert_eq!(recase.snake_case(), "example_string");          
14/// assert_eq!(recase.upper_snake_case(), "EXAMPLE_STRING");
15/// ```
16#[derive(Debug)]
17pub struct ReCase<'a> {
18    original_text: &'a str,
19}
20
21// Push lowercase of c into s
22macro_rules! push_lowercase {
23    ($s:expr, $c:expr) => {
24        for lc in $c.to_lowercase() {
25            $s.push(lc);
26        }
27    };
28}
29
30// Push uppercase of chars into s
31macro_rules! push_uppercase {
32    ($s:expr, $c:expr) => {
33        for uc in $c.to_uppercase() {
34            $s.push(uc);
35        }
36    };
37}
38
39impl<'a> ReCase<'a> {
40    pub fn new(original_text: &'a str) -> Self {
41        ReCase { original_text }
42    }
43
44    fn words_iter(&self) -> impl Iterator<Item = &str> {
45        WordSplit::new(self.original_text).map(|(x, y)| &self.original_text[x..y])
46    }
47
48    #[inline(always)]
49    fn allocate_buffer(&self) -> String {
50        String::with_capacity(self.original_text.len())
51    }
52
53    fn lowercase_with_delim(&self, delim: &str) -> String {
54        self.words_iter()
55            .fold(self.allocate_buffer(), |mut acc, s| {
56                if !acc.is_empty() {
57                    acc.push_str(delim);
58                }
59                for c in s.chars() {
60                    push_lowercase!(acc, c);
61                }
62                acc
63            })
64    }
65
66    /// Returns a `normal case` version of the input text as a new String
67    /// ## Example
68    /// ```
69    /// let recase = recase::ReCase::new("Example String");
70    /// assert_eq!(recase.normal_case(), "example string");
71    /// ```
72    pub fn normal_case(&self) -> String {
73        self.lowercase_with_delim(" ")
74    }
75
76    /// Returns a `camelCase` version of the input text as a new String
77    /// ## Example
78    /// ```
79    /// let recase = recase::ReCase::new("Example String");
80    /// assert_eq!(recase.camel_case(), "exampleString");
81    /// ```
82    pub fn camel_case(&self) -> String {
83        let words_iter = self.words_iter();
84        let mut acc = self.allocate_buffer();
85
86        for (i, word) in words_iter.enumerate() {
87            let mut chars = word.chars();
88            // Push first character
89            if let Some(first_char) = chars.next() {
90                if i == 0 {
91                    push_lowercase!(acc, first_char);
92                } else {
93                    push_uppercase!(acc, first_char);
94                }
95            }
96            // Push the rest
97            chars.for_each(|c| {
98                push_lowercase!(acc, c);
99            });
100        }
101
102        acc
103    }
104
105    /// Returns a `PascalCase` version of the input text as a new String
106    /// ## Example
107    /// ```
108    /// let recase = recase::ReCase::new("Example String");
109    /// assert_eq!(recase.pascal_case(), "ExampleString");
110    /// ```
111    pub fn pascal_case(&self) -> String {
112        let words_iter = self.words_iter();
113        words_iter.fold(self.allocate_buffer(), |mut acc, word| {
114            let mut chars = word.chars();
115            if let Some(first_char) = chars.next() {
116                push_uppercase!(acc, first_char);
117            }
118            // Push the rest
119            chars.for_each(|c| {
120                push_lowercase!(acc, c);
121            });
122            acc
123        })
124    }
125
126    /// Returns a `snake_case` version of the input text as a new String
127    /// ## Example
128    /// ```
129    /// let recase = recase::ReCase::new("Example String");
130    /// assert_eq!(recase.snake_case(), "example_string");
131    /// ```
132    pub fn snake_case(&self) -> String {
133        self.lowercase_with_delim("_")
134    }
135
136    /// Returns a `kebab-case` version of the input text as a new String
137    /// ## Example
138    /// ```
139    /// let recase = recase::ReCase::new("Example String");
140    /// assert_eq!(recase.kebab_case(), "example-string");
141    /// ```
142    pub fn kebab_case(&self) -> String {
143        self.lowercase_with_delim("-")
144    }
145
146    /// Returns a `dot.case` version of the input text as a new String
147    /// ## Example
148    /// ```
149    /// let recase = recase::ReCase::new("Example String");
150    /// assert_eq!(recase.dot_case(), "example.string");
151    /// ```
152    pub fn dot_case(&self) -> String {
153        self.lowercase_with_delim(".")
154    }
155
156    /// Returns a `path/case` version of the input text as a new String
157    /// ## Example
158    /// ```
159    /// let recase = recase::ReCase::new("Example String");
160    /// assert_eq!(recase.path_case(), "example/string");
161    /// ```
162    pub fn path_case(&self) -> String {
163        self.lowercase_with_delim("/")
164    }
165
166    /// Returns a `windows\path\case` version of the input text as a new String
167    /// ## Example
168    /// ```
169    /// let recase = recase::ReCase::new("Example String");
170    /// assert_eq!(recase.windows_path_case(), "example\\string");
171    /// ```
172    pub fn windows_path_case(&self) -> String {
173        self.lowercase_with_delim("\\")
174    }
175
176    /// Returns a `Sentence case` version of the input text as a new String
177    /// ## Example
178    /// ```
179    /// let recase = recase::ReCase::new("Example String");
180    /// assert_eq!(recase.sentence_case(), "Example string");
181    /// ```
182    pub fn sentence_case(&self) -> String {
183        let words_iter = self.words_iter();
184        let mut acc = self.allocate_buffer();
185
186        for (i, word) in words_iter.enumerate() {
187            let mut chars = word.chars();
188            if i != 0 {
189                acc.push_str(" ");
190            } else {
191                // Push first character
192                if let Some(first_char) = chars.next() {
193                    push_uppercase!(acc, first_char);
194                }
195            }
196            // Push the rest
197            chars.for_each(|c| {
198                push_lowercase!(acc, c);
199            });
200        }
201
202        acc
203    }
204
205    /// Returns a `Title Case` version of the input text as a new String
206    /// ## Example
207    /// ```
208    /// let recase = recase::ReCase::new("Example String");
209    /// assert_eq!(recase.title_case(), "Example String");
210    /// ```
211    pub fn title_case(&self) -> String {
212        let words_iter = self.words_iter();
213        let mut acc = self.allocate_buffer();
214
215        for (i, word) in words_iter.enumerate() {
216            let mut chars = word.chars();
217            if i != 0 {
218                acc.push_str(" ");
219            }
220
221            // Push first character
222            if let Some(first_char) = chars.next() {
223                push_uppercase!(acc, first_char);
224            }
225
226            // Push the rest
227            chars.for_each(|c| {
228                push_lowercase!(acc, c);
229            });
230        }
231
232        acc
233    }
234
235    /// Returns a `Header-Case` version of the input text as a new String
236    /// ## Example
237    /// ```
238    /// let recase = recase::ReCase::new("Example String");
239    /// assert_eq!(recase.header_case(), "Example-String");
240    /// ```
241    pub fn header_case(&self) -> String {
242        let words_iter = self.words_iter();
243        let mut acc = self.allocate_buffer();
244
245        for (i, word) in words_iter.enumerate() {
246            let mut chars = word.chars();
247            if i != 0 {
248                acc.push_str("-");
249            }
250
251            // Push first character
252            if let Some(first_char) = chars.next() {
253                push_uppercase!(acc, first_char);
254            }
255
256            // Push the rest
257            chars.for_each(|c| {
258                push_lowercase!(acc, c);
259            });
260        }
261
262        acc
263    }
264
265    /// Returns a `UPPER_SNAKE_CASE` version of the input text as a new String
266    /// ## Example
267    /// ```
268    /// let recase = recase::ReCase::new("Example String");
269    /// assert_eq!(recase.upper_snake_case(), "EXAMPLE_STRING");
270    /// ```
271    pub fn upper_snake_case(&self) -> String {
272        let mut acc = self.allocate_buffer();
273
274        for word in self.words_iter() {
275            if !acc.is_empty() {
276                acc.push_str("_");
277            }
278            let chars = word.chars();
279            for c in chars {
280                push_uppercase!(acc, c);
281            }
282        }
283
284        acc
285    }
286
287    /// Returns a `AlTeRnAtInG cAsE` version of the input text as a new String
288    /// ## Example
289    /// ```
290    /// let recase = recase::ReCase::new("Example String");
291    /// assert_eq!(recase.alternating_case(), "eXaMpLe StRiNg");
292    /// ```
293    pub fn alternating_case(&self) -> String {
294        let mut should_uppercase = false;
295        let mut acc = self.allocate_buffer();
296
297        for word in self.words_iter() {
298            if !acc.is_empty() {
299                acc.push_str(" ");
300            }
301
302            let chars = word.chars();
303            for c in chars {
304                if should_uppercase {
305                    push_uppercase!(acc, c);
306                } else {
307                    push_lowercase!(acc, c);
308                }
309                should_uppercase = !should_uppercase;
310            }
311        }
312
313        acc
314    }
315}
316
317pub trait Casing {
318    /// Returns a `normal case` version of the input text as a new String
319    /// ## Example
320    /// ```
321    /// use recase::Casing;
322    /// assert_eq!("Example String".to_normal_case(), "example string");
323    /// ```
324    fn to_normal_case(&self) -> String;
325
326    /// Returns a `camelCase` version of the input text as a new String
327    /// ## Example
328    /// ```
329    /// use recase::Casing;
330    /// assert_eq!("Example String".to_camel_case(), "exampleString");
331    /// ```
332    fn to_camel_case(&self) -> String;
333
334    /// Returns a `PascalCase` version of the input text as a new String
335    /// ## Example
336    /// ```
337    /// use recase::Casing;
338    /// assert_eq!("Example String".to_pascal_case(), "ExampleString");
339    /// ```
340    fn to_pascal_case(&self) -> String;
341
342    /// Returns a `snake_case` version of the input text as a new String
343    /// ## Example
344    /// ```
345    /// use recase::Casing;
346    /// assert_eq!("Example String".to_snake_case(), "example_string");
347    /// ```
348    fn to_snake_case(&self) -> String;
349
350    /// Returns a `kebab-case` version of the input text as a new String
351    /// ## Example
352    /// ```
353    /// use recase::Casing;
354    /// assert_eq!("Example String".to_kebab_case(), "example-string");
355    /// ```
356    fn to_kebab_case(&self) -> String;
357
358    /// Returns a `dot.case` version of the input text as a new String
359    /// ## Example
360    /// ```
361    /// use recase::Casing;
362    /// assert_eq!("Example String".to_dot_case(), "example.string");
363    /// ```
364    fn to_dot_case(&self) -> String;
365
366    /// Returns a `path/case` version of the input text as a new String
367    /// ## Example
368    /// ```
369    /// use recase::Casing;
370    /// assert_eq!("Example String".to_path_case(), "example/string");
371    /// ```
372    fn to_path_case(&self) -> String;
373
374    /// Returns a `windows\path\case` version of the input text as a new String
375    /// ## Example
376    /// ```
377    /// use recase::Casing;
378    /// assert_eq!("Example String".to_windows_path_case(), "example\\string");
379    /// ```
380    fn to_windows_path_case(&self) -> String;
381
382    /// Returns a `Sentence case` version of the input text as a new String
383    /// ## Example
384    /// ```
385    /// use recase::Casing;
386    /// assert_eq!("Example String".to_sentence_case(), "Example string");
387    /// ```
388    fn to_sentence_case(&self) -> String;
389
390    /// Returns a `Title Case` version of the input text as a new String
391    /// ## Example
392    /// ```
393    /// use recase::Casing;
394    /// assert_eq!("Example String".to_title_case(), "Example String");
395    /// ```
396    fn to_title_case(&self) -> String;
397
398    /// Returns a `Header-Case` version of the input text as a new String
399    /// ## Example
400    /// ```
401    /// use recase::Casing;
402    /// assert_eq!("Example String".to_header_case(), "Example-String");
403    /// ```
404    fn to_header_case(&self) -> String;
405
406    /// Returns a `UPPER_SNAKE_CASE` version of the input text as a new String
407    /// ## Example
408    /// ```
409    /// use recase::Casing;
410    /// assert_eq!("Example String".to_upper_snake_case(), "EXAMPLE_STRING");
411    /// ```
412    fn to_upper_snake_case(&self) -> String;
413
414    /// Returns a `AlTeRnAtInG cAsE` version of the input text as a new String
415    /// ## Example
416    /// ```
417    /// use recase::Casing;
418    /// assert_eq!("Example String".to_alternating_case(), "eXaMpLe StRiNg");
419    /// ```
420    fn to_alternating_case(&self) -> String;
421}
422
423impl Casing for str {
424    fn to_normal_case(&self) -> String {
425        ReCase::new(self).normal_case()
426    }
427
428    fn to_camel_case(&self) -> String {
429        ReCase::new(self).camel_case()
430    }
431
432    fn to_pascal_case(&self) -> String {
433        ReCase::new(self).pascal_case()
434    }
435
436    fn to_snake_case(&self) -> String {
437        ReCase::new(self).snake_case()
438    }
439
440    fn to_kebab_case(&self) -> String {
441        ReCase::new(self).kebab_case()
442    }
443
444    fn to_dot_case(&self) -> String {
445        ReCase::new(self).dot_case()
446    }
447
448    fn to_path_case(&self) -> String {
449        ReCase::new(self).path_case()
450    }
451
452    fn to_windows_path_case(&self) -> String {
453        ReCase::new(self).windows_path_case()
454    }
455
456    fn to_sentence_case(&self) -> String {
457        ReCase::new(self).sentence_case()
458    }
459
460    fn to_title_case(&self) -> String {
461        ReCase::new(self).title_case()
462    }
463
464    fn to_header_case(&self) -> String {
465        ReCase::new(self).header_case()
466    }
467
468    fn to_upper_snake_case(&self) -> String {
469        ReCase::new(self).upper_snake_case()
470    }
471
472    fn to_alternating_case(&self) -> String {
473        ReCase::new(self).alternating_case()
474    }
475}
476
477#[cfg(test)]
478mod recase_tests {
479    use crate::{Casing, ReCase};
480
481    #[test]
482    fn test_normal_case() {
483        let recase = ReCase::new("long_random_text");
484        assert_eq!(recase.normal_case(), "long random text");
485
486        let recase = ReCase::new("誰_long_random_text");
487        assert_eq!(recase.normal_case(), "誰 long random text");
488
489        let recase = ReCase::new("LONG_random_text");
490        assert_eq!(recase.normal_case(), "long random text");
491
492        let recase = ReCase::new("ßlong_random_text");
493        assert_eq!(recase.normal_case(), "ßlong random text");
494    }
495
496    #[test]
497    fn test_camel_case() {
498        let recase = ReCase::new("random_text");
499        assert_eq!(recase.camel_case(), "randomText");
500
501        let recase = ReCase::new("誰_randomText");
502        assert_eq!(recase.camel_case(), "誰RandomText");
503
504        let recase = ReCase::new("RanDom text");
505        assert_eq!(recase.camel_case(), "ranDomText");
506
507        let recase = ReCase::new("ßändom ßext");
508        assert_eq!(recase.camel_case(), "ßändomSSext");
509    }
510
511    #[test]
512    fn test_upper_snake_case() {
513        let recase = ReCase::new("HTML_parser");
514        assert_eq!(recase.upper_snake_case(), "HTML_PARSER");
515    }
516
517    #[test]
518    fn test_alternating_case() {
519        let recase = ReCase::new("random Text");
520        assert_eq!(recase.alternating_case(), "rAnDoM tExT");
521
522        let recase = ReCase::new("誰_random Text");
523        assert_eq!(recase.alternating_case(), "誰 RaNdOm TeXt");
524    }
525
526    #[test]
527    fn test_casing_trait() {
528        let s = "Hello World";
529
530        assert_eq!(s.to_kebab_case(), "hello-world");
531        assert_eq!(s.to_snake_case(), "hello_world");
532        assert_eq!(s.to_alternating_case(), "hElLo WoRlD");
533    }
534}