mod config;
mod track;
use std::ops::RangeInclusive;
use std::process;
use chrono::{Duration, Utc};
use clap::{Parser, Subcommand};
use config::{
DisplayConfig, DEFAULT_BREAK_MSG, DEFAULT_COMPLETE_BLOCK, DEFAULT_DELIMITER,
DEFAULT_FINISHED_MSG, DEFAULT_LEFT_PAD, DEFAULT_NUM_BLOCKS, DEFAULT_PENDING_BLOCK,
DEFAULT_RIGHT_PAD,
};
use dirs::data_dir;
use std::fs;
use std::path::PathBuf;
use track::{show_progress, start_tracking, stop_tracking, take_break};
const DATA_DIR: &str = "tomo";
const DATA_FILE: &str = ".tomo";
const ELAPSED_MINS_UPPER_LIMIT: u8 = 20;
const NUM_BLOCKS_RANGE: RangeInclusive<u8> = 3..=100;
#[derive(Parser, Debug)]
#[command(about, long_about=None)]
struct Args {
#[command(subcommand)]
action: Option<Action>,
#[arg(short = 'p', long = "pending-block", value_name = "STRING")]
#[clap(default_value = DEFAULT_PENDING_BLOCK)]
pending_block: String,
#[arg(short = 'c', long = "complete-block", value_name = "STRING")]
#[clap(default_value = DEFAULT_COMPLETE_BLOCK)]
complete_block: String,
#[arg(short = 'l', long = "left-pad", value_name = "STRING")]
#[clap(default_value = DEFAULT_LEFT_PAD)]
left_pad: String,
#[arg(short = 'r', long = "right-pad", value_name = "STRING")]
#[clap(default_value = DEFAULT_RIGHT_PAD)]
right_pad: String,
#[arg(short = 'd', long = "delimiter", value_name = "STRING")]
#[clap(default_value = DEFAULT_DELIMITER)]
delimiter: String,
#[arg(short = 'n', long = "num-blocks", value_name = "NUM")]
#[clap(default_value_t = DEFAULT_NUM_BLOCKS)]
num_blocks: u8,
#[arg(long = "finished-msg", value_name = "STRING")]
#[clap(default_value = DEFAULT_FINISHED_MSG)]
finished_msg: String,
#[arg(long = "break-msg", value_name = "STRING")]
#[clap(default_value = DEFAULT_BREAK_MSG)]
break_msg: String,
}
#[derive(Subcommand, Debug, Clone)]
enum Action {
Start {
#[arg(short = 'e', long = "elapsed-mins", value_name = "NUM")]
#[clap(default_value = "0")]
elapsed_mins: u8,
},
Stop,
Break,
}
fn main() {
let args = Args::parse();
if !(NUM_BLOCKS_RANGE).contains(&args.num_blocks) {
eprintln!("Error: number of blocks needs to be between 3 and 100");
process::exit(1);
}
let user_data_dir = data_dir().unwrap_or(PathBuf::from("."));
let data_dir = user_data_dir.join(PathBuf::from(DATA_DIR));
if !data_dir.exists() {
fs::create_dir_all(&data_dir).unwrap_or_else(|e| {
eprintln!("Error: could not create data directory {:?}", e);
process::exit(1);
});
}
let data_file_path = data_dir.join(PathBuf::from(DATA_FILE));
let now = Utc::now();
let result = match args.action {
None => {
let config = DisplayConfig {
pending_block: args.pending_block,
complete_block: args.complete_block,
left_pad: args.left_pad,
right_pad: args.right_pad,
delimiter: args.delimiter,
num_blocks: args.num_blocks,
finished_msg: args.finished_msg,
break_msg: args.break_msg,
};
show_progress(&data_file_path, now, &config)
}
Some(Action::Start { elapsed_mins }) => {
if elapsed_mins > ELAPSED_MINS_UPPER_LIMIT {
eprintln!(
"Error: elapsed mins cannot be greater than {}",
ELAPSED_MINS_UPPER_LIMIT
);
process::exit(1);
}
start_tracking(
&data_file_path,
now - Duration::minutes(elapsed_mins as i64),
)
}
Some(Action::Stop) => stop_tracking(&data_file_path),
Some(Action::Break) => take_break(&data_file_path),
};
if let Err(e) = result {
eprintln!("Error: {:?}", e);
process::exit(1);
}
}