Move stuff to files module
This commit is contained in:
parent
5674bcdc00
commit
b4b8dd53e0
8 changed files with 31 additions and 20 deletions
336
src/files/commands.rs
Normal file
336
src/files/commands.rs
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use chrono_tz::Tz;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Time {
|
||||
pub hour: u8,
|
||||
pub min: u8,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
pub fn new(hour: u32, min: u32) -> Option<Self> {
|
||||
if hour < 24 && min < 60 || hour == 24 && min == 0 {
|
||||
Some(Self {
|
||||
hour: hour as u8,
|
||||
min: min as u8,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Weekday {
|
||||
Monday,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday,
|
||||
Sunday,
|
||||
}
|
||||
|
||||
impl Weekday {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Weekday::Monday => "mon",
|
||||
Weekday::Tuesday => "tue",
|
||||
Weekday::Wednesday => "wed",
|
||||
Weekday::Thursday => "thu",
|
||||
Weekday::Friday => "fri",
|
||||
Weekday::Saturday => "sat",
|
||||
Weekday::Sunday => "sun",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DeltaStep {
|
||||
/// `y`, move by a year, keeping the same month and day
|
||||
Year(i32),
|
||||
/// `m`, move by a month, keeping the same day `d`
|
||||
Month(i32),
|
||||
/// `M`, move by a month, keeping the same day `D`
|
||||
MonthReverse(i32),
|
||||
/// `d`
|
||||
Day(i32),
|
||||
/// `w`, move by 7 days
|
||||
Week(i32),
|
||||
/// `h`
|
||||
Hour(i32),
|
||||
/// `m`
|
||||
Minute(i32),
|
||||
/// `mon`, `tue`, `wed`, `thu`, `fri`, `sat`, `sun`
|
||||
///
|
||||
/// Move to the next occurrence of the specified weekday
|
||||
Weekday(i32, Weekday),
|
||||
}
|
||||
|
||||
impl DeltaStep {
|
||||
pub fn amount(&self) -> i32 {
|
||||
match self {
|
||||
DeltaStep::Year(i) => *i,
|
||||
DeltaStep::Month(i) => *i,
|
||||
DeltaStep::MonthReverse(i) => *i,
|
||||
DeltaStep::Day(i) => *i,
|
||||
DeltaStep::Week(i) => *i,
|
||||
DeltaStep::Hour(i) => *i,
|
||||
DeltaStep::Minute(i) => *i,
|
||||
DeltaStep::Weekday(i, _) => *i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
DeltaStep::Year(_) => "y",
|
||||
DeltaStep::Month(_) => "m",
|
||||
DeltaStep::MonthReverse(_) => "M",
|
||||
DeltaStep::Day(_) => "d",
|
||||
DeltaStep::Week(_) => "w",
|
||||
DeltaStep::Hour(_) => "h",
|
||||
DeltaStep::Minute(_) => "min",
|
||||
DeltaStep::Weekday(_, wd) => wd.name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Delta(pub Vec<DeltaStep>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DateSpec {
|
||||
pub start: NaiveDate,
|
||||
pub start_delta: Option<Delta>,
|
||||
pub start_time: Option<Time>,
|
||||
pub end: Option<NaiveDate>,
|
||||
pub end_delta: Option<Delta>,
|
||||
pub end_time: Option<Time>,
|
||||
pub repeat: Option<Delta>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WeekdaySpec {
|
||||
pub start: Weekday,
|
||||
pub start_time: Option<Time>,
|
||||
pub end: Option<Weekday>,
|
||||
pub end_delta: Option<Delta>,
|
||||
pub end_time: Option<Time>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Var {
|
||||
/// `true`, always 1
|
||||
True,
|
||||
/// `false`, always 0
|
||||
False,
|
||||
/// `mon`, always 1
|
||||
Monday,
|
||||
/// `tue`, always 2
|
||||
Tuesday,
|
||||
/// `wed`, always 3
|
||||
Wednesday,
|
||||
/// `thu`, always 4
|
||||
Thursday,
|
||||
/// `fri`, always 5
|
||||
Friday,
|
||||
/// `sat`, always 6
|
||||
Saturday,
|
||||
/// `sun`, always 7
|
||||
Sunday,
|
||||
/// `j`, see <https://en.wikipedia.org/wiki/Julian_day>
|
||||
JulianDay,
|
||||
/// `y`
|
||||
Year,
|
||||
/// `yl`, length of the current year in days
|
||||
///
|
||||
/// Equal to `isLeapYear ? 366 : 365`
|
||||
YearLength,
|
||||
/// `yd`, day of the year
|
||||
YearDay,
|
||||
/// `yD`, day of the year starting from the end
|
||||
///
|
||||
/// Equal to `yl - yd + 1`
|
||||
YearDayReverse,
|
||||
/// `yw`, 1 during the first 7 days of the year, 2 during the next etc.
|
||||
///
|
||||
/// Equal to `((yd - 1) / 7) + 1`
|
||||
YearWeek,
|
||||
/// `yW`, 1 during the last 7 days of the year, 2 during the previous etc.
|
||||
///
|
||||
/// Equal to `((yD - 1) / 7) + 1`
|
||||
YearWeekReverse,
|
||||
/// `m`
|
||||
Month,
|
||||
/// `ml`, length of the current month in days
|
||||
MonthLength,
|
||||
/// `mw`, 1 during the first 7 days of the month, 2 during the next etc.
|
||||
///
|
||||
/// Equal to `((md - 1) / 7) + 1`
|
||||
MonthWeek,
|
||||
/// `mW`, 1 during the last 7 days of the month, 2 during the previous etc.
|
||||
///
|
||||
/// Equal to `((mD - 1) / 7) + 1`
|
||||
MonthWeekReverse,
|
||||
/// `d`, day of the month
|
||||
Day,
|
||||
/// `D`, day of the month starting from the end
|
||||
///
|
||||
/// Equal to `ml - md + 1`
|
||||
DayReverse,
|
||||
/// `iy`, ISO 8601 year
|
||||
IsoYear,
|
||||
/// `iyl`, length of current ISO 8601 year **in weeks**
|
||||
IsoYearLength,
|
||||
/// `iw`, ISO 8601 week
|
||||
IsoWeek,
|
||||
/// `wd`, day of the week, starting at monday with 1
|
||||
Weekday,
|
||||
/// `e`, day of the year that easter falls on
|
||||
Easter,
|
||||
/// `isWeekday`, whether the current day is one of mon-fri
|
||||
IsWeekday,
|
||||
/// `isWeekend`, whether the current day is one of sat-sun
|
||||
IsWeekend,
|
||||
/// `isLeapYear`, whether the current year is a leap year
|
||||
IsLeapYear,
|
||||
}
|
||||
|
||||
impl Var {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
// Constants
|
||||
Var::True => "true",
|
||||
Var::False => "false",
|
||||
Var::Monday => "mon",
|
||||
Var::Tuesday => "tue",
|
||||
Var::Wednesday => "wed",
|
||||
Var::Thursday => "thu",
|
||||
Var::Friday => "fri",
|
||||
Var::Saturday => "sat",
|
||||
Var::Sunday => "sun",
|
||||
// Variables
|
||||
Var::JulianDay => "j",
|
||||
Var::Year => "y",
|
||||
Var::YearLength => "yl",
|
||||
Var::YearDay => "yd",
|
||||
Var::YearDayReverse => "yD",
|
||||
Var::YearWeek => "yw",
|
||||
Var::YearWeekReverse => "yW",
|
||||
Var::Month => "m",
|
||||
Var::MonthLength => "ml",
|
||||
Var::MonthWeek => "mw",
|
||||
Var::MonthWeekReverse => "mW",
|
||||
Var::Day => "d",
|
||||
Var::DayReverse => "D",
|
||||
Var::IsoYear => "iy",
|
||||
Var::IsoYearLength => "iyl",
|
||||
Var::IsoWeek => "iw",
|
||||
Var::Weekday => "wd",
|
||||
Var::Easter => "e",
|
||||
// Variables with "boolean" values
|
||||
Var::IsWeekday => "isWeekday",
|
||||
Var::IsWeekend => "isWeekend",
|
||||
Var::IsLeapYear => "isLeapYear",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expr {
|
||||
Lit(i64),
|
||||
Var(Var),
|
||||
Paren(Box<Expr>),
|
||||
// Integer-y operations
|
||||
Neg(Box<Expr>),
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Sub(Box<Expr>, Box<Expr>),
|
||||
Mul(Box<Expr>, Box<Expr>),
|
||||
Div(Box<Expr>, Box<Expr>),
|
||||
Mod(Box<Expr>, Box<Expr>),
|
||||
// Comparisons
|
||||
Eq(Box<Expr>, Box<Expr>),
|
||||
Neq(Box<Expr>, Box<Expr>),
|
||||
Lt(Box<Expr>, Box<Expr>),
|
||||
Lte(Box<Expr>, Box<Expr>),
|
||||
Gt(Box<Expr>, Box<Expr>),
|
||||
Gte(Box<Expr>, Box<Expr>),
|
||||
// Boolean-y operations
|
||||
Not(Box<Expr>),
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
Xor(Box<Expr>, Box<Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FormulaSpec {
|
||||
pub start: Option<Expr>, // None: *
|
||||
pub start_delta: Option<Delta>,
|
||||
pub start_time: Option<Time>,
|
||||
pub end_delta: Option<Delta>,
|
||||
pub end_time: Option<Time>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Spec {
|
||||
Date(DateSpec),
|
||||
Weekday(WeekdaySpec),
|
||||
Formula(FormulaSpec),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Done {
|
||||
pub refering_to: Option<NaiveDate>,
|
||||
pub created_at: Option<(NaiveDate, Time)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Task {
|
||||
pub title: String,
|
||||
pub when: Vec<Spec>,
|
||||
pub from: Option<NaiveDate>,
|
||||
pub until: Option<NaiveDate>,
|
||||
pub except: Vec<NaiveDate>,
|
||||
pub done: Vec<Done>,
|
||||
pub desc: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Note {
|
||||
pub title: String,
|
||||
pub when: Vec<Spec>, // Should not be empty?
|
||||
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 desc: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Command {
|
||||
Task(Task),
|
||||
Note(Note),
|
||||
Birthday(Birthday),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct File {
|
||||
pub name: PathBuf,
|
||||
pub includes: Vec<PathBuf>,
|
||||
pub timezone: Option<Tz>,
|
||||
pub commands: Vec<Command>,
|
||||
}
|
||||
286
src/files/format.rs
Normal file
286
src/files/format.rs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
use std::fmt;
|
||||
|
||||
use chrono::Datelike;
|
||||
|
||||
use super::commands::{
|
||||
Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, Expr, File, FormulaSpec,
|
||||
Note, Spec, Task, Time, Var, Weekday, WeekdaySpec,
|
||||
};
|
||||
|
||||
fn format_desc(f: &mut fmt::Formatter<'_>, desc: &[String]) -> fmt::Result {
|
||||
for line in desc {
|
||||
if line.is_empty() {
|
||||
writeln!(f, "#")?;
|
||||
} else {
|
||||
writeln!(f, "# {}", line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl fmt::Display for Time {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:02}:{:02}", self.hour, self.min)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Weekday {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
fn format_delta_step(f: &mut fmt::Formatter<'_>, step: &DeltaStep, sign: &mut i32) -> fmt::Result {
|
||||
let amount = step.amount();
|
||||
if *sign == 0 || (amount != 0 && *sign != amount.signum()) {
|
||||
write!(f, "{}", if amount >= 0 { "+" } else { "-" })?;
|
||||
}
|
||||
*sign = if amount >= 0 { 1 } else { -1 };
|
||||
if amount.abs() != 1 {
|
||||
write!(f, "{}", amount.abs())?;
|
||||
}
|
||||
write!(f, "{}", step.name())
|
||||
}
|
||||
|
||||
impl fmt::Display for Delta {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut sign = 0;
|
||||
for step in &self.0 {
|
||||
format_delta_step(f, step, &mut sign)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DateSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Start
|
||||
write!(f, "{}", self.start)?;
|
||||
for delta in &self.start_delta {
|
||||
write!(f, " {}", delta)?;
|
||||
}
|
||||
for time in &self.start_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
|
||||
// End
|
||||
if self.end.is_some() || self.end_delta.is_some() || self.end_time.is_some() {
|
||||
write!(f, " --")?;
|
||||
if let Some(date) = self.end {
|
||||
write!(f, " {}", date)?;
|
||||
}
|
||||
if let Some(delta) = &self.end_delta {
|
||||
write!(f, " {}", delta)?;
|
||||
}
|
||||
if let Some(time) = &self.end_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat
|
||||
if let Some(repeat) = &self.repeat {
|
||||
write!(f, "; {}", repeat)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for WeekdaySpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Start
|
||||
write!(f, "{}", self.start)?;
|
||||
for time in &self.start_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
|
||||
// End
|
||||
if self.end.is_some() || self.end_delta.is_some() || self.end_time.is_some() {
|
||||
write!(f, " --")?;
|
||||
if let Some(wd) = self.end {
|
||||
write!(f, " {}", wd)?;
|
||||
}
|
||||
if let Some(delta) = &self.end_delta {
|
||||
write!(f, " {}", delta)?;
|
||||
}
|
||||
if let Some(time) = &self.end_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Var {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Expr::Lit(i) => write!(f, "{}", i),
|
||||
Expr::Var(v) => write!(f, "{}", v),
|
||||
Expr::Paren(e) => write!(f, "({})", e),
|
||||
Expr::Neg(e) => write!(f, "-{}", e),
|
||||
Expr::Add(a, b) => write!(f, "{} + {}", a, b),
|
||||
Expr::Sub(a, b) => write!(f, "{} - {}", a, b),
|
||||
Expr::Mul(a, b) => write!(f, "{} * {}", a, b),
|
||||
Expr::Div(a, b) => write!(f, "{} / {}", a, b),
|
||||
Expr::Mod(a, b) => write!(f, "{} % {}", a, b),
|
||||
Expr::Eq(a, b) => write!(f, "{} = {}", a, b),
|
||||
Expr::Neq(a, b) => write!(f, "{} != {}", a, b),
|
||||
Expr::Lt(a, b) => write!(f, "{} < {}", a, b),
|
||||
Expr::Lte(a, b) => write!(f, "{} <= {}", a, b),
|
||||
Expr::Gt(a, b) => write!(f, "{} > {}", a, b),
|
||||
Expr::Gte(a, b) => write!(f, "{} >= {}", a, b),
|
||||
Expr::Not(e) => write!(f, "!{}", e),
|
||||
Expr::And(a, b) => write!(f, "{} & {}", a, b),
|
||||
Expr::Or(a, b) => write!(f, "{} | {}", a, b),
|
||||
Expr::Xor(a, b) => write!(f, "{} ^ {}", a, b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FormulaSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Start
|
||||
if let Some(expr) = &self.start {
|
||||
write!(f, "({})", expr)?;
|
||||
} else {
|
||||
write!(f, "*")?;
|
||||
}
|
||||
for delta in &self.start_delta {
|
||||
write!(f, " {}", delta)?;
|
||||
}
|
||||
for time in &self.start_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
|
||||
// End
|
||||
if self.end_delta.is_some() || self.end_time.is_some() {
|
||||
write!(f, " --")?;
|
||||
if let Some(delta) = &self.end_delta {
|
||||
write!(f, " {}", delta)?;
|
||||
}
|
||||
if let Some(time) = &self.end_time {
|
||||
write!(f, " {}", time)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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)?,
|
||||
}
|
||||
writeln!(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Done {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DONE")?;
|
||||
if let Some(date) = &self.refering_to {
|
||||
write!(f, " {}", date)?;
|
||||
}
|
||||
if let Some((date, time)) = &self.created_at {
|
||||
write!(f, " ({} {})", date, time)?;
|
||||
}
|
||||
writeln!(f)
|
||||
}
|
||||
}
|
||||
|
||||
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 done in &self.done {
|
||||
write!(f, "{}", done)?;
|
||||
}
|
||||
format_desc(f, &self.desc)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Command {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Command::Task(task) => write!(f, "{}", task),
|
||||
Command::Note(note) => write!(f, "{}", note),
|
||||
Command::Birthday(birthday) => write!(f, "{}", birthday),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for File {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut commands = self.commands.iter();
|
||||
if let Some(command) = commands.next() {
|
||||
write!(f, "{}", command)?;
|
||||
for command in commands {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", command)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
141
src/files/grammar.pest
Normal file
141
src/files/grammar.pest
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
eol = _{ NEWLINE | EOI }
|
||||
WHITESPACE = _{ !eol ~ WHITE_SPACE }
|
||||
rest_some = { (!eol ~ ANY)+ }
|
||||
rest_any = { (!eol ~ ANY)* }
|
||||
|
||||
number = @{ ASCII_DIGIT{1,9} } // Fits into an i32
|
||||
|
||||
title = { WHITESPACE ~ rest_some ~ eol }
|
||||
|
||||
year = @{ ASCII_DIGIT{4} }
|
||||
month = @{ ASCII_DIGIT{2} }
|
||||
day = @{ ASCII_DIGIT{2} }
|
||||
datum = ${ year ~ "-" ~ month ~ "-" ~ day }
|
||||
bdatum = ${ (year | "?") ~ "-" ~ month ~ "-" ~ day }
|
||||
|
||||
hour = @{ ASCII_DIGIT{2} }
|
||||
minute = @{ ASCII_DIGIT{2} }
|
||||
time = ${ hour ~ ":" ~ minute }
|
||||
|
||||
weekday = { "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun" }
|
||||
|
||||
amount_sign = { "+" | "-" }
|
||||
amount = { amount_sign? ~ number? }
|
||||
delta_weekdays = { amount ~ weekday }
|
||||
delta_minutes = { amount ~ "min" }
|
||||
delta_years = { amount ~ "y" }
|
||||
delta_months = { amount ~ "m" }
|
||||
delta_months_reverse = { amount ~ "M" }
|
||||
delta_days = { amount ~ "d" }
|
||||
delta_weeks = { amount ~ "w" }
|
||||
delta_hours = { amount ~ "h" }
|
||||
delta = {
|
||||
(
|
||||
delta_weekdays
|
||||
| delta_minutes
|
||||
| delta_years
|
||||
| delta_months
|
||||
| delta_months_reverse
|
||||
| delta_days
|
||||
| delta_weeks
|
||||
| delta_hours
|
||||
)+
|
||||
}
|
||||
|
||||
boolean = { "true" | "false" }
|
||||
variable = {
|
||||
"mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun"
|
||||
| "isWeekday" | "isWeekend" | "isLeapYear"
|
||||
| "j"
|
||||
| "yl" | "yd" | "yD" | "yw" | "yW" | "y"
|
||||
| "ml" | "mw" | "mW" | "m"
|
||||
| "d" | "D"
|
||||
| "iyl" | "iy"
|
||||
| "wd"
|
||||
| "e"
|
||||
}
|
||||
|
||||
unop_neg = { "-" }
|
||||
unop_not = { "!" }
|
||||
unop = _{ unop_neg | unop_not }
|
||||
|
||||
op_add = { "+" }
|
||||
op_sub = { "-" }
|
||||
op_mul = { "*" }
|
||||
op_div = { "/" }
|
||||
op_mod = { "%" }
|
||||
op_eq = { "=" }
|
||||
op_neq = { "!=" }
|
||||
op_lt = { "<" }
|
||||
op_lte = { "<=" }
|
||||
op_gt = { ">" }
|
||||
op_gte = { ">=" }
|
||||
op_and = { "&" }
|
||||
op_or = { "|" }
|
||||
op_xor = { "^" }
|
||||
op = _{
|
||||
op_add | op_sub | op_mul | op_div | op_mod
|
||||
| op_eq | op_neq | op_lt | op_lte | op_gt | op_gte
|
||||
| op_and | op_or | op_xor
|
||||
}
|
||||
|
||||
paren_expr = { "(" ~ expr ~ ")" }
|
||||
unop_expr = { unop ~ expr }
|
||||
term = { number | boolean | variable | paren_expr | unop_expr }
|
||||
expr = { term ~ (op ~ term)* }
|
||||
|
||||
date_fixed_start = { datum ~ delta? ~ time? }
|
||||
date_fixed_end = { datum ~ delta? ~ time? | delta ~ time? | time }
|
||||
date_fixed_repeat = { delta }
|
||||
date_fixed = { date_fixed_start ~ ("--" ~ date_fixed_end)? ~ (";" ~ date_fixed_repeat)? }
|
||||
|
||||
date_expr_start = { ("*" | paren_expr) ~ delta? ~ time? }
|
||||
date_expr_end = { delta ~ time? | time }
|
||||
date_expr = { date_expr_start ~ ("--" ~ date_expr_end)? }
|
||||
|
||||
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 }
|
||||
|
||||
bdate = !{ "BDATE" ~ bdatum ~ eol }
|
||||
from = !{ "FROM" ~ datum ~ eol }
|
||||
until = !{ "UNTIL" ~ datum ~ eol }
|
||||
except = !{ "EXCEPT" ~ datum ~ eol }
|
||||
|
||||
donedate = { "(" ~ datum ~ time ~ ")" }
|
||||
done = !{ "DONE" ~ datum? ~ donedate? ~ eol }
|
||||
|
||||
desc_line = { "#" ~ (" " ~ rest_any)? ~ eol }
|
||||
description = { desc_line* }
|
||||
|
||||
task_options = { (date | from | until | except | done)* }
|
||||
|
||||
task = {
|
||||
"TASK"
|
||||
~ title
|
||||
~ task_options
|
||||
~ description
|
||||
}
|
||||
|
||||
note_options = { (date | from | until | except)* }
|
||||
|
||||
note = {
|
||||
"NOTE"
|
||||
~ title
|
||||
~ note_options
|
||||
~ description
|
||||
}
|
||||
|
||||
birthday = {
|
||||
"BIRTHDAY"
|
||||
~ title
|
||||
~ bdate
|
||||
~ description
|
||||
}
|
||||
|
||||
empty_line = _{ WHITESPACE* ~ NEWLINE }
|
||||
command = { task | note | birthday }
|
||||
|
||||
file = ${ SOI ~ (empty_line* ~ command)* ~ empty_line* ~ WHITESPACE* ~ EOI }
|
||||
724
src/files/parse.rs
Normal file
724
src/files/parse.rs
Normal file
|
|
@ -0,0 +1,724 @@
|
|||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use pest::error::ErrorVariant;
|
||||
use pest::iterators::Pair;
|
||||
use pest::prec_climber::{Assoc, Operator, PrecClimber};
|
||||
use pest::{Parser, Span};
|
||||
|
||||
use super::commands::{
|
||||
Birthday, BirthdaySpec, Command, DateSpec, Delta, DeltaStep, Done, Expr, File, FormulaSpec,
|
||||
Note, Spec, Task, Time, Var, Weekday, WeekdaySpec,
|
||||
};
|
||||
|
||||
#[derive(pest_derive::Parser)]
|
||||
#[grammar = "files/grammar.pest"]
|
||||
struct TodayfileParser;
|
||||
|
||||
pub type Error = pest::error::Error<Rule>;
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
fn error<S: Into<String>>(span: Span, message: S) -> Error {
|
||||
Error::new_from_span(
|
||||
ErrorVariant::CustomError {
|
||||
message: message.into(),
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn fail<S: Into<String>, T>(span: Span, message: S) -> Result<T> {
|
||||
Err(error(span, message))
|
||||
}
|
||||
|
||||
fn parse_number(p: Pair<Rule>) -> i32 {
|
||||
assert_eq!(p.as_rule(), Rule::number);
|
||||
p.as_str().parse().unwrap()
|
||||
}
|
||||
|
||||
fn parse_title(p: Pair<Rule>) -> String {
|
||||
assert_eq!(p.as_rule(), Rule::title);
|
||||
let p = p.into_inner().next().unwrap();
|
||||
assert_eq!(p.as_rule(), Rule::rest_some);
|
||||
p.as_str().trim().to_string()
|
||||
}
|
||||
|
||||
fn parse_datum(p: Pair<Rule>) -> Result<NaiveDate> {
|
||||
assert_eq!(p.as_rule(), Rule::datum);
|
||||
let span = p.as_span();
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let year = p.next().unwrap().as_str().parse().unwrap();
|
||||
let month = p.next().unwrap().as_str().parse().unwrap();
|
||||
let day = p.next().unwrap().as_str().parse().unwrap();
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
match NaiveDate::from_ymd_opt(year, month, day) {
|
||||
Some(date) => Ok(date),
|
||||
None => fail(span, "invalid date"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_time(p: Pair<Rule>) -> Result<Time> {
|
||||
assert_eq!(p.as_rule(), Rule::time);
|
||||
let span = p.as_span();
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let hour = p.next().unwrap().as_str().parse().unwrap();
|
||||
let min = p.next().unwrap().as_str().parse().unwrap();
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
match Time::new(hour, min) {
|
||||
Some(time) => Ok(time),
|
||||
None => fail(span, "invalid time"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Sign {
|
||||
Positive,
|
||||
Negative,
|
||||
}
|
||||
pub struct Amount {
|
||||
sign: Option<Sign>,
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl Amount {
|
||||
pub fn with_prev_sign(mut self, prev: Option<Sign>) -> Self {
|
||||
if self.sign.is_none() {
|
||||
self.sign = prev;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Option<i32> {
|
||||
match self.sign {
|
||||
None => None,
|
||||
Some(Sign::Positive) => Some(self.value),
|
||||
Some(Sign::Negative) => Some(-self.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_amount(p: Pair<Rule>) -> Amount {
|
||||
assert_eq!(p.as_rule(), Rule::amount);
|
||||
|
||||
let mut sign = None;
|
||||
let mut value = 1;
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::amount_sign => {
|
||||
sign = Some(match p.as_str() {
|
||||
"+" => Sign::Positive,
|
||||
"-" => Sign::Negative,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
Rule::number => value = parse_number(p),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Amount { sign, value }
|
||||
}
|
||||
|
||||
fn parse_weekday(p: Pair<Rule>) -> Weekday {
|
||||
assert_eq!(p.as_rule(), Rule::weekday);
|
||||
match p.as_str() {
|
||||
"mon" => Weekday::Monday,
|
||||
"tue" => Weekday::Tuesday,
|
||||
"wed" => Weekday::Wednesday,
|
||||
"thu" => Weekday::Thursday,
|
||||
"fri" => Weekday::Friday,
|
||||
"sat" => Weekday::Saturday,
|
||||
"sun" => Weekday::Sunday,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_delta_weekdays(p: Pair<Rule>, sign: &mut Option<Sign>) -> Result<DeltaStep> {
|
||||
assert_eq!(p.as_rule(), Rule::delta_weekdays);
|
||||
let span = p.as_span();
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let amount = parse_amount(p.next().unwrap()).with_prev_sign(*sign);
|
||||
let weekday = parse_weekday(p.next().unwrap());
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
let value = amount
|
||||
.value()
|
||||
.ok_or_else(|| error(span, "ambiguous sign"))?;
|
||||
*sign = amount.sign;
|
||||
|
||||
Ok(DeltaStep::Weekday(value, weekday))
|
||||
}
|
||||
|
||||
fn parse_delta_step(
|
||||
p: Pair<Rule>,
|
||||
sign: &mut Option<Sign>,
|
||||
f: impl FnOnce(i32) -> DeltaStep,
|
||||
) -> Result<DeltaStep> {
|
||||
assert!(matches!(
|
||||
p.as_rule(),
|
||||
Rule::delta_years
|
||||
| Rule::delta_months
|
||||
| Rule::delta_months_reverse
|
||||
| Rule::delta_days
|
||||
| Rule::delta_weeks
|
||||
| Rule::delta_hours
|
||||
| Rule::delta_minutes
|
||||
));
|
||||
|
||||
let span = p.as_span();
|
||||
let amount = parse_amount(p.into_inner().next().unwrap()).with_prev_sign(*sign);
|
||||
let value = amount
|
||||
.value()
|
||||
.ok_or_else(|| error(span, "ambiguous sign"))?;
|
||||
|
||||
*sign = amount.sign;
|
||||
Ok(f(value))
|
||||
}
|
||||
|
||||
fn parse_delta(p: Pair<Rule>) -> Result<Delta> {
|
||||
assert_eq!(p.as_rule(), Rule::delta);
|
||||
|
||||
let mut sign = None;
|
||||
let mut steps = vec![];
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::delta_weekdays => steps.push(parse_delta_weekdays(p, &mut sign)?),
|
||||
Rule::delta_minutes => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Minute)?),
|
||||
Rule::delta_years => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Year)?),
|
||||
Rule::delta_months => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Month)?),
|
||||
Rule::delta_months_reverse => {
|
||||
steps.push(parse_delta_step(p, &mut sign, DeltaStep::MonthReverse)?)
|
||||
}
|
||||
Rule::delta_days => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Day)?),
|
||||
Rule::delta_weeks => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Week)?),
|
||||
Rule::delta_hours => steps.push(parse_delta_step(p, &mut sign, DeltaStep::Hour)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Delta(steps))
|
||||
}
|
||||
|
||||
fn parse_date_fixed_start(p: Pair<Rule>, spec: &mut DateSpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_fixed_start);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::datum => spec.start = parse_datum(p)?,
|
||||
Rule::delta => spec.start_delta = Some(parse_delta(p)?),
|
||||
Rule::time => spec.start_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_fixed_end(p: Pair<Rule>, spec: &mut DateSpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_fixed_end);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::datum => spec.end = Some(parse_datum(p)?),
|
||||
Rule::delta => spec.end_delta = Some(parse_delta(p)?),
|
||||
Rule::time => spec.end_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_fixed_repeat(p: Pair<Rule>, spec: &mut DateSpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_fixed_repeat);
|
||||
let mut p = p.into_inner();
|
||||
|
||||
if let Some(p) = p.next() {
|
||||
spec.repeat = Some(parse_delta(p)?);
|
||||
}
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_fixed(p: Pair<Rule>) -> Result<DateSpec> {
|
||||
assert_eq!(p.as_rule(), Rule::date_fixed);
|
||||
|
||||
let mut spec = DateSpec {
|
||||
start: NaiveDate::from_ymd(0, 1, 1),
|
||||
start_delta: None,
|
||||
start_time: None,
|
||||
end: None,
|
||||
end_delta: None,
|
||||
end_time: None,
|
||||
repeat: None,
|
||||
};
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::date_fixed_start => parse_date_fixed_start(p, &mut spec)?,
|
||||
Rule::date_fixed_end => parse_date_fixed_end(p, &mut spec)?,
|
||||
Rule::date_fixed_repeat => parse_date_fixed_repeat(p, &mut spec)?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn parse_boolean(p: Pair<Rule>) -> Var {
|
||||
assert_eq!(p.as_rule(), Rule::boolean);
|
||||
match p.as_str() {
|
||||
"true" => Var::True,
|
||||
"false" => Var::False,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_variable(p: Pair<Rule>) -> Var {
|
||||
assert_eq!(p.as_rule(), Rule::variable);
|
||||
match p.as_str() {
|
||||
"j" => Var::JulianDay,
|
||||
"y" => Var::Year,
|
||||
"yl" => Var::YearLength,
|
||||
"yd" => Var::YearDay,
|
||||
"yD" => Var::YearDayReverse,
|
||||
"yw" => Var::YearWeek,
|
||||
"yW" => Var::YearWeekReverse,
|
||||
"m" => Var::Month,
|
||||
"ml" => Var::MonthLength,
|
||||
"mw" => Var::MonthWeek,
|
||||
"mW" => Var::MonthWeekReverse,
|
||||
"d" => Var::Day,
|
||||
"D" => Var::DayReverse,
|
||||
"iy" => Var::IsoYear,
|
||||
"iyl" => Var::IsoYearLength,
|
||||
"iw" => Var::IsoWeek,
|
||||
"wd" => Var::Weekday,
|
||||
"e" => Var::Easter,
|
||||
"mon" => Var::Monday,
|
||||
"tue" => Var::Tuesday,
|
||||
"wed" => Var::Wednesday,
|
||||
"thu" => Var::Thursday,
|
||||
"fri" => Var::Friday,
|
||||
"sat" => Var::Saturday,
|
||||
"sun" => Var::Sunday,
|
||||
"isWeekday" => Var::IsWeekday,
|
||||
"isWeekend" => Var::IsWeekend,
|
||||
"isLeapYear" => Var::IsLeapYear,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_unop_expr(p: Pair<Rule>) -> Expr {
|
||||
assert_eq!(p.as_rule(), Rule::unop_expr);
|
||||
let mut p = p.into_inner();
|
||||
let p_op = p.next().unwrap();
|
||||
let p_expr = p.next().unwrap();
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
let expr = parse_expr(p_expr);
|
||||
match p_op.as_rule() {
|
||||
Rule::unop_neg => Expr::Neg(Box::new(expr)),
|
||||
Rule::unop_not => Expr::Not(Box::new(expr)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_paren_expr(p: Pair<Rule>) -> Expr {
|
||||
assert_eq!(p.as_rule(), Rule::paren_expr);
|
||||
let inner = parse_expr(p.into_inner().next().unwrap());
|
||||
Expr::Paren(Box::new(inner))
|
||||
}
|
||||
|
||||
fn parse_term(p: Pair<Rule>) -> Expr {
|
||||
assert_eq!(p.as_rule(), Rule::term);
|
||||
let p = p.into_inner().next().unwrap();
|
||||
match p.as_rule() {
|
||||
Rule::number => Expr::Lit(parse_number(p).into()),
|
||||
Rule::boolean => Expr::Var(parse_boolean(p)),
|
||||
Rule::variable => Expr::Var(parse_variable(p)),
|
||||
Rule::unop_expr => parse_unop_expr(p),
|
||||
Rule::paren_expr => parse_paren_expr(p),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_op(l: Expr, p: Pair<Rule>, r: Expr) -> Expr {
|
||||
match p.as_rule() {
|
||||
// Integer-y operations
|
||||
Rule::op_add => Expr::Add(Box::new(l), Box::new(r)),
|
||||
Rule::op_sub => Expr::Sub(Box::new(l), Box::new(r)),
|
||||
Rule::op_mul => Expr::Mul(Box::new(l), Box::new(r)),
|
||||
Rule::op_div => Expr::Div(Box::new(l), Box::new(r)),
|
||||
Rule::op_mod => Expr::Mod(Box::new(l), Box::new(r)),
|
||||
|
||||
// Comparisons
|
||||
Rule::op_eq => Expr::Eq(Box::new(l), Box::new(r)),
|
||||
Rule::op_neq => Expr::Neq(Box::new(l), Box::new(r)),
|
||||
Rule::op_lt => Expr::Lt(Box::new(l), Box::new(r)),
|
||||
Rule::op_lte => Expr::Lte(Box::new(l), Box::new(r)),
|
||||
Rule::op_gt => Expr::Gt(Box::new(l), Box::new(r)),
|
||||
Rule::op_gte => Expr::Gte(Box::new(l), Box::new(r)),
|
||||
|
||||
// Boolean-y operations
|
||||
Rule::op_and => Expr::And(Box::new(l), Box::new(r)),
|
||||
Rule::op_or => Expr::Or(Box::new(l), Box::new(r)),
|
||||
Rule::op_xor => Expr::Xor(Box::new(l), Box::new(r)),
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(p: Pair<Rule>) -> Expr {
|
||||
assert_eq!(p.as_rule(), Rule::expr);
|
||||
|
||||
fn op(rule: Rule) -> Operator<Rule> {
|
||||
Operator::new(rule, Assoc::Left)
|
||||
}
|
||||
|
||||
let climber = PrecClimber::new(vec![
|
||||
// Precedence from low to high
|
||||
op(Rule::op_or) | op(Rule::op_xor),
|
||||
op(Rule::op_and),
|
||||
op(Rule::op_eq) | op(Rule::op_neq),
|
||||
op(Rule::op_lt) | op(Rule::op_lte) | op(Rule::op_gt) | op(Rule::op_gte),
|
||||
op(Rule::op_mul) | op(Rule::op_div) | op(Rule::op_mod),
|
||||
op(Rule::op_add) | op(Rule::op_sub),
|
||||
]);
|
||||
|
||||
climber.climb(p.into_inner(), parse_term, parse_op)
|
||||
}
|
||||
|
||||
fn parse_date_expr_start(p: Pair<Rule>, spec: &mut FormulaSpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_expr_start);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::paren_expr => spec.start = Some(parse_expr(p.into_inner().next().unwrap())),
|
||||
Rule::delta => spec.start_delta = Some(parse_delta(p)?),
|
||||
Rule::time => spec.start_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_expr_end(p: Pair<Rule>, spec: &mut FormulaSpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_expr_end);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::delta => spec.end_delta = Some(parse_delta(p)?),
|
||||
Rule::time => spec.end_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_expr(p: Pair<Rule>) -> Result<FormulaSpec> {
|
||||
assert_eq!(p.as_rule(), Rule::date_expr);
|
||||
|
||||
let mut spec = FormulaSpec {
|
||||
start: None,
|
||||
start_delta: None,
|
||||
start_time: None,
|
||||
end_delta: None,
|
||||
end_time: None,
|
||||
};
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::date_expr_start => parse_date_expr_start(p, &mut spec)?,
|
||||
Rule::date_expr_end => parse_date_expr_end(p, &mut spec)?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn parse_date_weekday_start(p: Pair<Rule>, spec: &mut WeekdaySpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_weekday_start);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::weekday => spec.start = parse_weekday(p),
|
||||
Rule::time => spec.start_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_weekday_end(p: Pair<Rule>, spec: &mut WeekdaySpec) -> Result<()> {
|
||||
assert_eq!(p.as_rule(), Rule::date_weekday_end);
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::weekday => spec.end = Some(parse_weekday(p)),
|
||||
Rule::delta => spec.end_delta = Some(parse_delta(p)?),
|
||||
Rule::time => spec.end_time = Some(parse_time(p)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_date_weekday(p: Pair<Rule>) -> Result<WeekdaySpec> {
|
||||
assert_eq!(p.as_rule(), Rule::date_weekday);
|
||||
|
||||
let mut spec = WeekdaySpec {
|
||||
start: Weekday::Monday,
|
||||
start_time: None,
|
||||
end: None,
|
||||
end_delta: None,
|
||||
end_time: None,
|
||||
};
|
||||
|
||||
for p in p.into_inner() {
|
||||
match p.as_rule() {
|
||||
Rule::date_weekday_start => parse_date_weekday_start(p, &mut spec)?,
|
||||
Rule::date_weekday_end => parse_date_weekday_end(p, &mut spec)?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn parse_date(p: Pair<Rule>) -> Result<Spec> {
|
||||
assert_eq!(p.as_rule(), Rule::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),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_from(p: Pair<Rule>) -> Result<NaiveDate> {
|
||||
assert_eq!(p.as_rule(), Rule::from);
|
||||
parse_datum(p.into_inner().next().unwrap())
|
||||
}
|
||||
|
||||
fn parse_until(p: Pair<Rule>) -> Result<NaiveDate> {
|
||||
assert_eq!(p.as_rule(), Rule::until);
|
||||
parse_datum(p.into_inner().next().unwrap())
|
||||
}
|
||||
|
||||
fn parse_except(p: Pair<Rule>) -> Result<NaiveDate> {
|
||||
assert_eq!(p.as_rule(), Rule::except);
|
||||
parse_datum(p.into_inner().next().unwrap())
|
||||
}
|
||||
|
||||
fn parse_donedate(p: Pair<Rule>) -> Result<(NaiveDate, Time)> {
|
||||
assert_eq!(p.as_rule(), Rule::donedate);
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let date = parse_datum(p.next().unwrap())?;
|
||||
let time = parse_time(p.next().unwrap())?;
|
||||
|
||||
assert_eq!(p.next(), None);
|
||||
|
||||
Ok((date, time))
|
||||
}
|
||||
|
||||
fn parse_done(p: Pair<Rule>) -> Result<Done> {
|
||||
assert_eq!(p.as_rule(), Rule::done);
|
||||
|
||||
let mut refering_to = None;
|
||||
let mut created_at = None;
|
||||
|
||||
for ele in p.into_inner() {
|
||||
match ele.as_rule() {
|
||||
Rule::datum => refering_to = Some(parse_datum(ele)?),
|
||||
Rule::donedate => created_at = Some(parse_donedate(ele)?),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Done {
|
||||
refering_to,
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
when: Vec<Spec>,
|
||||
from: Option<NaiveDate>,
|
||||
until: Option<NaiveDate>,
|
||||
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(opts)
|
||||
}
|
||||
|
||||
fn parse_desc_line(p: Pair<Rule>) -> Result<String> {
|
||||
assert_eq!(p.as_rule(), Rule::desc_line);
|
||||
Ok(match p.into_inner().next() {
|
||||
None => "".to_string(),
|
||||
Some(p) => {
|
||||
assert_eq!(p.as_rule(), Rule::rest_any);
|
||||
p.as_str().trim_end().to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_description(p: Pair<Rule>) -> Result<Vec<String>> {
|
||||
assert_eq!(p.as_rule(), Rule::description);
|
||||
p.into_inner().map(parse_desc_line).collect()
|
||||
}
|
||||
|
||||
fn parse_task(p: Pair<Rule>) -> Result<Task> {
|
||||
assert_eq!(p.as_rule(), Rule::task);
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let title = parse_title(p.next().unwrap());
|
||||
let opts = parse_options(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,
|
||||
desc,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_note(p: Pair<Rule>) -> Result<Note> {
|
||||
assert_eq!(p.as_rule(), Rule::note);
|
||||
let mut p = p.into_inner();
|
||||
|
||||
let title = parse_title(p.next().unwrap());
|
||||
let opts = parse_options(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,
|
||||
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>) -> Result<Command> {
|
||||
assert_eq!(p.as_rule(), Rule::command);
|
||||
|
||||
let p = p.into_inner().next().unwrap();
|
||||
match p.as_rule() {
|
||||
Rule::task => parse_task(p).map(Command::Task),
|
||||
Rule::note => parse_note(p).map(Command::Note),
|
||||
Rule::birthday => parse_birthday(p).map(Command::Birthday),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(path: PathBuf, input: &str) -> Result<File> {
|
||||
let pathstr = path.to_string_lossy();
|
||||
let mut pairs = TodayfileParser::parse(Rule::file, input)?;
|
||||
let file = pairs.next().unwrap();
|
||||
let commands = file
|
||||
.into_inner()
|
||||
// For some reason, the EOI in `file` always gets captured
|
||||
.take_while(|p| p.as_rule() == Rule::command)
|
||||
.map(parse_command)
|
||||
.collect::<Result<_>>()
|
||||
.map_err(|e| e.with_path(&pathstr))?;
|
||||
|
||||
Ok(File {
|
||||
name: path,
|
||||
includes: vec![],
|
||||
timezone: None,
|
||||
commands,
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue