diff --git a/org_mode_samples/lesser_element/clock/date_range_without_duration.org b/org_mode_samples/lesser_element/clock/date_range_without_duration.org new file mode 100644 index 0000000..13b153d --- /dev/null +++ b/org_mode_samples/lesser_element/clock/date_range_without_duration.org @@ -0,0 +1 @@ +CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35] diff --git a/org_mode_samples/lesser_element/clock/non_range_with_duration.org b/org_mode_samples/lesser_element/clock/non_range_with_duration.org new file mode 100644 index 0000000..d7a121d --- /dev/null +++ b/org_mode_samples/lesser_element/clock/non_range_with_duration.org @@ -0,0 +1 @@ +CLOCK: [2023-04-21 Fri 19:43] => 0:03 diff --git a/org_mode_samples/lesser_element/clock/time_range.org b/org_mode_samples/lesser_element/clock/time_range.org new file mode 100644 index 0000000..05d3e69 --- /dev/null +++ b/org_mode_samples/lesser_element/clock/time_range.org @@ -0,0 +1,2 @@ +CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d] => 0:03 +CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d] diff --git a/src/compare/diff.rs b/src/compare/diff.rs index cef815e..e9bd492 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -23,6 +23,7 @@ use crate::types::CheckboxType; use crate::types::Citation; use crate::types::CitationReference; use crate::types::Clock; +use crate::types::ClockStatus; use crate::types::Code; use crate::types::Comment; use crate::types::CommentBlock; @@ -2097,16 +2098,55 @@ fn compare_clock<'b, 's>( emacs: &'b Token<'s>, rust: &'b Clock<'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 :status :value :duration + // Compare value + let value = get_property(emacs, ":value")?; + match value { + Some(e) => { + let result = compare_ast_node(_source, e, (&rust.timestamp).into())?; + child_status.push(artificial_diff_scope("value", vec![result])?); + } + None => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Value mismatch (emacs != rust) {:?} != {:?}", + value, rust.timestamp + )); + } + } + + // Compare duration + let duration = get_property_quoted_string(emacs, ":duration")?; + if duration.as_ref().map(String::as_str) != rust.duration { + this_status = DiffStatus::Bad; + message = Some(format!( + "Duration mismatch (emacs != rust) {:?} != {:?}", + duration, rust.duration + )); + } + + // Compare status + let status = get_property_unquoted_atom(emacs, ":status")?; + match (status, &rust.status) { + (Some("running"), ClockStatus::Running) => {} + (Some("closed"), ClockStatus::Closed) => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Status mismatch (emacs != rust) {:?} != {:?}", + status, rust.status + )); + } + } 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/clock.rs b/src/parser/clock.rs index 8605444..5b4d8cd 100644 --- a/src/parser/clock.rs +++ b/src/parser/clock.rs @@ -1,23 +1,27 @@ use nom::branch::alt; -use nom::bytes::complete::is_not; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::digit1; -use nom::character::complete::line_ending; use nom::character::complete::space0; use nom::character::complete::space1; -use nom::combinator::eof; +use nom::combinator::map; +use nom::combinator::opt; use nom::combinator::recognize; -use nom::combinator::verify; use nom::sequence::tuple; use super::org_source::OrgSource; +use super::timestamp::inactive_date_range_timestamp; +use super::timestamp::inactive_time_range_timestamp; +use super::timestamp::inactive_timestamp; +use super::util::org_line_ending; use crate::context::parser_with_context; use crate::context::RefContext; use crate::error::Res; use crate::parser::util::get_consumed; use crate::parser::util::start_of_line; use crate::types::Clock; +use crate::types::ClockStatus; +use crate::types::Timestamp; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn clock<'b, 'g, 'r, 's>( @@ -29,52 +33,54 @@ pub(crate) fn clock<'b, 'g, 'r, 's>( let (remaining, _clock) = tag_no_case("clock:")(remaining)?; let (remaining, _gap_whitespace) = space1(remaining)?; - let (remaining, _timestamp_junk) = alt(( - parser_with_context!(inactive_timestamp_range_duration)(context), - parser_with_context!(inactive_timestamp)(context), - ))(remaining)?; + let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?; + let (remaining, _) = tuple((space0, org_line_ending))(remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, Clock { source: source.into(), + timestamp, + duration, + status: if duration.is_some() { + ClockStatus::Closed + } else { + ClockStatus::Running + }, }, )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, +fn clock_timestamp<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - recognize(tuple(( - tag("["), - is_not("\r\n]"), - tag("]--["), - is_not("\r\n]"), - tag("]"), - space1, - tag("=>"), - space1, - digit1, - tag(":"), - verify(digit1, |mm: &OrgSource<'_>| mm.len() == 2), - space0, - alt((line_ending, eof)), - )))(input) +) -> Res, (Timestamp<'s>, Option<&'s str>)> { + // TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation. + alt(( + // Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp. + map( + parser_with_context!(inactive_time_range_timestamp)(context), + |timestamp| (timestamp, None), + ), + map( + tuple(( + parser_with_context!(inactive_date_range_timestamp)(context), + opt(duration), + )), + |(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)), + ), + map( + parser_with_context!(inactive_timestamp)(context), + |timestamp| (timestamp, None), + ), + ))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn inactive_timestamp<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - recognize(tuple(( - tag("["), - is_not("\r\n]"), - tag("]"), - space0, - alt((line_ending, eof)), - )))(input) +fn duration<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + let (remaining, _) = tuple((tag("=>"), space1))(input)?; + let (remaining, duration) = recognize(tuple((digit1, tag(":"), digit1)))(remaining)?; + Ok((remaining, duration)) } diff --git a/src/parser/timestamp.rs b/src/parser/timestamp.rs index 03dd759..dd666f2 100644 --- a/src/parser/timestamp.rs +++ b/src/parser/timestamp.rs @@ -165,7 +165,7 @@ fn active_timestamp<'b, 'g, 'r, 's>( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn inactive_timestamp<'b, 'g, 'r, 's>( +pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { @@ -299,7 +299,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( +pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { @@ -332,7 +332,7 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( +pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Timestamp<'s>> { diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index 3a78c9d..dd19efe 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -84,9 +84,18 @@ pub struct SrcBlock<'s> { pub contents: String, } +#[derive(Debug)] +pub enum ClockStatus { + Running, + Closed, +} + #[derive(Debug)] pub struct Clock<'s> { pub source: &'s str, + pub timestamp: Timestamp<'s>, + pub duration: Option<&'s str>, + pub status: ClockStatus, } #[derive(Debug)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 5242562..7abd710 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -39,6 +39,7 @@ pub use greater_element::TableRowType; pub use lesser_element::BabelCall; pub use lesser_element::CharOffsetInLine; pub use lesser_element::Clock; +pub use lesser_element::ClockStatus; pub use lesser_element::Comment; pub use lesser_element::CommentBlock; pub use lesser_element::DiarySexp;