casey/
traits.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//!
//! String case transformation extension traits
//!

/// Word boundary separators
const SEPARATORS: &str = "-_";

pub trait PascalCaseExt {
    fn to_pascal_case(&self) -> String;
}

impl PascalCaseExt for String {
    fn to_pascal_case(&self) -> String {
        let mut s = String::new();
        let mut capitalise_next = true; // always on first char

        let mut char_stream = self.chars().peekable();
        while let Some(current_char) = char_stream.next() {
            if SEPARATORS.contains(current_char) | current_char.is_numeric() {
                capitalise_next = true;
                continue;
            }

            if capitalise_next {
                s.push(current_char.to_uppercase().next().unwrap());
                capitalise_next = false;
                continue;
            }

            let next_char = char_stream.peek();
            if next_char.is_none() {
                // `current_char` is last in the stream
                s.push(current_char.to_lowercase().next().unwrap());
                break;
            }

            // lowercase this char if followed by another uppercase or punctuation
            // e.g. AA => aA, A- => a-
            // has the affect of transforming: 'ABCDe' into 'AbcDe'
            if current_char.is_ascii_uppercase()
                && (next_char.unwrap().is_ascii_uppercase()
                    || next_char.unwrap().is_ascii_punctuation())
            {
                s.push(current_char.to_lowercase().next().unwrap());
            } else {
                s.push(current_char);
            }
        }
        s
    }
}

pub trait SnakeCaseExt {
    fn to_snake_case(&self) -> String;
}

impl SnakeCaseExt for String {
    fn to_snake_case(&self) -> String {
        let mut s = String::new();
        let mut was_sep = false;
        if let Some(c) = self.chars().next() {
            was_sep = SEPARATORS.contains(c);
            s.push(c.to_lowercase().next().unwrap());
        }
        for c in self.chars().skip(1) {
            if c.is_lowercase() {
                was_sep = false;
                s.push(c);
            } else {
                if !was_sep {
                    s.push('_');
                }
                was_sep = SEPARATORS.contains(c);
                if was_sep {
                    continue;
                } else {
                    s.push(c.to_lowercase().next().unwrap())
                }
            }
        }
        s
    }
}

pub trait ShoutySnakeCaseExt {
    fn to_shouty_snake_case(&self) -> String;
}

impl ShoutySnakeCaseExt for String {
    fn to_shouty_snake_case(&self) -> String {
        let mut s = String::new();
        if let Some(c) = self.chars().next() {
            s.push(c.to_uppercase().next().unwrap());
        }
        for c in self.chars().skip(1) {
            if c.is_uppercase() {
                s.push('_');
                s.push(c)
            } else {
                if SEPARATORS.contains(c) {
                    s.push('_');
                    continue;
                }
                if let Some(n) = c.to_uppercase().next() {
                    s.push(n)
                }
            }
        }
        s
    }
}