cron_when/cli/dispatch/
mod.rs1use crate::cli::actions::Action;
2use anyhow::Result;
3use clap::ArgMatches;
4use std::env;
5use std::io::{IsTerminal, stdout};
6use std::path::PathBuf;
7
8pub fn handler(matches: &ArgMatches) -> Result<Action> {
14 let verbose = matches.get_count("verbose") > 0;
16
17 let next = matches.get_one::<u32>("next").copied();
19
20 let color = if matches.get_flag("no-color") {
27 false
28 } else if matches.get_flag("color") {
29 true
30 } else if env::var_os("NO_COLOR").is_some() {
31 false
32 } else if env::var_os("CLICOLOR_FORCE").is_some_and(|v| v != "0") {
33 true
34 } else {
35 stdout().is_terminal()
36 };
37
38 if matches.get_flag("crontab") {
40 Ok(Action::Crontab { verbose, color })
41 } else if let Some(file_path) = matches.get_one::<String>("file") {
42 Ok(Action::File {
43 path: PathBuf::from(file_path),
44 verbose,
45 color,
46 })
47 } else if let Some(expression) = matches.get_one::<String>("cron") {
48 Ok(Action::Single {
49 expression: expression.clone(),
50 verbose,
51 next,
52 color,
53 })
54 } else {
55 anyhow::bail!(
56 "Please provide a cron expression, use --file, or use --crontab\n\
57 Run with --help for usage information"
58 )
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::cli::commands;
66 use std::sync::{Mutex, MutexGuard};
67
68 static ENV_LOCK: Mutex<()> = Mutex::new(());
69
70 fn env_guard() -> MutexGuard<'static, ()> {
71 ENV_LOCK
72 .lock()
73 .unwrap_or_else(std::sync::PoisonError::into_inner)
74 }
75
76 #[test]
77 fn test_handler_single() {
78 let matches = commands::new().get_matches_from(vec!["cron-when", "*/5 * * * *"]);
79 let action = handler(&matches);
80 assert!(action.is_ok());
81 if let Ok(action) = action {
82 assert!(matches!(action, Action::Single { .. }));
83 }
84 }
85
86 #[test]
87 fn test_handler_file() {
88 let matches = commands::new().get_matches_from(vec!["cron-when", "-f", "test.crontab"]);
89 let action = handler(&matches);
90 assert!(action.is_ok());
91 if let Ok(action) = action {
92 assert!(matches!(action, Action::File { .. }));
93 }
94 }
95
96 #[test]
97 fn test_handler_crontab() {
98 let matches = commands::new().get_matches_from(vec!["cron-when", "--crontab"]);
99 let action = handler(&matches);
100 assert!(action.is_ok());
101 if let Ok(action) = action {
102 assert!(matches!(action, Action::Crontab { verbose: false, .. }));
103 }
104 }
105
106 #[test]
107 fn test_handler_verbose() {
108 let matches = commands::new().get_matches_from(vec!["cron-when", "-v", "*/5 * * * *"]);
109 let action = handler(&matches);
110 assert!(action.is_ok());
111 if let Ok(Action::Single { verbose, .. }) = action {
112 assert!(verbose);
113 }
114 }
115
116 #[test]
117 fn test_handler_no_args() {
118 let matches = commands::new().get_matches_from(vec!["cron-when"]);
119 let result = handler(&matches);
120 assert!(result.is_err());
121 }
122
123 #[test]
124 fn test_handler_next() {
125 let matches =
126 commands::new().get_matches_from(vec!["cron-when", "--next", "5", "*/5 * * * *"]);
127 let action = handler(&matches);
128 assert!(action.is_ok());
129 if let Ok(Action::Single { next, .. }) = action {
130 assert_eq!(next, Some(5));
131 }
132 }
133
134 #[test]
135 #[allow(clippy::undocumented_unsafe_blocks)]
136 fn test_handler_no_color_env() {
137 let _guard = env_guard();
138 unsafe {
139 env::set_var("NO_COLOR", "1");
140 }
141 let matches = commands::new().get_matches_from(vec!["cron-when", "*/5 * * * *"]);
142 let action = handler(&matches);
143 assert!(action.is_ok());
144 if let Ok(Action::Single { color, .. }) = action {
145 assert!(!color);
147 }
148 unsafe {
149 env::remove_var("NO_COLOR");
150 }
151 }
152
153 #[test]
154 #[allow(clippy::undocumented_unsafe_blocks)]
155 fn test_handler_empty_no_color_env_disables_color() {
156 let _guard = env_guard();
157 unsafe {
158 env::set_var("NO_COLOR", "");
159 env::set_var("CLICOLOR_FORCE", "1");
160 }
161 let matches = commands::new().get_matches_from(vec!["cron-when", "*/5 * * * *"]);
162 let action = handler(&matches);
163 assert!(action.is_ok());
164 if let Ok(Action::Single { color, .. }) = action {
165 assert!(!color);
166 }
167 unsafe {
168 env::remove_var("NO_COLOR");
169 env::remove_var("CLICOLOR_FORCE");
170 }
171 }
172
173 #[test]
174 #[allow(clippy::undocumented_unsafe_blocks)]
175 fn test_handler_color_flag_overrides_no_color_env() {
176 let _guard = env_guard();
177 unsafe {
178 env::set_var("NO_COLOR", "1");
179 }
180 let matches = commands::new().get_matches_from(vec!["cron-when", "--color", "*/5 * * * *"]);
181 let action = handler(&matches);
182 assert!(action.is_ok());
183 if let Ok(Action::Single { color, .. }) = action {
184 assert!(color);
186 }
187 unsafe {
188 env::remove_var("NO_COLOR");
189 }
190 }
191
192 #[test]
193 #[allow(clippy::undocumented_unsafe_blocks)]
194 fn test_handler_no_color_flag_overrides_all() {
195 let _guard = env_guard();
196 unsafe {
197 env::set_var("CLICOLOR_FORCE", "1");
198 }
199 let matches =
200 commands::new().get_matches_from(vec!["cron-when", "--no-color", "*/5 * * * *"]);
201 let action = handler(&matches);
202 assert!(action.is_ok());
203 if let Ok(Action::Single { color, .. }) = action {
204 assert!(!color);
206 }
207 unsafe {
208 env::remove_var("CLICOLOR_FORCE");
209 }
210 }
211}