diff --git a/Cargo.lock b/Cargo.lock index 8a761a6..21ddeca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,78 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "bumpalo" version = "3.13.0" @@ -56,12 +122,101 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -85,6 +240,17 @@ dependencies = [ "cc", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.2", + "rustix", + "windows-sys", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -100,6 +266,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "log" version = "0.4.19" @@ -139,6 +311,25 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustix" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.23" @@ -165,7 +356,9 @@ dependencies = [ name = "time-track" version = "0.1.0" dependencies = [ + "atty", "chrono", + "clap", ] [[package]] @@ -174,6 +367,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -265,6 +464,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.48.1" diff --git a/Cargo.toml b/Cargo.toml index d0ed88f..470f9a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +atty = "0.2.14" chrono = "0.4.26" +clap = { version = "4.3.11", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index 3ed32dd..b09ae87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,17 @@ -use std::io::{stdin, Read}; -use chrono::{NaiveTime, Duration}; +use std::io::{stdin, BufRead}; -fn read_times() -> Vec { - println!("Write all times separated by newlines and finish with CTRL+D\n"); - let mut times = String::new(); - stdin().read_to_string(&mut times).expect("An error occurred reading a line"); - times.split("\n") - .map(|s| { s.trim().to_string() }) - .collect() +use chrono::{NaiveTime, Duration, Utc}; +use clap::Parser; +use atty::Stream; + +/// A simple program to track time spans +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// When passed the script will collect time spans live. Close a span by pressing ENTER and + /// finish the calulation by passing in an EOF character + #[arg(short, long, default_value_t = false)] + live: bool, } fn parse_time(time_str: &str) -> NaiveTime { @@ -15,18 +19,73 @@ fn parse_time(time_str: &str) -> NaiveTime { .expect(&format!("Failed to parse time from {time_str}")) } -fn main() { - let times = read_times(); - let mut durations = vec![]; - for i in (0..times.len() - 1).step_by(2) { - let first = parse_time(times.get(i).unwrap()); - let mut second = parse_time(times.get(i + 1).unwrap()); - if second < first { - second = second + Duration::hours(12); - } - durations.push(second - first); +fn all_at_once(is_terminal: bool) -> Vec { + if is_terminal { + println!("Enter simple times one per line. Send an EOF character to sum all time spans"); } + let mut durations = vec![]; + let mut seen: Option = None; + for line in stdin().lock().lines() { + let cleaned = line.expect("failed to read a line").trim().to_string(); + if let Some(previous) = &seen { + let first = parse_time(previous); + let mut second = parse_time(&cleaned); + if second < first { + second = second + Duration::hours(12); + } + durations.push(second - first); + seen = None; + } else { + seen = Some(cleaned.to_string()); + } + } + if let Some(unpaired) = seen { + println!("Ended with an open span from {unpaired}... Ignoring"); + } + return durations; +} + +fn live_spans() -> Vec { + println!("Tracking Spans Live. Press ENTER to start a span\n"); + let mut durations = vec![]; + let mut seen: Option = None; + for _line in stdin().lock().lines() { + let mut now = Utc::now().naive_local().time(); + if let Some(previous) = &seen { + if now < *previous { + now = now + Duration::hours(12); + } + println!("Closing span from {previous} - {now}"); + println!("Status: Away"); + durations.push(now - *previous); + seen = None; + } else { + println!("Starting span at {now}"); + println!("Status: Working"); + seen = Some(now); + } + } + if let Some(unpaired) = seen { + println!("Ended with an open span from {unpaired}... Ignoring"); + } + return durations; +} + +fn main() { + let args = Args::parse(); + let is_terminal = atty::is(Stream::Stdin); + + if args.live && !is_terminal { + panic!("Cannot run live mode on piped input"); + } + + let durations = if args.live { + live_spans() + } else { + all_at_once(is_terminal) + }; + let total_minutes: i64 = durations.iter().map(|d| { d.num_minutes() }).sum(); let minutes = total_minutes % 60; let hours = total_minutes / 60;