diff --git a/.envrc b/.envrc deleted file mode 100644 index 82b2b9e..0000000 --- a/.envrc +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# the shebang is ignored, but nice for editors - -if type -P lorri &>/dev/null; then - eval "$(lorri direnv)" -else - echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]' - use nix -fi diff --git a/Cargo.lock b/Cargo.lock index e66d302..8a761a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -26,73 +17,12 @@ 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 = "anyhow" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" - [[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" @@ -126,92 +56,12 @@ 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.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -235,17 +85,6 @@ 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", - "rustix", - "windows-sys", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -261,24 +100,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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - [[package]] name = "num-traits" version = "0.2.15" @@ -312,54 +139,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[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" @@ -384,12 +163,9 @@ dependencies = [ [[package]] name = "time-track" -version = "2.1.4" +version = "0.1.0" dependencies = [ - "anyhow", "chrono", - "clap", - "regex", ] [[package]] @@ -398,12 +174,6 @@ 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" @@ -495,15 +265,6 @@ 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 74de412..d0ed88f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,9 @@ [package] name = "time-track" -version = "2.1.4" +version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.72" chrono = "0.4.26" -clap = { version = "4.3.11", features = ["derive"] } -regex = "1.11.1" diff --git a/README.md b/README.md deleted file mode 100644 index bbcf4bc..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# time-track - -A simple CLI tool for tracking how much time you have left to work. I find myself stressing about whether I'm hitting 8 real hours, so this little tool helps me avoid wasting -time calculating when my work day will end. - -Simply enter times, one per line and send an EOF character when you're done. The first lines opens a span of work and the next line closes it so that you can build up working -time be clocking in and out. Finally, you can send additional arguments to the program to configure how long you intend to work (the default is 8 hours). - -``` -❯ time-track -Working for 8 hours -Input times one per line. Send an EOF character to finish inputting... -8:30 -9:30 -11:15 -12:30 -13:16 -18:20 -19:30 -20:11 # Send an EOF -Exactly done -``` diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 157b677..0000000 --- a/shell.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.mkShell { - buildInputs = [ - pkgs.rustc - pkgs.cargo - ] ++ [ pkgs.libiconv pkgs.darwin.apple_sdk.frameworks.CoreServices ]; -} diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index e24e8c3..0000000 --- a/src/args.rs +++ /dev/null @@ -1,19 +0,0 @@ -use clap::Parser; - -/// A simple program to track time spans and calculate remaining hours to work -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub struct Args { - /// How many hours you intend to work (sums with `minutes`) - /// Usually defaults to 8 hours, but if discount is set then it defaults to 0 - #[arg(long)] - pub hours: Option, - - /// How many minutes you intend to work (sums with `hours`) - #[arg(long, default_value_t = 0)] - pub minutes: i64, - - /// If true, the the hours and minutes fields are treated as subtracting from 8 hours - #[arg(long, default_value_t = false)] - pub discount: bool, -} diff --git a/src/main.rs b/src/main.rs index 19dc159..3ed32dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,65 +1,35 @@ -use std::io::{self, BufRead}; -use chrono::{DateTime, Local}; +use std::io::{stdin, Read}; +use chrono::{NaiveTime, Duration}; -use clap::Parser; -use anyhow::{anyhow, Result}; -mod args; -mod time; - -use args::Args; - -fn main() -> Result<()> { - let args = Args::parse(); - let stdin = io::stdin(); - let hours = args.hours.unwrap_or( - if args.discount { - 0 - } else { - 8 - } - ); - - let target_minutes = if args.discount { - (8 * 60) - ((hours * 60) + args.minutes) - } else { - hours * 60 + args.minutes - }; - let (hrs, mins) = time::to_hrs_minutes(target_minutes); - println!("Working for {}", time::show_time(hrs, mins)); - println!("Input times one per line. Send an EOF character to finish inputting..."); - let mut lines: Vec = vec![]; - for line in stdin.lock().lines() { - lines.push(line.expect("Issues when reading from stdin")); - } - - // This is immeidately going to be turned back into a DateTime, which the doc says is fine - #[allow(deprecated)] - let midnight = Local::now().date().and_hms_opt(0, 0, 0).ok_or(anyhow!("Expected midnight to exist"))?; - let times = time::from_stream(&midnight, lines.iter())?; - - let mut total_minutes: i64 = 0; - let mut first: Option> = None; - let mut last: Option> = None; - for time in times { - match first { - None => { - first = Some(time); - }, - Some(prev) => { - total_minutes += (time - prev).num_minutes(); - first = None; - last = Some(time); - } - } - } - - if let Some(remaining) = first { - let now = Local::now(); - let now_str = now.format("%-I:%M %p"); - println!("Ended with unclosed span... assuming ending now: {}", now_str); - total_minutes += (now - remaining).num_minutes(); - } - - println!("{}", time::get_charaterized_time_remaining(total_minutes, target_minutes, last.unwrap_or_else(|| Local::now()))); - Ok(()) +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() +} + +fn parse_time(time_str: &str) -> NaiveTime { + NaiveTime::parse_from_str(time_str, "%H:%M") + .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); + } + + let total_minutes: i64 = durations.iter().map(|d| { d.num_minutes() }).sum(); + let minutes = total_minutes % 60; + let hours = total_minutes / 60; + + println!("You have been working for {hours} hour(s) and {minutes} minute(s)"); } diff --git a/src/time.rs b/src/time.rs deleted file mode 100644 index 2b31d50..0000000 --- a/src/time.rs +++ /dev/null @@ -1,89 +0,0 @@ -use chrono::{Duration, Local, DateTime}; -use anyhow::{Result, anyhow}; -use std::iter::Iterator; -use regex::Regex; - -fn parse_datetime(reference_date: &DateTime, s: &str) -> Result> { - let re = Regex::new(r"^\s*([12]?\d):([012345]\d)\s*$")?; - let (_, [hrs, mins]) = re.captures(s).ok_or(anyhow!(format!("Failed to parse \"{s}\" as a time")))?.extract(); - let mins: u32 = mins.parse()?; - let mut hrs: u32 = hrs.parse()?; - let mut date = *reference_date; - - if hrs > 23 { - let num_days = hrs / 24; - date = *reference_date + Duration::days(num_days.into()); - hrs = hrs % 24; - } - #[allow(deprecated)] - Ok(date.date().and_hms_opt(hrs, mins, 0).ok_or(anyhow!(format!("{}:{:0>2} not a real time", hrs, mins)))?) -} - -pub fn from_stream<'a>(reference_date: &DateTime, stream: impl Iterator) -> Result>> { - let mut durations: Vec> = vec![]; - for val in stream { - durations.push(parse_datetime(reference_date, val)?); - } - return Ok(durations); -} - -pub fn show_time(hours: i64, minutes: i64) -> String { - let pluralized_hours = match hours { - 1 => "1 hour".to_string(), - _ => format!("{hours} hours"), - }; - let pluralized_minutes = match minutes { - 1 => "1 minute".to_string(), - _ => format!("{minutes} minutes"), - }; - - if hours == 0 { - return pluralized_minutes.to_string(); - } - - if minutes == 0 { - return pluralized_hours.to_string(); - } - - return format!("{pluralized_hours} and {pluralized_minutes}"); -} - -pub fn to_hrs_minutes(total_minutes: i64) -> (i64, i64) { - let minutes = total_minutes % 60; - let hours = total_minutes / 60; - (hours, minutes) -} - -pub fn get_charaterized_time_remaining( - total_minutes: i64, - target_minutes: i64, - ended_at: DateTime, -) -> String { - if total_minutes == target_minutes { - return "Exactly done".to_string(); - } - - if total_minutes > target_minutes { - let diff = total_minutes - target_minutes; - let (hours, minutes) = to_hrs_minutes(diff); - return format!("You have overworked {}", show_time(hours, minutes)) - } else { - let diff = target_minutes - total_minutes; - let (hours, minutes) = to_hrs_minutes(diff); - let now = Local::now(); - return if ended_at > now { - let end_at = (ended_at + Duration::minutes(diff)).time(); - let end_str = end_at.format("%-I:%M %p"); - format!( - "You have {} remaining (end at {} starting from {})", - show_time(hours, minutes), - end_str, - ended_at.format("%-I:%M %p"), - ) - } else { - let end_at = (now + Duration::minutes(diff)).time(); - let end_str = end_at.format("%-I:%M %p"); - format!("You have {} remaining (end at {} starting now)", show_time(hours, minutes), end_str) - } - } -}