From e7ec23af3d66fc55bbc60dd88df65ca7680ba243 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 2 Oct 2023 15:49:51 -0400 Subject: [PATCH] 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 a34c6f70..745f5568 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 9c001f2a..6f1c9220 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 8032a25c..b7e14817 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 8e3a9a6a..1aa29a72 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 ffa9831d..20d1a4c1 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 ab01b7ff..bb10ca42 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 b955ba29..b16cfb60 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 {