diff options
Diffstat (limited to 'src/parse.rs')
-rw-r--r-- | src/parse.rs | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..079e83a --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,144 @@ +use std::str::FromStr; +use chrono::{Datelike, NaiveDate, NaiveTime, Weekday, Duration, Timelike}; + +// mod task; +use crate::task::*; + +pub fn parse_file_str(file_str: &str) -> Vec<Task> { + let mut tasks = Vec::new(); + let mut start_date = None; + let mut the_date = None; + for l in file_str.split('\n') { + if l.starts_with("# ") { + // '# 12/27/21', starts a new week block + start_date = parse_date_line(l); + } else if l.starts_with("## ") { + // '## Monday/Tuesday/...', starts a new day block + // Need to compute the actual date, basically looking for the first one after + // start_date. + let dayofweek = parse_day_line(l); + let mut current = start_date.expect("Invalid or missing '# ' date"); + the_date = loop { + if current.weekday() == dayofweek { + break Some(current); + } + current = current.succ(); + }; + } else if l.starts_with("- [ ]") || l.starts_with("- [X]") { + // '- [ ] ...', starts a new task block + let date = the_date.expect("No current date parsed yet..."); + tasks.push(Task { + date: date, + start_time: None, + end_time: None, + details: "".to_string(), + tags: Vec::new(), + }); + let details = l.get(5..).expect("").trim(); + handle_task_details(details, tasks.last_mut().expect("Unexpected error...")); + } else if l.starts_with(" ") { + // Extends the last task. + handle_task_details(l, tasks.last_mut().expect("Unexpected error...")); + } else if l.trim().len() > 0 { + eprint!("Ignoring line: {}\n", l); + } + } + return tasks; +} + +fn parse_date_line(l: &str) -> Option<NaiveDate> { + for maybe_date_str in l.split(' ') { + if let Ok(date) = NaiveDate::parse_from_str(maybe_date_str, "%m/%d/%y") { + return Some(date); + } + }; + return None; +} + +fn parse_day_line(l: &str) -> Weekday { + let daystr = l.get(3..).expect("Day-of-week line not long enough..."); + return Weekday::from_str(daystr).expect("Misparse day-of-week str..."); +} + +fn parse_time(s_: &str) -> NaiveTime { + let formats = vec!["%l:%M%p", "%H:%M"]; + let mut s = s_.to_string(); + if !s.contains(":") { + if s.ends_with("M") { + s.insert_str(s.len() - 2, ":00"); + } else { + s.push_str(":00"); + } + } + for format in formats { + if let Ok(parsed) = NaiveTime::parse_from_str(&s, format) { + if !format.contains("%p") && parsed.hour() < 6 { + return parsed + Duration::hours(12); + } + return parsed; + } + } + panic!("Couldn't parse time {}", s); +} + +fn parse_duration(s: &str) -> chrono::Duration { + // We try to find Mm, HhMm, Hh + if s.contains("h") && s.contains("m") { + // TODO: Decompose this case into the two below. + let hstr = s.split("h").collect::<Vec<&str>>().get(0).expect("").to_string(); + let mstr = s.split("h").collect::<Vec<&str>>().get(1).expect("").split("m").collect::<Vec<&str>>().get(0).expect("Expected XhYm").to_string(); + let secs = ((hstr.parse::<u64>().unwrap() * 60) + + (mstr.parse::<u64>().unwrap())) * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } else if s.contains("h") { + let hstr = s.split("h").collect::<Vec<&str>>().get(0).expect("").to_string(); + let secs = hstr.parse::<u64>().unwrap() * 60 * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } else if s.contains("m") { + let hstr = s.split("m").collect::<Vec<&str>>().get(0).expect("").to_string(); + let secs = hstr.parse::<u64>().unwrap() * 60; + return chrono::Duration::from_std(std::time::Duration::new(secs, 0)).unwrap(); + } + panic!("Couldn't parse duration {}", s); +} + +fn handle_task_details(l: &str, t: &mut Task) { + for tok in l.split(' ') { + if tok.starts_with("+") { + let tag = tok.get(1..).expect("Unexpected"); + t.tags.push(tag.to_string()); + } else if tok.starts_with("@") { + let timestr = tok.get(1..).expect("Unexpected"); + if timestr.contains("+") { // @Start+Duration + let parts: Vec<&str> = timestr.split("+").collect(); + match parts[..] { + [startstr, durstr] => { + t.start_time = Some(parse_time(startstr)); + t.end_time = Some(t.start_time.unwrap() + parse_duration(durstr)); + }, + _ => panic!("Not 2 parts to {}\n", timestr) + } + } else if timestr.contains("--") { // @Start--End + let parts: Vec<&str> = timestr.split("--").collect(); + match parts[..] { + [startstr, endstr] => { + t.start_time = Some(parse_time(startstr)); + t.end_time = Some(parse_time(endstr)); + if t.start_time > t.end_time { + panic!("Start time {} interpreted as after end time {}", + startstr, endstr); + } + }, + _ => panic!("Not 2 parts to {}\n", timestr) + } + } else { + panic!("'{}' is not of the form Start+Duration or Start--End\n", timestr); + } + } else { + if t.details.len() > 0 { + t.details.push(' '); + } + t.details.push_str(tok.trim()); + } + } +} |