Evaluate REMINDs
This commit is contained in:
parent
fe22c66c5c
commit
66da16f4e3
6 changed files with 115 additions and 29 deletions
|
|
@ -2,11 +2,14 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
use crate::files::commands::{BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task};
|
use crate::files::commands::{
|
||||||
use crate::files::primitives::Span;
|
self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task,
|
||||||
|
};
|
||||||
|
use crate::files::primitives::{Span, Spanned};
|
||||||
use crate::files::SourcedCommand;
|
use crate::files::SourcedCommand;
|
||||||
|
|
||||||
use super::date::Dates;
|
use super::date::Dates;
|
||||||
|
use super::delta::Delta;
|
||||||
use super::{DateRange, Entry, EntryKind, Error, Result};
|
use super::{DateRange, Entry, EntryKind, Error, Result};
|
||||||
|
|
||||||
mod birthday;
|
mod birthday;
|
||||||
|
|
@ -19,6 +22,7 @@ pub struct CommandState<'a> {
|
||||||
|
|
||||||
from: Option<NaiveDate>,
|
from: Option<NaiveDate>,
|
||||||
until: Option<NaiveDate>,
|
until: Option<NaiveDate>,
|
||||||
|
remind: Option<Spanned<Delta>>,
|
||||||
|
|
||||||
dated: HashMap<NaiveDate, Entry>,
|
dated: HashMap<NaiveDate, Entry>,
|
||||||
undated: Vec<Entry>,
|
undated: Vec<Entry>,
|
||||||
|
|
@ -44,6 +48,7 @@ impl<'a> CommandState<'a> {
|
||||||
command,
|
command,
|
||||||
from: None,
|
from: None,
|
||||||
until: None,
|
until: None,
|
||||||
|
remind: None,
|
||||||
dated: HashMap::new(),
|
dated: HashMap::new(),
|
||||||
undated: Vec::new(),
|
undated: Vec::new(),
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +78,13 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn range_with_remind(&self) -> DateRange {
|
||||||
|
match &self.remind {
|
||||||
|
None => self.range,
|
||||||
|
Some(delta) => self.range.expand_by(&delta.value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Last root date mentioned in any `DONE`.
|
/// Last root date mentioned in any `DONE`.
|
||||||
fn last_done_root(&self) -> Option<NaiveDate> {
|
fn last_done_root(&self) -> Option<NaiveDate> {
|
||||||
match self.command.command {
|
match self.command.command {
|
||||||
|
|
@ -113,11 +125,31 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn entry_with_remind(&self, kind: EntryKind, dates: Option<Dates>) -> Result<Entry> {
|
||||||
|
let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) {
|
||||||
|
let index = self.command.source.file();
|
||||||
|
let start = dates.sorted().root();
|
||||||
|
let remind = delta.value.apply_date(index, dates.sorted().root())?;
|
||||||
|
if remind >= start {
|
||||||
|
return Err(Error::RemindDidNotMoveBackwards {
|
||||||
|
index,
|
||||||
|
span: delta.span,
|
||||||
|
from: start,
|
||||||
|
to: remind,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(remind)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Entry::new(self.command.source, kind, dates, remind))
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not
|
/// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not
|
||||||
/// overwrite existing entries if a root date is specified.
|
/// overwrite existing entries if a root date is specified.
|
||||||
fn add(&mut self, kind: EntryKind, dates: Option<Dates>) {
|
fn add(&mut self, entry: Entry) {
|
||||||
let entry = Entry::new(self.command.source, kind, dates);
|
if let Some(dates) = entry.dates {
|
||||||
if let Some(dates) = dates {
|
|
||||||
let root = dates.root();
|
let root = dates.root();
|
||||||
if let Some(from) = self.from {
|
if let Some(from) = self.from {
|
||||||
if root < from {
|
if root < from {
|
||||||
|
|
@ -137,9 +169,8 @@ impl<'a> CommandState<'a> {
|
||||||
|
|
||||||
/// Add an entry, ignoring [`Self::from`] and [`Self::until`]. Always
|
/// Add an entry, ignoring [`Self::from`] and [`Self::until`]. Always
|
||||||
/// overwrites existing entries if a root date is specified.
|
/// overwrites existing entries if a root date is specified.
|
||||||
fn add_forced(&mut self, kind: EntryKind, dates: Option<Dates>) {
|
fn add_forced(&mut self, entry: Entry) {
|
||||||
let entry = Entry::new(self.command.source, kind, dates);
|
if let Some(dates) = entry.dates {
|
||||||
if let Some(dates) = dates {
|
|
||||||
self.dated.insert(dates.root(), entry);
|
self.dated.insert(dates.root(), entry);
|
||||||
} else {
|
} else {
|
||||||
self.undated.push(entry);
|
self.undated.push(entry);
|
||||||
|
|
@ -160,11 +191,11 @@ impl<'a> CommandState<'a> {
|
||||||
self.eval_statement(statement)?;
|
self.eval_statement(statement)?;
|
||||||
}
|
}
|
||||||
} else if task.done.is_empty() {
|
} else if task.done.is_empty() {
|
||||||
self.add(self.kind(), None);
|
self.add(self.entry_with_remind(self.kind(), None)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for done in &task.done {
|
for done in &task.done {
|
||||||
self.eval_done(done);
|
self.eval_done(done)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -176,7 +207,7 @@ impl<'a> CommandState<'a> {
|
||||||
self.eval_statement(statement)?;
|
self.eval_statement(statement)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.add(self.kind(), None);
|
self.add(self.entry_with_remind(self.kind(), None)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -185,11 +216,12 @@ impl<'a> CommandState<'a> {
|
||||||
fn eval_statement(&mut self, statement: &Statement) -> Result<()> {
|
fn eval_statement(&mut self, statement: &Statement) -> Result<()> {
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Date(spec) => self.eval_date(spec)?,
|
Statement::Date(spec) => self.eval_date(spec)?,
|
||||||
Statement::BDate(spec) => self.eval_bdate(spec),
|
Statement::BDate(spec) => self.eval_bdate(spec)?,
|
||||||
Statement::From(date) => self.from = *date,
|
Statement::From(date) => self.from = *date,
|
||||||
Statement::Until(date) => self.until = *date,
|
Statement::Until(date) => self.until = *date,
|
||||||
Statement::Except(date) => self.eval_except(*date),
|
Statement::Except(date) => self.eval_except(*date),
|
||||||
Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?,
|
Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?,
|
||||||
|
Statement::Remind(delta) => self.eval_remind(delta),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -202,8 +234,8 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_bdate(&mut self, spec: &BirthdaySpec) {
|
fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<()> {
|
||||||
self.eval_birthday_spec(spec);
|
self.eval_birthday_spec(spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_except(&mut self, date: NaiveDate) {
|
fn eval_except(&mut self, date: NaiveDate) {
|
||||||
|
|
@ -226,10 +258,19 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_done(&mut self, done: &Done) {
|
fn eval_remind(&mut self, delta: &Option<Spanned<commands::Delta>>) {
|
||||||
self.add_forced(
|
if let Some(delta) = delta {
|
||||||
|
self.remind = Some(Spanned::new(delta.span, (&delta.value).into()));
|
||||||
|
} else {
|
||||||
|
self.remind = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_done(&mut self, done: &Done) -> Result<()> {
|
||||||
|
self.add_forced(self.entry_with_remind(
|
||||||
EntryKind::TaskDone(done.done_at),
|
EntryKind::TaskDone(done.done_at),
|
||||||
done.date.map(|date| date.into()),
|
done.date.map(|date| date.into()),
|
||||||
);
|
)?);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ use crate::files::commands::BirthdaySpec;
|
||||||
|
|
||||||
use super::super::command::CommandState;
|
use super::super::command::CommandState;
|
||||||
use super::super::date::Dates;
|
use super::super::date::Dates;
|
||||||
|
use super::super::error::Result;
|
||||||
use super::super::EntryKind;
|
use super::super::EntryKind;
|
||||||
|
|
||||||
impl<'a> CommandState<'a> {
|
impl<'a> CommandState<'a> {
|
||||||
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) {
|
pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<()> {
|
||||||
let range = match self.limit_from_until(self.range) {
|
let range = match self.limit_from_until(self.range_with_remind()) {
|
||||||
Some(range) => range,
|
Some(range) => range,
|
||||||
None => return,
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
for year in range.years() {
|
for year in range.years() {
|
||||||
|
|
@ -26,15 +27,19 @@ impl<'a> CommandState<'a> {
|
||||||
let kind = EntryKind::Birthday(age);
|
let kind = EntryKind::Birthday(age);
|
||||||
|
|
||||||
if let Some(date) = spec.date.with_year(year) {
|
if let Some(date) = spec.date.with_year(year) {
|
||||||
self.add(EntryKind::Birthday(age), Some(Dates::new(date, date)));
|
self.add(
|
||||||
|
self.entry_with_remind(EntryKind::Birthday(age), Some(Dates::new(date, date)))?,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(spec.date.month(), 2);
|
assert_eq!(spec.date.month(), 2);
|
||||||
assert_eq!(spec.date.day(), 29);
|
assert_eq!(spec.date.day(), 29);
|
||||||
|
|
||||||
let first = NaiveDate::from_ymd(year, 2, 28);
|
let first = NaiveDate::from_ymd(year, 2, 28);
|
||||||
let second = NaiveDate::from_ymd(year, 3, 1);
|
let second = NaiveDate::from_ymd(year, 3, 1);
|
||||||
self.add(kind, Some(Dates::new(first, second)));
|
self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ impl DateSpec {
|
||||||
.map(|date| date.succ())
|
.map(|date| date.succ())
|
||||||
.unwrap_or(self.start);
|
.unwrap_or(self.start);
|
||||||
let range = s
|
let range = s
|
||||||
.range
|
.range_with_remind()
|
||||||
.expand_by(&self.end_delta)
|
.expand_by(&self.end_delta)
|
||||||
.move_by(&self.start_delta)
|
.move_by(&self.start_delta)
|
||||||
.with_from(range_from)?;
|
.with_from(range_from)?;
|
||||||
|
|
@ -93,7 +93,7 @@ impl DateSpec {
|
||||||
Command::Note(_) => {
|
Command::Note(_) => {
|
||||||
let start = self.start;
|
let start = self.start;
|
||||||
let range = s
|
let range = s
|
||||||
.range
|
.range_with_remind()
|
||||||
.expand_by(&self.end_delta)
|
.expand_by(&self.end_delta)
|
||||||
.move_by(&self.start_delta);
|
.move_by(&self.start_delta);
|
||||||
(start, false, range)
|
(start, false, range)
|
||||||
|
|
@ -142,13 +142,13 @@ impl<'a> CommandState<'a> {
|
||||||
}
|
}
|
||||||
while start <= range.until() {
|
while start <= range.until() {
|
||||||
let dates = spec.dates(index, start)?;
|
let dates = spec.dates(index, start)?;
|
||||||
self.add(self.kind(), Some(dates));
|
self.add(self.entry_with_remind(self.kind(), Some(dates))?);
|
||||||
start = DateSpec::step(index, start, repeat)?;
|
start = DateSpec::step(index, start, repeat)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let dates = spec.dates(index, spec.start)?;
|
let dates = spec.dates(index, spec.start)?;
|
||||||
self.add(self.kind(), Some(dates));
|
self.add(self.entry_with_remind(self.kind(), Some(dates))?);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,7 @@ impl From<&commands::WeekdaySpec> for FormulaSpec {
|
||||||
impl FormulaSpec {
|
impl FormulaSpec {
|
||||||
fn range(&self, s: &CommandState<'_>) -> Option<DateRange> {
|
fn range(&self, s: &CommandState<'_>) -> Option<DateRange> {
|
||||||
let mut range = s
|
let mut range = s
|
||||||
.range
|
.range_with_remind()
|
||||||
.expand_by(&self.end_delta)
|
.expand_by(&self.end_delta)
|
||||||
.move_by(&self.start_delta);
|
.move_by(&self.start_delta);
|
||||||
|
|
||||||
|
|
@ -367,7 +367,7 @@ impl<'a> CommandState<'a> {
|
||||||
for day in range.days() {
|
for day in range.days() {
|
||||||
if spec.eval(index, day)? {
|
if spec.eval(index, day)? {
|
||||||
let dates = spec.dates(index, day)?;
|
let dates = spec.dates(index, day)?;
|
||||||
self.add(self.kind(), Some(dates));
|
self.add(self.entry_with_remind(self.kind(), Some(dates))?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,31 @@ pub struct Entry {
|
||||||
pub source: Source,
|
pub source: Source,
|
||||||
pub kind: EntryKind,
|
pub kind: EntryKind,
|
||||||
pub dates: Option<Dates>,
|
pub dates: Option<Dates>,
|
||||||
|
/// Remind the user of an entry before it occurs. This date should always be
|
||||||
|
/// before the entry's start date, or `None` if there is no start date.
|
||||||
|
pub remind: Option<NaiveDate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
pub fn new(source: Source, kind: EntryKind, dates: Option<Dates>) -> Self {
|
pub fn new(
|
||||||
|
source: Source,
|
||||||
|
kind: EntryKind,
|
||||||
|
dates: Option<Dates>,
|
||||||
|
remind: Option<NaiveDate>,
|
||||||
|
) -> Self {
|
||||||
|
if let Some(dates) = dates {
|
||||||
|
if let Some(remind) = remind {
|
||||||
|
assert!(remind < dates.sorted().root());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert!(remind.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
kind,
|
kind,
|
||||||
dates,
|
dates,
|
||||||
|
remind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,16 @@ pub enum Error {
|
||||||
from: NaiveDate,
|
from: NaiveDate,
|
||||||
to: NaiveDate,
|
to: NaiveDate,
|
||||||
},
|
},
|
||||||
|
/// A `REMIND`'s delta did not move backwards in time from the entry's start
|
||||||
|
/// date. Instead, it either remained at the start date (`to == from`) or
|
||||||
|
/// moved forwards in time (`from < to`).
|
||||||
|
#[error("remind delta did not move backwards")]
|
||||||
|
RemindDidNotMoveBackwards {
|
||||||
|
index: usize,
|
||||||
|
span: Span,
|
||||||
|
from: NaiveDate,
|
||||||
|
to: NaiveDate,
|
||||||
|
},
|
||||||
/// A `MOVE a TO b` statement was executed, but there was no entry at the
|
/// A `MOVE a TO b` statement was executed, but there was no entry at the
|
||||||
/// date `a`.
|
/// date `a`.
|
||||||
#[error("tried to move nonexisting entry")]
|
#[error("tried to move nonexisting entry")]
|
||||||
|
|
@ -135,6 +145,19 @@ impl Error {
|
||||||
);
|
);
|
||||||
Self::print_at(sources, index, span, msg);
|
Self::print_at(sources, index, span, msg);
|
||||||
}
|
}
|
||||||
|
Error::RemindDidNotMoveBackwards {
|
||||||
|
index,
|
||||||
|
span,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
} => {
|
||||||
|
let msg = format!(
|
||||||
|
"Remind delta did not move backwards\
|
||||||
|
\nMoved from {} to {}",
|
||||||
|
from, to
|
||||||
|
);
|
||||||
|
Self::print_at(sources, index, span, msg);
|
||||||
|
}
|
||||||
Error::MoveWithoutSource { index, span } => {
|
Error::MoveWithoutSource { index, span } => {
|
||||||
let msg = "Tried to move nonexisting entry".to_string();
|
let msg = "Tried to move nonexisting entry".to_string();
|
||||||
Self::print_at(sources, index, span, msg);
|
Self::print_at(sources, index, span, msg);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue