From f1ff3f7aaa3a4921489ccae2066312112c5471ed Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 20 Nov 2021 16:08:13 +0100 Subject: [PATCH] Evaluate birthdays --- src/eval.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++ src/eval/entries.rs | 100 ++++++++++++++++++++++++++++++++++ src/main.rs | 13 ++++- 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/eval.rs create mode 100644 src/eval/entries.rs diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..42e90f4 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,130 @@ +use std::collections::HashSet; +use std::result; + +use chrono::{Datelike, NaiveDate}; + +use crate::commands::{Birthday, Command, File, Note, Spec, Task}; + +use self::entries::{DateRange, Entry, EntryKind, EntryMap}; + +pub mod entries; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("TODO")] + Todo, +} + +type Result = result::Result; + +fn eval_spec(spec: &Spec, index: usize, map: &mut EntryMap) -> Result<()> { + Ok(()) +} + +fn eval_task(task: &Task, index: usize, range: DateRange) -> Result> { + let mut map = EntryMap::new(range); + + map.from = task.from; + map.until = task.until; + + for date in &task.except { + map.block(*date); + } + + for spec in &task.when { + eval_spec(spec, index, &mut map)?; + } + + let done: HashSet = task + .done + .iter() + .filter_map(|done| done.refering_to) + .collect(); + + for (date, entry) in map.map.iter_mut() { + if let Some(entry) = entry { + if done.contains(date) { + entry.kind.done(); + } + } + } + + Ok(map.drain()) +} + +fn eval_note(task: &Note, index: usize, range: DateRange) -> Result> { + let mut map = EntryMap::new(range); + + map.from = task.from; + map.until = task.until; + + for date in &task.except { + map.block(*date); + } + + for spec in &task.when { + eval_spec(spec, index, &mut map)?; + } + + Ok(map.drain()) +} + +fn eval_birthday(bd: &Birthday, index: usize, range: DateRange) -> Result> { + let mut map = EntryMap::new(range); + + for year in range.years() { + if bd.when.year_known && year < bd.when.date.year() { + continue; + } + + let title = if bd.when.year_known { + let age = year - bd.when.date.year(); + format!("{} ({})", bd.title, age) + } else { + bd.title.to_string() + }; + + match bd.when.date.with_year(year) { + Some(date) => { + let mut entry = Entry::new(index, EntryKind::Birthday, title); + entry.start = Some(date); + map.insert(date, entry); + } + None => { + // We must've hit a non-leapyear + assert_eq!(bd.when.date.month(), 2); + assert_eq!(bd.when.date.day(), 29); + + let first_date = NaiveDate::from_ymd(year, 2, 28); + let first_title = format!("{} (first half)", title); + let mut first_entry = Entry::new(index, EntryKind::Birthday, first_title); + first_entry.start = Some(first_date); + map.insert(first_date, first_entry); + + let second_date = NaiveDate::from_ymd(year, 3, 1); + let second_title = format!("{} (second half)", title); + let mut second_entry = Entry::new(index, EntryKind::Birthday, second_title); + second_entry.start = Some(second_date); + map.insert(second_date, second_entry); + } + } + } + + Ok(map.drain()) +} + +fn eval_command(command: &Command, index: usize, range: DateRange) -> Result> { + match command { + Command::Task(task) => eval_task(task, index, range), + Command::Note(note) => eval_note(note, index, range), + Command::Birthday(birthday) => eval_birthday(birthday, index, range), + } +} + +pub fn eval(file: &File, range: DateRange) -> Result> { + let mut result = vec![]; + for (index, command) in file.commands.iter().enumerate() { + result.append(&mut eval_command(command, index, range)?); + } + Ok(result) +} diff --git a/src/eval/entries.rs b/src/eval/entries.rs new file mode 100644 index 0000000..24ebcd5 --- /dev/null +++ b/src/eval/entries.rs @@ -0,0 +1,100 @@ +use std::collections::HashMap; +use std::ops::RangeInclusive; +use std::str::from_boxed_utf8_unchecked; + +use chrono::{Datelike, NaiveDate}; + +use crate::commands::Time; + +#[derive(Debug, PartialEq, Eq)] +pub enum EntryKind { + Task, + DoneTask, + Note, + Birthday, +} + +impl EntryKind { + pub fn done(&mut self) { + if matches!(self, Self::Task) { + *self = Self::DoneTask; + } + } +} + +#[derive(Debug)] +pub struct Entry { + pub kind: EntryKind, + pub title: String, + pub desc: Vec, + + /// Index in the source file + pub source: usize, + + pub start: Option, + pub start_time: Option