1#[derive(Debug, Default, PartialEq, Eq)]
17pub struct StrIndex {
18 pub char_index: usize,
20 pub byte_index: usize,
22}
23
24impl StrIndex {
25 pub fn new(char_index: usize, byte_index: usize) -> Self {
27 Self { char_index, byte_index }
28 }
29}
30
31#[must_use]
41pub fn camel_case_until(s: &str) -> StrIndex {
42 let mut iter = s.char_indices().enumerate();
43 if let Some((_char_index, (_, first))) = iter.next() {
44 if !first.is_uppercase() {
45 return StrIndex::new(0, 0);
46 }
47 } else {
48 return StrIndex::new(0, 0);
49 }
50 let mut up = true;
51 let mut last_index = StrIndex::new(0, 0);
52 for (char_index, (byte_index, c)) in iter {
53 if up {
54 if c.is_lowercase() {
55 up = false;
56 } else {
57 return last_index;
58 }
59 } else if c.is_uppercase() {
60 up = true;
61 last_index.byte_index = byte_index;
62 last_index.char_index = char_index;
63 } else if !c.is_lowercase() {
64 return StrIndex::new(char_index, byte_index);
65 }
66 }
67
68 if up {
69 last_index
70 } else {
71 StrIndex::new(s.chars().count(), s.len())
72 }
73}
74
75#[must_use]
86pub fn camel_case_start(s: &str) -> StrIndex {
87 camel_case_start_from_idx(s, 0)
88}
89
90pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex {
101 let char_count = s.chars().count();
102 let range = 0..char_count;
103 let mut iter = range.rev().zip(s.char_indices().rev());
104 if let Some((_, (_, first))) = iter.next() {
105 if !first.is_lowercase() {
106 return StrIndex::new(char_count, s.len());
107 }
108 } else {
109 return StrIndex::new(char_count, s.len());
110 }
111
112 let mut down = true;
113 let mut last_index = StrIndex::new(char_count, s.len());
114 for (char_index, (byte_index, c)) in iter {
115 if byte_index < start_idx {
116 break;
117 }
118 if down {
119 if c.is_uppercase() {
120 down = false;
121 last_index.byte_index = byte_index;
122 last_index.char_index = char_index;
123 } else if !c.is_lowercase() {
124 return last_index;
125 }
126 } else if c.is_lowercase() {
127 down = true;
128 } else if c.is_uppercase() {
129 last_index.byte_index = byte_index;
130 last_index.char_index = char_index;
131 } else {
132 return last_index;
133 }
134 }
135
136 last_index
137}
138
139pub fn camel_case_indices(s: &str) -> Vec<StrIndex> {
153 let mut result = Vec::new();
154 let mut str_idx = camel_case_start(s);
155
156 while str_idx.byte_index < s.len() {
157 let next_idx = str_idx.byte_index + 1;
158 result.push(str_idx);
159 str_idx = camel_case_start_from_idx(s, next_idx);
160 }
161 result.push(str_idx);
162
163 result
164}
165
166pub fn camel_case_split(s: &str) -> Vec<&str> {
173 let mut offsets = camel_case_indices(s)
174 .iter()
175 .map(|e| e.byte_index)
176 .collect::<Vec<usize>>();
177 if offsets[0] != 0 {
178 offsets.insert(0, 0);
179 }
180
181 offsets.windows(2).map(|w| &s[w[0]..w[1]]).collect()
182}
183
184#[derive(Debug, Default, PartialEq, Eq)]
189pub struct StrCount {
190 pub char_count: usize,
192 pub byte_count: usize,
194}
195
196impl StrCount {
197 pub fn new(char_count: usize, byte_count: usize) -> Self {
199 Self { char_count, byte_count }
200 }
201}
202
203#[must_use]
213pub fn count_match_start(str1: &str, str2: &str) -> StrCount {
214 let char_count = str1.chars().count();
216 let iter1 = (0..=char_count).zip(str1.chars());
217 let iter2 = str2.char_indices();
219
220 iter1
221 .zip(iter2)
222 .take_while(|((_, c1), (_, c2))| c1 == c2)
223 .last()
224 .map_or_else(StrCount::default, |((char_index, _), (byte_index, character))| {
225 StrCount::new(char_index + 1, byte_index + character.len_utf8())
226 })
227}
228
229#[must_use]
239pub fn count_match_end(str1: &str, str2: &str) -> StrCount {
240 let char_count = str1.chars().count();
241 if char_count == 0 {
242 return StrCount::default();
243 }
244
245 let iter1 = (0..char_count).rev().zip(str1.chars().rev());
247 let byte_count = str2.len();
249 let iter2 = str2.char_indices().rev();
250
251 iter1
252 .zip(iter2)
253 .take_while(|((_, c1), (_, c2))| c1 == c2)
254 .last()
255 .map_or_else(StrCount::default, |((char_index, _), (byte_index, _))| {
256 StrCount::new(char_count - char_index, byte_count - byte_index)
257 })
258}
259
260pub fn to_snake_case(name: &str) -> String {
269 let mut s = String::new();
270 for (i, c) in name.chars().enumerate() {
271 if c.is_uppercase() {
272 if i != 0 {
274 s.push('_');
275 }
276 s.extend(c.to_lowercase());
277 } else {
278 s.push(c);
279 }
280 }
281 s
282}
283pub fn to_camel_case(item_name: &str) -> String {
292 let mut s = String::new();
293 let mut up = true;
294 for c in item_name.chars() {
295 if c.is_uppercase() {
296 return item_name.to_string();
298 }
299 if c == '_' {
300 up = true;
301 continue;
302 }
303 if up {
304 up = false;
305 s.extend(c.to_uppercase());
306 } else {
307 s.push(c);
308 }
309 }
310 s
311}
312
313#[cfg(test)]
314mod test {
315 use super::*;
316
317 #[test]
318 fn camel_case_start_full() {
319 assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0));
320 assert_eq!(camel_case_start("Abc"), StrIndex::new(0, 0));
321 assert_eq!(camel_case_start("ABcd"), StrIndex::new(0, 0));
322 assert_eq!(camel_case_start("ABcdEf"), StrIndex::new(0, 0));
323 assert_eq!(camel_case_start("AabABcd"), StrIndex::new(0, 0));
324 }
325
326 #[test]
327 fn camel_case_start_partial() {
328 assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3));
329 assert_eq!(camel_case_start("aDbc"), StrIndex::new(1, 1));
330 assert_eq!(camel_case_start("aabABcd"), StrIndex::new(3, 3));
331 assert_eq!(camel_case_start("\u{f6}\u{f6}AabABcd"), StrIndex::new(2, 4));
332 }
333
334 #[test]
335 fn camel_case_start_not() {
336 assert_eq!(camel_case_start("AbcDef_"), StrIndex::new(7, 7));
337 assert_eq!(camel_case_start("AbcDD"), StrIndex::new(5, 5));
338 assert_eq!(camel_case_start("all_small"), StrIndex::new(9, 9));
339 assert_eq!(camel_case_start("\u{f6}_all_small"), StrIndex::new(11, 12));
340 }
341
342 #[test]
343 fn camel_case_start_caps() {
344 assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4));
345 }
346
347 #[test]
348 fn camel_case_until_full() {
349 assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6));
350 assert_eq!(camel_case_until("Abc"), StrIndex::new(3, 3));
351 assert_eq!(camel_case_until("Abc\u{f6}\u{f6}\u{f6}"), StrIndex::new(6, 9));
352 }
353
354 #[test]
355 fn camel_case_until_not() {
356 assert_eq!(camel_case_until("abcDef"), StrIndex::new(0, 0));
357 assert_eq!(camel_case_until("aDbc"), StrIndex::new(0, 0));
358 }
359
360 #[test]
361 fn camel_case_until_partial() {
362 assert_eq!(camel_case_until("AbcDef_"), StrIndex::new(6, 6));
363 assert_eq!(camel_case_until("CallTypeC"), StrIndex::new(8, 8));
364 assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3));
365 assert_eq!(camel_case_until("Abc\u{f6}\u{f6}DD"), StrIndex::new(5, 7));
366 }
367
368 #[test]
369 fn until_caps() {
370 assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0));
371 }
372
373 #[test]
374 fn camel_case_start_from_idx_full() {
375 assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0));
376 assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3));
377 assert_eq!(camel_case_start_from_idx("AbcDef", 4), StrIndex::new(6, 6));
378 assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0));
379 assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3));
380 assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7));
381 }
382
383 #[test]
384 fn camel_case_indices_full() {
385 assert_eq!(camel_case_indices("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]);
386 }
387
388 #[test]
389 fn camel_case_split_full() {
390 assert_eq!(camel_case_split("A"), vec!["A"]);
391 assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]);
392 assert_eq!(camel_case_split("Abc"), vec!["Abc"]);
393 assert_eq!(camel_case_split("abcDef"), vec!["abc", "Def"]);
394 assert_eq!(
395 camel_case_split("\u{f6}\u{f6}AabABcd"),
396 vec!["\u{f6}\u{f6}", "Aab", "A", "Bcd"]
397 );
398 }
399}