cron_when/cli/dispatch/
mod.rs

1use crate::cli::actions::Action;
2use anyhow::Result;
3use clap::ArgMatches;
4use std::env;
5use std::path::PathBuf;
6
7/// Convert `ArgMatches` into an Action
8///
9/// # Errors
10///
11/// Returns an error if no valid action can be determined from the matches
12pub fn handler(matches: &ArgMatches) -> Result<Action> {
13    // Determine verbosity level
14    let verbose = matches.get_count("verbose") > 0;
15
16    // Extract next count if provided
17    let next = matches.get_one::<u32>("next").copied();
18
19    // Extract color flag - check CLI flag first, then environment variable
20    let color = matches.get_flag("color")
21        || env::var("CRON_WHEN_COLOR")
22            .map(|v| v == "1")
23            .unwrap_or(false);
24
25    // Check which mode was requested
26    if matches.get_flag("crontab") {
27        Ok(Action::Crontab { verbose, color })
28    } else if let Some(file_path) = matches.get_one::<String>("file") {
29        Ok(Action::File {
30            path: PathBuf::from(file_path),
31            verbose,
32            color,
33        })
34    } else if let Some(expression) = matches.get_one::<String>("cron") {
35        Ok(Action::Single {
36            expression: expression.clone(),
37            verbose,
38            next,
39            color,
40        })
41    } else {
42        anyhow::bail!(
43            "Please provide a cron expression, use --file, or use --crontab\n\
44             Run with --help for usage information"
45        )
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::cli::commands;
53
54    #[test]
55    fn test_handler_single() {
56        let matches = commands::new().get_matches_from(vec!["cron-when", "*/5 * * * *"]);
57        let action = handler(&matches);
58        assert!(action.is_ok());
59        if let Ok(action) = action {
60            assert!(matches!(action, Action::Single { .. }));
61        }
62    }
63
64    #[test]
65    fn test_handler_file() {
66        let matches = commands::new().get_matches_from(vec!["cron-when", "-f", "test.crontab"]);
67        let action = handler(&matches);
68        assert!(action.is_ok());
69        if let Ok(action) = action {
70            assert!(matches!(action, Action::File { .. }));
71        }
72    }
73
74    #[test]
75    fn test_handler_crontab() {
76        let matches = commands::new().get_matches_from(vec!["cron-when", "--crontab"]);
77        let action = handler(&matches);
78        assert!(action.is_ok());
79        if let Ok(action) = action {
80            assert!(matches!(
81                action,
82                Action::Crontab {
83                    verbose: false,
84                    color: false
85                }
86            ));
87        }
88    }
89
90    #[test]
91    fn test_handler_verbose() {
92        let matches = commands::new().get_matches_from(vec!["cron-when", "-v", "*/5 * * * *"]);
93        let action = handler(&matches);
94        assert!(action.is_ok());
95        if let Ok(Action::Single { verbose, .. }) = action {
96            assert!(verbose);
97        }
98    }
99
100    #[test]
101    fn test_handler_no_args() {
102        let matches = commands::new().get_matches_from(vec!["cron-when"]);
103        let result = handler(&matches);
104        assert!(result.is_err());
105    }
106
107    #[test]
108    fn test_handler_next() {
109        let matches =
110            commands::new().get_matches_from(vec!["cron-when", "--next", "5", "*/5 * * * *"]);
111        let action = handler(&matches);
112        assert!(action.is_ok());
113        if let Ok(Action::Single { next, .. }) = action {
114            assert_eq!(next, Some(5));
115        }
116    }
117}