diff --git a/src/main.rs b/src/main.rs index 30455b7..d07eb86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,16 +4,17 @@ use chrono::{NaiveTime, Duration, Local}; use clap::Parser; use atty::Stream; +mod modes; +use modes::Modes; + /// A simple program to track time spans #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - /// When passed the script will collect time spans live. Close a span by pressing ENTER and - /// finish the calulation by passing in an EOF character - #[arg(short, long, default_value_t = false)] - live: bool, + /// Specify how the program should behave + #[clap(value_enum, default_value_t = Modes::default())] + mode: Modes, } - fn parse_time(time_str: &str) -> NaiveTime { NaiveTime::parse_from_str(time_str, "%H:%M") .expect(&format!("Failed to parse time from {time_str}")) @@ -75,18 +76,37 @@ fn live_spans() -> Vec { return durations; } +fn get_prediction() -> NaiveTime { + println!("Calculating an end time prediction...\nWhen did you start?\n"); + let mut start_time = String::new(); + let _ = stdin().read_line(&mut start_time); + let start = parse_time(start_time.trim()); + println!("Started at {0}", start.format("%H:%M")); + println!("How many minutes were you on break?\n"); + let mut break_time = String::new(); + let _ = stdin().read_line(&mut break_time); + let break_duration = break_time.trim().parse::().unwrap(); + let work_time = Duration::hours(8) + Duration::minutes(break_duration); + return start + work_time; +} + fn main() { let args = Args::parse(); let is_terminal = atty::is(Stream::Stdin); - if args.live && !is_terminal { - panic!("Cannot run live mode on piped input"); + if !is_terminal && !args.mode.supports_piped_input() { + panic!("Cannot run {0} mode on piped input", args.mode); } - let durations = if args.live { - live_spans() - } else { - all_at_once(is_terminal) + if args.mode == Modes::Prediction { + println!("Your work will end at {}", get_prediction()); + return; + } + + let durations = match args.mode { + Modes::TimeTable => all_at_once(is_terminal), + Modes::Live => live_spans(), + Modes::Prediction => panic!("Should have already returned before this"), }; let total_minutes: i64 = durations.iter().map(|d| { d.num_minutes() }).sum(); diff --git a/src/modes.rs b/src/modes.rs new file mode 100644 index 0000000..17bb3a2 --- /dev/null +++ b/src/modes.rs @@ -0,0 +1,40 @@ +use clap::ValueEnum; + +#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq)] +pub enum Modes { + /// Perform calculations by treating stdin as a stream of time pairs separated by newlines + TimeTable, + /// Perform live tracking of the user's time until an EOF character is received. + Live, + /// Predict a user's day end time (assuming a 9 hour work day) based on the user's start time + /// and the duration of a user's break. + Prediction, +} + +impl Modes { + /// Determine whether or not the variant supports piped input vs being used as a CLI tool + /// directly by a human + pub fn supports_piped_input(&self) -> bool { + match self { + Modes::TimeTable => true, + Modes::Live => false, + Modes::Prediction => false, + } + } +} + + +impl Default for Modes { + fn default() -> Self { + Self::TimeTable + } +} + +impl std::fmt::Display for Modes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } +}