make it work more oneshot like

This commit is contained in:
Ryan Heywood 2024-11-21 11:43:13 -05:00
parent 93c2a806f1
commit 75482004ec
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
1 changed files with 65 additions and 60 deletions

View File

@ -1,10 +1,10 @@
use std::path::PathBuf;
use std::io::Write; use std::io::Write;
use std::time::{SystemTime, Duration}; use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use chrono::{DateTime, offset::Utc}; use chrono::{offset::Utc, DateTime};
use serde::{Deserialize, Serialize};
use clap::Parser; use clap::Parser;
use serde::{Deserialize, Serialize};
const SECONDS_IN_MINUTE: u64 = 60; const SECONDS_IN_MINUTE: u64 = 60;
@ -68,7 +68,7 @@ enum PunchIn {
/// The file to store time-tracking information. /// The file to store time-tracking information.
file: PathBuf, file: PathBuf,
} },
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -82,7 +82,10 @@ fn main() {
let args = PunchIn::parse(); let args = PunchIn::parse();
match args { match args {
PunchIn::Start { file: path, round_up_to } => { PunchIn::Start {
file: path,
round_up_to,
} => {
let tt = if std::fs::exists(&path).is_ok_and(|v| v) { let tt = if std::fs::exists(&path).is_ok_and(|v| v) {
let file = std::fs::File::open(&path).unwrap(); let file = std::fs::File::open(&path).unwrap();
let mut tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap(); let mut tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
@ -106,36 +109,49 @@ fn main() {
}; };
println!("Writing to file: {tt:?}"); println!("Writing to file: {tt:?}");
std::fs::write(&path, serde_json::to_string(&tt).unwrap()).unwrap(); std::fs::write(&path, serde_json::to_string(&tt).unwrap()).unwrap();
}, }
PunchIn::Report { format: ReportFormat::Markdown, timescale, file: path } => { PunchIn::Report {
format: ReportFormat::Markdown,
timescale,
file: path,
} => {
let header = timescale.header(); let header = timescale.header();
println!("| Date | {header} | Project |"); println!("| Date | {header} | Project |");
println!("|------------|-{}-|---------|", String::from("-").repeat(header.len())); println!(
"|------------|-{}-|---------|",
String::from("-").repeat(header.len())
);
let file = std::fs::File::open(&path).unwrap(); let file = std::fs::File::open(&path).unwrap();
let tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap(); let tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
for entry in tt { for entry in tt {
let date = DateTime::<Utc>::from(entry.start); let date = DateTime::<Utc>::from(entry.start);
println!("| {date} | {duration:>4}{timescale} | {project} |", println!(
date = date.date_naive(), "| {date} | {duration:>4}{timescale} | {project} |",
duration = entry.end.as_secs() as f64 / timescale.divisor(), date = date.date_naive(),
project = entry.project, duration = entry.end.as_secs() as f64 / timescale.divisor(),
); project = entry.project,
);
} }
}, }
PunchIn::Report { format: ReportFormat::CSV, timescale, file: path } => { PunchIn::Report {
format: ReportFormat::CSV,
timescale,
file: path,
} => {
let header = timescale.header().to_lowercase(); let header = timescale.header().to_lowercase();
println!("date,{header},project"); println!("date,{header},project");
let file = std::fs::File::open(&path).unwrap(); let file = std::fs::File::open(&path).unwrap();
let tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap(); let tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
for entry in tt { for entry in tt {
let date = DateTime::<Utc>::from(entry.start); let date = DateTime::<Utc>::from(entry.start);
println!("{date},{duration}{timescale},{project}", println!(
date = date.date_naive(), "{date},{duration}{timescale},{project}",
duration = entry.end.as_secs() as f64 / timescale.divisor(), date = date.date_naive(),
project = entry.project, duration = entry.end.as_secs() as f64 / timescale.divisor(),
); project = entry.project,
);
} }
}, }
} }
} }
@ -144,42 +160,31 @@ fn start(tt: &mut Vec<TimeTracking>, round_up_to: u64) {
let time_modulus = round_up_to * SECONDS_IN_MINUTE; let time_modulus = round_up_to * SECONDS_IN_MINUTE;
let mut lines = std::io::stdin().lines(); let mut lines = std::io::stdin().lines();
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut last_time = SystemTime::now();
let mut last_project = String::new(); print!("Project: ");
loop { stdout.lock().flush().unwrap();
println!(); let project = lines
println!("Enter the project you're working on, or send EOL to terminate. When starting"); .next()
println!("work on a new project, or when sending EOL, your time will be locked in for"); .transpose()
println!("the current project"); .ok()
print!("Project: "); .flatten()
stdout.lock().flush().unwrap(); .filter(|l| !l.is_empty())
let mut has_line = false; .unwrap();
let line = lines.next(); let start_time = SystemTime::now();
// invariant: because line is never empty, last_project is only empty on first run
if !last_project.is_empty() { print!("Press Enter to save the current project and elapsed time.");
// append the last project to the tt list stdout.lock().flush().unwrap();
let elapsed_secs = last_time.elapsed().unwrap().as_secs(); lines.next();
// TODO: Rounding error: If this somehow gets to _exactly_ a round_up_to boundary, to
// the second, it will round up. Is there a better math way to do this? // append the project to the tt list
let rounded_secs = elapsed_secs + time_modulus - let elapsed_secs = start_time.elapsed().unwrap().as_secs();
((elapsed_secs + time_modulus) % time_modulus); // TODO: Rounding error: If this somehow gets to _exactly_ a round_up_to boundary, to
tt.push(TimeTracking { // the second, it will round up. Is there a better math way to do this?
start: last_time, let rounded_secs = elapsed_secs + time_modulus - ((elapsed_secs + time_modulus) % time_modulus);
// Assume the human is not faster than the machine's ability to go back in time. tt.push(TimeTracking {
end: Duration::from_secs(rounded_secs), start: start_time,
project: last_project.clone(), // Assume the human is not faster than the machine's ability to go back in time.
}); end: Duration::from_secs(rounded_secs),
} project: project.clone(),
if let Some(Ok(line)) = line { });
if !line.is_empty() {
has_line = true;
println!("Starting tracking for: {line}");
last_project = line;
last_time = SystemTime::now();
}
}
if !has_line {
break;
}
}
} }