diff --git a/org_mode_samples/lesser_element/babel_call/empty.org b/org_mode_samples/lesser_element/babel_call/empty.org new file mode 100644 index 0000000..0710532 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/empty.org @@ -0,0 +1,3 @@ +#+call: + +#+call: diff --git a/org_mode_samples/lesser_element/babel_call/header_arguments.org b/org_mode_samples/lesser_element/babel_call/header_arguments.org new file mode 100644 index 0000000..5416e05 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/header_arguments.org @@ -0,0 +1,3 @@ +#+call: foo[inside](bar="baz")[outside] + +#+call: foo[](bar="baz")[] diff --git a/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org b/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org new file mode 100644 index 0000000..e9be825 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org @@ -0,0 +1 @@ +#+call: foo(bar="baz" diff --git a/org_mode_samples/lesser_element/babel_call/simple.org b/org_mode_samples/lesser_element/babel_call/simple.org index 281b00c..bafcb01 100644 --- a/org_mode_samples/lesser_element/babel_call/simple.org +++ b/org_mode_samples/lesser_element/babel_call/simple.org @@ -1 +1,7 @@ #+call: foo(bar="baz") + +#+call: lorem ipsum + +#+call: dolar cat(dog) + +#+call: (bat) diff --git a/org_mode_samples/lesser_element/babel_call/spaces.org b/org_mode_samples/lesser_element/babel_call/spaces.org new file mode 100644 index 0000000..55f9b85 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/spaces.org @@ -0,0 +1,3 @@ +#+call: foo [inside] (bar="baz") [outside] + +#+call: foo (bar="baz") [outside] diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 7f76639..9f9de03 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2403,7 +2403,7 @@ fn compare_babel_call<'b, 's>( let mut this_status = DiffStatus::Good; let mut message = None; - // TODO: Compare :call :inside-header :arguments :end-header + // TODO: Compare :inside-header :end-header // TODO: Compare :caption // Compare name @@ -2417,19 +2417,55 @@ fn compare_babel_call<'b, 's>( } // Compare value - let value = unquote( - get_property(emacs, ":value")? - .ok_or("Emacs keywords should have a :value")? - .as_atom()?, - )?; + let value = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new()); if value != rust.value { this_status = DiffStatus::Bad; message = Some(format!( - "Mismatchs keyword values (emacs != rust) {:?} != {:?}", + "Value mismatch (emacs != rust) {:?} != {:?}", value, rust.value )) } + // Compare call + let call = get_property_quoted_string(emacs, ":call")?; + if call.as_ref().map(String::as_str) != rust.call { + this_status = DiffStatus::Bad; + message = Some(format!( + "Call mismatch (emacs != rust) {:?} != {:?}", + call, rust.call + )) + } + + // Compare arguments + let arguments = get_property_quoted_string(emacs, ":arguments")?; + if arguments.as_ref().map(String::as_str) != rust.arguments { + this_status = DiffStatus::Bad; + message = Some(format!( + "Arguments mismatch (emacs != rust) {:?} != {:?}", + arguments, rust.arguments + )) + } + + // Compare inside header + let inside_header = get_property_quoted_string(emacs, ":inside-header")?; + if inside_header.as_ref().map(String::as_str) != rust.inside_header { + this_status = DiffStatus::Bad; + message = Some(format!( + "Inside header mismatch (emacs != rust) {:?} != {:?}", + inside_header, rust.inside_header + )) + } + + // Compare end header + let end_header = get_property_quoted_string(emacs, ":end-header")?; + if end_header.as_ref().map(String::as_str) != rust.end_header { + this_status = DiffStatus::Bad; + message = Some(format!( + "End header mismatch (emacs != rust) {:?} != {:?}", + end_header, rust.end_header + )) + } + Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), diff --git a/src/parser/babel_call.rs b/src/parser/babel_call.rs index 0961332..79731cf 100644 --- a/src/parser/babel_call.rs +++ b/src/parser/babel_call.rs @@ -1,10 +1,14 @@ +use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::anychar; +use nom::character::complete::one_of; use nom::character::complete::space0; use nom::combinator::consumed; +use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; +use nom::combinator::verify; use nom::multi::many0; use nom::multi::many_till; use nom::sequence::tuple; @@ -12,6 +16,7 @@ use nom::InputTake; use super::keyword::affiliated_keyword; use super::util::get_name; +use super::util::start_of_line; use super::OrgSource; use crate::context::RefContext; use crate::error::Res; @@ -26,8 +31,8 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>( ) -> Res, BabelCall<'s>> { let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; - let (remaining, (consumed_input, (_, _, parsed_key, _))) = - consumed(tuple((space0, tag("#+"), tag_no_case("call"), tag(":"))))(input)?; + start_of_line(input)?; + let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(input)?; if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) { let source = get_consumed(input, remaining); @@ -37,13 +42,17 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>( source: Into::<&str>::into(source), name: get_name(&affiliated_keywords), value: Into::<&str>::into(line_break.take(0)), + call: None, + inside_header: None, + arguments: None, + end_header: None, }, )); } let (remaining, _ws) = space0(remaining)?; - let (remaining, parsed_value) = - recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?; + let (remaining, (value, (call, inside_header, arguments, end_header))) = + consumed(babel_call_value)(remaining)?; let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?; let source = get_consumed(input, remaining); @@ -53,7 +62,106 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>( BabelCall { source: Into::<&str>::into(source), name: get_name(&affiliated_keywords), - value: Into::<&str>::into(parsed_value), + value: Into::<&str>::into(value).trim_end(), + call: call.map(Into::<&str>::into), + inside_header: inside_header.map(Into::<&str>::into), + arguments: arguments.map(Into::<&str>::into), + end_header: end_header.map(Into::<&str>::into), }, )) } + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn babel_call_value<'s>( + input: OrgSource<'s>, +) -> Res< + OrgSource<'s>, + ( + Option>, + Option>, + Option>, + Option>, + ), +> { + let (remaining, call) = opt(babel_call_call)(input)?; + let (remaining, inside_header) = opt(inside_header)(remaining)?; + let (remaining, arguments) = opt(arguments)(remaining)?; + let (remaining, end_header) = opt(end_header)(remaining)?; + Ok(( + remaining, + (call, inside_header, arguments.flatten(), end_header), + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn babel_call_call<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + verify( + recognize(many_till( + anychar, + alt(( + peek(recognize(one_of("[("))), + recognize(tuple((space0, org_line_ending))), + )), + )), + |s| s.len() > 0, + )(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn inside_header<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + let (remaining, _) = tag("[")(input)?; + + let contents_start = remaining; + let (remaining, contents) = opt(recognize(many_till( + anychar, + alt(( + peek(recognize(one_of("]"))), + recognize(tuple((space0, org_line_ending))), + )), + )))(remaining)?; + let (remaining, _) = tag("]")(remaining)?; + Ok((remaining, contents.unwrap_or(contents_start.take(0)))) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn arguments<'s>(input: OrgSource<'s>) -> Res, Option>> { + let (remaining, _) = tag("(")(input)?; + + let (remaining, contents) = opt(verify( + recognize(many_till( + anychar, + alt(( + peek(recognize(one_of(")"))), + recognize(tuple((space0, org_line_ending))), + )), + )), + |s| s.len() > 0, + ))(remaining)?; + let (remaining, _) = tag(")")(remaining)?; + Ok((remaining, contents)) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn end_header<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + let (remaining, _) = space0(input)?; + verify( + recognize(many_till(anychar, peek(tuple((space0, org_line_ending))))), + |s| s.len() > 0, + )(remaining) +} + +#[cfg(test)] +mod tests { + use nom::combinator::opt; + + use super::*; + + #[test] + fn simple_call() -> Result<(), Box> { + let input = OrgSource::new("()"); + let (remaining, call) = opt(babel_call_call)(input)?; + assert_eq!(Into::<&str>::into(remaining), "()"); + assert_eq!(call, None); + Ok(()) + } +} diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index 665da39..ee126d6 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -139,6 +139,10 @@ pub struct BabelCall<'s> { pub source: &'s str, pub name: Option<&'s str>, pub value: &'s str, + pub call: Option<&'s str>, + pub inside_header: Option<&'s str>, + pub arguments: Option<&'s str>, + pub end_header: Option<&'s str>, } #[derive(Debug)]