diff --git a/example.today b/example.today index 9e85170..91d1504 100644 --- a/example.today +++ b/example.today @@ -58,18 +58,17 @@ DATE 0016-04-09 +dsun; +19y DATE 0017-03-29 +dsun; +19y DATE 0018-04-17 +dsun; +19y -BIRTHDAY Max +NOTE Max BDATE 1987-05-14 -BIRTHDAY Martha +NOTE Martha BDATE ?-09-21 NOTE Physics lecture -DATE wed 14:00 -- 15:30 -DATE 2021-05-07 14:00 -- 15:30 FROM 2021-04-14 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 # doesn't fit into the title. # diff --git a/src/files/commands.rs b/src/files/commands.rs index dc9d49a..9181eb1 100644 --- a/src/files/commands.rs +++ b/src/files/commands.rs @@ -342,6 +342,22 @@ pub enum Spec { 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), + Until(Option), + Except(NaiveDate), // TODO Allow excluding ranges + Move(NaiveDate, NaiveDate), +} + #[derive(Debug, Clone, Copy)] pub enum DoneDate { Date { @@ -406,10 +422,7 @@ pub struct Done { #[derive(Debug)] pub struct Task { pub title: String, - pub when: Vec, - pub from: Option, - pub until: Option, - pub except: Vec, + pub statements: Vec, pub done: Vec, pub desc: Vec, } @@ -417,24 +430,7 @@ pub struct Task { #[derive(Debug)] pub struct Note { pub title: String, - pub when: Vec, // Should not be empty? - pub from: Option, - pub until: Option, - pub except: Vec, - pub desc: Vec, -} - -#[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, // TODO Add UNTIL to birthday + pub statements: Vec, pub desc: Vec, } @@ -442,7 +438,6 @@ pub struct Birthday { pub enum Command { Task(Task), Note(Note), - Birthday(Birthday), } #[derive(Debug)] diff --git a/src/files/format.rs b/src/files/format.rs index 76921de..97be489 100644 --- a/src/files/format.rs +++ b/src/files/format.rs @@ -3,8 +3,8 @@ use std::fmt; use chrono::Datelike; use super::commands::{ - Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, - FormulaSpec, Note, Repeat, Spanned, Spec, Task, Time, Var, Weekday, WeekdaySpec, + BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec, + Note, Repeat, Spanned, Spec, Statement, Task, Time, Var, Weekday, WeekdaySpec, }; impl fmt::Display for Spanned { @@ -191,13 +191,36 @@ impl fmt::Display for FormulaSpec { impl fmt::Display for Spec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "DATE ")?; match self { - Spec::Date(spec) => write!(f, "{}", spec)?, - Spec::Weekday(spec) => write!(f, "{}", spec)?, - Spec::Formula(spec) => write!(f, "{}", spec)?, + Spec::Date(spec) => write!(f, "{}", spec), + Spec::Weekday(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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "TASK {}", self.title)?; - for spec in &self.when { - write!(f, "{}", spec)?; - } - 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 statement in &self.statements { + write!(f, "{}", statement)?; } for done in &self.done { write!(f, "{}", done)?; @@ -253,37 +267,9 @@ impl fmt::Display for Task { impl fmt::Display for Note { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "NOTE {}", self.title)?; - for spec in &self.when { - write!(f, "{}", spec)?; + for statement in &self.statements { + 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)?; Ok(()) } @@ -294,7 +280,6 @@ impl fmt::Display for Command { match self { Command::Task(task) => write!(f, "{}", task), Command::Note(note) => write!(f, "{}", note), - Command::Birthday(birthday) => write!(f, "{}", birthday), } } } diff --git a/src/files/grammar.pest b/src/files/grammar.pest index aa027da..c334f2d 100644 --- a/src/files/grammar.pest +++ b/src/files/grammar.pest @@ -101,12 +101,14 @@ date_weekday_start = { weekday ~ time? } date_weekday_end = { weekday ~ time? | delta ~ time? | time } 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 } -from = !{ "FROM" ~ datum ~ eol } -until = !{ "UNTIL" ~ datum ~ eol } -except = !{ "EXCEPT" ~ datum ~ eol } +statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move)* } donedate = { datum ~ time ~ "--" ~ datum ~ time @@ -115,36 +117,27 @@ donedate = { | datum } done = !{ "DONE" ~ "[" ~ datum ~ "]" ~ donedate? ~ eol } +dones = { done* } desc_line = { "#" ~ (" " ~ rest_any)? ~ eol } description = { desc_line* } -task_options = { (date | from | until | except | done)* } - task = { "TASK" ~ title - ~ task_options + ~ statements + ~ dones ~ description } -note_options = { (date | from | until | except)* } - note = { "NOTE" ~ title - ~ note_options - ~ description -} - -birthday = { - "BIRTHDAY" - ~ title - ~ bdate + ~ statements ~ description } empty_line = _{ WHITESPACE* ~ NEWLINE } -command = { include | timezone | task | note | birthday } +command = { include | timezone | task | note } file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI } diff --git a/src/files/parse.rs b/src/files/parse.rs index 200ebf9..f4013a7 100644 --- a/src/files/parse.rs +++ b/src/files/parse.rs @@ -10,8 +10,8 @@ use pest::{Parser, Span}; use crate::files::commands::{Repeat, Spanned}; use super::commands::{ - Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, - FormulaSpec, Note, Spec, Task, Time, Var, Weekday, WeekdaySpec, + BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, DoneDate, Expr, File, FormulaSpec, + Note, Spec, Statement, Task, Time, Var, Weekday, WeekdaySpec, }; #[derive(pest_derive::Parser)] @@ -532,33 +532,101 @@ fn parse_date_weekday(p: Pair<'_, Rule>) -> Result { Ok(spec) } -fn parse_date(p: Pair<'_, Rule>) -> Result { - assert_eq!(p.as_rule(), Rule::date); +fn parse_stmt_date(p: Pair<'_, Rule>) -> Result { + assert_eq!(p.as_rule(), Rule::stmt_date); let p = p.into_inner().next().unwrap(); - match p.as_rule() { - Rule::date_fixed => parse_date_fixed(p).map(Spec::Date), - Rule::date_expr => parse_date_expr(p).map(Spec::Formula), - Rule::date_weekday => parse_date_weekday(p).map(Spec::Weekday), + let spec = match p.as_rule() { + Rule::date_fixed => Spec::Date(parse_date_fixed(p)?), + Rule::date_expr => Spec::Formula(parse_date_expr(p)?), + Rule::date_weekday => Spec::Weekday(parse_date_weekday(p)?), _ => unreachable!(), + }; + Ok(Statement::Date(spec)) +} + +fn parse_bdatum(p: Pair<'_, Rule>) -> Result { + assert_eq!(p.as_rule(), Rule::bdatum); + let span = p.as_span(); + let p = p.into_inner().collect::>(); + 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 { + 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 { + 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 { + 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 { + 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 { + 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> { + 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!(), + }); } -} - -fn parse_from(p: Pair<'_, Rule>) -> Result { - 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 { - 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 { - assert_eq!(p.as_rule(), Rule::except); - let datum = parse_datum(p.into_inner().next().unwrap())?; - Ok(datum.value) + Ok(statements) } fn parse_donedate(p: Pair<'_, Rule>) -> Result { @@ -609,36 +677,13 @@ fn parse_done(p: Pair<'_, Rule>) -> Result { Ok(Done { date, done_at }) } -#[derive(Default)] -struct Options { - when: Vec, - from: Option, - until: Option, - except: Vec, - done: Vec, -} - -fn parse_options(p: Pair<'_, Rule>) -> Result { - 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!(), - } +fn parse_dones(p: Pair<'_, Rule>) -> Result> { + assert_eq!(p.as_rule(), Rule::dones); + let mut dones = vec![]; + for p in p.into_inner() { + dones.push(parse_done(p)?); } - - Ok(opts) + Ok(dones) } fn parse_desc_line(p: Pair<'_, Rule>) -> Result { @@ -662,18 +707,16 @@ fn parse_task(p: Pair<'_, Rule>) -> Result { let mut p = p.into_inner(); 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())?; assert_eq!(p.next(), None); Ok(Task { title, - when: opts.when, - from: opts.from, - until: opts.until, - except: opts.except, - done: opts.done, + statements, + done, desc, }) } @@ -683,63 +726,18 @@ fn parse_note(p: Pair<'_, Rule>) -> Result { let mut p = p.into_inner(); 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())?; assert_eq!(p.next(), None); - assert!(opts.done.is_empty()); Ok(Note { title, - when: opts.when, - from: opts.from, - until: opts.until, - except: opts.except, + statements, desc, }) } -fn parse_bdatum(p: Pair<'_, Rule>) -> Result { - assert_eq!(p.as_rule(), Rule::bdatum); - let span = p.as_span(); - let p = p.into_inner().collect::>(); - 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 { - assert_eq!(p.as_rule(), Rule::bdate); - parse_bdatum(p.into_inner().next().unwrap()) -} - -fn parse_birthday(p: Pair<'_, Rule>) -> Result { - 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<()> { 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::note => file.commands.push(Command::Note(parse_note(p)?)), - Rule::birthday => file.commands.push(Command::Birthday(parse_birthday(p)?)), _ => unreachable!(), } diff --git a/src/main.rs b/src/main.rs index 6fd540a..e6b79ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,10 @@ use std::path::PathBuf; use chrono::NaiveDate; use structopt::StructOpt; -use crate::eval::DateRange; +// use crate::eval::DateRange; use crate::files::Files; -mod eval; +// mod eval; mod files; #[derive(Debug, StructOpt)] @@ -26,11 +26,11 @@ fn main() -> anyhow::Result<()> { let mut files = Files::load(&opt.file)?; 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)); + // 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.save()?;