Skip to main content

max / goingson

1.2 KB · 42 lines History Blame Raw
1 //! Shared text utilities for parsing and matching.
2
3 /// Case-insensitive prefix check without allocation.
4 ///
5 /// Returns the remaining part of the string if `s` starts with `prefix`
6 /// (case-insensitive), otherwise returns `None`. Safe for multi-byte UTF-8.
7 pub fn strip_prefix_ci<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
8 if s.len() < prefix.len() || !s.is_char_boundary(prefix.len()) {
9 return None;
10 }
11 let s_prefix = &s[..prefix.len()];
12 if s_prefix.eq_ignore_ascii_case(prefix) {
13 Some(&s[prefix.len()..])
14 } else {
15 None
16 }
17 }
18
19 #[cfg(test)]
20 mod tests {
21 use super::*;
22
23 #[test]
24 fn test_basic_match() {
25 assert_eq!(strip_prefix_ci("PROJECT:test", "project:"), Some("test"));
26 assert_eq!(strip_prefix_ci("Project:test", "project:"), Some("test"));
27 }
28
29 #[test]
30 fn test_no_match() {
31 assert_eq!(strip_prefix_ci("proj:test", "project:"), None);
32 assert_eq!(strip_prefix_ci("pr", "project:"), None);
33 }
34
35 #[test]
36 fn test_multibyte_utf8_safe() {
37 // Ensure no panic on multi-byte characters near prefix boundary
38 assert_eq!(strip_prefix_ci("pr😀:H", "pri:"), None);
39 assert_eq!(strip_prefix_ci("düe:tom", "due:"), None);
40 }
41 }
42