make it work more oneshot like
This commit is contained in:
parent
93c2a806f1
commit
75482004ec
125
src/main.rs
125
src/main.rs
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue