caseconv/
lib.rs

1extern crate libc;
2
3/// Represents a case that can be parsed in to components
4pub trait DefiningCase<'a, T: Iterator<Item=&'a str>> {
5    fn components_iter(self, &'a str) -> T;
6}
7
8/// Represents a case that can be rendered from components
9pub trait Case {
10    fn build_identifier<'a, It: Iterator<Item=&'a str>>(self, components: It) -> String;
11}
12
13/// Converts a string between two different cases (compile-time resolution)
14pub fn convert<'a, It: Iterator<Item=&'a str>, A: DefiningCase<'a, It>, B: Case>(src: &'a str, src_case: A, dst_case: B) -> String {
15    dst_case.build_identifier(src_case.components_iter(src))
16}
17
18/// Guess's the source case of a string, then converts it to the destination
19/// case
20pub fn guess_and_convert<C: Case>(s: &str, dst_case: C) -> String {
21    let src_case = dynamic::CaseType::guess(s);
22    convert::<_, _, C>(s, &src_case, dst_case)
23}
24
25pub mod dynamic;
26
27pub mod case {
28    use super::{ Case, DefiningCase };
29
30    /// Represents a case that is defined by a singular delimeter character
31    pub trait DelimetedCase {
32        fn delimeter() -> char;
33    }
34
35    /// camelCase
36    pub struct Camel {}
37
38    /// camelCase phantom value
39    pub const CAMEL: Camel = Camel {};
40
41    /// Iterator data for `Camel`
42    pub struct CamelIterator<'a> {
43        src: &'a str
44    }
45
46    impl<'a> CamelIterator<'a> {
47        fn new(src: &'a str) -> CamelIterator<'a> {
48            CamelIterator {
49                src: src
50            }
51        }
52    }
53
54    impl<'a> Iterator for CamelIterator<'a> {
55        type Item = &'a str;
56
57        fn next(&mut self) -> Option<&'a str> {
58            match self.src.char_indices().skip(1).find(|&(_, c)| c.is_uppercase()) {
59                Some((idx, _)) => {
60                    let (component, new_src) = self.src.split_at(idx);
61                    self.src = new_src;
62                    Some(component)
63                }
64                None => if self.src.is_empty() {
65                    None
66                } else {
67                    let ret = self.src;
68                    self.src = "";
69                    Some(ret)
70                }
71            }
72        }
73    }
74
75    impl<'a> DefiningCase<'a, CamelIterator<'a>> for Camel {
76        fn components_iter(self, src: &'a str) -> CamelIterator<'a> {
77            CamelIterator::new(src)
78        }
79    }
80
81    impl Case for Camel {
82        fn build_identifier<'a, It: Iterator<Item=&'a str>>(self, mut components: It) -> String {
83            components.next().map_or(Default::default(), |first_component| {
84                let mut buf = first_component.to_lowercase();
85                for comp in components {
86                    let (first_letter, remainder) = comp.split_at(1);
87                    let beginning = first_letter.to_uppercase();
88                    buf.push_str(beginning.as_ref());
89                    buf.push_str(remainder);
90                }
91                buf
92            })
93        }
94    }
95
96    /// snake_case
97    pub struct Snake {}
98
99    /// snake_case phantom value
100    pub const SNAKE: Snake = Snake {};
101
102    /// Parsing iterator for `Snake`
103    pub struct DelimeterIterator<'a> {
104        delimeter: char,
105        src: &'a str
106    }
107
108    impl<'a> DelimeterIterator<'a> {
109        fn new(src: &'a str, delimeter: char) -> DelimeterIterator<'a> {
110            DelimeterIterator {
111                delimeter: delimeter,
112                src: src
113            }
114        }
115    }
116
117    impl<'a> Iterator for DelimeterIterator<'a> {
118        type Item = &'a str;
119
120        fn next(&mut self) -> Option<&'a str> {
121            if self.src.is_empty() {
122                None
123            } else {
124                match self.src.char_indices().find(|&(_, c)| c.eq(&self.delimeter)) {
125                    Some((idx, _)) => {
126                        let (component, new_src_prefixed) = self.src.split_at(idx);
127                        let (_, new_src) = new_src_prefixed.split_at(1);
128                        self.src = new_src;
129                        Some(component)
130                    }
131                    None => {
132                        let ret = self.src;
133                        self.src = "";
134                        Some(ret)
135                    }
136                }
137            }
138        }
139    }
140
141    impl DelimetedCase for Snake {
142        fn delimeter() -> char {
143            '_'
144        }
145    }
146
147    /// kebab-case
148    pub struct Kebab {}
149
150    /// kebab-case phantom value
151    pub const KEBAB: Kebab = Kebab {};
152
153    impl DelimetedCase for Kebab {
154        fn delimeter() -> char {
155            '-'
156        }
157    }
158
159    impl<'a, T: DelimetedCase> DefiningCase<'a, DelimeterIterator<'a>> for T {
160        fn components_iter(self, src: &'a str) -> DelimeterIterator<'a> {
161            DelimeterIterator::new(src, T::delimeter())
162        }
163    }
164
165    impl<T: DelimetedCase> Case for T {
166        fn build_identifier<'a, It: Iterator<Item=&'a str>>(self, components: It) -> String {
167            let components = components.map(|c| c.to_lowercase());
168            let mut is_first = false;
169            let mut buf = String::from("");
170            for c in components {
171                if is_first {
172                    buf.push(T::delimeter());
173                } else {
174                    is_first = true;
175                }
176                buf.push_str(c.as_ref());
177            }
178            buf
179        }
180    }
181}
182
183pub mod ffi;
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use super::case::*;
189
190    const CAMEL_CASE_TEST_VAL: &'static str = "simpleCamelCase";
191    const SNAKE_CASE_TEST_VAL: &'static str = "simple_snake_case";
192    const KEBAB_CASE_TEST_VAL: &'static str = "simple-kebab-case";
193
194    #[test]
195    fn simple_camel_case_counted_correctly() {
196        let it = CAMEL.components_iter(CAMEL_CASE_TEST_VAL);
197        assert_eq!(it.count(), 3);
198    }
199
200    #[test]
201    fn simple_camel_case_defined_correctly() {
202        let it = CAMEL.components_iter(CAMEL_CASE_TEST_VAL);
203        let components: Vec<&'static str> = it.collect();
204        assert_eq!(components, vec!["simple", "Camel", "Case"]);
205    }
206
207    #[test]
208    fn simple_camel_case_converts_self() {
209        let id = convert(CAMEL_CASE_TEST_VAL, CAMEL, CAMEL);
210        assert_eq!(id, "simpleCamelCase");
211    }
212
213    #[test]
214    fn simple_snake_case_converts_camel() {
215        let id = convert(SNAKE_CASE_TEST_VAL, SNAKE, CAMEL);
216        assert_eq!(id, "simpleSnakeCase");
217    }
218
219    #[test]
220    fn simple_kebab_case_converts_camel() {
221        let id = convert(KEBAB_CASE_TEST_VAL, KEBAB, CAMEL);
222        assert_eq!(id, "simpleKebabCase");
223    }
224
225    #[test]
226    fn simple_camel_case_converts_kebab() {
227        let id = convert(CAMEL_CASE_TEST_VAL, CAMEL, KEBAB);
228        assert_eq!(id, "simple-camel-case");
229    }
230
231    #[test]
232    fn simple_snake_case_converts_kebab() {
233        let id = convert(SNAKE_CASE_TEST_VAL, SNAKE, KEBAB);
234        assert_eq!(id, "simple-snake-case");
235    }
236
237    #[test]
238    fn simple_kebab_case_converts_snake() {
239        let id = convert(KEBAB_CASE_TEST_VAL, KEBAB, SNAKE);
240        assert_eq!(id, "simple_kebab_case");
241    }
242
243    #[test]
244    fn simple_camel_case_converts_snake() {
245        let id = convert(CAMEL_CASE_TEST_VAL, CAMEL, SNAKE);
246        assert_eq!(id, "simple_camel_case");
247    }
248
249    #[test]
250    fn simple_snake_case_defined_correctly() {
251        let it = SNAKE.components_iter(SNAKE_CASE_TEST_VAL);
252        let components: Vec<&'static str> = it.collect();
253        assert_eq!(components, vec!["simple", "snake", "case"]);
254    }
255
256    #[test]
257    fn simple_kebab_case_defined_correctly() {
258        let it = KEBAB.components_iter(KEBAB_CASE_TEST_VAL);
259        let components: Vec<&'static str> = it.collect();
260        assert_eq!(components, vec!["simple", "kebab", "case"]);
261    }
262}