diff --git a/src/eval.rs b/src/eval.rs index e596ab1..80f3033 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,14 +1,14 @@ use chrono::NaiveDate; use crate::files::arguments::{Range, RangeDate}; -use crate::files::Files; +use crate::files::{FileSource, Files}; -use self::command::CommandState; +use self::command::{CommandState, EvalCommand}; pub use self::date::Dates; use self::delta::Delta; use self::entry::Entries; pub use self::entry::{Entry, EntryKind, EntryMode}; -pub use self::error::{Error, Result, SourceInfo}; +pub use self::error::Error; pub use self::range::DateRange; mod command; @@ -20,11 +20,14 @@ mod range; mod util; impl Files { - pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result> { + pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result, Error> { let mut entries = Entries::new(mode, range); for command in self.commands() { - for entry in CommandState::new(command, range).eval()?.entries() { - entries.add(entry); + let source = command.source; + if let Some(command) = EvalCommand::new(command.command) { + for entry in CommandState::new(command, source, range).eval()?.entries() { + entries.add(entry); + } } } Ok(entries.entries()) @@ -32,7 +35,7 @@ impl Files { } impl Range { - pub fn eval(&self, index: usize, today: NaiveDate) -> Result { + pub fn eval(&self, index: S, today: NaiveDate) -> Result> { let mut start = match self.start { RangeDate::Date(d) => d, RangeDate::Today => today, diff --git a/src/eval/command.rs b/src/eval/command.rs index b0ec4a5..e50e580 100644 --- a/src/eval/command.rs +++ b/src/eval/command.rs @@ -6,18 +6,69 @@ use crate::files::commands::{ self, BirthdaySpec, Command, Done, DoneDate, DoneKind, Note, Spec, Statement, Task, }; use crate::files::primitives::{Span, Spanned, Time}; -use crate::files::SourcedCommand; +use crate::files::{FileSource, Source}; use super::date::Dates; use super::delta::Delta; -use super::{DateRange, Entry, EntryKind, Error, Result}; +use super::{DateRange, Entry, EntryKind, Error}; mod birthday; mod date; mod formula; +/// A command that can be evaluated. +pub enum EvalCommand<'a> { + Task(&'a Task), + Note(&'a Note), +} + +impl<'a> EvalCommand<'a> { + pub fn new(command: &'a Command) -> Option { + match command { + Command::Task(task) => Some(Self::Task(task)), + Command::Note(note) => Some(Self::Note(note)), + _ => None, + } + } + + fn statements(&self) -> &[Statement] { + match self { + Self::Task(task) => &task.statements, + Self::Note(note) => ¬e.statements, + } + } + + fn kind(&self) -> EntryKind { + match self { + Self::Task(_) => EntryKind::Task, + Self::Note(_) => EntryKind::Note, + } + } + + /// Last root date mentioned in any `DONE`. + fn last_done_root(&self) -> Option { + match self { + Self::Task(task) => task + .done + .iter() + .filter_map(|done| done.date.map(DoneDate::root)) + .max(), + Self::Note(_) => None, + } + } + + /// Last completion date mentioned in any `DONE`. + fn last_done_completion(&self) -> Option { + match self { + Self::Task(task) => task.done.iter().map(|done| done.done_at).max(), + Self::Note(_) => None, + } + } +} + pub struct CommandState<'a> { - command: SourcedCommand<'a>, + command: EvalCommand<'a>, + source: Source, range: DateRange, from: Option, @@ -29,7 +80,7 @@ pub struct CommandState<'a> { } impl<'a> CommandState<'a> { - pub fn new(command: SourcedCommand<'a>, mut range: DateRange) -> Self { + pub fn new(command: EvalCommand<'a>, source: Source, mut range: DateRange) -> Self { // If we don't calculate entries for the source of the move command, it // fails even though the user did nothing wrong. Also, move commands (or // chains thereof) may move an initially out-of-range entry into range. @@ -37,15 +88,16 @@ impl<'a> CommandState<'a> { // To fix this, we just expand the range to contain all move command // sources. This is a quick fix, but until it becomes a performance // issue (if ever), it's probably fine. - for statement in command.command.statements() { + for statement in command.statements() { if let Statement::Move { from, .. } = statement { range = range.containing(*from) } } Self { - range, command, + source, + range, from: None, until: None, remind: None, @@ -54,10 +106,10 @@ impl<'a> CommandState<'a> { } } - pub fn eval(mut self) -> Result { - match self.command.command { - Command::Task(task) => self.eval_task(task)?, - Command::Note(note) => self.eval_note(note)?, + pub fn eval(mut self) -> Result> { + match self.command { + EvalCommand::Task(task) => self.eval_task(task)?, + EvalCommand::Note(note) => self.eval_note(note)?, } Ok(self) } @@ -71,13 +123,6 @@ impl<'a> CommandState<'a> { // Helper functions - fn kind(&self) -> EntryKind { - match self.command.command { - Command::Task(_) => EntryKind::Task, - Command::Note(_) => EntryKind::Note, - } - } - fn range_with_remind(&self) -> DateRange { match &self.remind { None => self.range, @@ -85,26 +130,6 @@ impl<'a> CommandState<'a> { } } - /// Last root date mentioned in any `DONE`. - fn last_done_root(&self) -> Option { - match self.command.command { - Command::Task(task) => task - .done - .iter() - .filter_map(|done| done.date.map(DoneDate::root)) - .max(), - Command::Note(_) => None, - } - } - - /// Last completion date mentioned in any `DONE`. - fn last_done_completion(&self) -> Option { - match self.command.command { - Command::Task(task) => task.done.iter().map(|done| done.done_at).max(), - Command::Note(_) => None, - } - } - fn limit_from_until(&self, range: DateRange) -> Option { let range_from = range.from(); let from = self @@ -125,9 +150,13 @@ impl<'a> CommandState<'a> { } } - fn entry_with_remind(&self, kind: EntryKind, dates: Option) -> Result { + fn entry_with_remind( + &self, + kind: EntryKind, + dates: Option, + ) -> Result> { let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) { - let index = self.command.source.file(); + let index = self.source.file(); let start = dates.sorted().root(); let remind = delta.value.apply_date(index, dates.sorted().root())?; if remind >= start { @@ -143,7 +172,7 @@ impl<'a> CommandState<'a> { None }; - Ok(Entry::new(self.command.source, kind, dates, remind)) + Ok(Entry::new(self.source, kind, dates, remind)) } /// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not @@ -185,13 +214,13 @@ impl<'a> CommandState<'a> { .any(|s| matches!(s, Statement::Date(_) | Statement::BDate(_))) } - fn eval_task(&mut self, task: &Task) -> Result<()> { + fn eval_task(&mut self, task: &Task) -> Result<(), Error> { if Self::has_date_stmt(&task.statements) { for statement in &task.statements { self.eval_statement(statement)?; } } else if task.done.is_empty() { - self.add(self.entry_with_remind(self.kind(), None)?); + self.add(self.entry_with_remind(self.command.kind(), None)?); } for done in &task.done { @@ -201,19 +230,19 @@ impl<'a> CommandState<'a> { Ok(()) } - fn eval_note(&mut self, note: &Note) -> Result<()> { + fn eval_note(&mut self, note: &Note) -> Result<(), Error> { if Self::has_date_stmt(¬e.statements) { for statement in ¬e.statements { self.eval_statement(statement)?; } } else { - self.add(self.entry_with_remind(self.kind(), None)?); + self.add(self.entry_with_remind(self.command.kind(), None)?); } Ok(()) } - fn eval_statement(&mut self, statement: &Statement) -> Result<()> { + fn eval_statement(&mut self, statement: &Statement) -> Result<(), Error> { match statement { Statement::Date(spec) => self.eval_date(spec)?, Statement::BDate(spec) => self.eval_bdate(spec)?, @@ -231,7 +260,7 @@ impl<'a> CommandState<'a> { Ok(()) } - fn eval_date(&mut self, spec: &Spec) -> Result<()> { + fn eval_date(&mut self, spec: &Spec) -> Result<(), Error> { match spec { Spec::Date(spec) => self.eval_date_spec(spec.into()), Spec::Weekday(spec) => self.eval_formula_spec(spec.into()), @@ -239,7 +268,7 @@ impl<'a> CommandState<'a> { } } - fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<()> { + fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<(), Error> { self.eval_birthday_spec(spec) } @@ -254,7 +283,7 @@ impl<'a> CommandState<'a> { from: NaiveDate, to: Option, to_time: Option>, - ) -> Result<()> { + ) -> Result<(), Error> { if let Some(mut entry) = self.dated.remove(&from) { let mut dates = entry.dates.expect("comes from self.dated"); @@ -268,7 +297,7 @@ impl<'a> CommandState<'a> { delta = delta + Duration::minutes(root.minutes_to(to_time.value)); } else { return Err(Error::TimedMoveWithoutTime { - index: self.command.source.file(), + index: self.source.file(), span: to_time.span, }); } @@ -281,7 +310,7 @@ impl<'a> CommandState<'a> { Ok(()) } else { Err(Error::MoveWithoutSource { - index: self.command.source.file(), + index: self.source.file(), span, }) } @@ -295,7 +324,7 @@ impl<'a> CommandState<'a> { } } - fn eval_done(&mut self, done: &Done) -> Result<()> { + fn eval_done(&mut self, done: &Done) -> Result<(), Error> { let kind = match done.kind { DoneKind::Done => EntryKind::TaskDone(done.done_at), DoneKind::Canceled => EntryKind::TaskCanceled(done.done_at), diff --git a/src/eval/command/birthday.rs b/src/eval/command/birthday.rs index b10d83f..cec1d2e 100644 --- a/src/eval/command/birthday.rs +++ b/src/eval/command/birthday.rs @@ -1,14 +1,15 @@ use chrono::{Datelike, NaiveDate}; use crate::files::commands::BirthdaySpec; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; -use super::super::error::Result; +use super::super::error::Error; use super::super::EntryKind; impl<'a> CommandState<'a> { - pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<()> { + pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error> { let range = match self.limit_from_until(self.range_with_remind()) { Some(range) => range, None => return Ok(()), diff --git a/src/eval/command/date.rs b/src/eval/command/date.rs index 8ef5ff6..f7ddacb 100644 --- a/src/eval/command/date.rs +++ b/src/eval/command/date.rs @@ -1,12 +1,14 @@ use chrono::NaiveDate; -use crate::files::commands::{self, Command}; +use crate::files::commands; use crate::files::primitives::{Spanned, Time}; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; -use super::super::{DateRange, Error, Result}; +use super::super::{DateRange, Error}; +use super::EvalCommand; pub struct DateSpec { pub start: NaiveDate, @@ -72,14 +74,16 @@ impl DateSpec { /// `start` date itself should be skipped (and thus not result in an entry). /// This may be necessary if [`Self::start_at_done`] is set. fn start_and_range(&self, s: &CommandState<'_>) -> Option<(NaiveDate, bool, DateRange)> { - let (start, skip, range) = match s.command.command { - Command::Task(_) => { + let (start, skip, range) = match s.command { + EvalCommand::Task(_) => { let (start, skip) = s + .command .last_done_completion() .map(|start| (start, true)) .filter(|_| self.start_at_done) .unwrap_or((self.start, false)); let range_from = s + .command .last_done_root() .map(|date| date.succ()) .unwrap_or(self.start); @@ -90,7 +94,7 @@ impl DateSpec { .with_from(range_from)?; (start, skip, range) } - Command::Note(_) => { + EvalCommand::Note(_) => { let start = self.start; let range = s .range_with_remind() @@ -103,7 +107,11 @@ impl DateSpec { Some((start, skip, range)) } - fn step(index: usize, from: NaiveDate, repeat: &Spanned) -> Result { + fn step( + index: FileSource, + from: NaiveDate, + repeat: &Spanned, + ) -> Result> { let to = repeat.value.apply_date(index, from)?; if to > from { Ok(to) @@ -117,7 +125,7 @@ impl DateSpec { } } - fn dates(&self, index: usize, start: NaiveDate) -> Result { + fn dates(&self, index: FileSource, start: NaiveDate) -> Result> { let root = self.start_delta.apply_date(index, start)?; Ok(if let Some(root_time) = self.start_time { let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; @@ -130,8 +138,8 @@ impl DateSpec { } impl<'a> CommandState<'a> { - pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<()> { - let index = self.command.source.file(); + pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error> { + let index = self.source.file(); if let Some(repeat) = &spec.repeat { if let Some((mut start, skip, range)) = spec.start_and_range(self) { if skip { @@ -142,13 +150,13 @@ impl<'a> CommandState<'a> { } while start <= range.until() { let dates = spec.dates(index, start)?; - self.add(self.entry_with_remind(self.kind(), Some(dates))?); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); start = DateSpec::step(index, start, repeat)?; } } } else { let dates = spec.dates(index, spec.start)?; - self.add(self.entry_with_remind(self.kind(), Some(dates))?); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); } Ok(()) } diff --git a/src/eval/command/formula.rs b/src/eval/command/formula.rs index 5ecc6a2..e733937 100644 --- a/src/eval/command/formula.rs +++ b/src/eval/command/formula.rs @@ -1,12 +1,14 @@ use chrono::{Datelike, Duration, NaiveDate}; -use crate::files::commands::{self, Command}; +use crate::files::commands; use crate::files::primitives::{Span, Spanned, Time, Weekday}; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; -use super::super::{util, DateRange, Error, Result}; +use super::super::{util, DateRange, Error}; +use super::EvalCommand; fn b2i(b: bool) -> i64 { if b { @@ -47,7 +49,7 @@ pub enum Var { } impl Var { - fn eval(self, index: usize, date: NaiveDate) -> Result { + fn eval(self, index: S, date: NaiveDate) -> Result> { Ok(match self { Var::JulianDay => date.num_days_from_ce().into(), Var::Year => date.year().into(), @@ -202,7 +204,7 @@ impl From for Expr { } impl Expr { - fn eval(&self, index: usize, date: NaiveDate) -> Result { + fn eval(&self, index: S, date: NaiveDate) -> Result> { Ok(match self { Expr::Lit(l) => *l, Expr::Var(v) => v.eval(index, date)?, @@ -328,12 +330,12 @@ impl FormulaSpec { .expand_by(&self.end_delta) .move_by(&self.start_delta); - if let Command::Task(_) = s.command.command { - if let Some(last_done_root) = s.last_done_root() { + if let EvalCommand::Task(_) = s.command { + if let Some(last_done_root) = s.command.last_done_root() { range = range.with_from(last_done_root.succ())?; } else if let Some(from) = s.from { range = range.with_from(from)?; - } else if matches!(s.command.command, Command::Task(_)) { + } else if matches!(s.command, EvalCommand::Task(_)) { // We have no idea if we missed any tasks since the user hasn't // specified a `FROM`, so we just just look back one year. Any // task older than a year is probably not important anyways... @@ -344,7 +346,7 @@ impl FormulaSpec { s.limit_from_until(range) } - fn dates(&self, index: usize, start: NaiveDate) -> Result { + fn dates(&self, index: FileSource, start: NaiveDate) -> Result> { let root = self.start_delta.apply_date(index, start)?; Ok(if let Some(root_time) = self.start_time { let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; @@ -355,19 +357,19 @@ impl FormulaSpec { }) } - fn eval(&self, index: usize, date: NaiveDate) -> Result { + fn eval(&self, index: FileSource, date: NaiveDate) -> Result> { Ok(i2b(self.start.eval(index, date)?)) } } impl<'a> CommandState<'a> { - pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<()> { + pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<(), Error> { if let Some(range) = spec.range(self) { - let index = self.command.source.file(); + let index = self.source.file(); for day in range.days() { if spec.eval(index, day)? { let dates = spec.dates(index, day)?; - self.add(self.entry_with_remind(self.kind(), Some(dates))?); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); } } } @@ -386,7 +388,7 @@ mod tests { use super::{Expr, Var}; fn expr(expr: &Expr, date: NaiveDate, target: i64) { - if let Ok(result) = expr.eval(0, date) { + if let Ok(result) = expr.eval((), date) { assert_eq!(result, target); } else { panic!("formula produced error for day {}", date); @@ -400,7 +402,7 @@ mod tests { for delta in -1000..1000 { let d1 = NaiveDate::from_ymd(2021, 12, 19); let d2 = d1 + Duration::days(delta); - assert_eq!(e.eval(0, d2).unwrap() - e.eval(0, d1).unwrap(), delta); + assert_eq!(e.eval((), d2).unwrap() - e.eval((), d1).unwrap(), delta); } } diff --git a/src/eval/delta.rs b/src/eval/delta.rs index dcc1706..0872d7b 100644 --- a/src/eval/delta.rs +++ b/src/eval/delta.rs @@ -5,7 +5,7 @@ use chrono::{Datelike, Duration, NaiveDate}; use crate::files::commands; use crate::files::primitives::{Span, Spanned, Time, Weekday}; -use super::{util, Error, Result}; +use super::{util, Error}; /// Like [`commands::DeltaStep`] but includes a new constructor, /// [`DeltaStep::Time`]. @@ -142,16 +142,16 @@ impl From<&commands::Delta> for Delta { } } -struct DeltaEval { - index: usize, +struct DeltaEval { + index: I, start: NaiveDate, start_time: Option