1pub trait StrExt {
2 fn char_at(&self, idx: usize) -> Option<char>;
3 fn previous(&self, idx: usize) -> Option<char>;
4}
5
6impl StrExt for str {
7 fn char_at(&self, at: usize) -> Option<char> {
8 self.char_indices()
9 .skip_while(|&(idx, _c)| idx < at)
10 .filter_map(|(idx, c)| if idx == at { Some(c) } else { None })
11 .next()
12 }
13
14 fn previous(&self, current: usize) -> Option<char> {
15 self.char_indices()
16 .skip_while(|&(idx, c)| idx + c.len_utf8() < current)
17 .filter_map(|(idx, c)| {
18 if idx + c.len_utf8() == current {
19 Some(c)
20 } else {
21 None
22 }
23 })
24 .next()
25 }
26}
27
28#[cfg(test)]
29mod test {
30 use super::*;
31
32 #[test]
33 fn various_len() {
34 let data = "абвГДеёlжз";
35 assert_eq!(Some('а'), data.char_at(0));
36 assert_eq!(Some('б'), data.char_at(2));
37 assert_eq!(Some('в'), data.char_at(4));
38 assert_eq!(Some('Г'), data.char_at(6));
39 assert_eq!(Some('Д'), data.char_at(8));
40 assert_eq!(Some('е'), data.char_at(10));
41 assert_eq!(Some('ё'), data.char_at(12));
42 assert_eq!(Some('l'), data.char_at(14));
43 assert_eq!(Some('ж'), data.char_at(15));
44 assert_eq!(Some('з'), data.char_at(17));
45 assert_eq!(None, data.previous(0));
46 assert_eq!(Some('а'), data.previous(2));
47 assert_eq!(Some('б'), data.previous(4));
48 assert_eq!(Some('в'), data.previous(6));
49 assert_eq!(Some('Г'), data.previous(8));
50 assert_eq!(Some('Д'), data.previous(10));
51 assert_eq!(Some('е'), data.previous(12));
52 assert_eq!(Some('ё'), data.previous(14));
53 assert_eq!(Some('l'), data.previous(15));
54 assert_eq!(Some('ж'), data.previous(17));
55 }
56
57 #[test]
58 fn boundary() {
59 let data = "ф";
60 assert_eq!(None, data.char_at(1));
61 }
62}