Change commands to a more statement-driven format

This commit is contained in:
Joscha 2021-12-04 19:39:15 +01:00
parent f42027e378
commit b5ef9e8134
6 changed files with 181 additions and 212 deletions

View file

@ -58,18 +58,17 @@ DATE 0016-04-09 +dsun; +19y
DATE 0017-03-29 +dsun; +19y DATE 0017-03-29 +dsun; +19y
DATE 0018-04-17 +dsun; +19y DATE 0018-04-17 +dsun; +19y
BIRTHDAY Max NOTE Max
BDATE 1987-05-14 BDATE 1987-05-14
BIRTHDAY Martha NOTE Martha
BDATE ?-09-21 BDATE ?-09-21
NOTE Physics lecture NOTE Physics lecture
DATE wed 14:00 -- 15:30
DATE 2021-05-07 14:00 -- 15:30
FROM 2021-04-14 FROM 2021-04-14
UNTIL 2021-07-28 UNTIL 2021-07-28
EXCEPT 2021-05-06 DATE wed 14:00 -- 15:30
MOVE 2021-05-06 TO 2021-05-07
# This is a description of the event. It might mention further information that # This is a description of the event. It might mention further information that
# doesn't fit into the title. # doesn't fit into the title.
# #

View file

@ -342,6 +342,22 @@ pub enum Spec {
Formula(FormulaSpec), Formula(FormulaSpec),
} }
#[derive(Debug)]
pub struct BirthdaySpec {
pub date: NaiveDate,
pub year_known: bool, // If year is unknown, use NaiveDate of year 0
}
#[derive(Debug)]
pub enum Statement {
Date(Spec),
BDate(BirthdaySpec),
From(Option<NaiveDate>),
Until(Option<NaiveDate>),
Except(NaiveDate), // TODO Allow excluding ranges
Move(NaiveDate, NaiveDate),
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum DoneDate { pub enum DoneDate {
Date { Date {
@ -406,10 +422,7 @@ pub struct Done {
#[derive(Debug)] #[derive(Debug)]
pub struct Task { pub struct Task {
pub title: String, pub title: String,
pub when: Vec<Spec>, pub statements: Vec<Statement>,
pub from: Option<NaiveDate>,
pub until: Option<NaiveDate>,
pub except: Vec<NaiveDate>,
pub done: Vec<Done>, pub done: Vec<Done>,
pub desc: Vec<String>, pub desc: Vec<String>,
} }
@ -417,24 +430,7 @@ pub struct Task {
#[derive(Debug)] #[derive(Debug)]
pub struct Note { pub struct Note {
pub title: String, pub title: String,
pub when: Vec<Spec>, // Should not be empty? pub statements: Vec<Statement>,
pub from: Option<NaiveDate>,
pub until: Option<NaiveDate>,
pub except: Vec<NaiveDate>,
pub desc: Vec<String>,
}
#[derive(Debug)]
pub struct BirthdaySpec {
pub date: NaiveDate,
pub year_known: bool, // If year is unknown, use NaiveDate of year 0
}
#[derive(Debug)]
pub struct Birthday {
pub title: String,
pub when: BirthdaySpec,
// pub until: Option<NaiveDate>, // TODO Add UNTIL to birthday
pub desc: Vec<String>, pub desc: Vec<String>,
} }
@ -442,7 +438,6 @@ pub struct Birthday {
pub enum Command { pub enum Command {
Task(Task), Task(Task),
Note(Note), Note(Note),
Birthday(Birthday),
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -3,8 +3,8 @@ use std::fmt;
use chrono::Datelike; use chrono::Datelike;
use super::commands::{ use super::commands::{
Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec,
FormulaSpec, Note, Repeat, Spanned, Spec, Task, Time, Var, Weekday, WeekdaySpec, Note, Repeat, Spanned, Spec, Statement, Task, Time, Var, Weekday, WeekdaySpec,
}; };
impl<T: fmt::Display> fmt::Display for Spanned<T> { impl<T: fmt::Display> fmt::Display for Spanned<T> {
@ -191,13 +191,36 @@ impl fmt::Display for FormulaSpec {
impl fmt::Display for Spec { impl fmt::Display for Spec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DATE ")?;
match self { match self {
Spec::Date(spec) => write!(f, "{}", spec)?, Spec::Date(spec) => write!(f, "{}", spec),
Spec::Weekday(spec) => write!(f, "{}", spec)?, Spec::Weekday(spec) => write!(f, "{}", spec),
Spec::Formula(spec) => write!(f, "{}", spec)?, Spec::Formula(spec) => write!(f, "{}", spec),
}
}
}
impl fmt::Display for BirthdaySpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.year_known {
write!(f, "{}", self.date)
} else {
write!(f, "?-{:02}-{:02}", self.date.month(), self.date.day())
}
}
}
impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Statement::Date(spec) => writeln!(f, "DATE {}", spec),
Statement::BDate(spec) => writeln!(f, "BDATE {}", spec),
Statement::From(Some(date)) => writeln!(f, "FROM {}", date),
Statement::From(None) => writeln!(f, "FROM *"),
Statement::Until(Some(date)) => writeln!(f, "UNTIL {}", date),
Statement::Until(None) => writeln!(f, "UNTIL *"),
Statement::Except(date) => writeln!(f, "EXCEPT {}", date),
Statement::Move(from, to) => writeln!(f, "MOVE {} TO {}", from, to),
} }
writeln!(f)
} }
} }
@ -230,17 +253,8 @@ impl fmt::Display for Done {
impl fmt::Display for Task { impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "TASK {}", self.title)?; writeln!(f, "TASK {}", self.title)?;
for spec in &self.when { for statement in &self.statements {
write!(f, "{}", spec)?; write!(f, "{}", statement)?;
}
if let Some(date) = self.from {
writeln!(f, "FROM {}", date)?;
}
if let Some(date) = self.until {
writeln!(f, "UNTIL {}", date)?;
}
for date in &self.except {
writeln!(f, "EXCEPT {}", date)?;
} }
for done in &self.done { for done in &self.done {
write!(f, "{}", done)?; write!(f, "{}", done)?;
@ -253,37 +267,9 @@ impl fmt::Display for Task {
impl fmt::Display for Note { impl fmt::Display for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "NOTE {}", self.title)?; writeln!(f, "NOTE {}", self.title)?;
for spec in &self.when { for statement in &self.statements {
write!(f, "{}", spec)?; write!(f, "{}", statement)?;
} }
if let Some(date) = self.from {
writeln!(f, "FROM {}", date)?;
}
if let Some(date) = self.until {
writeln!(f, "UNTIL {}", date)?;
}
for date in &self.except {
writeln!(f, "EXCEPT {}", date)?;
}
format_desc(f, &self.desc)?;
Ok(())
}
}
impl fmt::Display for BirthdaySpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.year_known {
writeln!(f, "BDATE {}", self.date)
} else {
writeln!(f, "BDATE ?-{:02}-{:02}", self.date.month(), self.date.day())
}
}
}
impl fmt::Display for Birthday {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "BIRTHDAY {}", self.title)?;
write!(f, "{}", self.when)?;
format_desc(f, &self.desc)?; format_desc(f, &self.desc)?;
Ok(()) Ok(())
} }
@ -294,7 +280,6 @@ impl fmt::Display for Command {
match self { match self {
Command::Task(task) => write!(f, "{}", task), Command::Task(task) => write!(f, "{}", task),
Command::Note(note) => write!(f, "{}", note), Command::Note(note) => write!(f, "{}", note),
Command::Birthday(birthday) => write!(f, "{}", birthday),
} }
} }
} }

