cron_when/cli/commands/
mod.rs1use clap::{
2 Arg, ArgAction, ColorChoice, Command,
3 builder::styling::{AnsiColor, Effects, Styles},
4};
5
6pub mod built_info {
7 include!(concat!(env!("OUT_DIR"), "/built.rs"));
8}
9
10pub fn new() -> Command {
12 let styles = Styles::styled()
13 .header(AnsiColor::Yellow.on_default() | Effects::BOLD)
14 .usage(AnsiColor::Green.on_default() | Effects::BOLD)
15 .literal(AnsiColor::Blue.on_default() | Effects::BOLD)
16 .placeholder(AnsiColor::Green.on_default());
17
18 let git_hash = built_info::GIT_COMMIT_HASH.unwrap_or("unknown");
19 let long_version: &'static str =
20 Box::leak(format!("{} - {}", env!("CARGO_PKG_VERSION"), git_hash).into_boxed_str());
21
22 Command::new(env!("CARGO_PKG_NAME"))
23 .version(env!("CARGO_PKG_VERSION"))
24 .long_version(long_version)
25 .author(env!("CARGO_PKG_AUTHORS"))
26 .about(env!("CARGO_PKG_DESCRIPTION"))
27 .color(ColorChoice::Auto)
28 .styles(styles)
29 .arg(
30 Arg::new("cron")
31 .value_name("CRON_EXPRESSION")
32 .help("Cron expression (e.g., \"*/5 * * * *\")")
33 .index(1),
34 )
35 .arg(
36 Arg::new("file")
37 .short('f')
38 .long("file")
39 .value_name("FILE")
40 .help("Read from file (crontab format)"),
41 )
42 .arg(
43 Arg::new("crontab")
44 .short('l')
45 .long("crontab")
46 .help("Parse current user's crontab")
47 .action(ArgAction::SetTrue),
48 )
49 .arg(
50 Arg::new("verbose")
51 .short('v')
52 .long("verbose")
53 .help("Show verbose output with cron expression")
54 .action(ArgAction::Count),
55 )
56 .arg(
57 Arg::new("next")
58 .short('n')
59 .long("next")
60 .value_name("COUNT")
61 .help("Show next N occurrences of the cron expression")
62 .value_parser(clap::value_parser!(u32).range(1..=100)),
63 )
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn test_cli_structure() {
72 let cmd = new();
73 assert_eq!(cmd.get_name(), env!("CARGO_PKG_NAME"));
74 }
75
76 #[test]
77 fn test_parse_cron_expression() {
78 let matches = new().get_matches_from(vec!["cron-when", "*/5 * * * *"]);
79 assert!(matches.contains_id("cron"));
80 assert_eq!(matches.get_one::<String>("cron").unwrap(), "*/5 * * * *");
81 }
82
83 #[test]
84 fn test_parse_file_flag() {
85 let matches = new().get_matches_from(vec!["cron-when", "-f", "test.crontab"]);
86 assert!(matches.contains_id("file"));
87 assert_eq!(matches.get_one::<String>("file").unwrap(), "test.crontab");
88 }
89
90 #[test]
91 fn test_parse_crontab_flag() {
92 let matches = new().get_matches_from(vec!["cron-when", "--crontab"]);
93 assert!(matches.get_flag("crontab"));
94 }
95
96 #[test]
97 fn test_verbose_count() {
98 let matches = new().get_matches_from(vec!["cron-when", "-vvv", "*/5 * * * *"]);
99 assert_eq!(matches.get_count("verbose"), 3);
100 }
101
102 #[test]
103 fn test_parse_next_flag() {
104 let matches = new().get_matches_from(vec!["cron-when", "--next", "5", "*/5 * * * *"]);
105 assert_eq!(matches.get_one::<u32>("next"), Some(&5));
106 }
107
108 #[test]
109 fn test_parse_next_short() {
110 let matches = new().get_matches_from(vec!["cron-when", "-n", "10", "*/5 * * * *"]);
111 assert_eq!(matches.get_one::<u32>("next"), Some(&10));
112 }
113}