tui_dispatch_core/debug/
action_logger.rs1use crate::store::Middleware;
24use crate::Action;
25
26#[derive(Debug, Clone)]
40pub struct ActionLoggerConfig {
41 pub include_patterns: Vec<String>,
43 pub exclude_patterns: Vec<String>,
45}
46
47impl Default for ActionLoggerConfig {
48 fn default() -> Self {
49 Self {
50 include_patterns: Vec::new(),
51 exclude_patterns: vec!["Tick".to_string(), "Render".to_string()],
53 }
54 }
55}
56
57impl ActionLoggerConfig {
58 pub fn new(include: Option<&str>, exclude: Option<&str>) -> Self {
74 let include_patterns = include
75 .map(|s| s.split(',').map(|p| p.trim().to_string()).collect())
76 .unwrap_or_default();
77
78 let exclude_patterns = exclude
79 .map(|s| s.split(',').map(|p| p.trim().to_string()).collect())
80 .unwrap_or_else(|| vec!["Tick".to_string(), "Render".to_string()]);
81
82 Self {
83 include_patterns,
84 exclude_patterns,
85 }
86 }
87
88 pub fn with_patterns(include: Vec<String>, exclude: Vec<String>) -> Self {
90 Self {
91 include_patterns: include,
92 exclude_patterns: exclude,
93 }
94 }
95
96 pub fn should_log(&self, action_name: &str) -> bool {
98 if !self.include_patterns.is_empty() {
100 let matches_include = self
101 .include_patterns
102 .iter()
103 .any(|p| glob_match(p, action_name));
104 if !matches_include {
105 return false;
106 }
107 }
108
109 let matches_exclude = self
111 .exclude_patterns
112 .iter()
113 .any(|p| glob_match(p, action_name));
114
115 !matches_exclude
116 }
117}
118
119#[derive(Debug, Clone)]
135pub struct ActionLoggerMiddleware {
136 config: ActionLoggerConfig,
137}
138
139impl ActionLoggerMiddleware {
140 pub fn new(config: ActionLoggerConfig) -> Self {
142 Self { config }
143 }
144
145 pub fn default_filtering() -> Self {
147 Self::new(ActionLoggerConfig::default())
148 }
149
150 pub fn log_all() -> Self {
152 Self::new(ActionLoggerConfig::with_patterns(vec![], vec![]))
153 }
154
155 pub fn config(&self) -> &ActionLoggerConfig {
157 &self.config
158 }
159
160 pub fn config_mut(&mut self) -> &mut ActionLoggerConfig {
162 &mut self.config
163 }
164}
165
166impl<A: Action> Middleware<A> for ActionLoggerMiddleware {
167 fn before(&mut self, action: &A) {
168 let name = action.name();
169 if self.config.should_log(name) {
170 tracing::debug!(action = %name, "action");
171 }
172 }
173
174 fn after(&mut self, _action: &A, _state_changed: bool) {
175 }
177}
178
179pub fn glob_match(pattern: &str, text: &str) -> bool {
184 let pattern: Vec<char> = pattern.chars().collect();
185 let text: Vec<char> = text.chars().collect();
186 glob_match_impl(&pattern, &text)
187}
188
189fn glob_match_impl(pattern: &[char], text: &[char]) -> bool {
190 let mut pi = 0;
191 let mut ti = 0;
192 let mut star_pi = None;
193 let mut star_ti = 0;
194
195 while ti < text.len() {
196 if pi < pattern.len() && (pattern[pi] == '?' || pattern[pi] == text[ti]) {
197 pi += 1;
198 ti += 1;
199 } else if pi < pattern.len() && pattern[pi] == '*' {
200 star_pi = Some(pi);
201 star_ti = ti;
202 pi += 1;
203 } else if let Some(spi) = star_pi {
204 pi = spi + 1;
205 star_ti += 1;
206 ti = star_ti;
207 } else {
208 return false;
209 }
210 }
211
212 while pi < pattern.len() && pattern[pi] == '*' {
213 pi += 1;
214 }
215
216 pi == pattern.len()
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_glob_match_exact() {
225 assert!(glob_match("Tick", "Tick"));
226 assert!(!glob_match("Tick", "Tock"));
227 assert!(!glob_match("Tick", "TickTock"));
228 }
229
230 #[test]
231 fn test_glob_match_star() {
232 assert!(glob_match("Search*", "SearchAddChar"));
233 assert!(glob_match("Search*", "SearchDeleteChar"));
234 assert!(glob_match("Search*", "Search"));
235 assert!(!glob_match("Search*", "StartSearch"));
236
237 assert!(glob_match("*Search", "StartSearch"));
238 assert!(glob_match("*Search*", "StartSearchNow"));
239
240 assert!(glob_match("Did*", "DidConnect"));
241 assert!(glob_match("Did*", "DidScanKeys"));
242 }
243
244 #[test]
245 fn test_glob_match_question() {
246 assert!(glob_match("Tick?", "Ticks"));
247 assert!(!glob_match("Tick?", "Tick"));
248 assert!(!glob_match("Tick?", "Tickss"));
249 }
250
251 #[test]
252 fn test_glob_match_combined() {
253 assert!(glob_match("*Add*", "SearchAddChar"));
254 assert!(glob_match("Connection*Add*", "ConnectionFormAddChar"));
255 }
256
257 #[test]
258 fn test_action_logger_config_include() {
259 let config = ActionLoggerConfig::new(Some("Search*,Connect"), None);
260 assert!(config.should_log("SearchAddChar"));
261 assert!(config.should_log("Connect"));
262 assert!(!config.should_log("Tick"));
263 assert!(!config.should_log("LoadKeys"));
264 }
265
266 #[test]
267 fn test_action_logger_config_exclude() {
268 let config = ActionLoggerConfig::new(None, Some("Tick,Render,LoadValue*"));
269 assert!(!config.should_log("Tick"));
270 assert!(!config.should_log("Render"));
271 assert!(!config.should_log("LoadValueDebounced"));
272 assert!(config.should_log("SearchAddChar"));
273 assert!(config.should_log("Connect"));
274 }
275
276 #[test]
277 fn test_action_logger_config_include_and_exclude() {
278 let config = ActionLoggerConfig::new(Some("Did*"), Some("DidFail*"));
280 assert!(config.should_log("DidConnect"));
281 assert!(config.should_log("DidScanKeys"));
282 assert!(!config.should_log("DidFailConnect"));
283 assert!(!config.should_log("DidFailScanKeys"));
284 assert!(!config.should_log("SearchAddChar")); }
286
287 #[test]
288 fn test_action_logger_config_default() {
289 let config = ActionLoggerConfig::default();
290 assert!(!config.should_log("Tick"));
291 assert!(!config.should_log("Render"));
292 assert!(config.should_log("Connect"));
293 assert!(config.should_log("SearchAddChar"));
294 }
295}