View file

@ -101,12 +101,14 @@ date_weekday_start = { weekday ~ time? }
date_weekday_end = { weekday ~ time? | delta ~ time? | time } date_weekday_end = { weekday ~ time? | delta ~ time? | time }
date_weekday = { date_weekday_start ~ ("--" ~ date_weekday_end)? } date_weekday = { date_weekday_start ~ ("--" ~ date_weekday_end)? }
date = !{ "DATE" ~ (date_fixed | date_expr | date_weekday) ~ eol } stmt_date = !{ "DATE" ~ (date_fixed | date_expr | date_weekday) ~ eol }
stmt_bdate = !{ "BDATE" ~ bdatum ~ eol }
stmt_from = !{ "FROM" ~ (datum | "*") ~ eol }
stmt_until = !{ "UNTIL" ~ (datum | "*") ~ eol }
stmt_except = !{ "EXCEPT" ~ datum ~ eol }
stmt_move = !{ "MOVE" ~ datum ~ "TO" ~ datum ~ eol }
bdate = !{ "BDATE" ~ bdatum ~ eol } statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move)* }
from = !{ "FROM" ~ datum ~ eol }
until = !{ "UNTIL" ~ datum ~ eol }
except = !{ "EXCEPT" ~ datum ~ eol }
donedate = { donedate = {
datum ~ time ~ "--" ~ datum ~ time datum ~ time ~ "--" ~ datum ~ time
@ -115,36 +117,27 @@ donedate = {
| datum | datum
} }
done = !{ "DONE" ~ "[" ~ datum ~ "]" ~ donedate? ~ eol } done = !{ "DONE" ~ "[" ~ datum ~ "]" ~ donedate? ~ eol }
dones = { done* }
desc_line = { "#" ~ (" " ~ rest_any)? ~ eol } desc_line = { "#" ~ (" " ~ rest_any)? ~ eol }
description = { desc_line* } description = { desc_line* }
task_options = { (date | from | until | except | done)* }
task = { task = {
"TASK" "TASK"
~ title ~ title
~ task_options ~ statements
~ dones
~ description ~ description
} }
note_options = { (date | from | until | except)* }
note = { note = {
"NOTE" "NOTE"
~ title ~ title
~ note_options ~ statements
~ description
}
birthday = {
"BIRTHDAY"
~ title
~ bdate
~ description ~ description
} }
empty_line = _{ WHITESPACE* ~ NEWLINE } empty_line = _{ WHITESPACE* ~ NEWLINE }
command = { include | timezone | task | note | birthday } command = { include | timezone | task | note }
file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI } file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI }

