feat: make it somewhat pretty
This commit is contained in:
@@ -17,3 +17,4 @@ uuid = { version = "1.7.0", features = ["v4"] }
|
||||
dirs = "6.0.0"
|
||||
serde_json = "1.0.140"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
inquire = { version = "0.7.5", features = ["chrono"] }
|
||||
|
@@ -1,5 +1,7 @@
|
||||
use chrono::Timelike;
|
||||
use anyhow::Context;
|
||||
use chrono::{Local, Timelike, Utc};
|
||||
use clap::{Parser, Subcommand};
|
||||
use inquire::validator::Validation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
@@ -31,6 +33,7 @@ enum Commands {
|
||||
#[arg(long = "project")]
|
||||
project: Option<String>,
|
||||
},
|
||||
Resolve {},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -73,20 +76,27 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
for day in days {
|
||||
println!(
|
||||
"day: {}{}\n {}:{}{}",
|
||||
day.clock_in.format("%Y/%m/%d"),
|
||||
"{}{}{}\n {}{}\n",
|
||||
day.clock_in.with_timezone(&Local {}).format("%Y-%m-%d"),
|
||||
if let Some(project) = &day.project {
|
||||
format!(" project: {}", project)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
day.clock_in.hour(),
|
||||
day.clock_in.minute(),
|
||||
if day.breaks.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(
|
||||
" breaks: {}min",
|
||||
day.breaks.iter().fold(0, |acc, _| acc + 30)
|
||||
)
|
||||
},
|
||||
day.clock_in.with_timezone(&Local {}).format("%H:%M"),
|
||||
if let Some(clockout) = &day.clock_out {
|
||||
format!(" - {}:{}", clockout.hour(), clockout.minute())
|
||||
format!(" - {}", clockout.with_timezone(&Local {}).format("%H:%M"))
|
||||
} else {
|
||||
" - unclosed".into()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -106,18 +116,95 @@ async fn main() -> anyhow::Result<()> {
|
||||
Some(day) => day.breaks.push(Break {}),
|
||||
None => todo!(),
|
||||
},
|
||||
Commands::Resolve {} => {
|
||||
let to_resolve = timetable
|
||||
.days
|
||||
.iter_mut()
|
||||
.filter(|d| d.clock_out.is_none())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if to_resolve.is_empty() {
|
||||
println!("Nothing to resolve, good job... :)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for day in to_resolve {
|
||||
let local = day.clock_in.with_timezone(&Local {});
|
||||
let clock_in = local.time();
|
||||
println!(
|
||||
"Resolve day: {}{}\n clocked in: {}",
|
||||
day.clock_in.format("%Y/%m/%d"),
|
||||
if let Some(project) = &day.project {
|
||||
format!("\n project: {}", project)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
day.clock_in.format("%H:%M")
|
||||
);
|
||||
|
||||
let output = inquire::Text::new("When did you clock out (16 or 16:30)")
|
||||
.with_validator(move |v: &str| match parse_string_to_time(v) {
|
||||
Ok(time) => {
|
||||
if time <= clock_in {
|
||||
return Ok(Validation::Invalid(
|
||||
inquire::validator::ErrorMessage::Custom(
|
||||
"clock out has to be after clockin".into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Validation::Valid)
|
||||
}
|
||||
Err(e) => Ok(Validation::Invalid(
|
||||
inquire::validator::ErrorMessage::Custom(e.to_string()),
|
||||
)),
|
||||
})
|
||||
.prompt()?;
|
||||
|
||||
let time = parse_string_to_time(&output)?;
|
||||
day.clock_out = Some(
|
||||
local
|
||||
.with_hour(time.hour())
|
||||
.expect("to be able to set hour")
|
||||
.with_minute(time.minute())
|
||||
.expect("to be able to set minute")
|
||||
.with_timezone(&Utc {}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parent) = dir.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
let mut file = tokio::fs::File::create(dir).await?;
|
||||
file.write_all(&serde_json::to_vec(&timetable)?).await?;
|
||||
file.write_all(&serde_json::to_vec_pretty(&timetable)?)
|
||||
.await?;
|
||||
file.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_string_to_time(v: &str) -> anyhow::Result<chrono::NaiveTime> {
|
||||
chrono::NaiveTime::parse_from_str(v, "%H:%M")
|
||||
.or_else(|_| {
|
||||
v.parse::<u32>()
|
||||
.context("failed to parse to hour")
|
||||
.and_then(|h| {
|
||||
if (0..=23).contains(&h) {
|
||||
Ok(h)
|
||||
} else {
|
||||
anyhow::bail!("hours have to be within 0 and 23")
|
||||
}
|
||||
})
|
||||
.map(|h| chrono::NaiveTime::from_hms_opt(h, 0, 0))
|
||||
.ok()
|
||||
.flatten()
|
||||
.context("failed to parse value")
|
||||
})
|
||||
.context("failed to parse int to hour")
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct Day {
|
||||
clock_in: chrono::DateTime<chrono::Utc>,
|
||||
@@ -143,7 +230,7 @@ impl TimeTable {
|
||||
now: chrono::DateTime<chrono::Utc>,
|
||||
) -> Option<&mut Day> {
|
||||
let item = self.days.iter_mut().find(|d| {
|
||||
if d.project == project {
|
||||
if d.project != project {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user