1use crate::args::LoopOptions;
2use crate::config::LoopConfig;
3use regex::Regex;
4use std::io::{self, Write};
5use std::path::Path;
6use std::process::{Command, Stdio};
7
8pub fn should_process_directory(
13 dir_path: &Path,
14 options: &LoopOptions,
15 config: &LoopConfig,
16) -> bool {
17 let dir_name = dir_path.file_name().unwrap_or_default().to_str().unwrap();
18
19 if let Some(ref include_only) = options.include_only {
20 return include_only.contains(&dir_name.to_string())
21 || include_only.contains(&".".to_string());
22 }
23
24 if let Some(ref exclude_only) = options.exclude_only {
25 return !exclude_only.contains(&dir_name.to_string());
26 }
27
28 if let Some(ref include) = options.include {
29 if include.contains(&dir_name.to_string()) {
30 return true;
31 }
32 }
33
34 if let Some(ref exclude) = options.exclude {
35 if exclude.contains(&dir_name.to_string()) {
36 return false;
37 }
38 }
39
40 if let Some(ref include_pattern) = options.include_pattern {
41 let re = Regex::new(include_pattern).unwrap();
42 if !re.is_match(dir_name) {
43 return false;
44 }
45 }
46
47 if let Some(ref exclude_pattern) = options.exclude_pattern {
48 let re = Regex::new(exclude_pattern).unwrap();
49 if re.is_match(dir_name) {
50 return false;
51 }
52 }
53
54 if config.ignore.contains(&dir_name.to_string()) {
55 return false;
56 }
57
58 true
59}
60
61pub fn execute_command_in_directory(dir: &Path, command: &[String]) -> i32 {
67 let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
68 let command_str = command.join(" ");
69
70 let script = if shell.ends_with("zsh") {
71 format!(
72 r#"
73 source ~/.zshrc 2>/dev/null
74 eval "{}"
75 "#,
76 command_str.replace('"', r#"\""#)
77 )
78 } else if shell.ends_with("fish") {
79 format!(
80 r#"
81 source ~/.config/fish/config.fish 2>/dev/null
82 {}
83 "#,
84 command_str
85 )
86 } else {
87 format!(
89 r#"
90 if [ -f ~/.bashrc ]; then . ~/.bashrc; fi
91 {}
92 "#,
93 command_str
94 )
95 };
96
97 println!();
98
99 let status = Command::new(&shell)
100 .arg("-c")
101 .arg(&script)
102 .env(
103 "HOME",
104 std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string()),
105 )
106 .current_dir(dir)
107 .stdout(Stdio::inherit())
108 .stderr(Stdio::inherit())
109 .status()
110 .expect("Failed to execute command");
111
112 let exit_code = status.code().unwrap_or(-1);
113 let dir_name = dir.file_name().unwrap_or_default().to_str().unwrap();
114
115 if status.success() {
116 println!("\x1b[32m{} ✓\x1b[0m", dir_name);
117 } else {
118 println!("\x1b[31m{} ✗: exited code {}\x1b[0m", dir_name, exit_code);
119 }
120
121 io::stdout().flush().unwrap();
122
123 exit_code
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::config::LoopConfig;
130 use std::path::PathBuf;
131
132 #[test]
133 fn test_should_process_directory() {
134 let args = LoopOptions {
135 command: vec!["test".to_string()],
136 cwd: None,
137 include: Some(vec!["include_dir".to_string()]),
138 exclude: Some(vec!["exclude_dir".to_string()]),
139 include_only: None,
140 exclude_only: None,
141 include_pattern: None,
142 exclude_pattern: None,
143 init: false,
144 };
145
146 let config = LoopConfig {
147 ignore: vec![".git".to_string()],
148 };
149
150 assert!(should_process_directory(
151 &PathBuf::from("include_dir"),
152 &args,
153 &config
154 ));
155 assert!(!should_process_directory(
156 &PathBuf::from("exclude_dir"),
157 &args,
158 &config
159 ));
160 assert!(!should_process_directory(
161 &PathBuf::from(".git"),
162 &args,
163 &config
164 ));
165 assert!(should_process_directory(
166 &PathBuf::from("normal_dir"),
167 &args,
168 &config
169 ));
170 }
171
172 #[test]
173 fn test_should_process_directory_with_patterns() {
174 let args = LoopOptions {
175 command: vec!["test".to_string()],
176 cwd: None,
177 include: None,
178 exclude: None,
179 include_only: None,
180 exclude_only: None,
181 include_pattern: Some("src.*".to_string()),
182 exclude_pattern: Some("test.*".to_string()),
183 init: false,
184 };
185
186 let config = LoopConfig {
187 ignore: vec!["node_modules".to_string(), "target".to_string()],
188 };
189
190 assert!(should_process_directory(
191 &PathBuf::from("src"),
192 &args,
193 &config
194 ));
195 assert!(!should_process_directory(
196 &PathBuf::from("test"),
197 &args,
198 &config
199 ));
200 assert!(!should_process_directory(
201 &PathBuf::from("node_modules"),
202 &args,
203 &config
204 ));
205 assert!(!should_process_directory(
206 &PathBuf::from("target"),
207 &args,
208 &config
209 ));
210 }
211
212 #[test]
213 fn test_execute_command_in_directory() {
214 let temp_dir = tempfile::tempdir().unwrap();
215 let dir_path = temp_dir.path();
216
217 let exit_code =
218 execute_command_in_directory(dir_path, &["echo".to_string(), "test".to_string()]);
219 assert_eq!(exit_code, 0);
220
221 let exit_code = execute_command_in_directory(dir_path, &["false".to_string()]);
222 assert_ne!(exit_code, 0);
223 }
224}