From fe22c66c5c7cc8210f9c59fec27e9ef9b90338a5 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 21 Dec 2021 00:13:06 +0100 Subject: [PATCH 01/80] Parse REMINDs --- src/files/commands.rs | 1 + src/files/format.rs | 2 ++ src/files/grammar.pest | 3 ++- src/files/parse.rs | 12 ++++++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/files/commands.rs b/src/files/commands.rs index 80a9065..a699ce3 100644 --- a/src/files/commands.rs +++ b/src/files/commands.rs @@ -267,6 +267,7 @@ pub enum Statement { from: NaiveDate, to: NaiveDate, }, + Remind(Option>), } #[derive(Debug, Clone, Copy)] diff --git a/src/files/format.rs b/src/files/format.rs index 81fa4e0..a7764f4 100644 --- a/src/files/format.rs +++ b/src/files/format.rs @@ -221,6 +221,8 @@ impl fmt::Display for Statement { Statement::Until(None) => writeln!(f, "UNTIL *"), Statement::Except(date) => writeln!(f, "EXCEPT {}", date), Statement::Move { from, to, .. } => writeln!(f, "MOVE {} TO {}", from, to), + Statement::Remind(Some(delta)) => writeln!(f, "REMIND {}", delta), + Statement::Remind(None) => writeln!(f, "REMIND *"), } } } diff --git a/src/files/grammar.pest b/src/files/grammar.pest index c999230..d071cbb 100644 --- a/src/files/grammar.pest +++ b/src/files/grammar.pest @@ -107,8 +107,9 @@ stmt_from = !{ "FROM" ~ (datum | "*") ~ eol } stmt_until = !{ "UNTIL" ~ (datum | "*") ~ eol } stmt_except = !{ "EXCEPT" ~ datum ~ eol } stmt_move = !{ "MOVE" ~ datum ~ "TO" ~ datum ~ eol } +stmt_remind = !{ "REMIND" ~ (delta | "*") ~ eol } -statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move)* } +statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move | stmt_remind)* } donedate = { datum ~ time ~ "--" ~ datum ~ time diff --git a/src/files/parse.rs b/src/files/parse.rs index d8807b9..cfcaaf6 100644 --- a/src/files/parse.rs +++ b/src/files/parse.rs @@ -623,6 +623,17 @@ fn parse_stmt_move(p: Pair<'_, Rule>) -> Result { Ok(Statement::Move { span, from, to }) } +fn parse_stmt_remind(p: Pair<'_, Rule>) -> Result { + assert_eq!(p.as_rule(), Rule::stmt_remind); + let mut p = p.into_inner(); + let delta = match p.next() { + Some(p) => Some(parse_delta(p)?), + None => None, + }; + assert_eq!(p.next(), None); + Ok(Statement::Remind(delta)) +} + fn parse_statements(p: Pair<'_, Rule>, task: bool) -> Result> { assert_eq!(p.as_rule(), Rule::statements); let mut statements = vec![]; @@ -635,6 +646,7 @@ fn parse_statements(p: Pair<'_, Rule>, task: bool) -> Result> { Rule::stmt_until => parse_stmt_until(p)?, Rule::stmt_except => parse_stmt_except(p)?, Rule::stmt_move => parse_stmt_move(p)?, + Rule::stmt_remind => parse_stmt_remind(p)?, _ => unreachable!(), }); } From 66da16f4e309281d4aad48954c53551b374bf625 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 21 Dec 2021 00:14:14 +0100 Subject: [PATCH 02/80] Evaluate REMINDs --- src/eval/command.rs | 75 ++++++++++++++++++++++++++++-------- src/eval/command/birthday.rs | 15 +++++--- src/eval/command/date.rs | 8 ++-- src/eval/command/formula.rs | 4 +- src/eval/entry.rs | 19 ++++++++- src/eval/error.rs | 23 +++++++++++ 6 files changed, 115 insertions(+), 29 deletions(-) diff --git a/src/eval/command.rs b/src/eval/command.rs index cec8e2d..8bdac96 100644 --- a/src/eval/command.rs +++ b/src/eval/command.rs @@ -2,11 +2,14 @@ use std::collections::HashMap; use chrono::NaiveDate; -use crate::files::commands::{BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task}; -use crate::files::primitives::Span; +use crate::files::commands::{ + self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task, +}; +use crate::files::primitives::{Span, Spanned}; use crate::files::SourcedCommand; use super::date::Dates; +use super::delta::Delta; use super::{DateRange, Entry, EntryKind, Error, Result}; mod birthday; @@ -19,6 +22,7 @@ pub struct CommandState<'a> { from: Option, until: Option, + remind: Option>, dated: HashMap, undated: Vec, @@ -44,6 +48,7 @@ impl<'a> CommandState<'a> { command, from: None, until: None, + remind: None, dated: HashMap::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`. fn last_done_root(&self) -> Option { match self.command.command { @@ -113,11 +125,31 @@ impl<'a> CommandState<'a> { } } + 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 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 /// overwrite existing entries if a root date is specified. - fn add(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { let root = dates.root(); if let Some(from) = self.from { if root < from { @@ -137,9 +169,8 @@ impl<'a> CommandState<'a> { /// Add an entry, ignoring [`Self::from`] and [`Self::until`]. Always /// overwrites existing entries if a root date is specified. - fn add_forced(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add_forced(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { self.dated.insert(dates.root(), entry); } else { self.undated.push(entry); @@ -160,11 +191,11 @@ impl<'a> CommandState<'a> { self.eval_statement(statement)?; } } 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 { - self.eval_done(done); + self.eval_done(done)?; } Ok(()) @@ -176,7 +207,7 @@ impl<'a> CommandState<'a> { self.eval_statement(statement)?; } } else { - self.add(self.kind(), None); + self.add(self.entry_with_remind(self.kind(), None)?); } Ok(()) @@ -185,11 +216,12 @@ impl<'a> CommandState<'a> { fn eval_statement(&mut self, statement: &Statement) -> Result<()> { match statement { 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::Until(date) => self.until = *date, Statement::Except(date) => self.eval_except(*date), Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?, + Statement::Remind(delta) => self.eval_remind(delta), } Ok(()) } @@ -202,8 +234,8 @@ impl<'a> CommandState<'a> { } } - fn eval_bdate(&mut self, spec: &BirthdaySpec) { - self.eval_birthday_spec(spec); + fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<()> { + self.eval_birthday_spec(spec) } fn eval_except(&mut self, date: NaiveDate) { @@ -226,10 +258,19 @@ impl<'a> CommandState<'a> { } } - fn eval_done(&mut self, done: &Done) { - self.add_forced( + fn eval_remind(&mut self, delta: &Option>) { + 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), done.date.map(|date| date.into()), - ); + )?); + Ok(()) } } diff --git a/src/eval/command/birthday.rs b/src/eval/command/birthday.rs index 829160e..b10d83f 100644 --- a/src/eval/command/birthday.rs +++ b/src/eval/command/birthday.rs @@ -4,13 +4,14 @@ use crate::files::commands::BirthdaySpec; use super::super::command::CommandState; use super::super::date::Dates; +use super::super::error::Result; use super::super::EntryKind; impl<'a> CommandState<'a> { - pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) { - let range = match self.limit_from_until(self.range) { + pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<()> { + let range = match self.limit_from_until(self.range_with_remind()) { Some(range) => range, - None => return, + None => return Ok(()), }; for year in range.years() { @@ -26,15 +27,19 @@ impl<'a> CommandState<'a> { let kind = EntryKind::Birthday(age); 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 { assert_eq!(spec.date.month(), 2); assert_eq!(spec.date.day(), 29); let first = NaiveDate::from_ymd(year, 2, 28); 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(()) } } diff --git a/src/eval/command/date.rs b/src/eval/command/date.rs index 9fe1292..8ef5ff6 100644 --- a/src/eval/command/date.rs +++ b/src/eval/command/date.rs @@ -84,7 +84,7 @@ impl DateSpec { .map(|date| date.succ()) .unwrap_or(self.start); let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta) .with_from(range_from)?; @@ -93,7 +93,7 @@ impl DateSpec { Command::Note(_) => { let start = self.start; let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); (start, false, range) @@ -142,13 +142,13 @@ impl<'a> CommandState<'a> { } while start <= range.until() { 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)?; } } } else { let dates = spec.dates(index, spec.start)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.kind(), Some(dates))?); } Ok(()) } diff --git a/src/eval/command/formula.rs b/src/eval/command/formula.rs index 43f7d21..5ecc6a2 100644 --- a/src/eval/command/formula.rs +++ b/src/eval/command/formula.rs @@ -324,7 +324,7 @@ impl From<&commands::WeekdaySpec> for FormulaSpec { impl FormulaSpec { fn range(&self, s: &CommandState<'_>) -> Option { let mut range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); @@ -367,7 +367,7 @@ impl<'a> CommandState<'a> { for day in range.days() { if spec.eval(index, day)? { let dates = spec.dates(index, day)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.kind(), Some(dates))?); } } } diff --git a/src/eval/entry.rs b/src/eval/entry.rs index cdfb0f8..d3daf26 100644 --- a/src/eval/entry.rs +++ b/src/eval/entry.rs @@ -19,14 +19,31 @@ pub struct Entry { pub source: Source, pub kind: EntryKind, pub dates: Option, + /// 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, } impl Entry { - pub fn new(source: Source, kind: EntryKind, dates: Option) -> Self { + pub fn new( + source: Source, + kind: EntryKind, + dates: Option, + remind: Option, + ) -> Self { + if let Some(dates) = dates { + if let Some(remind) = remind { + assert!(remind < dates.sorted().root()); + } + } else { + assert!(remind.is_none()); + } + Self { source, kind, dates, + remind, } } diff --git a/src/eval/error.rs b/src/eval/error.rs index 16fdf5c..b485143 100644 --- a/src/eval/error.rs +++ b/src/eval/error.rs @@ -34,6 +34,16 @@ pub enum Error { from: 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 /// date `a`. #[error("tried to move nonexisting entry")] @@ -135,6 +145,19 @@ impl Error { ); 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 } => { let msg = "Tried to move nonexisting entry".to_string(); Self::print_at(sources, index, span, msg); From 05a4582f13dd6e5a54a333251e59c3b2597506c3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 21 Dec 2021 00:14:28 +0100 Subject: [PATCH 03/80] Display reminders correctly --- CHANGELOG.md | 5 +++++ src/cli/layout/day.rs | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a494d90..c0b9fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +### Added +- `REMIND` statement + ## 0.1.0 - 2021-12-20 ### Added diff --git a/src/cli/layout/day.rs b/src/cli/layout/day.rs index 0cb4c0b..71b2016 100644 --- a/src/cli/layout/day.rs +++ b/src/cli/layout/day.rs @@ -81,10 +81,13 @@ impl DayLayout { fn layout_task(&mut self, index: usize, entry: &Entry) { if let Some(dates) = entry.dates { let (start, end) = dates.sorted().dates(); - if self.today < start && (start - self.today).num_days() < 7 { - // TODO Make this adjustable, maybe even per-command - let days = (start - self.today).num_days(); - self.insert(self.today, DayEntry::ReminderUntil(index, days)); + if self.today < start { + if let Some(remind) = entry.remind { + if remind <= self.today { + let days = (start - self.today).num_days(); + self.insert(self.today, DayEntry::ReminderUntil(index, days)); + } + } } else if start < self.today && self.today < end { let days = (end - self.today).num_days(); self.insert(self.today, DayEntry::ReminderWhile(index, days)); From 1ac39c69f238cb949b88865daff155760b05e1e5 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 21 Dec 2021 19:24:27 +0100 Subject: [PATCH 04/80] Move entries to a different time --- CHANGELOG.md | 1 + src/eval/command.rs | 39 +++++++++++++++++++++++++++++++-------- src/eval/date.rs | 21 +++++++++++++++++---- src/eval/delta.rs | 8 ++++---- src/files/commands.rs | 3 ++- src/files/format.rs | 9 ++++++++- src/files/grammar.pest | 2 +- src/files/parse.rs | 20 +++++++++++++++++--- src/files/primitives.rs | 22 ++++++++++++++++++---- 9 files changed, 99 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b9fae..d12e3d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - `REMIND` statement +- `MOVE` entries to a different time ## 0.1.0 - 2021-12-20 diff --git a/src/eval/command.rs b/src/eval/command.rs index 8bdac96..b277a7d 100644 --- a/src/eval/command.rs +++ b/src/eval/command.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use chrono::NaiveDate; +use chrono::{Duration, NaiveDate}; use crate::files::commands::{ self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task, }; -use crate::files::primitives::{Span, Spanned}; +use crate::files::primitives::{Span, Spanned, Time}; use crate::files::SourcedCommand; use super::date::Dates; @@ -220,7 +220,12 @@ impl<'a> CommandState<'a> { Statement::From(date) => self.from = *date, Statement::Until(date) => self.until = *date, Statement::Except(date) => self.eval_except(*date), - Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?, + Statement::Move { + span, + from, + to, + to_time, + } => self.eval_move(*span, *from, *to, *to_time)?, Statement::Remind(delta) => self.eval_remind(delta), } Ok(()) @@ -242,13 +247,31 @@ impl<'a> CommandState<'a> { self.dated.remove(&date); } - fn eval_move(&mut self, span: Span, from: NaiveDate, to: NaiveDate) -> Result<()> { + fn eval_move( + &mut self, + span: Span, + from: NaiveDate, + to: Option, + to_time: Option