use anyhow::{Context, Result};
use chrono::TimeDelta;
use clap::{Parser, Subcommand};
use regex::Regex;
use std::path::PathBuf;
const LONG_ABOUT: &str = r#"
tomate sets timers and keeps a log of completed tasks for the Pomodoro productivity system.
There are three types of timers in tomate: pomodoros, short breaks, and long breaks.
Each timer has its own configured duration, with these defaults:
- pomodoros are 25 minutes long
- short breaks are 5 minutes long
- long breaks are 30 minutes long
You can change these settings by editing ~/.config/tomate/config.toml
"#;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = LONG_ABOUT)]
pub struct Args {
#[command(subcommand)]
pub command: Command,
#[arg(short, long)]
pub config: Option<PathBuf>,
#[command(flatten)]
pub verbose: clap_verbosity_flag::Verbosity,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Status {
#[arg(short, long)]
format: Option<String>,
},
#[command(visible_alias("pom"))]
Pomodoro {
#[command(subcommand)]
command: PomodoroCommand,
},
#[command(visible_alias("short"))]
ShortBreak {
#[command(subcommand)]
command: ShortBreakCommand,
},
#[command(visible_alias("long"))]
LongBreak {
#[command(subcommand)]
command: LongBreakCommand,
},
Timer {
#[command(subcommand)]
command: TimerCommand,
},
History {
#[arg(long, default_value_t = false)]
json: bool,
},
Purge,
}
#[derive(Debug, Subcommand)]
pub enum PomodoroCommand {
Show {
#[arg(short, long)]
format: Option<String>,
},
Start {
#[arg(short, long, value_parser = duration_from_human)]
duration: Option<TimeDelta>,
description: Option<String>,
#[arg(short, long)]
tag: Vec<String>,
},
Stop,
}
#[derive(Debug, Subcommand)]
pub enum ShortBreakCommand {
Start {
#[arg(short, long, value_parser = duration_from_human)]
duration: Option<TimeDelta>,
},
Stop,
}
#[derive(Debug, Subcommand)]
pub enum LongBreakCommand {
Start {
#[arg(short, long, value_parser = duration_from_human)]
duration: Option<TimeDelta>,
},
Stop,
}
#[derive(Debug, Subcommand)]
pub enum TimerCommand {
Check,
}
fn duration_from_human(input: &str) -> Result<TimeDelta> {
let re = Regex::new(r"^(?:([0-9])h)?(?:([0-9]+)m)?(?:([0-9]+)s)?$").unwrap();
let caps = re.captures(input)
.with_context(|| "Failed to parse duration string, format is <HOURS>h<MINUTES>m<SECONDS>s (each section is optional) example: 22m30s")?;
let hours: i64 = caps.get(1).map_or("0", |c| c.as_str()).parse()?;
let minutes: i64 = caps.get(2).map_or("0", |c| c.as_str()).parse()?;
let seconds: i64 = caps.get(3).map_or("0", |c| c.as_str()).parse()?;
let total_seconds = (hours * 3600) + (minutes * 60) + seconds;
Ok(TimeDelta::new(total_seconds, 0).expect("Expected duration to be nonzero."))
}