1extern crate libc;
2
3pub trait DefiningCase<'a, T: Iterator<Item=&'a str>> {
5 fn components_iter(self, &'a str) -> T;
6}
7
8pub trait Case {
10 fn build_identifier<'a, It: Iterator<Item=&'a str>>(self, components: It) -> String;
11}
12
13pub 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
18pub 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 pub trait DelimetedCase {
32 fn delimeter() -> char;
33 }
34
35 pub struct Camel {}
37
38 pub const CAMEL: Camel = Camel {};
40
41 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 pub struct Snake {}
98
99 pub const SNAKE: Snake = Snake {};
101
102 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 pub struct Kebab {}
149
150 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}