use clap::{Parser, Subcommand};
use clap_complete::Shell;
#[derive(Debug, Parser)]
#[command(
name = "timer",
version,
about = "A small foreground/background terminal timer",
args_conflicts_with_subcommands = true
)]
pub struct Cli {
#[arg(value_name = "DURATION")]
pub duration: Option<String>,
#[arg(short = 'n', long, value_name = "TEXT")]
pub note: Option<String>,
#[arg(short = 'd', long)]
pub detach: bool,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
#[command(visible_alias = "sw")]
Stopwatch {
#[arg(short = 'n', long, value_name = "TEXT")]
note: Option<String>,
#[arg(short = 'd', long)]
detach: bool,
},
#[command(visible_alias = "cd")]
Countdown {
#[arg(value_name = "DURATION")]
duration: String,
#[arg(short = 'n', long, value_name = "TEXT")]
note: Option<String>,
#[arg(short = 'd', long)]
detach: bool,
},
#[command(visible_alias = "a")]
Attach {
id: Option<u64>,
},
#[command(visible_alias = "ls")]
List,
#[command(visible_alias = "p")]
Pause {
id: Option<u64>,
},
#[command(visible_alias = "r")]
Resume {
id: Option<u64>,
},
#[command(visible_alias = "x")]
Stop {
id: Option<u64>,
},
Clear,
#[command(
visible_alias = "completion",
after_help = r#"Examples:
# Fish: enable now
timer completions fish | source
# Fish: install permanently
mkdir -p ~/.config/fish/completions
timer completions fish > ~/.config/fish/completions/timer.fish
# Bash: enable now
source <(timer completions bash)
# Zsh: enable now
source <(timer completions zsh)
Run `timer completions <SHELL> > FILE` to save generated completion code."#
)]
Completions {
#[arg(value_enum)]
shell: Shell,
},
Shutdown,
#[command(name = "__daemon", hide = true)]
Daemon,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_direct_countdown() {
let cli = Cli::try_parse_from(["timer", "15m", "-d", "-n", "tea"]).unwrap();
assert_eq!(cli.duration.as_deref(), Some("15m"));
assert!(cli.detach);
assert_eq!(cli.note.as_deref(), Some("tea"));
assert!(cli.command.is_none());
}
#[test]
fn parses_readable_commands_and_aliases() {
let full = Cli::try_parse_from(["timer", "attach", "3"]).unwrap();
assert!(matches!(
full.command,
Some(Command::Attach { id: Some(3) })
));
let short = Cli::try_parse_from(["timer", "a"]).unwrap();
assert!(matches!(short.command, Some(Command::Attach { id: None })));
let countdown = Cli::try_parse_from(["timer", "cd", "5m", "-d", "-n", "break"]).unwrap();
assert!(matches!(
countdown.command,
Some(Command::Countdown {
duration,
note: Some(note),
detach: true,
}) if duration == "5m" && note == "break"
));
let completions = Cli::try_parse_from(["timer", "completions", "fish"]).unwrap();
assert!(matches!(
completions.command,
Some(Command::Completions { shell: Shell::Fish })
));
}
#[test]
fn completion_help_explains_how_to_enable_it() {
let error = Cli::try_parse_from(["timer", "completions", "--help"]).unwrap_err();
let help = error.to_string();
assert!(help.contains("timer completions fish | source"));
assert!(help.contains("timer completions fish > ~/.config/fish/completions/timer.fish"));
assert!(help.contains("source <(timer completions bash)"));
assert!(help.contains("source <(timer completions zsh)"));
}
}