guard_core_engine/patterns/
chars_util.rs1#[must_use]
7pub fn py_is_word(c: char) -> bool {
8 c.is_alphanumeric() || c == '_'
9}
10
11#[must_use]
14pub fn char_at(s: &str, byte_idx: usize) -> Option<(usize, char)> {
15 s[byte_idx..].chars().next().map(|c| (byte_idx, c))
16}
17
18#[must_use]
20pub fn char_before(s: &str, byte_idx: usize) -> Option<(usize, char)> {
21 s[..byte_idx].char_indices().next_back()
22}
23
24#[must_use]
27pub fn walk_back_while<F>(s: &str, mut byte_idx: usize, mut pred: F) -> usize
28where
29 F: FnMut(char) -> bool,
30{
31 while byte_idx > 0
32 && let Some((i, c)) = char_before(s, byte_idx)
33 && pred(c)
34 {
35 byte_idx = i;
36 }
37 byte_idx
38}
39
40#[must_use]
42pub fn walk_forward_while<F>(s: &str, mut byte_idx: usize, mut pred: F) -> usize
43where
44 F: FnMut(char) -> bool,
45{
46 while let Some((i, c)) = char_at(s, byte_idx)
47 && pred(c)
48 {
49 byte_idx = i + c.len_utf8();
50 }
51 byte_idx
52}
53
54#[must_use]
62pub fn str_find_from(s: &str, needle: &str, from: usize) -> Option<usize> {
63 if from > s.len() {
64 return None;
65 }
66 let mut from = from;
67 while from < s.len() && !s.is_char_boundary(from) {
68 from += 1;
69 }
70 s[from..].find(needle).map(|i| i + from)
71}
72
73#[must_use]
75pub fn str_rfind_in(s: &str, needle: &str, start: usize, end: usize) -> Option<usize> {
76 let limit = end.min(s.len());
77 if needle.is_empty() || start > limit {
78 return None;
79 }
80 let capped = &s[..limit];
81 capped[start..]
82 .rfind(needle)
83 .map(|i| i + start)
84 .filter(|i| i + needle.len() <= limit)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn walks_respect_char_boundaries() {
93 let s = "a\u{00e9}b";
94 assert_eq!(walk_back_while(s, s.len(), |c| c != '\u{00e9}'), 3);
95 assert_eq!(walk_forward_while(s, 0, |c| c == 'a'), 1);
96 }
97
98 #[test]
99 fn rfind_within_bounds() {
100 assert_eq!(str_rfind_in("a.b.c", ".", 0, 3), Some(1));
101 assert_eq!(str_rfind_in("a.b.c", ".", 2, 5), Some(3));
102 assert_eq!(str_rfind_in("abc", "z", 0, 3), None);
103 }
104}
105
106#[cfg(test)]
107mod coverage_tests {
108 use super::*;
109
110 #[test]
111 fn find_from_rejects_out_of_bounds_and_snaps_forward() {
112 assert_eq!(str_find_from("abc", "b", 9), None);
114 let s = "a\u{00e9}b";
116 assert_eq!(str_find_from(s, "b", 2), Some(3));
117 }
118
119 #[test]
120 fn rfind_in_rejects_empty_needles_and_bad_bounds() {
121 assert_eq!(str_rfind_in("abc", "", 0, 3), None);
122 assert_eq!(str_rfind_in("abc", "b", 9, 9), None);
123 }
124}