diff --git a/.gitignore b/.gitignore index 9d8e66a..ee0896e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ do_process.bak2017-11-06_file-test 2020* 2021* staging +rust/*/target/* diff --git a/rust/chug/Cargo.lock b/rust/chug/Cargo.lock new file mode 100644 index 0000000..8d1d914 --- /dev/null +++ b/rust/chug/Cargo.lock @@ -0,0 +1,407 @@ +# 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 = "chug" +version = "0.1.0" +dependencies = [ + "chrono", + "itertools", + "regex", +] + +[[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.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[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 = "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/chug/Cargo.toml b/rust/chug/Cargo.toml new file mode 100644 index 0000000..a084dd5 --- /dev/null +++ b/rust/chug/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "chug" +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" diff --git a/rust/chug/src/main.rs b/rust/chug/src/main.rs new file mode 100644 index 0000000..68400fc --- /dev/null +++ b/rust/chug/src/main.rs @@ -0,0 +1,158 @@ +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) + } + }; + // 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 new file mode 100644 index 0000000..16bb4fb --- /dev/null +++ b/rust/timetracker/Cargo.lock @@ -0,0 +1,407 @@ +# 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/Cargo.toml b/rust/timetracker/Cargo.toml new file mode 100644 index 0000000..40a8538 --- /dev/null +++ b/rust/timetracker/Cargo.toml @@ -0,0 +1,11 @@ +[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" diff --git a/rust/timetracker/src/main.rs b/rust/timetracker/src/main.rs new file mode 100644 index 0000000..4eed087 --- /dev/null +++ b/rust/timetracker/src/main.rs @@ -0,0 +1,179 @@ +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 dp_opts = Regex::new(r"^(-d|--do-process)").unwrap(); + + let mut dp: bool = false; + + let mut categories: Vec = Vec::new(); + + // Process options + let mut params: Vec = env::args().collect(); + if do_process.is_match(¶ms[0]) { + dp = true; + } else if dp_opts.is_match(¶ms[1]) { + dp = true; + params.remove(1); + }; + + // Process the file (stdin or file argument) + let input: Option<_> = Some(¶ms[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 +}