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) } }