diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 7138fa1..08c0e86 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -719,9 +719,58 @@ fn compare_heading<'b, 's>( )); } - // TODO: Compare :pre-blank :scheduled :closed - // - // :scheduled and :closed seem to only appear when the headline has a planning + // Compare scheduled + let scheduled = get_property(emacs, ":scheduled")?; + match (scheduled, &rust.scheduled) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Scheduled mismatch (emacs != rust) {:?} != {:?}", + scheduled, rust.scheduled + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("scheduled", vec![result])?); + } + } + + // Compare deadline + let deadline = get_property(emacs, ":deadline")?; + match (deadline, &rust.deadline) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Deadline mismatch (emacs != rust) {:?} != {:?}", + deadline, rust.deadline + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("deadline", vec![result])?); + } + } + + // Compare closed + let closed = get_property(emacs, ":closed")?; + match (closed, &rust.closed) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Closed mismatch (emacs != rust) {:?} != {:?}", + closed, rust.closed + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("closed", vec![result])?); + } + } + + // TODO: Compare :pre-blank // Compare section let section_status = children @@ -1402,20 +1451,70 @@ fn compare_diary_sexp<'b, 's>( } fn compare_planning<'b, 's>( - _source: &'s str, + source: &'s str, emacs: &'b Token<'s>, rust: &'b Planning<'s>, ) -> Result, Box> { - let this_status = DiffStatus::Good; - let message = None; + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + let mut message = None; - // TODO: Compare :closed :deadline :scheduled + // Compare scheduled + let scheduled = get_property(emacs, ":scheduled")?; + match (scheduled, &rust.scheduled) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Scheduled mismatch (emacs != rust) {:?} != {:?}", + scheduled, rust.scheduled + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("scheduled", vec![result])?); + } + } + + // Compare deadline + let deadline = get_property(emacs, ":deadline")?; + match (deadline, &rust.deadline) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Deadline mismatch (emacs != rust) {:?} != {:?}", + deadline, rust.deadline + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("deadline", vec![result])?); + } + } + + // Compare closed + let closed = get_property(emacs, ":closed")?; + match (closed, &rust.closed) { + (None, None) => {} + (None, Some(_)) | (Some(_), None) => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Closed mismatch (emacs != rust) {:?} != {:?}", + closed, rust.closed + )); + } + (Some(emacs_child), Some(rust_child)) => { + let result = compare_ast_node(source, emacs_child, rust_child.into())?; + child_status.push(artificial_diff_scope("closed", vec![result])?); + } + } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, - children: Vec::new(), + children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } diff --git a/src/parser/headline.rs b/src/parser/headline.rs index 3868362..f59bde2 100644 --- a/src/parser/headline.rs +++ b/src/parser/headline.rs @@ -34,6 +34,7 @@ use crate::error::Res; use crate::parser::object_parser::standard_set_object; use crate::parser::util::blank_line; use crate::types::DocumentElement; +use crate::types::Element; use crate::types::Heading; use crate::types::HeadlineLevel; use crate::types::Object; @@ -55,6 +56,9 @@ fn _heading<'b, 'g, 'r, 's>( input: OrgSource<'s>, parent_star_count: HeadlineLevel, ) -> Res, Heading<'s>> { + let mut scheduled = None; + let mut deadline = None; + let mut closed = None; not(|i| context.check_exit_matcher(i))(input)?; let (remaining, pre_headline) = headline(context, input, parent_star_count)?; let section_matcher = parser_with_context!(section)(context); @@ -65,6 +69,14 @@ fn _heading<'b, 'g, 'r, 's>( let (remaining, mut children) = many0(map(heading_matcher, DocumentElement::Heading))(remaining)?; if let Some(section) = maybe_section { + // If the section has a planning then the timestamp values are copied to the heading. + if let DocumentElement::Section(inner_section) = §ion { + if let Some(Element::Planning(planning)) = inner_section.children.first() { + scheduled = planning.scheduled.clone(); + deadline = planning.deadline.clone(); + closed = planning.closed.clone(); + } + } children.insert(0, section); } let remaining = if children.is_empty() { @@ -94,6 +106,9 @@ fn _heading<'b, 'g, 'r, 's>( is_comment: pre_headline.comment.is_some(), is_archived, is_footnote_section: pre_headline.is_footnote_section, + scheduled, + deadline, + closed, }, )) } diff --git a/src/parser/planning.rs b/src/parser/planning.rs index b50a9ea..d7fe70e 100644 --- a/src/parser/planning.rs +++ b/src/parser/planning.rs @@ -3,6 +3,7 @@ use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::space0; use nom::character::complete::space1; +use nom::combinator::map; use nom::multi::many1; use nom::sequence::tuple; @@ -16,6 +17,7 @@ use crate::error::Res; use crate::parser::util::get_consumed; use crate::parser::util::start_of_line; use crate::types::Planning; +use crate::types::Timestamp; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn planning<'b, 'g, 'r, 's>( @@ -24,7 +26,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>( ) -> Res, Planning<'s>> { start_of_line(input)?; let (remaining, _leading_whitespace) = space0(input)?; - let (remaining, _planning_parameters) = + let (remaining, planning_parameters) = many1(parser_with_context!(planning_parameter)(context))(remaining)?; let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?; @@ -32,26 +34,54 @@ pub(crate) fn planning<'b, 'g, 'r, 's>( maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; let source = get_consumed(input, remaining); + let mut scheduled = None; + let mut deadline = None; + let mut closed = None; + + for (timestamp_type, timestamp) in planning_parameters.into_iter() { + match timestamp_type { + PlanningTimestampType::Scheduled => { + scheduled = Some(timestamp); + } + PlanningTimestampType::Deadline => { + deadline = Some(timestamp); + } + PlanningTimestampType::Closed => { + closed = Some(timestamp); + } + } + } + Ok(( remaining, Planning { source: source.into(), + scheduled, + deadline, + closed, }, )) } +enum PlanningTimestampType { + Scheduled, + Deadline, + Closed, +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn planning_parameter<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - let (remaining, _planning_type) = alt(( - tag_no_case("DEADLINE"), - tag_no_case("SCHEDULED"), - tag_no_case("CLOSED"), +) -> Res, (PlanningTimestampType, Timestamp<'s>)> { + let (remaining, planning_type) = alt(( + map(tag_no_case("DEADLINE"), |_| PlanningTimestampType::Deadline), + map(tag_no_case("SCHEDULED"), |_| { + PlanningTimestampType::Scheduled + }), + map(tag_no_case("CLOSED"), |_| PlanningTimestampType::Closed), ))(input)?; let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?; - let (remaining, _timestamp) = timestamp(context, remaining)?; - let source = get_consumed(input, remaining); - Ok((remaining, source)) + let (remaining, timestamp) = timestamp(context, remaining)?; + Ok((remaining, (planning_type, timestamp))) } diff --git a/src/types/document.rs b/src/types/document.rs index 31f471c..e3aa933 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -4,6 +4,7 @@ use super::Element; use super::GetStandardProperties; use super::Object; use super::StandardProperties; +use super::Timestamp; pub type PriorityCookie = u8; pub type HeadlineLevel = u16; @@ -29,6 +30,9 @@ pub struct Heading<'s> { pub is_comment: bool, pub is_archived: bool, pub is_footnote_section: bool, + pub scheduled: Option>, + pub deadline: Option>, + pub closed: Option>, } #[derive(Debug)] diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index 1061139..e0e9762 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -1,6 +1,7 @@ use super::object::Object; use super::PlainText; use super::StandardProperties; +use super::Timestamp; #[derive(Debug)] pub struct Paragraph<'s> { @@ -72,6 +73,9 @@ pub struct DiarySexp<'s> { #[derive(Debug)] pub struct Planning<'s> { pub source: &'s str, + pub scheduled: Option>, + pub deadline: Option>, + pub closed: Option>, } #[derive(Debug)] diff --git a/src/types/object.rs b/src/types/object.rs index 3440997..8501365 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -182,7 +182,7 @@ pub struct Superscript<'s> { pub source: &'s str, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Timestamp<'s> { pub source: &'s str, pub timestamp_type: TimestampType, @@ -195,7 +195,7 @@ pub struct Timestamp<'s> { pub warning_delay: Option, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum TimestampType { Diary, Active, @@ -204,7 +204,7 @@ pub enum TimestampType { InactiveRange, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum TimestampRangeType { None, DateRange,