From d52eebbd248fe00264ec9cbb5b8e676d55ca8db8 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Tue, 25 Mar 2025 22:07:06 +0100 Subject: [PATCH] feat: make it somewhat pretty --- Cargo.lock | 232 ++++++++++++++++++++++++++++++++++++--- crates/clock/Cargo.toml | 1 + crates/clock/src/main.rs | 105 ++++++++++++++++-- 3 files changed, 313 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cae5ce..275bca3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,15 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -121,6 +127,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -206,6 +218,7 @@ dependencies = [ "clap", "dirs", "dotenvy", + "inquire", "serde", "serde_json", "tokio", @@ -226,6 +239,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "dirs" version = "6.0.0" @@ -253,6 +291,30 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -312,6 +374,24 @@ dependencies = [ "cc", ] +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.9.0", + "chrono", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -352,7 +432,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", ] @@ -387,6 +467,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -398,6 +490,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -464,7 +565,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -503,7 +604,7 @@ version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -588,6 +689,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -669,7 +791,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -753,6 +875,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "utf8parse" version = "0.2.2" @@ -875,7 +1009,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -884,13 +1018,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -899,7 +1042,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -908,28 +1066,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -942,24 +1118,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -972,5 +1172,5 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] diff --git a/crates/clock/Cargo.toml b/crates/clock/Cargo.toml index c826783..e9f6fbe 100644 --- a/crates/clock/Cargo.toml +++ b/crates/clock/Cargo.toml @@ -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"] } diff --git a/crates/clock/src/main.rs b/crates/clock/src/main.rs index 773dc50..f1638b1 100644 --- a/crates/clock/src/main.rs +++ b/crates/clock/src/main.rs @@ -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, }, + 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::>(); + + 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::parse_from_str(v, "%H:%M") + .or_else(|_| { + v.parse::() + .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, @@ -143,7 +230,7 @@ impl TimeTable { now: chrono::DateTime, ) -> Option<&mut Day> { let item = self.days.iter_mut().find(|d| { - if d.project == project { + if d.project != project { return false; }