From 7f4ab49c915c21e88318a2d0388b7614aa1196b6 Mon Sep 17 00:00:00 2001 From: Trey Blancher Date: Fri, 28 Oct 2022 10:46:51 -0400 Subject: [PATCH] Refactored for timetracking workspace --- rust/chug/src/main.rs | 161 ------- rust/timetracker/Cargo.lock | 407 ------------------ rust/timetracker/src/main.rs | 175 -------- rust/{chug => timetracking}/Cargo.lock | 33 +- rust/timetracking/Cargo.toml | 8 + rust/{ => timetracking}/chug/Cargo.toml | 1 + rust/timetracking/chug/src/main.rs | 73 ++++ rust/timetracking/do_process/Cargo.toml | 12 + rust/timetracking/do_process/src/main.rs | 51 +++ .../timelogging}/Cargo.toml | 2 +- rust/timetracking/timelogging/src/lib.rs | 133 ++++++ rust/timetracking/timetracker/Cargo.toml | 12 + rust/timetracking/timetracker/src/main.rs | 35 ++ 13 files changed, 357 insertions(+), 746 deletions(-) delete mode 100644 rust/chug/src/main.rs delete mode 100644 rust/timetracker/Cargo.lock delete mode 100644 rust/timetracker/src/main.rs rename rust/{chug => timetracking}/Cargo.lock (95%) create mode 100644 rust/timetracking/Cargo.toml rename rust/{ => timetracking}/chug/Cargo.toml (83%) create mode 100644 rust/timetracking/chug/src/main.rs create mode 100644 rust/timetracking/do_process/Cargo.toml create mode 100644 rust/timetracking/do_process/src/main.rs rename rust/{timetracker => timetracking/timelogging}/Cargo.toml (90%) create mode 100644 rust/timetracking/timelogging/src/lib.rs create mode 100644 rust/timetracking/timetracker/Cargo.toml create mode 100644 rust/timetracking/timetracker/src/main.rs diff --git a/rust/chug/src/main.rs b/rust/chug/src/main.rs deleted file mode 100644 index cd03bce..0000000 --- a/rust/chug/src/main.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::env; -use std::fs; -use std::io::{BufReader, BufRead, ErrorKind}; -use std::collections::HashMap; -use regex::Regex; -use chrono::{Datelike, Duration, offset::Local, NaiveDate, NaiveDateTime, Weekday}; -use itertools::Itertools; - -fn main() { - - // Regular Expressions - let empty = Regex::new(r"^\s*$").unwrap(); // regex for finding empty lines (and skipping them) - let comment = Regex::new(r"^\s*#").unwrap(); // regex for finding empty lines (and skipping them) - let delim = Regex::new(r":\s{2}").unwrap(); // regex for splitting timestamp entry - let begin = Regex::new(r"Begin\s+(?P.*)\s*$").unwrap(); - let end = Regex::new(r"End\s+(?P.*)\s*$").unwrap(); - let offset = Regex::new(r"^[+-]*\d+").unwrap(); - - // Process options - let params: Vec = env::args().collect(); - - // If week offset is provided, grab it - let mut week_offset: i64 = 0; - for arg in ¶ms { - if offset.is_match(&arg.to_string()) { - week_offset = arg.parse::().unwrap(); - }; - }; - - let day: NaiveDate = last_monday(week_offset); - let mut filenames: Vec = Vec::new(); - for d in 0..7 { - filenames.push((day + Duration::days(d)).format("%Y-%m-%d.log").to_string()); - }; - - assert_eq!(filenames.len(), 7); - - let mut grand_total: f64 = 0.0; - for filename in filenames { - println!("{} ", filename); - let mut gtoth: f64 = 0.0; - // Process the file - match fs::File::open(filename) { - Ok(file) => { - // Hash Maps (akin to Python Dictionaires) - let mut start: HashMap> = HashMap::new(); - let mut finish: HashMap> = HashMap::new(); - let reader = BufReader::new(file); - // For each line of input (build internal data structures) - for line in reader.lines() { - // need to get the string out of line (which is an option enum) - let l: String = line.unwrap(); - - // if the line is empty or is a comment - if empty.is_match(&l) || comment.is_match(&l) { - continue - }; - - // Parse the timestamp and entry - let splits: Vec<_> = delim.split(&l.trim_end()).into_iter().collect(); - // splits should only ever have two elements (only one delimiter per log line) - let datetime = splits[0]; - let entry = splits[1]; - let date_time = NaiveDateTime::parse_from_str(datetime,"%Y-%m-%d %H:%M:%S").unwrap(); - let timestamp = date_time.timestamp(); - - // Process a Begin - if begin.is_match(&entry) { - let caps = begin.captures(&entry).unwrap(); - let action = caps.name("action").unwrap().as_str(); - start.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); - }; - - // Process an End - if end.is_match(&entry) { - let caps = end.captures(&entry).unwrap(); - let action = caps.name("action").unwrap().as_str(); - finish.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); - }; - - }; // end for - - - for (act, tstamps) in start.iter_mut().sorted_by_key(|x| x.0) { - let bc: i64 = tstamps.len().try_into().unwrap(); - let ec: i64 = finish.get_mut(&act.to_string()) - .expect("Somehow, not a vector!") - .len() - .try_into() - .unwrap(); - - // Debugging - // println!("bc: {}, ec: {}", bc, ec); - // This block ensures the lengths of start and finish for this action are equal - if bc - ec > 1 { - panic!("ERROR: Missing more than one End"); // need to fix log file - } else if bc > ec { // bc should be exactly one greater - tstamps.pop(); - } else if ec > bc { - panic!("ERROR: Missing a Begin"); // need to fix log file - }; - - // Debugging - //for t in tstamps.into_iter() { - // print!("{} ", t); - //}; - //println!(); - //for t in finish.get_mut(&act.to_string()).expect("Somehow, not a vector!").into_iter() { - // print!("{} ", t); - //}; - //println!(); - - - let mut delta: i64 = 0; - for i in 0..tstamps.len() { - let beg = tstamps[i]; - let en = finish.get_mut(&act.to_string()).unwrap()[i]; - delta += en - beg; - }; - - let d: f64 = nearest(delta as f64 / 3600.00); - if d == 0.0 { - gtoth += 0.08; - } else { - gtoth += d; - }; - }; - }, - Err(error) => match error.kind() { - ErrorKind::NotFound => (), - _ => panic!("Error reading file! Error: {}", error) - } - }; - if gtoth > 24.0 { - gtoth = 24.0; - }; - // print the output - println!("{}", format!("{:.2}", gtoth)); - println!(); - grand_total += gtoth; - }; - println!("Grand total: {:.2}", grand_total); -} - -fn nearest(hr: f64) -> f64 { - let t = hr * 4.0; - t.round() / 4.0 -} - -fn last_monday(offset: i64) -> NaiveDate { - let day: NaiveDate = Local::today().naive_local() - Duration::days(7 * offset); - match day.weekday() { - Weekday::Mon => day - Duration::days(0), - Weekday::Tue => day - Duration::days(1), - Weekday::Wed => day - Duration::days(2), - Weekday::Thu => day - Duration::days(3), - Weekday::Fri => day - Duration::days(4), - Weekday::Sat => day - Duration::days(5), - Weekday::Sun => day - Duration::days(6) - } -} diff --git a/rust/timetracker/Cargo.lock b/rust/timetracker/Cargo.lock deleted file mode 100644 index 16bb4fb..0000000 --- a/rust/timetracker/Cargo.lock +++ /dev/null @@ -1,407 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "cxx" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "iana-time-zone" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - -[[package]] -name = "itertools" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -dependencies = [ - "either", -] - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" - -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi", -] - -[[package]] -name = "timetracker" -version = "0.1.0" -dependencies = [ - "chrono", - "itertools", - "regex", -] - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust/timetracker/src/main.rs b/rust/timetracker/src/main.rs deleted file mode 100644 index f4e4816..0000000 --- a/rust/timetracker/src/main.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::env; -use std::fs; -use std::io::{self, BufReader, BufRead}; -use std::collections::HashMap; -use regex::Regex; -use chrono::NaiveDateTime; -use itertools::Itertools; - -fn main() { - // Hash Maps (akin to Python Dictionaires) - let mut start: HashMap> = HashMap::new(); - let mut finish: HashMap> = HashMap::new(); - let mut ind: HashMap = HashMap::new(); - - // Regular Expressions - let empty = Regex::new(r"^\s*$").unwrap(); // regex for finding empty lines (and skipping them) - let comment = Regex::new(r"^\s*#").unwrap(); // regex for finding empty lines (and skipping them) - let delim = Regex::new(r":\s{2}").unwrap(); // regex for splitting timestamp entry - let begin = Regex::new(r"Begin\s+(?P.*)\s*$").unwrap(); - let end = Regex::new(r"End\s+(?P.*)\s*$").unwrap(); - let category = Regex::new(r"\[(?P.*)\]\s+(?P.*)\s*$").unwrap(); - let do_process = Regex::new(r"do_process").unwrap(); - - let mut dp: bool = false; - - let mut categories: Vec = Vec::new(); - - // Determine if we'r the do_process variant - let params: Vec = env::args().collect(); - if do_process.is_match(¶ms[0]) { - dp = true; - }; - - // Process the file (stdin or file argument) - let input = env::args().nth(1); - let reader: Box = match input { - None => Box::new(BufReader::new(io::stdin())), - Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap())) - }; - - // For each line of input (build internal data structures) - for line in reader.lines() { - // need to get the string out of line (which is an option enum) - let l: String = line.unwrap(); - - // if the line is empty or is a comment - if empty.is_match(&l) || comment.is_match(&l) { - continue - }; - - // Parse the timestamp and entry - let splits: Vec<_> = delim.split(&l.trim_end()).into_iter().collect(); - // splits should only ever have two elements (only one delimiter per log line) - let datetime = splits[0]; - let entry = splits[1]; - let date_time = NaiveDateTime::parse_from_str(datetime,"%Y-%m-%d %H:%M:%S").unwrap(); - let timestamp = date_time.timestamp(); - - // Process a Begin - if begin.is_match(&entry) { - let caps = begin.captures(&entry).unwrap(); - let action = caps.name("action").unwrap().as_str(); - start.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); - let cats = category.captures(&entry).unwrap(); - let cat = cats.name("category").unwrap().as_str(); - categories.push(cat.to_string()); - - }; - - // Process an End - if end.is_match(&entry) { - let caps = end.captures(&entry).unwrap(); - let action = caps.name("action").unwrap().as_str(); - finish.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); - let cats = category.captures(&entry).unwrap(); - let cat = cats.name("category").unwrap().as_str(); - categories.push(cat.to_string()); - }; - - }; // end for - - // sort and dedup categories - categories.sort_unstable(); - categories.dedup(); - - let mut gtoth: f64 = 0.0; - - for (act, tstamps) in start.iter_mut().sorted_by_key(|x| x.0) { - let bc: i64 = tstamps.len().try_into().unwrap(); - let ec: i64 = finish.get_mut(&act.to_string()).expect("Somehow, not a vector!").len().try_into().unwrap(); - - // Debugging - // println!("bc: {}, ec: {}", bc, ec); - // This block ensures the lengths of start and finish for this action are equal - if bc - ec > 1 { - panic!("ERROR: Missing more than one End"); // need to fix log file - } else if bc > ec { // bc should be exactly one greater - tstamps.pop(); - } else if ec > bc { - panic!("ERROR: Missing a Begin"); // need to fix log file - }; - - // Debugging - //for t in tstamps.into_iter() { - // print!("{} ", t); - //}; - //println!(); - //for t in finish.get_mut(&act.to_string()).expect("Somehow, not a vector!").into_iter() { - // print!("{} ", t); - //}; - //println!(); - - - let mut delta: i64 = 0; - for i in 0..tstamps.len() { - let beg = tstamps[i]; - let en = finish.get_mut(&act.to_string()).unwrap()[i]; - delta += en - beg; - }; - - ind.insert(act.to_string(), delta); - let d: f64 = nearest(delta as f64 / 3600.00); - if d == 0.0 { - gtoth += 0.08; - } else { - gtoth += d; - }; - - } - - // print the output - if dp { - let mut running_total: f64 = 0.0; - for cat in categories { - let mut subtotal: f64 = 0.0; - let catre = Regex::new(&format!(r"\[{}\]\s+.*\s*$", cat).to_string()).unwrap(); - for (act, duration) in ind.iter().sorted_by_key(|x| x.0) { - if ! catre.is_match(&act.to_string()) { - continue - }; - let mut f: f64 = nearest(*duration as f64/3600.00); - if f == 0.0 { - f = 0.08; - }; - let fhrs: String = format!("{:.2}hrs", f); - println!("{}", format!("{:<75}{:>10}", act.to_string(), fhrs.to_string())); - subtotal += f; - }; - running_total += subtotal; - println!(); - println!("{}", format!("{:<20}{:.2}hrs", "Section total:", subtotal)); - println!("{}", format!("{:<20}{:.2}hrs", "Subtotal:", running_total)); - println!(); - println!(); - }; - } else { - for (act, duration) in ind.iter().sorted_by_key(|x| x.0) { - let f: f64 = nearest(*duration as f64/3600.00); - let mut fhrs: String = format!("{:2}hrs", 0.08); - if f > 0.0 { - fhrs = format!("{:.2}hrs", f); - }; - println!("{}", format!("{:<75}{:>10}", act.to_string(), fhrs.to_string())); - }; - }; - - println!(); - - println!("{}", format!("Grand total: {:.2}", gtoth)); -} - -fn nearest(hr: f64) -> f64 { - let t = hr * 4.0; - t.round() / 4.0 -} diff --git a/rust/chug/Cargo.lock b/rust/timetracking/Cargo.lock similarity index 95% rename from rust/chug/Cargo.lock rename to rust/timetracking/Cargo.lock index 8d1d914..5fd4f56 100644 --- a/rust/chug/Cargo.lock +++ b/rust/timetracking/Cargo.lock @@ -66,6 +66,7 @@ dependencies = [ "chrono", "itertools", "regex", + "timelogging", ] [[package]] @@ -128,6 +129,15 @@ dependencies = [ "syn", ] +[[package]] +name = "do_process" +version = "0.1.0" +dependencies = [ + "itertools", + "regex", + "timelogging", +] + [[package]] name = "either" version = "1.8.0" @@ -136,9 +146,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "iana-time-zone" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "c422fb4f6e80490d0afcacf5c3de2c22ab8e631e0cd7cb2d4a3baf844720a52a" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -303,6 +313,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "timelogging" +version = "0.1.0" +dependencies = [ + "chrono", + "itertools", + "regex", +] + +[[package]] +name = "timetracker" +version = "0.1.0" +dependencies = [ + "chrono", + "itertools", + "regex", + "timelogging", +] + [[package]] name = "unicode-ident" version = "1.0.5" diff --git a/rust/timetracking/Cargo.toml b/rust/timetracking/Cargo.toml new file mode 100644 index 0000000..5aaa60e --- /dev/null +++ b/rust/timetracking/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +members = [ + "timetracker", + "do_process", + "chug", + "timelogging" +] diff --git a/rust/chug/Cargo.toml b/rust/timetracking/chug/Cargo.toml similarity index 83% rename from rust/chug/Cargo.toml rename to rust/timetracking/chug/Cargo.toml index a084dd5..ff876ec 100644 --- a/rust/chug/Cargo.toml +++ b/rust/timetracking/chug/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" regex = "1" chrono = "0.4" itertools = "0.8" +timelogging = { path = "../timelogging" } diff --git a/rust/timetracking/chug/src/main.rs b/rust/timetracking/chug/src/main.rs new file mode 100644 index 0000000..f40e7e4 --- /dev/null +++ b/rust/timetracking/chug/src/main.rs @@ -0,0 +1,73 @@ +use std::env; +use std::fs; +use std::io::{BufReader, BufRead, ErrorKind}; +use regex::Regex; +use chrono::{Datelike, Duration, offset::Local, NaiveDate, Weekday}; +use timelogging::timetracking::{process_input_file, generate_individual_timecards}; + +fn main() { + + // Regular Expressions + let offset = Regex::new(r"^[+-]*\d+").unwrap(); + + // Process options + let params: Vec = env::args().collect(); + + // If week offset is provided, grab it + let mut week_offset: i64 = 0; + for arg in ¶ms { + if offset.is_match(&arg.to_string()) { + week_offset = arg.parse::().unwrap(); + }; + }; + + let day: NaiveDate = last_monday(week_offset); + let mut filenames: Vec = Vec::new(); + for d in 0..7 { + filenames.push((day + Duration::days(d)).format("%Y-%m-%d.log").to_string()); + }; + + assert_eq!(filenames.len(), 7); + + let mut grand_total: f64 = 0.0; + for filename in filenames { + println!("{} ", filename); + // Process the file + let mut gtoth = match fs::File::open(filename) { + Ok(file) => { + // Hash Maps (akin to Python Dictionaires) + let mut reader: Box = Box::new(BufReader::new(file)); + // For each line of input (build internal data structures) + let (mut start, mut finish, _) = process_input_file(&mut reader); + // generate total for this file + let (_, total) = generate_individual_timecards(&mut start, &mut finish); + total + }, + Err(error) => match error.kind() { + ErrorKind::NotFound => 0.0, + _ => panic!("Error reading file! Error: {}", error) + } + }; + if gtoth > 24.0 { + gtoth = 24.0; + }; + // print the output + println!("{}", format!("{:.2}", gtoth)); + println!(); + grand_total += gtoth; + }; + println!("Grand total: {:.2}", grand_total); +} + +fn last_monday(offset: i64) -> NaiveDate { + let day: NaiveDate = Local::today().naive_local() - Duration::days(7 * offset); + match day.weekday() { + Weekday::Mon => day - Duration::days(0), + Weekday::Tue => day - Duration::days(1), + Weekday::Wed => day - Duration::days(2), + Weekday::Thu => day - Duration::days(3), + Weekday::Fri => day - Duration::days(4), + Weekday::Sat => day - Duration::days(5), + Weekday::Sun => day - Duration::days(6) + } +} diff --git a/rust/timetracking/do_process/Cargo.toml b/rust/timetracking/do_process/Cargo.toml new file mode 100644 index 0000000..94d38df --- /dev/null +++ b/rust/timetracking/do_process/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "do_process" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "1" +itertools = "0.8" +timelogging = { path = "../timelogging" } + diff --git a/rust/timetracking/do_process/src/main.rs b/rust/timetracking/do_process/src/main.rs new file mode 100644 index 0000000..4e77e4e --- /dev/null +++ b/rust/timetracking/do_process/src/main.rs @@ -0,0 +1,51 @@ +use std::env; +use std::fs; +use std::io::{self, BufReader, BufRead}; +use regex::Regex; +use itertools::Itertools; +use timelogging::timetracking::{nearest, process_input_file, generate_individual_timecards}; + +fn main() { + // Process the file (stdin or file argument) + let input = env::args().nth(1); + let mut reader: Box = match input { + None => Box::new(BufReader::new(io::stdin())), + Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap())) + }; + + // For each line of input (build internal data structures) + let (mut start, mut finish, categories) = process_input_file(&mut reader); + + + // generate individual timecards + let (ind, gtoth) = generate_individual_timecards(&mut start, &mut finish); + + // print the output + let mut running_total: f64 = 0.0; + for cat in categories { + let mut subtotal: f64 = 0.0; + let catre = Regex::new(&format!(r"\[{}\]\s+.*\s*$", cat).to_string()).unwrap(); + for (act, duration) in ind.iter().sorted_by_key(|x| x.0) { + if ! catre.is_match(&act.to_string()) { + continue + }; + let mut f: f64 = nearest(*duration as f64/3600.00); + if f == 0.0 { + f = 0.08; + }; + let fhrs: String = format!("{:.2}hrs", f); + println!("{}", format!("{:<75}{:>10}", act.to_string(), fhrs.to_string())); + subtotal += f; + }; + running_total += subtotal; + println!(); + println!("{}", format!("{:<20}{:.2}hrs", "Section total:", subtotal)); + println!("{}", format!("{:<20}{:.2}hrs", "Subtotal:", running_total)); + println!(); + println!(); + }; + + println!(); + + println!("{}", format!("Grand total: {:.2}", gtoth)); +} diff --git a/rust/timetracker/Cargo.toml b/rust/timetracking/timelogging/Cargo.toml similarity index 90% rename from rust/timetracker/Cargo.toml rename to rust/timetracking/timelogging/Cargo.toml index 40a8538..54c93cf 100644 --- a/rust/timetracker/Cargo.toml +++ b/rust/timetracking/timelogging/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "timetracker" +name = "timelogging" version = "0.1.0" edition = "2021" diff --git a/rust/timetracking/timelogging/src/lib.rs b/rust/timetracking/timelogging/src/lib.rs new file mode 100644 index 0000000..433fc97 --- /dev/null +++ b/rust/timetracking/timelogging/src/lib.rs @@ -0,0 +1,133 @@ + +pub mod timetracking { + use std::io::BufRead; + use std::collections::HashMap; + use regex::Regex; + use chrono::NaiveDateTime; + use itertools::Itertools; + + pub fn nearest(hr: f64) -> f64 { + let t = hr * 4.0; + t.round() / 4.0 + } + + pub fn process_input_file(reader: &mut Box) -> ( + HashMap>, + HashMap>, + Vec) { + let mut start: HashMap> = HashMap::new(); + let mut finish: HashMap> = HashMap::new(); + let mut categories: Vec = Vec::new(); + + // regex + let empty = Regex::new(r"^\s*$").unwrap(); // regex for finding empty lines (and skipping them) + let comment = Regex::new(r"^\s*#").unwrap(); // regex for finding empty lines (and skipping them) + let delim = Regex::new(r":\s{2}").unwrap(); // regex for splitting timestamp entry + let begin = Regex::new(r"Begin\s+(?P.*)\s*$").unwrap(); + let end = Regex::new(r"End\s+(?P.*)\s*$").unwrap(); + let category = Regex::new(r"\[(?P.*)\]\s+(?P.*)\s*$").unwrap(); + + for line in reader.lines() { + // need to get the string out of line (which is an option enum) + let l: String = line.unwrap(); + + // if the line is empty or is a comment + if empty.is_match(&l) || comment.is_match(&l) { + continue + }; + + // Parse the timestamp and entry + let splits: Vec<_> = delim.split(&l.trim_end()).into_iter().collect(); + // splits should only ever have two elements (only one delimiter per log line) + let datetime = splits[0]; + let entry = splits[1]; + let date_time = NaiveDateTime::parse_from_str(datetime,"%Y-%m-%d %H:%M:%S").unwrap(); + let timestamp = date_time.timestamp(); + + // Process a Begin + if begin.is_match(&entry) { + let caps = begin.captures(&entry).unwrap(); + let action = caps.name("action").unwrap().as_str(); + start.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); + let cats = category.captures(&entry).unwrap(); + let cat = cats.name("category").unwrap().as_str(); + categories.push(cat.to_string()); + + }; + + // Process an End + if end.is_match(&entry) { + let caps = end.captures(&entry).unwrap(); + let action = caps.name("action").unwrap().as_str(); + finish.entry(action.to_string()).or_insert(Vec::new()).push(timestamp); + let cats = category.captures(&entry).unwrap(); + let cat = cats.name("category").unwrap().as_str(); + categories.push(cat.to_string()); + }; + + }; // end for + // sort and dedup categories + categories.sort_unstable(); + categories.dedup(); + + (start, finish, categories) + } + + pub fn generate_individual_timecards ( + start: &mut HashMap>, + finish: &mut HashMap> + ) -> ( + HashMap, + f64 + ) + { + let mut ind: HashMap = HashMap::new(); + let mut gtoth: f64 = 0.0; + for (act, tstamps) in start.iter_mut().sorted_by_key(|x| x.0) { + let bc: i64 = tstamps.len().try_into().unwrap(); + let ec: i64 = finish.get(&act.to_string()).expect("Somehow, not a vector!").len().try_into().unwrap(); + + // Debugging + // println!("bc: {}, ec: {}", bc, ec); + // This block ensures the lengths of start and finish for this action are equal + if bc - ec > 1 { + panic!("ERROR: Missing more than one End"); // need to fix log file + } else if bc > ec { // bc should be exactly one greater + tstamps.pop(); + } else if ec > bc { + panic!("ERROR: Missing a Begin"); // need to fix log file + }; + + // Debugging + //for t in tstamps.into_iter() { + // print!("{} ", t); + //}; + //println!(); + //for t in finish.get_mut(&act.to_string()).expect("Somehow, not a vector!").into_iter() { + // print!("{} ", t); + //}; + //println!(); + + + let mut delta: i64 = 0; + for i in 0..tstamps.len() { + let beg = tstamps[i]; + let en = finish.get_mut(&act.to_string()).unwrap()[i]; + delta += en - beg; + }; + + ind.insert(act.to_string(), delta); + let d: f64 = nearest(delta as f64 / 3600.00); + if d == 0.0 { + gtoth += 0.08; + } else { + gtoth += d; + }; + + } + + (ind, gtoth) + + } + +} diff --git a/rust/timetracking/timetracker/Cargo.toml b/rust/timetracking/timetracker/Cargo.toml new file mode 100644 index 0000000..afa04ae --- /dev/null +++ b/rust/timetracking/timetracker/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "timetracker" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "1" +chrono = "0.4" +itertools = "0.8" +timelogging = { path = "../timelogging" } diff --git a/rust/timetracking/timetracker/src/main.rs b/rust/timetracking/timetracker/src/main.rs new file mode 100644 index 0000000..d38f6ec --- /dev/null +++ b/rust/timetracking/timetracker/src/main.rs @@ -0,0 +1,35 @@ +use std::env; +use std::fs; +use std::io::{self, BufReader, BufRead}; +use itertools::Itertools; +use timelogging::timetracking::{nearest, process_input_file, generate_individual_timecards}; + +fn main() { + // Process the file (stdin or file argument) + let input = env::args().nth(1); + let mut reader: Box = match input { + None => Box::new(BufReader::new(io::stdin())), + Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap())) + }; + + // For each line of input (build internal data structures) + let (mut start, mut finish, _) = process_input_file(&mut reader); + + + // generate individual timecards + let (ind, gtoth) = generate_individual_timecards(&mut start, &mut finish); + + // print the output + for (act, duration) in ind.iter().sorted_by_key(|x| x.0) { + let f: f64 = nearest(*duration as f64/3600.00); + let mut fhrs: String = format!("{:2}hrs", 0.08); + if f > 0.0 { + fhrs = format!("{:.2}hrs", f); + }; + println!("{}", format!("{:<75}{:>10}", act.to_string(), fhrs.to_string())); + }; + + println!(); + + println!("{}", format!("Grand total: {:.2}", gtoth)); +}