1#[allow(unused_imports)]
3use crate::ported::vm_helper::ShellExecutor;
4#[allow(unused_imports)]
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub enum AdviceKind {
15 Before,
17 After,
19 Around,
21}
22
23#[derive(Debug, Clone)]
25pub struct Intercept {
28 pub pattern: String,
30 pub kind: AdviceKind,
32 pub code: String,
34 pub id: u32,
36}
37
38pub(crate) fn intercept_matches(pattern: &str, cmd_name: &str, full_cmd: &str) -> bool {
41 if pattern == "*" || pattern == "all" {
42 return true;
43 }
44 if pattern == cmd_name {
45 return true;
46 }
47 if pattern.contains('*') || pattern.contains('?') {
48 if let Ok(pat) = glob::Pattern::new(pattern) {
49 return pat.matches(cmd_name) || pat.matches(full_cmd);
50 }
51 }
52 false
53}
54
55impl crate::ported::vm_helper::ShellExecutor {
63 pub(crate) fn run_intercepts(
66 &mut self,
67 cmd_name: &str,
68 full_cmd: &str,
69 args: &[String],
70 ) -> Option<Result<i32, String>> {
71 let matching: Vec<Intercept> = self
73 .intercepts
74 .iter()
75 .filter(|i| intercept_matches(&i.pattern, cmd_name, full_cmd))
76 .cloned()
77 .collect();
78
79 if matching.is_empty() {
80 return None;
81 }
82
83 self.set_scalar("INTERCEPT_NAME".to_string(), cmd_name.to_string());
85 self.set_scalar("INTERCEPT_ARGS".to_string(), args.join(" "));
86 self.set_scalar("INTERCEPT_CMD".to_string(), full_cmd.to_string());
87
88 for advice in matching
90 .iter()
91 .filter(|i| matches!(i.kind, AdviceKind::Before))
92 {
93 let _ = self.execute_advice(&advice.code);
94 }
95
96 let around = matching
98 .iter()
99 .find(|i| matches!(i.kind, AdviceKind::Around));
100
101 let t0 = std::time::Instant::now();
102
103 let result = if let Some(advice) = around {
104 self.set_scalar("__intercept_proceed".to_string(), "0".to_string());
107 let advice_result = self.execute_advice(&advice.code);
108
109 let proceeded = self
111 .scalar("__intercept_proceed")
112 .map(|v| v == "1")
113 .unwrap_or(false);
114
115 if proceeded {
116 advice_result
118 } else {
119 advice_result
121 }
122 } else {
123 let has_after = matching.iter().any(|i| matches!(i.kind, AdviceKind::After));
128 if !has_after {
129 return None;
131 }
132
133 self.run_original_command(cmd_name, args)
135 };
136
137 let elapsed = t0.elapsed();
138
139 let ms = elapsed.as_secs_f64() * 1000.0;
141 self.set_scalar("INTERCEPT_MS".to_string(), format!("{:.3}", ms));
142 self.set_scalar("INTERCEPT_US".to_string(), format!("{:.0}", ms * 1000.0));
143
144 for advice in matching
146 .iter()
147 .filter(|i| matches!(i.kind, AdviceKind::After))
148 {
149 let _ = self.execute_advice(&advice.code);
150 }
151
152 self.unset_scalar("INTERCEPT_NAME");
154 self.unset_scalar("INTERCEPT_ARGS");
155 self.unset_scalar("INTERCEPT_CMD");
156 self.unset_scalar("INTERCEPT_MS");
157 self.unset_scalar("INTERCEPT_US");
158 self.unset_scalar("__intercept_proceed");
159
160 Some(result)
161 }
162 pub(crate) fn execute_advice(&mut self, code: &str) -> Result<i32, String> {
166 let code = code.trim();
167 if code.starts_with('@') {
168 let stryke_code = code.trim_start_matches('@').trim();
169 if let Some(status) = crate::try_stryke_dispatch(stryke_code) {
170 self.set_last_status(status);
171 return Ok(status);
172 }
173 }
175 self.execute_script(code)
176 }
177 pub(crate) fn run_original_command(
178 &mut self,
179 cmd_name: &str,
180 args: &[String],
181 ) -> Result<i32, String> {
182 if let Some(status) = self.dispatch_function_call(cmd_name, args) {
185 return Ok(status);
186 }
187 self.execute_external(cmd_name, args, &[])
189 }
190}
191#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn star_matches_anything() {
199 assert!(intercept_matches("*", "anything", "anything --here"));
200 assert!(intercept_matches("*", "", ""));
201 }
202
203 #[test]
204 fn all_matches_anything() {
205 assert!(intercept_matches("all", "ls", "ls -la"));
206 assert!(intercept_matches("all", "git", "git status"));
207 }
208
209 #[test]
210 fn exact_match_on_cmd_name() {
211 assert!(intercept_matches("git", "git", "git push"));
212 assert!(intercept_matches("ls", "ls", "ls -la"));
213 }
214
215 #[test]
216 fn exact_pattern_does_not_match_different_name() {
217 assert!(!intercept_matches("git", "svn", "svn diff"));
218 assert!(!intercept_matches("ls", "lsof", "lsof -p 1"));
219 }
220
221 #[test]
222 fn glob_star_matches_prefix() {
223 assert!(intercept_matches("git *", "git", "git push origin"));
225 }
226
227 #[test]
228 fn glob_star_underscore_prefix_matches_completion_funcs() {
229 assert!(intercept_matches("_*", "_files", "_files"));
231 assert!(intercept_matches("_*", "_describe", "_describe"));
232 }
233
234 #[test]
235 fn glob_star_does_not_match_non_prefix() {
236 assert!(!intercept_matches("_*", "files", "files"));
237 }
238
239 #[test]
240 fn question_mark_glob_matches_single_char() {
241 assert!(intercept_matches("l?", "ls", "ls"));
242 assert!(!intercept_matches("l?", "lsof", "lsof"));
243 }
244
245 #[test]
246 fn unmatched_pattern_without_glob_chars_returns_false() {
247 assert!(!intercept_matches("nope", "git", "git push"));
248 }
249
250 #[test]
251 fn invalid_glob_pattern_returns_false() {
252 assert!(!intercept_matches("[invalid", "git", "git push"));
256 }
257
258 #[test]
259 fn empty_pattern_does_not_match_non_empty_cmd() {
260 assert!(!intercept_matches("", "ls", "ls -la"));
261 }
262
263 #[test]
264 fn empty_pattern_matches_empty_cmd_exactly() {
265 assert!(intercept_matches("", "", ""));
267 }
268
269 #[test]
270 fn advice_kind_variants_round_trip_clone() {
271 let b = AdviceKind::Before;
272 let a = AdviceKind::After;
273 let r = AdviceKind::Around;
274 assert!(matches!(b.clone(), AdviceKind::Before));
275 assert!(matches!(a.clone(), AdviceKind::After));
276 assert!(matches!(r.clone(), AdviceKind::Around));
277 }
278
279 #[test]
280 fn intercept_struct_clone_preserves_fields() {
281 let i = Intercept {
282 pattern: "git *".into(),
283 kind: AdviceKind::Before,
284 code: "echo before".into(),
285 id: 42,
286 };
287 let c = i.clone();
288 assert_eq!(c.pattern, "git *");
289 assert!(matches!(c.kind, AdviceKind::Before));
290 assert_eq!(c.code, "echo before");
291 assert_eq!(c.id, 42);
292 }
293}