From 06ecf416636d9cdba74fa76d64032a40350c693c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 11:58:02 -0400 Subject: [PATCH 01/19] Add notes about the fields for timestamps. --- src/compare/diff.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 1075383..654a1ed 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2119,7 +2119,18 @@ fn compare_timestamp<'b, 's>( let this_status = DiffStatus::Good; let message = None; - // TODO: Compare :type :range-type :raw-value :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end + // TODO: Compare :type :range-type :raw-value :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit + // + // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. + // :range-type unquoted atom either nil, daterange + // :raw-value quoted string of the source + // :*-start :*-end unquoted integers + // :repeater-type optional unquoted atom with value cumulate + // :repeater-value unquoted integer + // :repeater-unit unquoted atom with value week + // :warning-type optional unquoted atom with value all + // :warning-value unquoted integer + // :warning-unit unquoted atom with value day Ok(DiffResult { status: this_status, From d04c8c832ccb79d45a8be6b6cb9e0fd333784141 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 13:33:00 -0400 Subject: [PATCH 02/19] Compare timestamp type. --- src/compare/diff.rs | 24 +++++++++++++++++++++--- src/parser/timestamp.rs | 9 +++++++++ src/types/mod.rs | 1 + src/types/object.rs | 11 +++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 654a1ed..511cda2 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -12,8 +12,8 @@ use super::util::get_property; use super::util::get_property_boolean; use super::util::get_property_quoted_string; use super::util::get_property_unquoted_atom; -use crate::types::AstNode; use crate::types::AngleLink; +use crate::types::AstNode; use crate::types::BabelCall; use crate::types::Bold; use crate::types::CheckboxType; @@ -74,6 +74,7 @@ use crate::types::TableCell; use crate::types::TableRow; use crate::types::Target; use crate::types::Timestamp; +use crate::types::TimestampType; use crate::types::TodoKeywordType; use crate::types::Underline; use crate::types::Verbatim; @@ -2116,8 +2117,25 @@ fn compare_timestamp<'b, 's>( emacs: &'b Token<'s>, rust: &'b Timestamp<'s>, ) -> Result, Box> { - let this_status = DiffStatus::Good; - let message = None; + let mut this_status = DiffStatus::Good; + let mut message = None; + + // Compare type + let timestamp_type = get_property_unquoted_atom(emacs, ":type")?; + match (timestamp_type, &rust.timestamp_type) { + (Some("diary"), TimestampType::Diary) => {} + (Some("active"), TimestampType::Active) => {} + (Some("inactive"), TimestampType::Inactive) => {} + (Some("active-range"), TimestampType::ActiveRange) => {} + (Some("inactive-range"), TimestampType::InactiveRange) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Timestamp type mismatch (emacs != rust) {:?} != {:?}", + timestamp_type, rust.timestamp_type + )); + } + } // TODO: Compare :type :range-type :raw-value :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 1d4e50c..a23f9c5 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -22,6 +22,7 @@ use crate::context::RefContext; use crate::error::Res; use crate::parser::util::get_consumed; use crate::types::Timestamp; +use crate::types::TimestampType; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn timestamp<'b, 'g, 'r, 's>( @@ -57,6 +58,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::Diary, }, )) } @@ -121,6 +123,7 @@ fn active_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::Active, }, )) } @@ -155,6 +158,7 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::Inactive, }, )) } @@ -177,6 +181,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::ActiveRange, }, )) } @@ -218,6 +223,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::Active, }, )) } @@ -240,6 +246,8 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + + timestamp_type: TimestampType::InactiveRange, }, )) } @@ -281,6 +289,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), + timestamp_type: TimestampType::Inactive, }, )) } diff --git a/src/types/mod.rs b/src/types/mod.rs index 6f41156..d635cd5 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -75,6 +75,7 @@ pub use object::Subscript; pub use object::Superscript; pub use object::Target; pub use object::Timestamp; +pub use object::TimestampType; pub use object::Underline; pub use object::Verbatim; pub(crate) use source::SetSource; diff --git a/src/types/object.rs b/src/types/object.rs index 2d8e06e..90f095d 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -1,6 +1,7 @@ use super::GetStandardProperties; use super::StandardProperties; +// TODO: Why did we make Object implement PartialEq again? Was it just for tests? #[derive(Debug, PartialEq)] pub enum Object<'s> { Bold(Bold<'s>), @@ -184,6 +185,16 @@ pub struct Superscript<'s> { #[derive(Debug, PartialEq)] pub struct Timestamp<'s> { pub source: &'s str, + pub timestamp_type: TimestampType, +} + +#[derive(Debug, PartialEq)] +pub enum TimestampType { + Diary, + Active, + Inactive, + ActiveRange, + InactiveRange, } impl<'s> GetStandardProperties<'s> for Object<'s> { From 3ed9b552e2d1763a576bd320dd0e1c8d49babe95 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 13:42:46 -0400 Subject: [PATCH 03/19] Compare range type. --- src/compare/diff.rs | 14 ++++++++++++++ src/parser/timestamp.rs | 8 ++++++++ src/types/mod.rs | 1 + src/types/object.rs | 7 +++++++ 4 files changed, 30 insertions(+) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 511cda2..b3b27a4 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -74,6 +74,7 @@ use crate::types::TableCell; use crate::types::TableRow; use crate::types::Target; use crate::types::Timestamp; +use crate::types::TimestampRangeType; use crate::types::TimestampType; use crate::types::TodoKeywordType; use crate::types::Underline; @@ -2137,6 +2138,19 @@ fn compare_timestamp<'b, 's>( } } + let range_type = get_property_unquoted_atom(emacs, ":range-type")?; + match (range_type, &rust.range_type) { + (Some("daterange"), TimestampRangeType::DateRange) => {} + (None, TimestampRangeType::None) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Range type mismatch (emacs != rust) {:?} != {:?}", + range_type, rust.range_type + )); + } + } + // TODO: Compare :type :range-type :raw-value :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index a23f9c5..4be2845 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -22,6 +22,7 @@ use crate::context::RefContext; use crate::error::Res; use crate::parser::util::get_consumed; use crate::types::Timestamp; +use crate::types::TimestampRangeType; use crate::types::TimestampType; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -59,6 +60,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::Diary, + range_type: TimestampRangeType::None, }, )) } @@ -124,6 +126,7 @@ fn active_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::Active, + range_type: TimestampRangeType::None, }, )) } @@ -159,6 +162,7 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::Inactive, + range_type: TimestampRangeType::None, }, )) } @@ -182,6 +186,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::ActiveRange, + range_type: TimestampRangeType::DateRange, }, )) } @@ -224,6 +229,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::Active, + range_type: TimestampRangeType::None, }, )) } @@ -248,6 +254,7 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::InactiveRange, + range_type: TimestampRangeType::DateRange, }, )) } @@ -290,6 +297,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( Timestamp { source: source.into(), timestamp_type: TimestampType::Inactive, + range_type: TimestampRangeType::None, }, )) } diff --git a/src/types/mod.rs b/src/types/mod.rs index d635cd5..ab01b7f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -75,6 +75,7 @@ pub use object::Subscript; pub use object::Superscript; pub use object::Target; pub use object::Timestamp; +pub use object::TimestampRangeType; pub use object::TimestampType; pub use object::Underline; pub use object::Verbatim; diff --git a/src/types/object.rs b/src/types/object.rs index 90f095d..b955ba2 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -186,6 +186,7 @@ pub struct Superscript<'s> { pub struct Timestamp<'s> { pub source: &'s str, pub timestamp_type: TimestampType, + pub range_type: TimestampRangeType, } #[derive(Debug, PartialEq)] @@ -197,6 +198,12 @@ pub enum TimestampType { InactiveRange, } +#[derive(Debug, PartialEq)] +pub enum TimestampRangeType { + None, + DateRange, +} + impl<'s> GetStandardProperties<'s> for Object<'s> { fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> { match self { From ecdfd7087f2fb3db6b46862070eaaa7b8b3b7484 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 14:45:20 -0400 Subject: [PATCH 04/19] Compare raw-value. --- src/compare/diff.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index b3b27a4..a34c6f7 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2138,6 +2138,7 @@ fn compare_timestamp<'b, 's>( } } + // Compare range-type let range_type = get_property_unquoted_atom(emacs, ":range-type")?; match (range_type, &rust.range_type) { (Some("daterange"), TimestampRangeType::DateRange) => {} @@ -2151,7 +2152,18 @@ fn compare_timestamp<'b, 's>( } } - // TODO: Compare :type :range-type :raw-value :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit + // Compare raw-value + let raw_value = get_property_quoted_string(emacs, ":raw-value")? + .ok_or("Timestamps should have a :raw-value.")?; + if raw_value != rust.source { + this_status = DiffStatus::Bad; + message = Some(format!( + "Raw value mismatch (emacs != rust) {:?} != {:?}", + raw_value, rust.source + )); + } + + // TODO: Compare :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. // :range-type unquoted atom either nil, daterange From 10ae36a419c2e6ef8217bc57c2ef29e47ff9d042 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 15:10:39 -0400 Subject: [PATCH 05/19] Implement date types with basic validation. --- src/parser/timestamp.rs | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 4be2845..ffa9831 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -302,6 +302,109 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( )) } +pub struct Year(u16); +pub struct Month(u8); +pub struct DayOfMonth(u8); + +impl Year { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let year = source.parse::()?; + Ok(Year(year)) + } + + pub fn get_value(&self) -> u16 { + self.0 + } +} + +impl Month { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let month = source.parse::()?; + if month < 1 || month > 12 { + Err("Month exceeds possible range.")?; + } + Ok(Month(month)) + } + + pub fn get_value(&self) -> u8 { + self.0 + } +} + +impl DayOfMonth { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let day_of_month = source.parse::()?; + if day_of_month < 1 || day_of_month > 31 { + Err("Day of month exceeds possible range.")?; + } + Ok(DayOfMonth(day_of_month)) + } + + pub fn get_value(&self) -> u8 { + self.0 + } +} + +pub struct Date<'s> { + year: Year, + month: Month, + day_of_month: DayOfMonth, + day_name: &'s str, +} + +impl<'s> Date<'s> { + // TODO: Make a real error type instead of a boxed any error. + pub fn new( + year: Year, + month: Month, + day_of_month: DayOfMonth, + day_name: &'s str, + ) -> Result> { + // TODO: Does org-mode support non-gregorian calendars? + // TODO: Do I want to validate leap year? + match (month.get_value(), day_of_month.get_value()) { + (1, 1..=31) => {} + (2, 1..=29) => {} + (3, 1..=31) => {} + (4, 1..=30) => {} + (5, 1..=31) => {} + (6, 1..=30) => {} + (7, 1..=31) => {} + (8, 1..=31) => {} + (9, 1..=30) => {} + (10, 1..=31) => {} + (11, 1..=30) => {} + (12, 1..=31) => {} + _ => Err("Invalid day of month for the month.")?, + }; + Ok(Date { + year, + month, + day_of_month, + day_name, + }) + } + + pub fn get_year(&self) -> &Year { + &self.year + } + + pub fn get_month(&self) -> &Month { + &self.month + } + + pub fn get_day_of_month(&self) -> &DayOfMonth { + &self.day_of_month + } + + pub fn get_day_name(&self) -> &'s str { + self.day_name + } +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn date<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, From e7ec23af3d66fc55bbc60dd88df65ca7680ba243 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 15:49:51 -0400 Subject: [PATCH 06/19] Move the Date struct into types and implement a get_property_numeric. --- src/compare/diff.rs | 16 ++++ src/compare/util.rs | 22 ++++++ src/error/error.rs | 7 ++ src/parser/org_source.rs | 1 + src/parser/timestamp.rs | 154 +++++++++++---------------------------- src/types/mod.rs | 4 + src/types/object.rs | 111 ++++++++++++++++++++++++++++ 7 files changed, 203 insertions(+), 112 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index a34c6f7..745f556 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -10,6 +10,7 @@ use super::sexp::Token; use super::util::compare_standard_properties; use super::util::get_property; use super::util::get_property_boolean; +use super::util::get_property_numeric; use super::util::get_property_quoted_string; use super::util::get_property_unquoted_atom; use crate::types::AngleLink; @@ -2163,6 +2164,21 @@ fn compare_timestamp<'b, 's>( )); } + // Compare start + let year_start: Option = get_property_unquoted_atom(emacs, ":year-start")? + .map(|val| val.parse()) + .map_or(Ok(None), |r| r.map(Some))?; + let month_start: Option = get_property_unquoted_atom(emacs, ":month-start")? + .map(|val| val.parse()) + .map_or(Ok(None), |r| r.map(Some))?; + let day_of_month_start: Option = get_property_unquoted_atom(emacs, ":day-start")? + .map(|val| val.parse()) + .map_or(Ok(None), |r| r.map(Some))?; + + let year_end = get_property_numeric::(emacs, ":year-end")?; + + // Compare end + // TODO: Compare :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. diff --git a/src/compare/util.rs b/src/compare/util.rs index 9c001f2..6f1c922 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use super::elisp_fact::GetElispFact; use super::sexp::Token; use crate::compare::sexp::unquote; @@ -234,3 +236,23 @@ pub(crate) fn get_property_boolean<'b, 's, 'x>( .unwrap_or("nil") != "nil") } + +/// Get a named property containing an unquoted numeric value. +/// +/// Returns None if key is not found. +pub(crate) fn get_property_numeric<'b, 's, 'x, N: FromStr>( + emacs: &'b Token<'s>, + key: &'x str, +) -> Result, Box> +where + ::Err: std::error::Error, + ::Err: 'static, +{ + let foo = get_property(emacs, key)? + .map(Token::as_atom) + .map_or(Ok(None), |r| r.map(Some))?; + let bar = foo + .map(|val| val.parse::()) + .map_or(Ok(None), |r| r.map(Some))?; + Ok(bar) +} diff --git a/src/error/error.rs b/src/error/error.rs index 8032a25..b7e1481 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -9,6 +9,7 @@ pub enum CustomError { MyError(MyError<&'static str>), Nom(I, ErrorKind), IO(std::io::Error), + BoxedError(Box), } #[derive(Debug)] @@ -36,3 +37,9 @@ impl From<&'static str> for CustomError { CustomError::MyError(MyError(value)) } } + +impl From> for CustomError { + fn from(value: Box) -> Self { + CustomError::BoxedError(value) + } +} diff --git a/src/parser/org_source.rs b/src/parser/org_source.rs index 8e3a9a6..1aa29a7 100644 --- a/src/parser/org_source.rs +++ b/src/parser/org_source.rs @@ -388,6 +388,7 @@ impl<'s> From>> for CustomError<&'s str> { CustomError::MyError(err) => CustomError::MyError(err.into()), CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind), CustomError::IO(err) => CustomError::IO(err), + CustomError::BoxedError(err) => CustomError::BoxedError(err), } } } diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index ffa9831..20d1a4c 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -21,9 +21,13 @@ use crate::context::ExitMatcherNode; use crate::context::RefContext; use crate::error::Res; use crate::parser::util::get_consumed; +use crate::types::Date; +use crate::types::DayOfMonth; +use crate::types::Month; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; +use crate::types::Year; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn timestamp<'b, 'g, 'r, 's>( @@ -61,6 +65,8 @@ fn diary_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Diary, range_type: TimestampRangeType::None, + start: None, + end: None, }, )) } @@ -101,7 +107,7 @@ fn active_timestamp<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { let (remaining, _) = tag("<")(input)?; - let (remaining, _date) = date(context, remaining)?; + let (remaining, start) = date(context, remaining)?; let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &active_time_rest_end, @@ -127,6 +133,8 @@ fn active_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Active, range_type: TimestampRangeType::None, + start: Some(start), + end: None, }, )) } @@ -137,7 +145,7 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { let (remaining, _) = tag("[")(input)?; - let (remaining, _date) = date(context, remaining)?; + let (remaining, start) = date(context, remaining)?; let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &inactive_time_rest_end, @@ -163,6 +171,8 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Inactive, range_type: TimestampRangeType::None, + start: Some(start), + end: None, }, )) } @@ -187,6 +197,8 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::ActiveRange, range_type: TimestampRangeType::DateRange, + start: None, // TODO + end: None, // TODO }, )) } @@ -230,6 +242,8 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Active, range_type: TimestampRangeType::None, + start: None, // TODO + end: None, // TODO }, )) } @@ -255,6 +269,8 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( timestamp_type: TimestampType::InactiveRange, range_type: TimestampRangeType::DateRange, + start: None, // TODO + end: None, // TODO }, )) } @@ -298,129 +314,43 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Inactive, range_type: TimestampRangeType::None, + start: None, // TODO + end: None, // TODO }, )) } -pub struct Year(u16); -pub struct Month(u8); -pub struct DayOfMonth(u8); - -impl Year { - // TODO: Make a real error type instead of a boxed any error. - pub fn new<'s>(source: &'s str) -> Result> { - let year = source.parse::()?; - Ok(Year(year)) - } - - pub fn get_value(&self) -> u16 { - self.0 - } -} - -impl Month { - // TODO: Make a real error type instead of a boxed any error. - pub fn new<'s>(source: &'s str) -> Result> { - let month = source.parse::()?; - if month < 1 || month > 12 { - Err("Month exceeds possible range.")?; - } - Ok(Month(month)) - } - - pub fn get_value(&self) -> u8 { - self.0 - } -} - -impl DayOfMonth { - // TODO: Make a real error type instead of a boxed any error. - pub fn new<'s>(source: &'s str) -> Result> { - let day_of_month = source.parse::()?; - if day_of_month < 1 || day_of_month > 31 { - Err("Day of month exceeds possible range.")?; - } - Ok(DayOfMonth(day_of_month)) - } - - pub fn get_value(&self) -> u8 { - self.0 - } -} - -pub struct Date<'s> { - year: Year, - month: Month, - day_of_month: DayOfMonth, - day_name: &'s str, -} - -impl<'s> Date<'s> { - // TODO: Make a real error type instead of a boxed any error. - pub fn new( - year: Year, - month: Month, - day_of_month: DayOfMonth, - day_name: &'s str, - ) -> Result> { - // TODO: Does org-mode support non-gregorian calendars? - // TODO: Do I want to validate leap year? - match (month.get_value(), day_of_month.get_value()) { - (1, 1..=31) => {} - (2, 1..=29) => {} - (3, 1..=31) => {} - (4, 1..=30) => {} - (5, 1..=31) => {} - (6, 1..=30) => {} - (7, 1..=31) => {} - (8, 1..=31) => {} - (9, 1..=30) => {} - (10, 1..=31) => {} - (11, 1..=30) => {} - (12, 1..=31) => {} - _ => Err("Invalid day of month for the month.")?, - }; - Ok(Date { - year, - month, - day_of_month, - day_name, - }) - } - - pub fn get_year(&self) -> &Year { - &self.year - } - - pub fn get_month(&self) -> &Month { - &self.month - } - - pub fn get_day_of_month(&self) -> &DayOfMonth { - &self.day_of_month - } - - pub fn get_day_name(&self) -> &'s str { - self.day_name - } -} - #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn date<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?; +) -> Res, Date<'s>> { + let (remaining, year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?; + let (remaining, month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| { + let (remaining, day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| { day_of_month.len() == 2 })(remaining)?; - let (remaining, _dayname) = + let (remaining, day_name) = opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?; - let source = get_consumed(input, remaining); - Ok((remaining, source)) + + let year = Year::new(Into::<&str>::into(year)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + let month = Month::new(Into::<&str>::into(month)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + let day_of_month = DayOfMonth::new(Into::<&str>::into(day_of_month)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + + let date = Date::new( + year, + month, + day_of_month, + day_name.map(|(_, day_name)| Into::<&str>::into(day_name)), + ) + .expect("TODO: I should be able to return CustomError from nom parsers."); + + Ok((remaining, date)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] diff --git a/src/types/mod.rs b/src/types/mod.rs index ab01b7f..bb10ca4 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -54,6 +54,8 @@ pub use object::Bold; pub use object::Citation; pub use object::CitationReference; pub use object::Code; +pub use object::Date; +pub use object::DayOfMonth; pub use object::Entity; pub use object::ExportSnippet; pub use object::FootnoteReference; @@ -62,6 +64,7 @@ pub use object::InlineSourceBlock; pub use object::Italic; pub use object::LatexFragment; pub use object::LineBreak; +pub use object::Month; pub use object::Object; pub use object::OrgMacro; pub use object::PlainLink; @@ -79,5 +82,6 @@ pub use object::TimestampRangeType; pub use object::TimestampType; pub use object::Underline; pub use object::Verbatim; +pub use object::Year; pub(crate) use source::SetSource; pub use standard_properties::StandardProperties; diff --git a/src/types/object.rs b/src/types/object.rs index b955ba2..b16cfb6 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -187,6 +187,8 @@ pub struct Timestamp<'s> { pub source: &'s str, pub timestamp_type: TimestampType, pub range_type: TimestampRangeType, + pub start: Option>, + pub end: Option>, } #[derive(Debug, PartialEq)] @@ -204,6 +206,115 @@ pub enum TimestampRangeType { DateRange, } +#[derive(Debug, PartialEq)] +pub struct Year(u16); + +#[derive(Debug, PartialEq)] +pub struct Month(u8); + +#[derive(Debug, PartialEq)] +pub struct DayOfMonth(u8); + +impl Year { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let year = source.parse::()?; + Ok(Year(year)) + } + + pub fn get_value(&self) -> u16 { + self.0 + } +} + +impl Month { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let month = source.parse::()?; + if month < 1 || month > 12 { + Err("Month exceeds possible range.")?; + } + Ok(Month(month)) + } + + pub fn get_value(&self) -> u8 { + self.0 + } +} + +impl DayOfMonth { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let day_of_month = source.parse::()?; + if day_of_month < 1 || day_of_month > 31 { + Err("Day of month exceeds possible range.")?; + } + Ok(DayOfMonth(day_of_month)) + } + + pub fn get_value(&self) -> u8 { + self.0 + } +} + +#[derive(Debug, PartialEq)] +pub struct Date<'s> { + year: Year, + month: Month, + day_of_month: DayOfMonth, + day_name: Option<&'s str>, +} + +impl<'s> Date<'s> { + // TODO: Make a real error type instead of a boxed any error. + pub fn new( + year: Year, + month: Month, + day_of_month: DayOfMonth, + day_name: Option<&'s str>, + ) -> Result> { + // TODO: Does org-mode support non-gregorian calendars? + // TODO: Do I want to validate leap year? + match (month.get_value(), day_of_month.get_value()) { + (1, 1..=31) => {} + (2, 1..=29) => {} + (3, 1..=31) => {} + (4, 1..=30) => {} + (5, 1..=31) => {} + (6, 1..=30) => {} + (7, 1..=31) => {} + (8, 1..=31) => {} + (9, 1..=30) => {} + (10, 1..=31) => {} + (11, 1..=30) => {} + (12, 1..=31) => {} + _ => Err("Invalid day of month for the month.")?, + }; + Ok(Date { + year, + month, + day_of_month, + day_name, + }) + } + + pub fn get_year(&self) -> &Year { + &self.year + } + + pub fn get_month(&self) -> &Month { + &self.month + } + + pub fn get_day_of_month(&self) -> &DayOfMonth { + &self.day_of_month + } + + pub fn get_day_name(&self) -> Option<&'s str> { + self.day_name + } +} + impl<'s> GetStandardProperties<'s> for Object<'s> { fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> { match self { From c55fae86f848ce6aff7712157f218b730c97023c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 15:51:29 -0400 Subject: [PATCH 07/19] Improve lifetimes for get_property_numeric. --- src/compare/diff.rs | 2 +- src/compare/util.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 745f556..f0a1b8d 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2118,7 +2118,7 @@ fn compare_timestamp<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Timestamp<'s>, -) -> Result, Box> { +) -> Result, Box> { let mut this_status = DiffStatus::Good; let mut message = None; diff --git a/src/compare/util.rs b/src/compare/util.rs index 6f1c922..0a96f49 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -243,10 +243,10 @@ pub(crate) fn get_property_boolean<'b, 's, 'x>( pub(crate) fn get_property_numeric<'b, 's, 'x, N: FromStr>( emacs: &'b Token<'s>, key: &'x str, -) -> Result, Box> +) -> Result, Box> where ::Err: std::error::Error, - ::Err: 'static, + ::Err: 's, { let foo = get_property(emacs, key)? .map(Token::as_atom) From a8a34e2d9c164bcea57c0fb355e90d5bdd8ab0c7 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 15:59:06 -0400 Subject: [PATCH 08/19] Compare date start/end. --- src/compare/diff.rs | 86 +++++++++++++++++++++++++++++++++++------ src/compare/util.rs | 6 +-- src/parser/timestamp.rs | 36 ++++++++--------- src/types/mod.rs | 3 ++ src/types/object.rs | 30 +++++++------- 5 files changed, 115 insertions(+), 46 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index f0a1b8d..98a7bdc 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -24,6 +24,9 @@ use crate::types::Clock; use crate::types::Code; use crate::types::Comment; use crate::types::CommentBlock; +use crate::types::Date; +use crate::types::DayOfMonth; +use crate::types::DayOfMonthInner; use crate::types::DiarySexp; use crate::types::Document; use crate::types::DocumentElement; @@ -47,6 +50,8 @@ use crate::types::Keyword; use crate::types::LatexEnvironment; use crate::types::LatexFragment; use crate::types::LineBreak; +use crate::types::Month; +use crate::types::MonthInner; use crate::types::NodeProperty; use crate::types::OrgMacro; use crate::types::Paragraph; @@ -81,6 +86,8 @@ use crate::types::TodoKeywordType; use crate::types::Underline; use crate::types::Verbatim; use crate::types::VerseBlock; +use crate::types::Year; +use crate::types::YearInner; #[derive(Debug)] pub enum DiffEntry<'b, 's> { @@ -2165,21 +2172,76 @@ fn compare_timestamp<'b, 's>( } // Compare start - let year_start: Option = get_property_unquoted_atom(emacs, ":year-start")? - .map(|val| val.parse()) - .map_or(Ok(None), |r| r.map(Some))?; - let month_start: Option = get_property_unquoted_atom(emacs, ":month-start")? - .map(|val| val.parse()) - .map_or(Ok(None), |r| r.map(Some))?; - let day_of_month_start: Option = get_property_unquoted_atom(emacs, ":day-start")? - .map(|val| val.parse()) - .map_or(Ok(None), |r| r.map(Some))?; - - let year_end = get_property_numeric::(emacs, ":year-end")?; + let year_start: Option = get_property_numeric(emacs, ":year-start")?; + let month_start: Option = get_property_numeric(emacs, ":month-start")?; + let day_of_month_start: Option = get_property_numeric(emacs, ":day-start")?; + let rust_year_start = rust.start.as_ref().map(Date::get_year).map(Year::get_value); + let rust_month_start = rust + .start + .as_ref() + .map(Date::get_month) + .map(Month::get_value); + let rust_day_of_month_start = rust + .start + .as_ref() + .map(Date::get_day_of_month) + .map(DayOfMonth::get_value); + if year_start != rust_year_start { + this_status = DiffStatus::Bad; + message = Some(format!( + "year start mismatch (emacs != rust) {:?} != {:?}", + year_start, rust_year_start + )); + } + if month_start != rust_month_start { + this_status = DiffStatus::Bad; + message = Some(format!( + "month start mismatch (emacs != rust) {:?} != {:?}", + month_start, rust_month_start + )); + } + if day_of_month_start != rust_day_of_month_start { + this_status = DiffStatus::Bad; + message = Some(format!( + "day of month start mismatch (emacs != rust) {:?} != {:?}", + day_of_month_start, rust_day_of_month_start + )); + } // Compare end + let year_end: Option = get_property_numeric(emacs, ":year-end")?; + let month_end: Option = get_property_numeric(emacs, ":month-end")?; + let day_of_month_end: Option = get_property_numeric(emacs, ":day-end")?; + let rust_year_end = rust.end.as_ref().map(Date::get_year).map(Year::get_value); + let rust_month_end = rust.end.as_ref().map(Date::get_month).map(Month::get_value); + let rust_day_of_month_end = rust + .end + .as_ref() + .map(Date::get_day_of_month) + .map(DayOfMonth::get_value); + if year_end != rust_year_end { + this_status = DiffStatus::Bad; + message = Some(format!( + "year end mismatch (emacs != rust) {:?} != {:?}", + year_end, rust_year_end + )); + } + if month_end != rust_month_end { + this_status = DiffStatus::Bad; + message = Some(format!( + "month end mismatch (emacs != rust) {:?} != {:?}", + month_end, rust_month_end + )); + } + if day_of_month_end != rust_day_of_month_end { + this_status = DiffStatus::Bad; + message = Some(format!( + "day of month end mismatch (emacs != rust) {:?} != {:?}", + day_of_month_end, rust_day_of_month_end + )); + } - // TODO: Compare :year-start :month-start :day-start :hour-start :minute-start :year-end :month-end :day-end :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit + // TODO: Compare :hour-start :minute-start :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. // :range-type unquoted atom either nil, daterange diff --git a/src/compare/util.rs b/src/compare/util.rs index 0a96f49..afaedf3 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -248,11 +248,11 @@ where ::Err: std::error::Error, ::Err: 's, { - let foo = get_property(emacs, key)? + let unparsed_string = get_property(emacs, key)? .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))?; - let bar = foo + let parsed_number = unparsed_string .map(|val| val.parse::()) .map_or(Ok(None), |r| r.map(Some))?; - Ok(bar) + Ok(parsed_number) } diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 20d1a4c..71ee6f4 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -133,8 +133,8 @@ fn active_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Active, range_type: TimestampRangeType::None, - start: Some(start), - end: None, + start: Some(start.clone()), + end: Some(start), }, )) } @@ -171,8 +171,8 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Inactive, range_type: TimestampRangeType::None, - start: Some(start), - end: None, + start: Some(start.clone()), + end: Some(start), }, )) } @@ -182,10 +182,10 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { - let (remaining, _first_timestamp) = active_timestamp(context, input)?; + let (remaining, first_timestamp) = active_timestamp(context, input)?; // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace let (remaining, _separator) = tag("--")(remaining)?; - let (remaining, _second_timestamp) = active_timestamp(context, remaining)?; + let (remaining, second_timestamp) = active_timestamp(context, remaining)?; let (remaining, _trailing_whitespace) = maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; @@ -197,8 +197,8 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::ActiveRange, range_type: TimestampRangeType::DateRange, - start: None, // TODO - end: None, // TODO + start: first_timestamp.start, + end: second_timestamp.end, }, )) } @@ -209,7 +209,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { let (remaining, _) = tag("<")(input)?; - let (remaining, _date) = date(context, remaining)?; + let (remaining, start_date) = date(context, remaining)?; let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &active_time_rest_end, @@ -242,8 +242,8 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Active, range_type: TimestampRangeType::None, - start: None, // TODO - end: None, // TODO + start: Some(start_date.clone()), + end: Some(start_date), }, )) } @@ -253,10 +253,10 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { - let (remaining, _first_timestamp) = inactive_timestamp(context, input)?; + let (remaining, first_timestamp) = inactive_timestamp(context, input)?; // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace let (remaining, _separator) = tag("--")(remaining)?; - let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?; + let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?; let (remaining, _trailing_whitespace) = maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; @@ -269,8 +269,8 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( timestamp_type: TimestampType::InactiveRange, range_type: TimestampRangeType::DateRange, - start: None, // TODO - end: None, // TODO + start: first_timestamp.start, + end: second_timestamp.end, }, )) } @@ -281,7 +281,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { let (remaining, _) = tag("[")(input)?; - let (remaining, _date) = date(context, remaining)?; + let (remaining, start_date) = date(context, remaining)?; let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &inactive_time_rest_end, @@ -314,8 +314,8 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( source: source.into(), timestamp_type: TimestampType::Inactive, range_type: TimestampRangeType::None, - start: None, // TODO - end: None, // TODO + start: Some(start_date.clone()), + end: Some(start_date), }, )) } diff --git a/src/types/mod.rs b/src/types/mod.rs index bb10ca4..13fc8db 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -56,6 +56,7 @@ pub use object::CitationReference; pub use object::Code; pub use object::Date; pub use object::DayOfMonth; +pub use object::DayOfMonthInner; pub use object::Entity; pub use object::ExportSnippet; pub use object::FootnoteReference; @@ -65,6 +66,7 @@ pub use object::Italic; pub use object::LatexFragment; pub use object::LineBreak; pub use object::Month; +pub use object::MonthInner; pub use object::Object; pub use object::OrgMacro; pub use object::PlainLink; @@ -83,5 +85,6 @@ pub use object::TimestampType; pub use object::Underline; pub use object::Verbatim; pub use object::Year; +pub use object::YearInner; pub(crate) use source::SetSource; pub use standard_properties::StandardProperties; diff --git a/src/types/object.rs b/src/types/object.rs index b16cfb6..dcc72bc 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -206,23 +206,27 @@ pub enum TimestampRangeType { DateRange, } -#[derive(Debug, PartialEq)] -pub struct Year(u16); +pub type YearInner = u16; +pub type MonthInner = u8; +pub type DayOfMonthInner = u8; -#[derive(Debug, PartialEq)] -pub struct Month(u8); +#[derive(Debug, PartialEq, Clone)] +pub struct Year(YearInner); -#[derive(Debug, PartialEq)] -pub struct DayOfMonth(u8); +#[derive(Debug, PartialEq, Clone)] +pub struct Month(MonthInner); + +#[derive(Debug, PartialEq, Clone)] +pub struct DayOfMonth(DayOfMonthInner); impl Year { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { - let year = source.parse::()?; + let year = source.parse::()?; Ok(Year(year)) } - pub fn get_value(&self) -> u16 { + pub fn get_value(&self) -> YearInner { self.0 } } @@ -230,14 +234,14 @@ impl Year { impl Month { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { - let month = source.parse::()?; + let month = source.parse::()?; if month < 1 || month > 12 { Err("Month exceeds possible range.")?; } Ok(Month(month)) } - pub fn get_value(&self) -> u8 { + pub fn get_value(&self) -> MonthInner { self.0 } } @@ -245,19 +249,19 @@ impl Month { impl DayOfMonth { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { - let day_of_month = source.parse::()?; + let day_of_month = source.parse::()?; if day_of_month < 1 || day_of_month > 31 { Err("Day of month exceeds possible range.")?; } Ok(DayOfMonth(day_of_month)) } - pub fn get_value(&self) -> u8 { + pub fn get_value(&self) -> DayOfMonthInner { self.0 } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Date<'s> { year: Year, month: Month, From dec3242e729d8cef4ee088dd3cb921798301d464 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 16:24:51 -0400 Subject: [PATCH 09/19] Implement the Time struct. --- src/types/mod.rs | 5 ++++ src/types/object.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/types/mod.rs b/src/types/mod.rs index 13fc8db..8c7bd4b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -60,11 +60,15 @@ pub use object::DayOfMonthInner; pub use object::Entity; pub use object::ExportSnippet; pub use object::FootnoteReference; +pub use object::Hour; +pub use object::HourInner; pub use object::InlineBabelCall; pub use object::InlineSourceBlock; pub use object::Italic; pub use object::LatexFragment; pub use object::LineBreak; +pub use object::Minute; +pub use object::MinuteInner; pub use object::Month; pub use object::MonthInner; pub use object::Object; @@ -79,6 +83,7 @@ pub use object::StrikeThrough; pub use object::Subscript; pub use object::Superscript; pub use object::Target; +pub use object::Time; pub use object::Timestamp; pub use object::TimestampRangeType; pub use object::TimestampType; diff --git a/src/types/object.rs b/src/types/object.rs index dcc72bc..885745a 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -209,6 +209,8 @@ pub enum TimestampRangeType { pub type YearInner = u16; pub type MonthInner = u8; pub type DayOfMonthInner = u8; +pub type HourInner = u8; +pub type MinuteInner = u8; #[derive(Debug, PartialEq, Clone)] pub struct Year(YearInner); @@ -219,6 +221,12 @@ pub struct Month(MonthInner); #[derive(Debug, PartialEq, Clone)] pub struct DayOfMonth(DayOfMonthInner); +#[derive(Debug, PartialEq, Clone)] +pub struct Hour(HourInner); + +#[derive(Debug, PartialEq, Clone)] +pub struct Minute(MinuteInner); + impl Year { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { @@ -261,6 +269,36 @@ impl DayOfMonth { } } +impl Hour { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let hour = source.parse::()?; + if hour < 1 || hour > 23 { + Err("Hour exceeds possible range.")?; + } + Ok(Hour(hour)) + } + + pub fn get_value(&self) -> HourInner { + self.0 + } +} + +impl Minute { + // TODO: Make a real error type instead of a boxed any error. + pub fn new<'s>(source: &'s str) -> Result> { + let minute = source.parse::()?; + if minute < 1 || minute > 59 { + Err("Minute exceeds possible range.")?; + } + Ok(Minute(minute)) + } + + pub fn get_value(&self) -> MinuteInner { + self.0 + } +} + #[derive(Debug, PartialEq, Clone)] pub struct Date<'s> { year: Year, @@ -319,6 +357,40 @@ impl<'s> Date<'s> { } } +#[derive(Debug, PartialEq, Clone)] +pub struct Time<'s> { + hour: Hour, + minute: Minute, + postfix: Option<&'s str>, +} + +impl<'s> Time<'s> { + // TODO: Make a real error type instead of a boxed any error. + pub fn new( + hour: Hour, + minute: Minute, + postfix: Option<&'s str>, + ) -> Result> { + Ok(Time { + hour, + minute, + postfix, + }) + } + + pub fn get_hour(&self) -> &Hour { + &self.hour + } + + pub fn get_minute(&self) -> &Minute { + &self.minute + } + + pub fn get_postfix(&self) -> Option<&'s str> { + self.postfix + } +} + impl<'s> GetStandardProperties<'s> for Object<'s> { fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> { match self { From 9846cde2f04f8a3f93a1440d21f3e0982abdeff9 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 16:32:33 -0400 Subject: [PATCH 10/19] Trim whitespace from raw value. --- src/compare/diff.rs | 11 +++++++++-- src/types/object.rs | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 98a7bdc..79288b7 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -43,6 +43,7 @@ use crate::types::GetStandardProperties; use crate::types::GreaterBlock; use crate::types::Heading; use crate::types::HorizontalRule; +use crate::types::HourInner; use crate::types::InlineBabelCall; use crate::types::InlineSourceBlock; use crate::types::Italic; @@ -50,6 +51,7 @@ use crate::types::Keyword; use crate::types::LatexEnvironment; use crate::types::LatexFragment; use crate::types::LineBreak; +use crate::types::MinuteInner; use crate::types::Month; use crate::types::MonthInner; use crate::types::NodeProperty; @@ -2163,11 +2165,12 @@ fn compare_timestamp<'b, 's>( // Compare raw-value let raw_value = get_property_quoted_string(emacs, ":raw-value")? .ok_or("Timestamps should have a :raw-value.")?; - if raw_value != rust.source { + if raw_value != rust.get_raw_value() { this_status = DiffStatus::Bad; message = Some(format!( "Raw value mismatch (emacs != rust) {:?} != {:?}", - raw_value, rust.source + raw_value, + rust.get_raw_value() )); } @@ -2241,6 +2244,10 @@ fn compare_timestamp<'b, 's>( )); } + // Compare time start + let hour_start: Option = get_property_numeric(emacs, ":hour-start")?; + let minute_start: Option = get_property_numeric(emacs, ":minute-start")?; + // TODO: Compare :hour-start :minute-start :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. diff --git a/src/types/object.rs b/src/types/object.rs index 885745a..229e2a9 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -586,3 +586,9 @@ impl<'s> StandardProperties<'s> for PlainText<'s> { self.source } } + +impl<'s> Timestamp<'s> { + pub fn get_raw_value(&self) -> &'s str { + self.source.trim_end() + } +} From 890cd3e4fdfc3c081e7013e4ebeff2dee70933ee Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 16:37:23 -0400 Subject: [PATCH 11/19] Compare start/end time. --- src/compare/diff.rs | 55 +++++++++++++++++++++++++++++++++++++++++ src/parser/timestamp.rs | 46 +++++++++++++++++++++++++--------- src/types/object.rs | 6 +++-- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 79288b7..49aa750 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -43,6 +43,7 @@ use crate::types::GetStandardProperties; use crate::types::GreaterBlock; use crate::types::Heading; use crate::types::HorizontalRule; +use crate::types::Hour; use crate::types::HourInner; use crate::types::InlineBabelCall; use crate::types::InlineSourceBlock; @@ -51,6 +52,7 @@ use crate::types::Keyword; use crate::types::LatexEnvironment; use crate::types::LatexFragment; use crate::types::LineBreak; +use crate::types::Minute; use crate::types::MinuteInner; use crate::types::Month; use crate::types::MonthInner; @@ -81,6 +83,7 @@ use crate::types::Table; use crate::types::TableCell; use crate::types::TableRow; use crate::types::Target; +use crate::types::Time; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; @@ -2247,6 +2250,58 @@ fn compare_timestamp<'b, 's>( // Compare time start let hour_start: Option = get_property_numeric(emacs, ":hour-start")?; let minute_start: Option = get_property_numeric(emacs, ":minute-start")?; + let rust_hour_start = rust + .start_time + .as_ref() + .map(Time::get_hour) + .map(Hour::get_value); + let rust_minute_start = rust + .start_time + .as_ref() + .map(Time::get_minute) + .map(Minute::get_value); + if hour_start != rust_hour_start { + this_status = DiffStatus::Bad; + message = Some(format!( + "hour start mismatch (emacs != rust) {:?} != {:?}", + hour_start, rust_hour_start + )); + } + if minute_start != rust_minute_start { + this_status = DiffStatus::Bad; + message = Some(format!( + "minute start mismatch (emacs != rust) {:?} != {:?}", + minute_start, rust_minute_start + )); + } + + // Compare time end + let hour_end: Option = get_property_numeric(emacs, ":hour-end")?; + let minute_end: Option = get_property_numeric(emacs, ":minute-end")?; + let rust_hour_end = rust + .end_time + .as_ref() + .map(Time::get_hour) + .map(Hour::get_value); + let rust_minute_end = rust + .end_time + .as_ref() + .map(Time::get_minute) + .map(Minute::get_value); + if hour_end != rust_hour_end { + this_status = DiffStatus::Bad; + message = Some(format!( + "hour end mismatch (emacs != rust) {:?} != {:?}", + hour_end, rust_hour_end + )); + } + if minute_end != rust_minute_end { + this_status = DiffStatus::Bad; + message = Some(format!( + "minute end mismatch (emacs != rust) {:?} != {:?}", + minute_end, rust_minute_end + )); + } // TODO: Compare :hour-start :minute-start :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 71ee6f4..e1cabd6 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -23,7 +23,10 @@ use crate::error::Res; use crate::parser::util::get_consumed; use crate::types::Date; use crate::types::DayOfMonth; +use crate::types::Hour; +use crate::types::Minute; use crate::types::Month; +use crate::types::Time; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; @@ -67,6 +70,8 @@ fn diary_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: None, end: None, + start_time: None, // TODO + end_time: None, // TODO }, )) } @@ -113,7 +118,7 @@ fn active_timestamp<'b, 'g, 'r, 's>( exit_matcher: &active_time_rest_end, }); let time_context = context.with_additional_node(&time_context); - let (remaining, _time) = + let (remaining, time) = opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; @@ -135,6 +140,8 @@ fn active_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: Some(start.clone()), end: Some(start), + start_time: time.as_ref().map(|(_, time)| time.clone()), + end_time: time.map(|(_, time)| time), }, )) } @@ -151,7 +158,7 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( exit_matcher: &inactive_time_rest_end, }); let time_context = context.with_additional_node(&time_context); - let (remaining, _time) = + let (remaining, time) = opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; @@ -173,6 +180,8 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: Some(start.clone()), end: Some(start), + start_time: time.as_ref().map(|(_, time)| time.clone()), + end_time: time.map(|(_, time)| time), }, )) } @@ -199,6 +208,8 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::DateRange, start: first_timestamp.start, end: second_timestamp.end, + start_time: first_timestamp.start_time, + end_time: second_timestamp.end_time, }, )) } @@ -220,10 +231,10 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( exit_matcher: &time_range_rest_end, }); let first_time_context = time_context.with_additional_node(&first_time_context); - let (remaining, _first_time) = + let (remaining, (_, first_time)) = tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?; + let (remaining, second_time) = parser_with_context!(time)(&time_context)(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -244,6 +255,8 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: Some(start_date.clone()), end: Some(start_date), + start_time: Some(first_time), + end_time: Some(second_time), }, )) } @@ -271,6 +284,8 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::DateRange, start: first_timestamp.start, end: second_timestamp.end, + start_time: first_timestamp.start_time, + end_time: second_timestamp.end_time, }, )) } @@ -292,10 +307,10 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( exit_matcher: &time_range_rest_end, }); let first_time_context = time_context.with_additional_node(&first_time_context); - let (remaining, _first_time) = + let (remaining, (_, first_time)) = tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?; + let (remaining, second_time) = parser_with_context!(time)(&time_context)(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -316,6 +331,8 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: Some(start_date.clone()), end: Some(start_date), + start_time: Some(first_time), + end_time: Some(second_time), }, )) } @@ -389,16 +406,23 @@ fn dayname_end<'b, 'g, 'r, 's>( fn time<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| { +) -> Res, Time<'s>> { + let (remaining, hour) = verify(digit1, |hour: &OrgSource<'_>| { hour.len() >= 1 && hour.len() <= 2 })(input)?; let (remaining, _) = tag(":")(remaining)?; - let (remaining, _minute) = + let (remaining, minute) = verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?; let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?; - let source = get_consumed(input, remaining); - Ok((remaining, source)) + + let hour = Hour::new(Into::<&str>::into(hour)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + let minute = Minute::new(Into::<&str>::into(minute)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + let time = Time::new(hour, minute, _time_rest.map(Into::<&str>::into)) + .expect("TODO: I should be able to return CustomError from nom parsers."); + + Ok((remaining, time)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] diff --git a/src/types/object.rs b/src/types/object.rs index 229e2a9..dd3117a 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -189,6 +189,8 @@ pub struct Timestamp<'s> { pub range_type: TimestampRangeType, pub start: Option>, pub end: Option>, + pub start_time: Option>, + pub end_time: Option>, } #[derive(Debug, PartialEq)] @@ -273,7 +275,7 @@ impl Hour { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { let hour = source.parse::()?; - if hour < 1 || hour > 23 { + if hour > 23 { Err("Hour exceeds possible range.")?; } Ok(Hour(hour)) @@ -288,7 +290,7 @@ impl Minute { // TODO: Make a real error type instead of a boxed any error. pub fn new<'s>(source: &'s str) -> Result> { let minute = source.parse::()?; - if minute < 1 || minute > 59 { + if minute > 59 { Err("Minute exceeds possible range.")?; } Ok(Minute(minute)) From 512432c5f00698ec62258f8acaf33182fe5ff609 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 17:47:09 -0400 Subject: [PATCH 12/19] Do not allow time range timestamps with REST on the first TIME. --- org_mode_samples/object/timestamp/simple.org | 8 ++- src/compare/diff.rs | 3 +- src/parser/timestamp.rs | 60 +++++++++++++------- src/types/object.rs | 1 + 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/org_mode_samples/object/timestamp/simple.org b/org_mode_samples/object/timestamp/simple.org index c84b6c7..5f584d7 100644 --- a/org_mode_samples/object/timestamp/simple.org +++ b/org_mode_samples/object/timestamp/simple.org @@ -2,13 +2,17 @@ <%%(foo bar baz)> # active <1970-01-01 Thu 8:15rest +1w -1d> +# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range. +<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d> # inactive [1970-01-01 Thu 8:15rest +1w -1d] +# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range. +[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d] # active date range <1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d> # active time range -<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d> +<1970-01-01 Thu 8:15-13:15otherrest +1w -1d> # inactive date range [1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d] # inactive time range -[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d] +[1970-01-01 Thu 8:15-13:15otherrest +1w -1d] diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 49aa750..275a957 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2155,6 +2155,7 @@ fn compare_timestamp<'b, 's>( let range_type = get_property_unquoted_atom(emacs, ":range-type")?; match (range_type, &rust.range_type) { (Some("daterange"), TimestampRangeType::DateRange) => {} + (Some("timerange"), TimestampRangeType::TimeRange) => {} (None, TimestampRangeType::None) => {} _ => { this_status = DiffStatus::Bad; @@ -2303,7 +2304,7 @@ fn compare_timestamp<'b, 's>( )); } - // TODO: Compare :hour-start :minute-start :hour-end :minute-end :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit + // TODO: Compare :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit // // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. // :range-type unquoted atom either nil, daterange diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index e1cabd6..68681fe 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -118,8 +118,10 @@ fn active_timestamp<'b, 'g, 'r, 's>( exit_matcher: &active_time_rest_end, }); let time_context = context.with_additional_node(&time_context); - let (remaining, time) = - opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?; + let (remaining, time) = opt(tuple(( + space1, + parser_with_context!(time(true))(&time_context), + )))(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -158,8 +160,10 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( exit_matcher: &inactive_time_rest_end, }); let time_context = context.with_additional_node(&time_context); - let (remaining, time) = - opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?; + let (remaining, time) = opt(tuple(( + space1, + parser_with_context!(time(true))(&time_context), + )))(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -231,10 +235,12 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( exit_matcher: &time_range_rest_end, }); let first_time_context = time_context.with_additional_node(&first_time_context); - let (remaining, (_, first_time)) = - tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?; + let (remaining, (_, first_time)) = tuple(( + space1, + parser_with_context!(time(false))(&first_time_context), + ))(remaining)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, second_time) = parser_with_context!(time)(&time_context)(remaining)?; + let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -251,8 +257,8 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), - timestamp_type: TimestampType::Active, - range_type: TimestampRangeType::None, + timestamp_type: TimestampType::ActiveRange, + range_type: TimestampRangeType::TimeRange, start: Some(start_date.clone()), end: Some(start_date), start_time: Some(first_time), @@ -307,10 +313,12 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( exit_matcher: &time_range_rest_end, }); let first_time_context = time_context.with_additional_node(&first_time_context); - let (remaining, (_, first_time)) = - tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?; + let (remaining, (_, first_time)) = tuple(( + space1, + parser_with_context!(time(false))(&first_time_context), + ))(remaining)?; let (remaining, _) = tag("-")(remaining)?; - let (remaining, second_time) = parser_with_context!(time)(&time_context)(remaining)?; + let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?; let (remaining, _repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, _warning_delay) = opt(tuple(( @@ -327,8 +335,8 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( remaining, Timestamp { source: source.into(), - timestamp_type: TimestampType::Inactive, - range_type: TimestampRangeType::None, + timestamp_type: TimestampType::InactiveRange, + range_type: TimestampRangeType::TimeRange, start: Some(start_date.clone()), end: Some(start_date), start_time: Some(first_time), @@ -402,10 +410,18 @@ fn dayname_end<'b, 'g, 'r, 's>( }))(input) } +const fn time<'c>( + allow_rest: bool, +) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res, Time<'s>> +{ + move |context, input| _time(context, input, allow_rest) +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn time<'b, 'g, 'r, 's>( +fn _time<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, + allow_rest: bool, ) -> Res, Time<'s>> { let (remaining, hour) = verify(digit1, |hour: &OrgSource<'_>| { hour.len() >= 1 && hour.len() <= 2 @@ -413,13 +429,17 @@ fn time<'b, 'g, 'r, 's>( let (remaining, _) = tag(":")(remaining)?; let (remaining, minute) = verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?; - let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?; + let (remaining, time_rest) = if allow_rest { + opt(parser_with_context!(time_rest)(context))(remaining)? + } else { + (remaining, None) + }; let hour = Hour::new(Into::<&str>::into(hour)) .expect("TODO: I should be able to return CustomError from nom parsers."); let minute = Minute::new(Into::<&str>::into(minute)) .expect("TODO: I should be able to return CustomError from nom parsers."); - let time = Time::new(hour, minute, _time_rest.map(Into::<&str>::into)) + let time = Time::new(hour, minute, time_rest.map(Into::<&str>::into)) .expect("TODO: I should be able to return CustomError from nom parsers."); Ok((remaining, time)) @@ -475,8 +495,10 @@ fn time_range_rest_end<'b, 'g, 'r, 's>( ) -> Res, OrgSource<'s>> { // We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found. let parent_node = context.get_parent().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None."); - let exit_contents = - recognize(tuple((tag("-"), parser_with_context!(time)(&parent_node))))(input); + let exit_contents = recognize(tuple(( + tag("-"), + parser_with_context!(time(true))(&parent_node), + )))(input); exit_contents } diff --git a/src/types/object.rs b/src/types/object.rs index dd3117a..a898694 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -206,6 +206,7 @@ pub enum TimestampType { pub enum TimestampRangeType { None, DateRange, + TimeRange, } pub type YearInner = u16; From 6a8ae9d838f89007e6c13a1dff716daf1d29bc58 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 18:58:30 -0400 Subject: [PATCH 13/19] Compare warning delay and repeater. --- src/compare/diff.rs | 114 +++++++++++++++++++++++++++++++---- src/parser/timestamp.rs | 128 +++++++++++++++++++++++++++------------- src/types/mod.rs | 6 ++ src/types/object.rs | 40 +++++++++++++ 4 files changed, 235 insertions(+), 53 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 275a957..7138fa1 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -72,6 +72,8 @@ use crate::types::PropertyDrawer; use crate::types::RadioLink; use crate::types::RadioTarget; use crate::types::RegularLink; +use crate::types::RepeaterType; +use crate::types::RepeaterWarningDelayValueType; use crate::types::Section; use crate::types::SrcBlock; use crate::types::StandardProperties; @@ -84,6 +86,7 @@ use crate::types::TableCell; use crate::types::TableRow; use crate::types::Target; use crate::types::Time; +use crate::types::TimeUnit; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; @@ -91,6 +94,7 @@ use crate::types::TodoKeywordType; use crate::types::Underline; use crate::types::Verbatim; use crate::types::VerseBlock; +use crate::types::WarningDelayType; use crate::types::Year; use crate::types::YearInner; @@ -2304,18 +2308,104 @@ fn compare_timestamp<'b, 's>( )); } - // TODO: Compare :repeater-type :repeater-value :repeater-unit :warning-type :warning-value :warning-unit - // - // :type unquoted atom either diary, active, inactive, active-range, or inactive-range. - // :range-type unquoted atom either nil, daterange - // :raw-value quoted string of the source - // :*-start :*-end unquoted integers - // :repeater-type optional unquoted atom with value cumulate - // :repeater-value unquoted integer - // :repeater-unit unquoted atom with value week - // :warning-type optional unquoted atom with value all - // :warning-value unquoted integer - // :warning-unit unquoted atom with value day + // Compare repeater + let repeater_type = get_property_unquoted_atom(emacs, ":repeater-type")?; + let repeater_value: Option = + get_property_numeric(emacs, ":repeater-value")?; + let repeater_unit = get_property_unquoted_atom(emacs, ":repeater-unit")?; + let rust_repeater_type = rust + .repeater + .as_ref() + .map(|repeater| &repeater.repeater_type); + let rust_repeater_value = rust.repeater.as_ref().map(|repeater| repeater.value); + let rust_repeater_unit = rust.repeater.as_ref().map(|repeater| &repeater.unit); + match (repeater_type, rust_repeater_type) { + (Some("cumulate"), Some(RepeaterType::Cumulative)) => {} + (Some("catch-up"), Some(RepeaterType::CatchUp)) => {} + (Some("restart"), Some(RepeaterType::Restart)) => {} + (None, None) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Repeater type mismatch (emacs != rust) {:?} != {:?}", + repeater_type, rust_repeater_type + )); + } + } + if repeater_value != rust_repeater_value { + this_status = DiffStatus::Bad; + message = Some(format!( + "Repeater value mismatch (emacs != rust) {:?} != {:?}", + repeater_value, rust_repeater_value + )); + } + match (repeater_unit, rust_repeater_unit) { + (Some("hour"), Some(TimeUnit::Hour)) => {} + (Some("day"), Some(TimeUnit::Day)) => {} + (Some("week"), Some(TimeUnit::Week)) => {} + (Some("month"), Some(TimeUnit::Month)) => {} + (Some("year"), Some(TimeUnit::Year)) => {} + (None, None) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Repeater unit mismatch (emacs != rust) {:?} != {:?}", + repeater_unit, rust_repeater_unit + )); + } + } + + // Compare warning_delay + let warning_delay_type = get_property_unquoted_atom(emacs, ":warning-type")?; + let warning_delay_value: Option = + get_property_numeric(emacs, ":warning-value")?; + let warning_delay_unit = get_property_unquoted_atom(emacs, ":warning-unit")?; + let rust_warning_delay_type = rust + .warning_delay + .as_ref() + .map(|warning_delay| &warning_delay.warning_delay_type); + let rust_warning_delay_value = rust + .warning_delay + .as_ref() + .map(|warning_delay| warning_delay.value); + let rust_warning_delay_unit = rust + .warning_delay + .as_ref() + .map(|warning_delay| &warning_delay.unit); + match (warning_delay_type, rust_warning_delay_type) { + (Some("all"), Some(WarningDelayType::All)) => {} + (Some("first"), Some(WarningDelayType::First)) => {} + (None, None) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Warning delay type mismatch (emacs != rust) {:?} != {:?}", + warning_delay_type, rust_warning_delay_type + )); + } + } + if warning_delay_value != rust_warning_delay_value { + this_status = DiffStatus::Bad; + message = Some(format!( + "Warning delay value mismatch (emacs != rust) {:?} != {:?}", + warning_delay_value, rust_warning_delay_value + )); + } + match (warning_delay_unit, rust_warning_delay_unit) { + (Some("hour"), Some(TimeUnit::Hour)) => {} + (Some("day"), Some(TimeUnit::Day)) => {} + (Some("week"), Some(TimeUnit::Week)) => {} + (Some("month"), Some(TimeUnit::Month)) => {} + (Some("year"), Some(TimeUnit::Year)) => {} + (None, None) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Warning delay unit mismatch (emacs != rust) {:?} != {:?}", + warning_delay_unit, rust_warning_delay_unit + )); + } + } Ok(DiffResult { status: this_status, diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 68681fe..f50b4b1 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -1,10 +1,10 @@ use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::anychar; -use nom::character::complete::digit0; use nom::character::complete::digit1; use nom::character::complete::one_of; use nom::character::complete::space1; +use nom::combinator::map; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::verify; @@ -26,10 +26,15 @@ use crate::types::DayOfMonth; use crate::types::Hour; use crate::types::Minute; use crate::types::Month; +use crate::types::Repeater; +use crate::types::RepeaterType; use crate::types::Time; +use crate::types::TimeUnit; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; +use crate::types::WarningDelay; +use crate::types::WarningDelayType; use crate::types::Year; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -70,8 +75,10 @@ fn diary_timestamp<'b, 'g, 'r, 's>( range_type: TimestampRangeType::None, start: None, end: None, - start_time: None, // TODO - end_time: None, // TODO + start_time: None, + end_time: None, + repeater: None, + warning_delay: None, }, )) } @@ -122,9 +129,9 @@ fn active_timestamp<'b, 'g, 'r, 's>( space1, parser_with_context!(time(true))(&time_context), )))(remaining)?; - let (remaining, _repeater) = + let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; - let (remaining, _warning_delay) = opt(tuple(( + let (remaining, warning_delay) = opt(tuple(( space1, parser_with_context!(warning_delay)(context), )))(remaining)?; @@ -144,6 +151,8 @@ fn active_timestamp<'b, 'g, 'r, 's>( end: Some(start), start_time: time.as_ref().map(|(_, time)| time.clone()), end_time: time.map(|(_, time)| time), + repeater: repeater.map(|(_, repeater)| repeater), + warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), }, )) } @@ -164,9 +173,9 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( space1, parser_with_context!(time(true))(&time_context), )))(remaining)?; - let (remaining, _repeater) = + let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; - let (remaining, _warning_delay) = opt(tuple(( + let (remaining, warning_delay) = opt(tuple(( space1, parser_with_context!(warning_delay)(context), )))(remaining)?; @@ -186,6 +195,8 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( end: Some(start), start_time: time.as_ref().map(|(_, time)| time.clone()), end_time: time.map(|(_, time)| time), + repeater: repeater.map(|(_, repeater)| repeater), + warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), }, )) } @@ -214,6 +225,10 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>( end: second_timestamp.end, start_time: first_timestamp.start_time, end_time: second_timestamp.end_time, + repeater: first_timestamp.repeater.or(second_timestamp.repeater), + warning_delay: first_timestamp + .warning_delay + .or(second_timestamp.warning_delay), }, )) } @@ -241,9 +256,9 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( ))(remaining)?; let (remaining, _) = tag("-")(remaining)?; let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?; - let (remaining, _repeater) = + let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; - let (remaining, _warning_delay) = opt(tuple(( + let (remaining, warning_delay) = opt(tuple(( space1, parser_with_context!(warning_delay)(context), )))(remaining)?; @@ -263,6 +278,8 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( end: Some(start_date), start_time: Some(first_time), end_time: Some(second_time), + repeater: repeater.map(|(_, repeater)| repeater), + warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), }, )) } @@ -292,6 +309,10 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( end: second_timestamp.end, start_time: first_timestamp.start_time, end_time: second_timestamp.end_time, + repeater: first_timestamp.repeater.or(second_timestamp.repeater), + warning_delay: first_timestamp + .warning_delay + .or(second_timestamp.warning_delay), }, )) } @@ -319,9 +340,9 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( ))(remaining)?; let (remaining, _) = tag("-")(remaining)?; let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?; - let (remaining, _repeater) = + let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; - let (remaining, _warning_delay) = opt(tuple(( + let (remaining, warning_delay) = opt(tuple(( space1, parser_with_context!(warning_delay)(context), )))(remaining)?; @@ -341,6 +362,8 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( end: Some(start_date), start_time: Some(first_time), end_time: Some(second_time), + repeater: repeater.map(|(_, repeater)| repeater), + warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), }, )) } @@ -460,32 +483,18 @@ fn time_rest<'b, 'g, 'r, 's>( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn active_time_rest_end<'b, 'g, 'r, 's>( - context: RefContext<'b, 'g, 'r, 's>, + _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - alt(( - recognize(verify(anychar, |c| ">\n".contains(*c))), - recognize(tuple((space1, parser_with_context!(repeater)(context)))), - recognize(tuple(( - space1, - parser_with_context!(warning_delay)(context), - ))), - ))(input) + recognize(verify(anychar, |c| ">\n".contains(*c)))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn inactive_time_rest_end<'b, 'g, 'r, 's>( - context: RefContext<'b, 'g, 'r, 's>, + _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - alt(( - recognize(verify(anychar, |c| "]\n".contains(*c))), - recognize(tuple((space1, parser_with_context!(repeater)(context)))), - recognize(tuple(( - space1, - parser_with_context!(warning_delay)(context), - ))), - ))(input) + recognize(verify(anychar, |c| "]\n".contains(*c)))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -506,29 +515,66 @@ fn time_range_rest_end<'b, 'g, 'r, 's>( fn repeater<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { +) -> Res, Repeater> { // + for cumulative type // ++ for catch-up type // .+ for restart type - let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?; - let (remaining, _value) = digit0(remaining)?; + let (remaining, repeater_type) = alt(( + map(tag("++"), |_| RepeaterType::Cumulative), + map(tag("+"), |_| RepeaterType::CatchUp), + map(tag(".+"), |_| RepeaterType::Restart), + ))(input)?; + let (remaining, value) = digit1(remaining)?; + let value = Into::<&str>::into(value) + .parse() + .expect("digit1 ensures this will parse as a number."); // h = hour, d = day, w = week, m = month, y = year - let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; - let source = get_consumed(input, remaining); - Ok((remaining, source)) + let (remaining, unit) = alt(( + map(tag("h"), |_| TimeUnit::Hour), + map(tag("d"), |_| TimeUnit::Day), + map(tag("w"), |_| TimeUnit::Week), + map(tag("m"), |_| TimeUnit::Month), + map(tag("y"), |_| TimeUnit::Year), + ))(remaining)?; + Ok(( + remaining, + Repeater { + repeater_type, + value, + unit, + }, + )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn warning_delay<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { +) -> Res, WarningDelay> { // - for all type // -- for first type - let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?; - let (remaining, _value) = digit0(remaining)?; + let (remaining, warning_delay_type) = alt(( + map(tag("--"), |_| WarningDelayType::First), + map(tag("-"), |_| WarningDelayType::All), + ))(input)?; + let (remaining, value) = digit1(remaining)?; + let value = Into::<&str>::into(value) + .parse() + .expect("digit1 ensures this will parse as a number."); // h = hour, d = day, w = week, m = month, y = year - let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; - let source = get_consumed(input, remaining); - Ok((remaining, source)) + let (remaining, unit) = alt(( + map(tag("h"), |_| TimeUnit::Hour), + map(tag("d"), |_| TimeUnit::Day), + map(tag("w"), |_| TimeUnit::Week), + map(tag("m"), |_| TimeUnit::Month), + map(tag("y"), |_| TimeUnit::Year), + ))(remaining)?; + Ok(( + remaining, + WarningDelay { + warning_delay_type, + value, + unit, + }, + )) } diff --git a/src/types/mod.rs b/src/types/mod.rs index 8c7bd4b..89d5015 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -78,17 +78,23 @@ pub use object::PlainText; pub use object::RadioLink; pub use object::RadioTarget; pub use object::RegularLink; +pub use object::Repeater; +pub use object::RepeaterType; +pub use object::RepeaterWarningDelayValueType; pub use object::StatisticsCookie; pub use object::StrikeThrough; pub use object::Subscript; pub use object::Superscript; pub use object::Target; pub use object::Time; +pub use object::TimeUnit; pub use object::Timestamp; pub use object::TimestampRangeType; pub use object::TimestampType; pub use object::Underline; pub use object::Verbatim; +pub use object::WarningDelay; +pub use object::WarningDelayType; pub use object::Year; pub use object::YearInner; pub(crate) use source::SetSource; diff --git a/src/types/object.rs b/src/types/object.rs index a898694..3440997 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -191,6 +191,8 @@ pub struct Timestamp<'s> { pub end: Option>, pub start_time: Option>, pub end_time: Option>, + pub repeater: Option, + pub warning_delay: Option, } #[derive(Debug, PartialEq)] @@ -394,6 +396,44 @@ impl<'s> Time<'s> { } } +#[derive(Debug, PartialEq, Clone)] +pub enum RepeaterType { + Cumulative, + CatchUp, + Restart, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum WarningDelayType { + All, + First, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TimeUnit { + Hour, + Day, + Week, + Month, + Year, +} + +pub type RepeaterWarningDelayValueType = u16; + +#[derive(Debug, PartialEq, Clone)] +pub struct Repeater { + pub repeater_type: RepeaterType, + pub value: RepeaterWarningDelayValueType, + pub unit: TimeUnit, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct WarningDelay { + pub warning_delay_type: WarningDelayType, + pub value: RepeaterWarningDelayValueType, + pub unit: TimeUnit, +} + impl<'s> GetStandardProperties<'s> for Object<'s> { fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> { match self { From 54c66fb4d6eb71b576da7ed9a0af4845e10c36be Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:07:12 -0400 Subject: [PATCH 14/19] Change get_property to allow absent values. We're returning an Option<> anyway so might as well handle absent values. --- src/compare/util.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/compare/util.rs b/src/compare/util.rs index afaedf3..4c2578d 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -172,9 +172,7 @@ fn maybe_token_to_usize( /// Get a named property from the emacs token. /// -/// Returns Ok(None) if value is nil. -/// -/// Returns error if the attribute is not specified on the token at all. +/// Returns Ok(None) if value is nil or absent. pub(crate) fn get_property<'b, 's, 'x>( emacs: &'b Token<'s>, key: &'x str, @@ -185,14 +183,15 @@ pub(crate) fn get_property<'b, 's, 'x>( .nth(1) .ok_or("Should have an attributes child.")?; let attributes_map = attributes_child.as_map()?; - let prop = attributes_map - .get(key) - .ok_or(format!("Missing {} attribute.", key))?; - match prop.as_atom() { - Ok("nil") => return Ok(None), + let prop = attributes_map.get(key).map(|token| *token); + match prop + .map(|token| token.as_atom()) + .map_or(Ok(None), |r| r.map(Some))? + { + Some("nil") => return Ok(None), _ => {} }; - Ok(Some(*prop)) + Ok(prop) } /// Get a named property containing an unquoted atom from the emacs token. From bc3224be7ae55fc7cf8f07984d0d2ab25d38d1f5 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:09:20 -0400 Subject: [PATCH 15/19] Revert the rest_end functions. --- src/parser/timestamp.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index f50b4b1..2fa5a04 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -483,18 +483,32 @@ fn time_rest<'b, 'g, 'r, 's>( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn active_time_rest_end<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, + context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - recognize(verify(anychar, |c| ">\n".contains(*c)))(input) + alt(( + recognize(verify(anychar, |c| ">\n".contains(*c))), + recognize(tuple((space1, parser_with_context!(repeater)(context)))), + recognize(tuple(( + space1, + parser_with_context!(warning_delay)(context), + ))), + ))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn inactive_time_rest_end<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, + context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - recognize(verify(anychar, |c| "]\n".contains(*c)))(input) + alt(( + recognize(verify(anychar, |c| "]\n".contains(*c))), + recognize(tuple((space1, parser_with_context!(repeater)(context)))), + recognize(tuple(( + space1, + parser_with_context!(warning_delay)(context), + ))), + ))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] From 5c929ffc13d20e1331c65db2ece31f2a799d4744 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:18:25 -0400 Subject: [PATCH 16/19] Fix repeater type. I had Cumulative and CatchUp backwards. --- .../object/timestamp/all_repeater_warning_delay_mark.org | 7 +++++++ src/parser/timestamp.rs | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 org_mode_samples/object/timestamp/all_repeater_warning_delay_mark.org diff --git a/org_mode_samples/object/timestamp/all_repeater_warning_delay_mark.org b/org_mode_samples/object/timestamp/all_repeater_warning_delay_mark.org new file mode 100644 index 0000000..2e9a867 --- /dev/null +++ b/org_mode_samples/object/timestamp/all_repeater_warning_delay_mark.org @@ -0,0 +1,7 @@ +# All the marks for repeater and warning delay +[1970-01-01 Thu 8:15-13:15foo +1h -2h] +[1970-01-01 Thu 8:15-13:15foo ++1d -2d] +[1970-01-01 Thu 8:15-13:15foo .+1w -2w] +[1970-01-01 Thu 8:15-13:15foo +1m --2m] +[1970-01-01 Thu 8:15-13:15foo ++1y --2y] +[1970-01-01 Thu 8:15-13:15foo .+1d --2h] diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 2fa5a04..e34598b 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -534,8 +534,8 @@ fn repeater<'b, 'g, 'r, 's>( // ++ for catch-up type // .+ for restart type let (remaining, repeater_type) = alt(( - map(tag("++"), |_| RepeaterType::Cumulative), - map(tag("+"), |_| RepeaterType::CatchUp), + map(tag("++"), |_| RepeaterType::CatchUp), + map(tag("+"), |_| RepeaterType::Cumulative), map(tag(".+"), |_| RepeaterType::Restart), ))(input)?; let (remaining, value) = digit1(remaining)?; From d53b9e1e1f88464e3c8011240c475c4c4ee0ca6e Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:22:35 -0400 Subject: [PATCH 17/19] Fix get_property. This was returning the error when a token was not an atom whereas we only wanted to check to see if it was the atom nil. --- src/compare/util.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/compare/util.rs b/src/compare/util.rs index 4c2578d..ff3c471 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -184,11 +184,8 @@ pub(crate) fn get_property<'b, 's, 'x>( .ok_or("Should have an attributes child.")?; let attributes_map = attributes_child.as_map()?; let prop = attributes_map.get(key).map(|token| *token); - match prop - .map(|token| token.as_atom()) - .map_or(Ok(None), |r| r.map(Some))? - { - Some("nil") => return Ok(None), + match prop.map(|token| token.as_atom()) { + Some(Ok("nil")) => return Ok(None), _ => {} }; Ok(prop) From 2b5df8395605d00ab8aae945371e743024e3740a Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:24:47 -0400 Subject: [PATCH 18/19] Format the code. --- src/compare/elisp_fact.rs | 2 +- src/parser/document.rs | 2 +- src/parser/in_buffer_settings.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compare/elisp_fact.rs b/src/compare/elisp_fact.rs index 6d846e5..6256023 100644 --- a/src/compare/elisp_fact.rs +++ b/src/compare/elisp_fact.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use crate::types::AstNode; use crate::types::AngleLink; +use crate::types::AstNode; use crate::types::BabelCall; use crate::types::Bold; use crate::types::Citation; diff --git a/src/parser/document.rs b/src/parser/document.rs index a7b8b8b..b49973e 100644 --- a/src/parser/document.rs +++ b/src/parser/document.rs @@ -20,9 +20,9 @@ use crate::context::RefContext; use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; -use crate::types::AstNode; use crate::parser::org_source::convert_error; use crate::parser::util::blank_line; +use crate::types::AstNode; use crate::types::Document; use crate::types::Object; diff --git a/src/parser/in_buffer_settings.rs b/src/parser/in_buffer_settings.rs index 95618e1..d71e413 100644 --- a/src/parser/in_buffer_settings.rs +++ b/src/parser/in_buffer_settings.rs @@ -11,8 +11,8 @@ use super::OrgSource; use crate::context::HeadlineLevelFilter; use crate::error::CustomError; use crate::error::Res; -use crate::types::AstNode; use crate::settings::GlobalSettings; +use crate::types::AstNode; use crate::types::Document; use crate::types::Keyword; From 94401dcf008f2f83a92b3416aededc70ce5d3486 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 19:51:29 -0400 Subject: [PATCH 19/19] Allow REST despite no TIME. --- src/parser/timestamp.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index e34598b..03dd759 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -129,6 +129,13 @@ fn active_timestamp<'b, 'g, 'r, 's>( space1, parser_with_context!(time(true))(&time_context), )))(remaining)?; + let remaining = if time.is_none() { + // Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'. + let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?; + remain + } else { + remaining + }; let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, warning_delay) = opt(tuple(( @@ -173,6 +180,13 @@ fn inactive_timestamp<'b, 'g, 'r, 's>( space1, parser_with_context!(time(true))(&time_context), )))(remaining)?; + let remaining = if time.is_none() { + // Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'. + let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?; + remain + } else { + remaining + }; let (remaining, repeater) = opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?; let (remaining, warning_delay) = opt(tuple((