libgir/
case.rs

1pub trait CaseExt {
2    type Owned;
3
4    /// Changes string to snake_case.
5    ///
6    /// Inserts underscores in-between lowercase and uppercase characters when
7    /// they appear in that order and in-between second last and last
8    /// uppercase character when going from sequence of three or more
9    /// uppercase characters to lowercase.
10    ///
11    /// Changes the whole string to lowercase.
12    fn to_snake(&self) -> Self::Owned;
13
14    /// Changes string to CamelCase.
15    ///
16    /// Uppercases each character that follows an underscore or is at the
17    /// beginning of the string.
18    ///
19    /// Removes all underscores.
20    fn to_camel(&self) -> Self::Owned;
21}
22
23impl CaseExt for str {
24    type Owned = String;
25
26    fn to_snake(&self) -> Self::Owned {
27        let mut s = String::new();
28        let mut upper = true;
29        let mut upper_count = 0;
30
31        for c in self.chars() {
32            let next_upper = if c.is_uppercase() {
33                true
34            } else if c.is_lowercase() {
35                false
36            } else {
37                upper
38            };
39
40            if !upper && next_upper {
41                s.push('_');
42            } else if upper && !next_upper && upper_count >= 3 {
43                let n = s.len() - s.chars().next_back().unwrap().len_utf8();
44                s.insert(n, '_');
45            }
46
47            s.extend(c.to_lowercase());
48
49            if next_upper {
50                upper_count += 1;
51                upper = true;
52            } else {
53                upper_count = 0;
54                upper = false;
55            }
56        }
57        s
58    }
59
60    fn to_camel(&self) -> Self::Owned {
61        let new_length = self.chars().filter(|c| *c != '_').count();
62        let mut s = String::with_capacity(new_length);
63        let mut under = true;
64        for c in self.chars() {
65            if under && c.is_lowercase() {
66                s.extend(c.to_uppercase());
67            } else if c != '_' {
68                s.push(c);
69            }
70            under = c == '_';
71        }
72        s
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn to_snake() {
82        let cases = [
83            ("AtkGObjectAccessible", "atk_gobject_accessible"),
84            ("AtkNoOpObject", "atk_no_op_object"),
85            ("DConfClient", "dconf_client"),
86            ("GCabCabinet", "gcab_cabinet"),
87            ("GstA52Dec", "gst_a52_dec"),
88            ("GstLameMP3Enc", "gst_lame_mp3_enc"),
89            ("GstMpg123AudioDec", "gst_mpg123_audio_dec"),
90            ("GstX264Enc", "gst_x264_enc"),
91            ("FileIOStream", "file_io_stream"),
92            ("IOStream", "io_stream"),
93            ("IMContext", "im_context"),
94            ("DBusMessage", "dbus_message"),
95            ("SoupCookieJarDB", "soup_cookie_jar_db"),
96            ("FooBarBaz", "foo_bar_baz"),
97            ("aBcDe", "a_bc_de"),
98            ("aXXbYYc", "a_xxb_yyc"),
99        ];
100
101        for &(input, expected) in &cases {
102            assert_eq!(expected, input.to_snake());
103        }
104    }
105
106    #[test]
107    fn to_camel() {
108        assert_eq!("foo_bar_baz".to_camel(), "FooBarBaz");
109        assert_eq!("_foo".to_camel(), "Foo");
110    }
111}