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