返回 CodeWhale
regex_cache.rs
根目录 / crates / tui / src / regex_cache.rs
1 use std::num::NonZeroUsize;
2 use std::sync::{Mutex, OnceLock};
3
4 use lru::LruCache;
5 use regex::Regex;
6
7 const DEFAULT_USER_REGEX_CACHE_CAPACITY: usize = 64;
8
9 static USER_REGEX_CACHE: OnceLock<UserRegexCache> = OnceLock::new();
10
11 pub(crate) fn compile_user_regex(pattern: &str) -> Result<Regex, regex::Error> {
12 user_regex_cache().compile(pattern)
13 }
14
15 fn user_regex_cache() -> &'static UserRegexCache {
16 USER_REGEX_CACHE.get_or_init(UserRegexCache::new)
17 }
18
19 struct UserRegexCache {
20 inner: Mutex<LruCache<String, Regex>>,
21 }
22
23 impl UserRegexCache {
24 fn new() -> Self {
25 Self::with_capacity(
26 NonZeroUsize::new(DEFAULT_USER_REGEX_CACHE_CAPACITY).expect("non-zero capacity"),
27 )
28 }
29
30 fn with_capacity(capacity: NonZeroUsize) -> Self {
31 Self {
32 inner: Mutex::new(LruCache::new(capacity)),
33 }
34 }
35
36 fn compile(&self, pattern: &str) -> Result<Regex, regex::Error> {
37 let Ok(mut cache) = self.inner.lock() else {
38 return Regex::new(pattern);
39 };
40 if let Some(regex) = cache.get(pattern) {
41 return Ok(regex.clone());
42 }
43
44 let regex = Regex::new(pattern)?;
45 cache.put(pattern.to_string(), regex.clone());
46 Ok(regex)
47 }
48
49 #[cfg(test)]
50 fn len(&self) -> usize {
51 self.inner.lock().expect("cache lock").len()
52 }
53
54 #[cfg(test)]
55 fn contains(&self, pattern: &str) -> bool {
56 self.inner.lock().expect("cache lock").contains(pattern)
57 }
58 }
59
60 #[cfg(test)]
61 mod tests {
62 use super::*;
63
64 #[test]
65 fn repeated_pattern_uses_one_cache_entry() {
66 let cache = UserRegexCache::with_capacity(NonZeroUsize::new(2).unwrap());
67
68 let first = cache.compile("alpha|beta").expect("regex compiles");
69 let second = cache.compile("alpha|beta").expect("regex cache hit");
70
71 assert!(first.is_match("alpha"));
72 assert!(second.is_match("beta"));
73 assert_eq!(cache.len(), 1);
74 }
75
76 #[test]
77 fn capacity_evicts_least_recently_used_pattern() {
78 let cache = UserRegexCache::with_capacity(NonZeroUsize::new(2).unwrap());
79
80 cache.compile("one").expect("one compiles");
81 cache.compile("two").expect("two compiles");
82 cache.compile("one").expect("one is refreshed");
83 cache.compile("three").expect("three compiles");
84
85 assert!(cache.contains("one"));
86 assert!(!cache.contains("two"));
87 assert!(cache.contains("three"));
88 }
89
90 #[test]
91 fn invalid_pattern_is_not_cached() {
92 let cache = UserRegexCache::with_capacity(NonZeroUsize::new(2).unwrap());
93
94 assert!(cache.compile("[").is_err());
95
96 assert_eq!(cache.len(), 0);
97 }
98 }
99
99 lines RUST