loop_lib/
args.rs

1use clap::{Arg, ArgAction, Command};
2
3/// Represents the command-line options for the loop command.
4///
5/// This struct holds all the possible options that can be passed to the loop command,
6/// including the command to execute, directories to include or exclude, and patterns
7/// for filtering directories.
8#[derive(Debug, Clone)]
9pub struct LoopOptions {
10    pub command: Vec<String>,
11    pub cwd: Option<String>,
12    pub include: Option<Vec<String>>,
13    pub exclude: Option<Vec<String>>,
14    pub include_only: Option<Vec<String>>,
15    pub exclude_only: Option<Vec<String>>,
16    pub include_pattern: Option<String>,
17    pub exclude_pattern: Option<String>,
18    pub init: bool,
19}
20
21/// Parses command-line arguments and returns a LoopOptions struct.
22///
23/// This function uses the clap library to define and parse command-line arguments,
24/// converting them into a LoopOptions struct for easy use in the rest of the program.
25pub fn parse_args() -> LoopOptions {
26    let matches = Command::new("loop")
27        .about("Loop through directories and execute a command")
28        .version(env!("CARGO_PKG_VERSION"))
29        .author("Matt")
30        .arg(
31            Arg::new("command")
32                .required_unless_present("init")
33                .num_args(1..)
34                .help("The command to execute in each directory"),
35        )
36        .arg(
37            Arg::new("cwd")
38                .short('C')
39                .long("cwd")
40                .help("The current working directory"),
41        )
42        .arg(
43            Arg::new("include")
44                .short('i')
45                .long("include")
46                .num_args(1..)
47                .help("Additional directories to include"),
48        )
49        .arg(
50            Arg::new("exclude")
51                .short('e')
52                .long("exclude")
53                .num_args(1..)
54                .help("Directories to exclude"),
55        )
56        .arg(
57            Arg::new("include_only")
58                .long("include-only")
59                .num_args(1..)
60                .help("Only include these directories"),
61        )
62        .arg(
63            Arg::new("exclude_only")
64                .long("exclude-only")
65                .num_args(1..)
66                .help("Exclude all directories except these"),
67        )
68        .arg(
69            Arg::new("include_pattern")
70                .long("include-pattern")
71                .help("A pattern to include directories"),
72        )
73        .arg(
74            Arg::new("exclude_pattern")
75                .long("exclude-pattern")
76                .help("A pattern to exclude directories"),
77        )
78        .arg(
79            Arg::new("init")
80                .long("init")
81                .action(ArgAction::SetTrue)
82                .help("Initialize the loop configuration"),
83        )
84        .get_matches();
85
86    LoopOptions {
87        command: matches
88            .get_many::<String>("command")
89            .map(|v| v.cloned().collect())
90            .unwrap_or_default(),
91        cwd: matches.get_one::<String>("cwd").cloned(),
92        include: matches
93            .get_many::<String>("include")
94            .map(|v| v.cloned().collect()),
95        exclude: matches
96            .get_many::<String>("exclude")
97            .map(|v| v.cloned().collect()),
98        include_only: matches
99            .get_many::<String>("include_only")
100            .map(|v| v.cloned().collect()),
101        exclude_only: matches
102            .get_many::<String>("exclude_only")
103            .map(|v| v.cloned().collect()),
104        include_pattern: matches.get_one::<String>("include_pattern").cloned(),
105        exclude_pattern: matches.get_one::<String>("exclude_pattern").cloned(),
106        init: matches.get_flag("init"),
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_args_parsing() {
116        let args = parse_args_from(&["loop", "gst", "--include", "~/bootstrap"]);
117        assert_eq!(args.command, vec!["gst"]);
118        assert_eq!(args.include, Some(vec!["~/bootstrap".to_string()]));
119        assert!(args.exclude.is_none());
120        assert!(!args.init);
121    }
122
123    #[test]
124    fn test_init_flag() {
125        let args = parse_args_from(&["loop", "--init"]);
126        assert!(args.init);
127        assert!(args.command.is_empty());
128    }
129
130    // Helper function for testing
131    fn parse_args_from(args: &[&str]) -> LoopOptions {
132        let matches = Command::new("loop")
133            .arg(
134                Arg::new("command")
135                    .required_unless_present("init")
136                    .num_args(1..),
137            )
138            .arg(Arg::new("cwd").short('C').long("cwd"))
139            .arg(Arg::new("include").short('i').long("include").num_args(1..))
140            .arg(Arg::new("exclude").short('e').long("exclude").num_args(1..))
141            .arg(Arg::new("include_only").long("include-only").num_args(1..))
142            .arg(Arg::new("exclude_only").long("exclude-only").num_args(1..))
143            .arg(Arg::new("include_pattern").long("include-pattern"))
144            .arg(Arg::new("exclude_pattern").long("exclude-pattern"))
145            .arg(Arg::new("init").long("init").action(ArgAction::SetTrue))
146            .try_get_matches_from(args)
147            .unwrap();
148
149        LoopOptions {
150            command: matches
151                .get_many::<String>("command")
152                .map(|v| v.cloned().collect())
153                .unwrap_or_default(),
154            cwd: matches.get_one::<String>("cwd").cloned(),
155            include: matches
156                .get_many::<String>("include")
157                .map(|v| v.cloned().collect()),
158            exclude: matches
159                .get_many::<String>("exclude")
160                .map(|v| v.cloned().collect()),
161            include_only: matches
162                .get_many::<String>("include_only")
163                .map(|v| v.cloned().collect()),
164            exclude_only: matches
165                .get_many::<String>("exclude_only")
166                .map(|v| v.cloned().collect()),
167            include_pattern: matches.get_one::<String>("include_pattern").cloned(),
168            exclude_pattern: matches.get_one::<String>("exclude_pattern").cloned(),
169            init: matches.get_flag("init"),
170        }
171    }
172}