From e678a2bb314e06093c795d69f7a139573fa976fe Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 20 Nov 2021 00:12:40 +0100 Subject: [PATCH] Implement Display for all commands --- src/commands.rs | 120 +++++++++++++++++--- src/format.rs | 286 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- src/parse.rs | 10 +- 4 files changed, 396 insertions(+), 23 deletions(-) create mode 100644 src/format.rs diff --git a/src/commands.rs b/src/commands.rs index 1182875..6dc55e3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,8 +2,8 @@ use chrono::NaiveDate; #[derive(Debug)] pub struct Time { - hour: u8, - min: u8, + pub hour: u8, + pub min: u8, } impl Time { @@ -19,7 +19,7 @@ impl Time { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum Weekday { Monday, Tuesday, @@ -30,6 +30,20 @@ pub enum Weekday { 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 @@ -52,6 +66,34 @@ pub enum DeltaStep { 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); @@ -77,6 +119,24 @@ pub struct WeekdaySpec { #[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 JulianDay, /// `y` @@ -127,20 +187,6 @@ pub enum Var { Weekday, /// `e`, day of the year that easter falls on Easter, - /// `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, /// `isWeekday`, whether the current day is one of mon-fri IsWeekday, /// `isWeekend`, whether the current day is one of sat-sun @@ -149,6 +195,46 @@ pub enum Var { 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), diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..ab847ba --- /dev/null +++ b/src/format.rs @@ -0,0 +1,286 @@ +use std::fmt; + +use chrono::Datelike; + +use crate::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(()) + } +} diff --git a/src/main.rs b/src/main.rs index ffc434b..530d952 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use structopt::StructOpt; mod commands; +mod format; mod parse; #[derive(Debug, StructOpt)] @@ -16,6 +17,6 @@ fn main() -> anyhow::Result<()> { let opt = Opt::from_args(); let content = fs::read_to_string(&opt.file)?; let file = parse::parse(&opt.file, &content)?; - println!("{:#?}", file); + print!("{}", file); Ok(()) } diff --git a/src/parse.rs b/src/parse.rs index fd68477..036fb28 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -275,11 +275,11 @@ fn parse_date_fixed(p: Pair) -> Result { Ok(spec) } -fn parse_boolean(p: Pair) -> bool { +fn parse_boolean(p: Pair) -> Var { assert_eq!(p.as_rule(), Rule::boolean); match p.as_str() { - "true" => true, - "false" => false, + "true" => Var::True, + "false" => Var::False, _ => unreachable!(), } } @@ -345,7 +345,7 @@ fn parse_term(p: Pair) -> Expr { let p = p.into_inner().next().unwrap(); match p.as_rule() { Rule::number => Expr::Lit(parse_number(p).into()), - Rule::boolean => Expr::Lit(if parse_boolean(p) { 1 } else { 0 }), + 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), @@ -404,7 +404,7 @@ fn parse_date_expr_start(p: Pair, spec: &mut FormulaSpec) -> Result<()> { for p in p.into_inner() { match p.as_rule() { - Rule::paren_expr => spec.start = Some(parse_paren_expr(p)), + 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!(),