loop_lib/
lib.rs

1pub mod args;
2pub mod config;
3pub mod executor;
4
5use crate::args::LoopOptions;
6use std::path::PathBuf;
7use walkdir::WalkDir;
8
9/// Runs the main loop command based on the provided options.
10///
11/// This function is the entry point for the loop command's functionality.
12/// It handles initialization if requested, or executes the loop command
13/// based on the provided options.
14pub fn run(args: LoopOptions) -> i32 {
15    if args.init {
16        config::create_looprc();
17        return exitcode::OK;
18    }
19
20    let options = LoopOptions {
21        command: args.command,
22        cwd: args.cwd,
23        include: args.include,
24        exclude: args.exclude,
25        include_only: args.include_only,
26        exclude_only: args.exclude_only,
27        include_pattern: args.include_pattern,
28        exclude_pattern: args.exclude_pattern,
29        init: args.init,
30    };
31
32    execute_loop(options)
33}
34
35/// Executes the loop command with the given options.
36///
37/// This function processes directories based on the provided options,
38/// executing the specified command in each relevant directory.
39pub fn execute_loop(options: LoopOptions) -> i32 {
40    let working_dir = options
41        .cwd
42        .clone()
43        .map(PathBuf::from)
44        .unwrap_or_else(|| std::env::current_dir().unwrap());
45    std::env::set_current_dir(working_dir).unwrap();
46
47    let config = config::read_looprc();
48
49    let mut first_error_code: Option<i32> = None;
50
51    // Process child directories
52    for entry in WalkDir::new(".")
53        .min_depth(1)
54        .max_depth(1)
55        .sort_by_file_name()
56    {
57        let entry = entry.unwrap();
58        if entry.file_type().is_dir() {
59            let dir_path = entry.path();
60            if executor::should_process_directory(dir_path, &options, &config) {
61                let exit_code = executor::execute_command_in_directory(dir_path, &options.command);
62                if exit_code != 0 && first_error_code.is_none() {
63                    first_error_code = Some(exit_code);
64                }
65            }
66        }
67    }
68
69    // Process included directories
70    if let Some(ref include_dirs) = options.include {
71        for dir in include_dirs {
72            let dir_path = PathBuf::from(dir);
73            if dir_path.is_dir() {
74                let exit_code = executor::execute_command_in_directory(&dir_path, &options.command);
75                if exit_code != 0 && first_error_code.is_none() {
76                    first_error_code = Some(exit_code);
77                }
78            } else {
79                eprintln!("Warning: {} is not a directory", dir);
80            }
81        }
82    }
83
84    match first_error_code {
85        Some(code) => code,
86        None => exitcode::OK,
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use std::fs;
94    use tempfile::tempdir;
95
96    #[test]
97    fn test_run() {
98        let temp_dir = tempdir().unwrap();
99        fs::create_dir(temp_dir.path().join("test_dir")).unwrap();
100
101        let args = LoopOptions {
102            command: vec!["echo".to_string(), "test".to_string()],
103            cwd: Some(temp_dir.path().to_string_lossy().into_owned()),
104            include: None,
105            exclude: None,
106            include_only: None,
107            exclude_only: None,
108            include_pattern: None,
109            exclude_pattern: None,
110            init: false,
111        };
112        let exit_code = run(args);
113        assert_eq!(exit_code, exitcode::OK);
114    }
115}