casey/
traits.rs

1//!
2//! String case transformation extension traits
3//!
4
5/// Word boundary separators
6const SEPARATORS: &str = "-_";
7
8pub trait PascalCaseExt {
9    fn to_pascal_case(&self) -> String;
10}
11
12impl PascalCaseExt for String {
13    fn to_pascal_case(&self) -> String {
14        let mut s = String::new();
15        let mut capitalise_next = true; // always on first char
16
17        let mut char_stream = self.chars().peekable();
18        while let Some(current_char) = char_stream.next() {
19            if SEPARATORS.contains(current_char) | current_char.is_numeric() {
20                capitalise_next = true;
21                continue;
22            }
23
24            if capitalise_next {
25                s.push(current_char.to_uppercase().next().unwrap());
26                capitalise_next = false;
27                continue;
28            }
29
30            let next_char = char_stream.peek();
31            if next_char.is_none() {
32                // `current_char` is last in the stream
33                s.push(current_char.to_lowercase().next().unwrap());
34                break;
35            }
36
37            // lowercase this char if followed by another uppercase or punctuation
38            // e.g. AA => aA, A- => a-
39            // has the affect of transforming: 'ABCDe' into 'AbcDe'
40            if current_char.is_ascii_uppercase()
41                && (next_char.unwrap().is_ascii_uppercase()
42                    || next_char.unwrap().is_ascii_punctuation())
43            {
44                s.push(current_char.to_lowercase().next().unwrap());
45            } else {
46                s.push(current_char);
47            }
48        }
49        s
50    }
51}
52
53pub trait SnakeCaseExt {
54    fn to_snake_case(&self) -> String;
55}
56
57impl SnakeCaseExt for String {
58    fn to_snake_case(&self) -> String {
59        let mut s = String::new();
60        let mut was_sep = false;
61        if let Some(c) = self.chars().next() {
62            was_sep = SEPARATORS.contains(c);
63            s.push(c.to_lowercase().next().unwrap());
64        }
65        for c in self.chars().skip(1) {
66            if c.is_lowercase() {
67                was_sep = false;
68                s.push(c);
69            } else {
70                if !was_sep {
71                    s.push('_');
72                }
73                was_sep = SEPARATORS.contains(c);
74                if was_sep {
75                    continue;
76                } else {
77                    s.push(c.to_lowercase().next().unwrap())
78                }
79            }
80        }
81        s
82    }
83}
84
85pub trait ShoutySnakeCaseExt {
86    fn to_shouty_snake_case(&self) -> String;
87}
88
89impl ShoutySnakeCaseExt for String {
90    fn to_shouty_snake_case(&self) -> String {
91        let mut s = String::new();
92        if let Some(c) = self.chars().next() {
93            s.push(c.to_uppercase().next().unwrap());
94        }
95        for c in self.chars().skip(1) {
96            if c.is_uppercase() {
97                s.push('_');
98                s.push(c)
99            } else {
100                if SEPARATORS.contains(c) {
101                    s.push('_');
102                    continue;
103                }
104                if let Some(n) = c.to_uppercase().next() {
105                    s.push(n)
106                }
107            }
108        }
109        s
110    }
111}