make it work more oneshot like
This commit is contained in:
parent
93c2a806f1
commit
75482004ec
93
src/main.rs
93
src/main.rs
|
@ -1,10 +1,10 @@
|
|||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use std::time::{SystemTime, Duration};
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use chrono::{DateTime, offset::Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const SECONDS_IN_MINUTE: u64 = 60;
|
||||
|
||||
|
@ -68,7 +68,7 @@ enum PunchIn {
|
|||
|
||||
/// The file to store time-tracking information.
|
||||
file: PathBuf,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
@ -82,7 +82,10 @@ fn main() {
|
|||
let args = PunchIn::parse();
|
||||
|
||||
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 file = std::fs::File::open(&path).unwrap();
|
||||
let mut tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
|
||||
|
@ -106,36 +109,49 @@ fn main() {
|
|||
};
|
||||
println!("Writing to file: {tt:?}");
|
||||
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();
|
||||
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 tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
|
||||
for entry in tt {
|
||||
let date = DateTime::<Utc>::from(entry.start);
|
||||
println!("| {date} | {duration:>4}{timescale} | {project} |",
|
||||
println!(
|
||||
"| {date} | {duration:>4}{timescale} | {project} |",
|
||||
date = date.date_naive(),
|
||||
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();
|
||||
println!("date,{header},project");
|
||||
let file = std::fs::File::open(&path).unwrap();
|
||||
let tt: Vec<TimeTracking> = serde_json::from_reader(&file).unwrap();
|
||||
for entry in tt {
|
||||
let date = DateTime::<Utc>::from(entry.start);
|
||||
println!("{date},{duration}{timescale},{project}",
|
||||
println!(
|
||||
"{date},{duration}{timescale},{project}",
|
||||
date = date.date_naive(),
|
||||
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 mut lines = std::io::stdin().lines();
|
||||
let stdout = std::io::stdout();
|
||||
let mut last_time = SystemTime::now();
|
||||
let mut last_project = String::new();
|
||||
loop {
|
||||
println!();
|
||||
println!("Enter the project you're working on, or send EOL to terminate. When starting");
|
||||
println!("work on a new project, or when sending EOL, your time will be locked in for");
|
||||
println!("the current project");
|
||||
|
||||
print!("Project: ");
|
||||
stdout.lock().flush().unwrap();
|
||||
let mut has_line = false;
|
||||
let line = lines.next();
|
||||
// invariant: because line is never empty, last_project is only empty on first run
|
||||
if !last_project.is_empty() {
|
||||
// append the last project to the tt list
|
||||
let elapsed_secs = last_time.elapsed().unwrap().as_secs();
|
||||
let project = lines
|
||||
.next()
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.filter(|l| !l.is_empty())
|
||||
.unwrap();
|
||||
let start_time = SystemTime::now();
|
||||
|
||||
print!("Press Enter to save the current project and elapsed time.");
|
||||
stdout.lock().flush().unwrap();
|
||||
lines.next();
|
||||
|
||||
// append the project to the tt list
|
||||
let elapsed_secs = start_time.elapsed().unwrap().as_secs();
|
||||
// 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?
|
||||
let rounded_secs = elapsed_secs + time_modulus -
|
||||
((elapsed_secs + time_modulus) % time_modulus);
|
||||
let rounded_secs = elapsed_secs + time_modulus - ((elapsed_secs + time_modulus) % time_modulus);
|
||||
tt.push(TimeTracking {
|
||||
start: last_time,
|
||||
start: start_time,
|
||||
// Assume the human is not faster than the machine's ability to go back in time.
|
||||
end: Duration::from_secs(rounded_secs),
|
||||
project: last_project.clone(),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue