Restructure eval module

This commit is contained in:
Joscha 2021-11-24 01:13:45 +01:00
parent 817732abf6
commit 35184e21e9
5 changed files with 208 additions and 199 deletions

View file

@ -1,14 +1,17 @@
use std::collections::HashSet;
use std::result; use std::result;
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use crate::files::commands::{Birthday, Command, File, Note, Spec, Task}; use crate::eval::entry::EntryDate;
use crate::files::commands::{Birthday, Command};
use crate::files::{Files, Source};
use self::entries::{DateRange, Entry, EntryKind, EntryMap}; use self::entry::{Entry, EntryKind, EntryMap};
pub use self::range::DateRange;
mod delta; mod delta;
pub mod entries; mod entry;
mod range;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -18,114 +21,85 @@ pub enum Error {
type Result<T> = result::Result<T, Error>; type Result<T> = result::Result<T, Error>;
fn eval_spec(spec: &Spec, index: usize, map: &mut EntryMap) -> Result<()> { struct Eval {
Ok(()) map: EntryMap,
source: Source,
} }
fn eval_task(task: &Task, index: usize, range: DateRange) -> Result<Vec<Entry>> { impl Eval {
let mut map = EntryMap::new(range); pub fn new(range: DateRange, source: Source) -> Self {
Self {
map.from = task.from; map: EntryMap::new(range),
map.until = task.until; source,
for date in &task.except {
map.block(*date);
}
for spec in &task.when {
eval_spec(spec, index, &mut map)?;
}
let done: HashSet<NaiveDate> = 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_birthday(&mut self, birthday: &Birthday) {
} for year in self.map.range().years() {
let when = &birthday.when;
let mut title = birthday.title.clone();
fn eval_note(task: &Note, index: usize, range: DateRange) -> Result<Vec<Entry>> { if when.year_known {
let mut map = EntryMap::new(range); let age = year - when.date.year();
if age < 0 {
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<Vec<Entry>> {
let mut map = EntryMap::new(range);
for year in range.years() {
if bd.when.year_known && year < bd.when.date.year() {
continue; continue;
} }
title.push_str(&format!(" ({})", age));
}
let title = if bd.when.year_known { if let Some(date) = when.date.with_year(year) {
let age = year - bd.when.date.year(); let entry = Entry {
format!("{} ({})", bd.title, age) kind: EntryKind::Birthday,
} else { title: title.clone(),
bd.title.to_string() desc: birthday.desc.clone(),
source: self.source,
date: EntryDate::Date { root: date },
}; };
self.map.insert(date, entry);
} else {
assert_eq!(when.date.month(), 2);
assert_eq!(when.date.day(), 29);
let date = NaiveDate::from_ymd(year, 2, 28);
let entry = Entry {
kind: EntryKind::Birthday,
title: format!("{} (first half)", title),
desc: birthday.desc.clone(),
source: self.source,
date: EntryDate::Date { root: date },
};
self.map.insert(date, entry);
match bd.when.date.with_year(year) { let date = NaiveDate::from_ymd(year, 3, 1);
Some(date) => { let entry = Entry {
let mut entry = Entry::new(index, EntryKind::Birthday, title); kind: EntryKind::Birthday,
entry.start = Some(date); title: format!("{} (second half)", title),
map.insert(date, entry); desc: birthday.desc.clone(),
} source: self.source,
None => { date: EntryDate::Date { root: date },
// We must've hit a non-leapyear };
assert_eq!(bd.when.date.month(), 2); self.map.insert(date, entry);
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()) pub fn eval(&mut self, command: &Command) -> Result<Vec<Entry>> {
} // This function fills the entry map and then drains it again, so in
// theory, the struct can even be reused afterwards.
fn eval_command(command: &Command, index: usize, range: DateRange) -> Result<Vec<Entry>> {
match command { match command {
Command::Task(task) => eval_task(task, index, range), Command::Task(task) => todo!(),
Command::Note(note) => eval_note(note, index, range), Command::Note(note) => todo!(),
Command::Birthday(birthday) => eval_birthday(birthday, index, range), Command::Birthday(birthday) => self.eval_birthday(birthday),
}
Ok(self.map.drain())
} }
} }
pub fn eval(file: &File, range: DateRange) -> Result<Vec<Entry>> { impl Files {
pub fn eval(&self, range: DateRange) -> Result<Vec<Entry>> {
let mut result = vec![]; let mut result = vec![];
for (index, command) in file.commands.iter().enumerate() { for command in self.commands() {
result.append(&mut eval_command(command, index, range)?); result.append(&mut Eval::new(range, command.source).eval(command.command)?);
} }
Ok(result) Ok(result)
}
} }

View file

@ -1,99 +0,0 @@
use std::collections::HashMap;
use std::ops::RangeInclusive;
use chrono::{Datelike, NaiveDate};
use crate::files::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<String>,
/// Index in the source file
pub source: usize,
pub start: Option<NaiveDate>,
pub start_time: Option<Time>,
pub end: Option<NaiveDate>,
pub end_time: Option<Time>,
}
impl Entry {
pub fn new(source: usize, kind: EntryKind, title: String) -> Self {
Self {
kind,
title,
desc: vec![],
source,
start: None,
start_time: None,
end: None,
end_time: None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DateRange {
pub from: NaiveDate,
pub until: NaiveDate,
}
impl DateRange {
pub fn new(from: NaiveDate, until: NaiveDate) -> Self {
assert!(from <= until);
Self { from, until }
}
pub fn years(&self) -> RangeInclusive<i32> {
self.from.year()..=self.until.year()
}
}
pub struct EntryMap {
pub range: DateRange,
pub from: Option<NaiveDate>,
pub until: Option<NaiveDate>,
pub map: HashMap<NaiveDate, Option<Entry>>,
}
impl EntryMap {
pub fn new(range: DateRange) -> Self {
Self {
range,
from: None,
until: None,
map: HashMap::new(),
}
}
pub fn block(&mut self, date: NaiveDate) {
self.map.entry(date).or_insert(None);
}
pub fn insert(&mut self, date: NaiveDate, entry: Entry) {
self.map.entry(date).or_insert(Some(entry));
}
pub fn drain(&mut self) -> Vec<Entry> {
self.map.drain().filter_map(|(_, entry)| entry).collect()
}
}

94
src/eval/entry.rs Normal file
View file

@ -0,0 +1,94 @@
use std::collections::HashMap;
use chrono::NaiveDate;
use crate::files::commands::Time;
use crate::files::Source;
use super::range::DateRange;
#[derive(Debug, PartialEq, Eq)]
pub enum EntryKind {
Task,
DoneTask,
Note,
Birthday,
}
#[derive(Debug)]
pub enum EntryDate {
None,
Date {
root: NaiveDate,
},
DateWithTime {
root: NaiveDate,
root_time: Time,
},
DateToDate {
root: NaiveDate,
other: NaiveDate,
},
DateToDateWithTime {
root: NaiveDate,
root_time: Time,
other: NaiveDate,
other_time: Time,
},
}
impl EntryDate {
pub fn root(&self) -> Option<NaiveDate> {
match self {
EntryDate::None => None,
EntryDate::Date { root, .. } => Some(*root),
EntryDate::DateWithTime { root, .. } => Some(*root),
EntryDate::DateToDate { root, .. } => Some(*root),
EntryDate::DateToDateWithTime { root, .. } => Some(*root),
}
}
}
#[derive(Debug)]
pub struct Entry {
pub kind: EntryKind,
pub title: String,
pub desc: Vec<String>,
pub source: Source,
pub date: EntryDate,
}
pub struct EntryMap {
range: DateRange,
map: HashMap<NaiveDate, Option<Entry>>,
}
impl EntryMap {
pub fn new(range: DateRange) -> Self {
Self {
range,
map: HashMap::new(),
}
}
pub fn range(&self) -> DateRange {
self.range
}
pub fn block(&mut self, date: NaiveDate) {
if self.range.contains(date) {
self.map.entry(date).or_insert(None);
}
}
pub fn insert(&mut self, date: NaiveDate, entry: Entry) {
if self.range.contains(date) {
self.map.entry(date).or_insert(Some(entry));
}
}
pub fn drain(&mut self) -> Vec<Entry> {
self.map.drain().filter_map(|(_, entry)| entry).collect()
}
}

32
src/eval/range.rs Normal file
View file

@ -0,0 +1,32 @@
use std::ops::RangeInclusive;
use chrono::{Datelike, NaiveDate};
#[derive(Debug, Clone, Copy)]
pub struct DateRange {
from: NaiveDate,
until: NaiveDate,
}
impl DateRange {
pub fn new(from: NaiveDate, until: NaiveDate) -> Self {
assert!(from <= until);
Self { from, until }
}
pub fn contains(&self, date: NaiveDate) -> bool {
self.from <= date && date <= self.until
}
pub fn from(&self) -> NaiveDate {
self.from
}
pub fn until(&self) -> NaiveDate {
self.until
}
pub fn years(&self) -> RangeInclusive<i32> {
self.from.year()..=self.until.year()
}
}

View file

@ -5,8 +5,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use chrono::NaiveDate;
use structopt::StructOpt; use structopt::StructOpt;
use crate::eval::DateRange;
use crate::files::Files; use crate::files::Files;
mod eval; mod eval;
@ -24,6 +26,12 @@ fn main() -> anyhow::Result<()> {
let mut files = Files::load(&opt.file)?; let mut files = Files::load(&opt.file)?;
println!("{}", files.now().format("%F %T %Z")); println!("{}", files.now().format("%F %T %Z"));
let range = DateRange::new(
NaiveDate::from_ymd(2021, 11, 20),
NaiveDate::from_ymd(2021, 11, 26),
);
println!("{:#?}", files.eval(range));
files.mark_all_dirty(); files.mark_all_dirty();
files.save()?; files.save()?;