View file

@ -10,8 +10,8 @@ use pest::{Parser, Span};
use crate::files::commands::{Repeat, Spanned}; use crate::files::commands::{Repeat, Spanned};
use super::commands::{ use super::commands::{
Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec,
FormulaSpec, Note, Spec, Task, Time, Var, Weekday, WeekdaySpec, Note, Spec, Statement, Task, Time, Var, Weekday, WeekdaySpec,
}; };
#[derive(pest_derive::Parser)] #[derive(pest_derive::Parser)]
@ -532,33 +532,101 @@ fn parse_date_weekday(p: Pair<'_, Rule>) -> Result<WeekdaySpec> {
Ok(spec) Ok(spec)
} }
fn parse_date(p: Pair<'_, Rule>) -> Result<Spec> { fn parse_stmt_date(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::date); assert_eq!(p.as_rule(), Rule::stmt_date);
let p = p.into_inner().next().unwrap(); let p = p.into_inner().next().unwrap();
match p.as_rule() { let spec = match p.as_rule() {
Rule::date_fixed => parse_date_fixed(p).map(Spec::Date), Rule::date_fixed => Spec::Date(parse_date_fixed(p)?),
Rule::date_expr => parse_date_expr(p).map(Spec::Formula), Rule::date_expr => Spec::Formula(parse_date_expr(p)?),
Rule::date_weekday => parse_date_weekday(p).map(Spec::Weekday), Rule::date_weekday => Spec::Weekday(parse_date_weekday(p)?),
_ => unreachable!(), _ => unreachable!(),
};
Ok(Statement::Date(spec))
}
fn parse_bdatum(p: Pair<'_, Rule>) -> Result<BirthdaySpec> {
assert_eq!(p.as_rule(), Rule::bdatum);
let span = p.as_span();
let p = p.into_inner().collect::<Vec<_>>();
assert!(p.len() == 2 || p.len() == 3);
let (y, m, d, year_known) = if p.len() == 3 {
let y = p[0].as_str().parse().unwrap();
let m = p[1].as_str().parse().unwrap();
let d = p[2].as_str().parse().unwrap();
(y, m, d, true)
} else {
let m = p[0].as_str().parse().unwrap();
let d = p[1].as_str().parse().unwrap();
(0, m, d, false)
};
let date = match NaiveDate::from_ymd_opt(y, m, d) {
Some(date) => Ok(date),
None => fail(span, "invalid date"),
}?;
Ok(BirthdaySpec { date, year_known })
}
fn parse_stmt_bdate(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::stmt_bdate);
let spec = parse_bdatum(p.into_inner().next().unwrap())?;
Ok(Statement::BDate(spec))
}
fn parse_stmt_from(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::stmt_from);
let mut p = p.into_inner();
let datum = match p.next() {
Some(p) => Some(parse_datum(p)?.value),
None => None,
};
assert_eq!(p.next(), None);
Ok(Statement::From(datum))
}
fn parse_stmt_until(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::stmt_until);
let mut p = p.into_inner();
let datum = match p.next() {
Some(p) => Some(parse_datum(p)?.value),
None => None,
};
assert_eq!(p.next(), None);
Ok(Statement::Until(datum))
}
fn parse_stmt_except(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::stmt_except);
let datum = parse_datum(p.into_inner().next().unwrap())?.value;
Ok(Statement::Except(datum))
}
fn parse_stmt_move(p: Pair<'_, Rule>) -> Result<Statement> {
assert_eq!(p.as_rule(), Rule::stmt_move);
let mut p = p.into_inner();
let from = parse_datum(p.next().unwrap())?.value;
let to = parse_datum(p.next().unwrap())?.value;
assert_eq!(p.next(), None);
Ok(Statement::Move(from, to))
}
fn parse_statements(p: Pair<'_, Rule>) -> Result<Vec<Statement>> {
assert_eq!(p.as_rule(), Rule::statements);
let mut statements = vec![];
for p in p.into_inner() {
statements.push(match p.as_rule() {
Rule::stmt_date => parse_stmt_date(p)?,
Rule::stmt_bdate => parse_stmt_bdate(p)?,
Rule::stmt_from => parse_stmt_from(p)?,
Rule::stmt_until => parse_stmt_until(p)?,
Rule::stmt_except => parse_stmt_except(p)?,
Rule::stmt_move => parse_stmt_move(p)?,
_ => unreachable!(),
});
} }
} Ok(statements)
fn parse_from(p: Pair<'_, Rule>) -> Result<NaiveDate> {
assert_eq!(p.as_rule(), Rule::from);
let datum = parse_datum(p.into_inner().next().unwrap())?;
Ok(datum.value)
}
fn parse_until(p: Pair<'_, Rule>) -> Result<NaiveDate> {
assert_eq!(p.as_rule(), Rule::until);
let datum = parse_datum(p.into_inner().next().unwrap())?;
Ok(datum.value)
}
fn parse_except(p: Pair<'_, Rule>) -> Result<NaiveDate> {
assert_eq!(p.as_rule(), Rule::except);
let datum = parse_datum(p.into_inner().next().unwrap())?;
Ok(datum.value)
} }
fn parse_donedate(p: Pair<'_, Rule>) -> Result<DoneDate> { fn parse_donedate(p: Pair<'_, Rule>) -> Result<DoneDate> {
@ -609,36 +677,13 @@ fn parse_done(p: Pair<'_, Rule>) -> Result<Done> {
Ok(Done { date, done_at }) Ok(Done { date, done_at })
} }
#[derive(Default)] fn parse_dones(p: Pair<'_, Rule>) -> Result<Vec<Done>> {
struct Options { assert_eq!(p.as_rule(), Rule::dones);
when: Vec<Spec>, let mut dones = vec![];
from: Option<NaiveDate>, for p in p.into_inner() {
until: Option<NaiveDate>, dones.push(parse_done(p)?);
except: Vec<NaiveDate>,
done: Vec<Done>,
}
fn parse_options(p: Pair<'_, Rule>) -> Result<Options> {
assert!(matches!(
p.as_rule(),
Rule::task_options | Rule::note_options
));
let mut opts = Options::default();
for opt in p.into_inner() {
match opt.as_rule() {
Rule::date => opts.when.push(parse_date(opt)?),
Rule::from if opts.from.is_none() => opts.from = Some(parse_from(opt)?),
Rule::from => fail(opt.as_span(), "FROM already defined earlier")?,
Rule::until if opts.until.is_none() => opts.until = Some(parse_until(opt)?),
Rule::until => fail(opt.as_span(), "UNTIL already defined earlier")?,
Rule::except => opts.except.push(parse_except(opt)?),
Rule::done => opts.done.push(parse_done(opt)?),
_ => unreachable!(),
} }
} Ok(dones)
Ok(opts)
} }
fn parse_desc_line(p: Pair<'_, Rule>) -> Result<String> { fn parse_desc_line(p: Pair<'_, Rule>) -> Result<String> {
@ -662,18 +707,16 @@ fn parse_task(p: Pair<'_, Rule>) -> Result<Task> {
let mut p = p.into_inner(); let mut p = p.into_inner();
let title = parse_title(p.next().unwrap()); let title = parse_title(p.next().unwrap());
let opts = parse_options(p.next().unwrap())?; let statements = parse_statements(p.next().unwrap())?;
let done = parse_dones(p.next().unwrap())?;
let desc = parse_description(p.next().unwrap())?; let desc = parse_description(p.next().unwrap())?;
assert_eq!(p.next(), None); assert_eq!(p.next(), None);
Ok(Task { Ok(Task {
title, title,
when: opts.when, statements,
from: opts.from, done,
until: opts.until,
except: opts.except,
done: opts.done,
desc, desc,
}) })
} }
@ -683,63 +726,18 @@ fn parse_note(p: Pair<'_, Rule>) -> Result<Note> {
let mut p = p.into_inner(); let mut p = p.into_inner();
let title = parse_title(p.next().unwrap()); let title = parse_title(p.next().unwrap());
let opts = parse_options(p.next().unwrap())?; let statements = parse_statements(p.next().unwrap())?;
let desc = parse_description(p.next().unwrap())?; let desc = parse_description(p.next().unwrap())?;
assert_eq!(p.next(), None); assert_eq!(p.next(), None);
assert!(opts.done.is_empty());
Ok(Note { Ok(Note {
title, title,
when: opts.when, statements,
from: opts.from,
until: opts.until,
except: opts.except,
desc, desc,
}) })
} }
fn parse_bdatum(p: Pair<'_, Rule>) -> Result<BirthdaySpec> {
assert_eq!(p.as_rule(), Rule::bdatum);
let span = p.as_span();
let p = p.into_inner().collect::<Vec<_>>();
assert!(p.len() == 2 || p.len() == 3);
let (y, m, d, year_known) = if p.len() == 3 {
let y = p[0].as_str().parse().unwrap();
let m = p[1].as_str().parse().unwrap();
let d = p[2].as_str().parse().unwrap();
(y, m, d, true)
} else {
let m = p[0].as_str().parse().unwrap();
let d = p[1].as_str().parse().unwrap();
(0, m, d, false)
};
let date = match NaiveDate::from_ymd_opt(y, m, d) {
Some(date) => Ok(date),
None => fail(span, "invalid date"),
}?;
Ok(BirthdaySpec { date, year_known })
}
fn parse_bdate(p: Pair<'_, Rule>) -> Result<BirthdaySpec> {
assert_eq!(p.as_rule(), Rule::bdate);
parse_bdatum(p.into_inner().next().unwrap())
}
fn parse_birthday(p: Pair<'_, Rule>) -> Result<Birthday> {
assert_eq!(p.as_rule(), Rule::birthday);
let mut p = p.into_inner();
let title = parse_title(p.next().unwrap());
let when = parse_bdate(p.next().unwrap())?;
let desc = parse_description(p.next().unwrap())?;
Ok(Birthday { title, when, desc })
}
fn parse_command(p: Pair<'_, Rule>, file: &mut File) -> Result<()> { fn parse_command(p: Pair<'_, Rule>, file: &mut File) -> Result<()> {
assert_eq!(p.as_rule(), Rule::command); assert_eq!(p.as_rule(), Rule::command);
@ -752,7 +750,6 @@ fn parse_command(p: Pair<'_, Rule>, file: &mut File) -> Result<()> {
}, },
Rule::task => file.commands.push(Command::Task(parse_task(p)?)), Rule::task => file.commands.push(Command::Task(parse_task(p)?)),
Rule::note => file.commands.push(Command::Note(parse_note(p)?)), Rule::note => file.commands.push(Command::Note(parse_note(p)?)),
Rule::birthday => file.commands.push(Command::Birthday(parse_birthday(p)?)),
_ => unreachable!(), _ => unreachable!(),
} }

View file

@ -8,10 +8,10 @@ use std::path::PathBuf;
use chrono::NaiveDate; use chrono::NaiveDate;
use structopt::StructOpt; use structopt::StructOpt;
use crate::eval::DateRange; // use crate::eval::DateRange;
use crate::files::Files; use crate::files::Files;
mod eval; // mod eval;
mod files; mod files;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@ -26,11 +26,11 @@ 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( // let range = DateRange::new(
NaiveDate::from_ymd(2021, 11, 20), // NaiveDate::from_ymd(2021, 11, 20),
NaiveDate::from_ymd(2021, 11, 26), // NaiveDate::from_ymd(2021, 11, 26),
); // );
println!("{:#?}", files.eval(range)); // println!("{:#?}", files.eval(range));
files.mark_all_dirty(); files.mark_all_dirty();
files.save()?; files.save()?;