This ensures the parsers can take into account the affiliated keywords when setting their source without needing the SetSource trait.
130 lines
4.7 KiB
Rust
130 lines
4.7 KiB
Rust
use nom::branch::alt;
|
|
use nom::combinator::eof;
|
|
use nom::combinator::recognize;
|
|
use nom::combinator::verify;
|
|
use nom::multi::many0;
|
|
use nom::multi::many1;
|
|
use nom::multi::many_till;
|
|
use nom::sequence::tuple;
|
|
|
|
use super::element_parser::detect_element;
|
|
use super::keyword::affiliated_keyword;
|
|
use super::org_source::OrgSource;
|
|
use super::util::blank_line;
|
|
use super::util::get_consumed;
|
|
use super::util::get_has_affiliated_keyword;
|
|
use super::util::get_name;
|
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
|
use crate::context::parser_with_context;
|
|
use crate::context::ContextElement;
|
|
use crate::context::ExitClass;
|
|
use crate::context::ExitMatcherNode;
|
|
use crate::context::HasAffiliatedKeywordInner;
|
|
use crate::context::RefContext;
|
|
use crate::error::CustomError;
|
|
use crate::error::MyError;
|
|
use crate::error::Res;
|
|
use crate::parser::object_parser::standard_set_object;
|
|
use crate::parser::util::exit_matcher_parser;
|
|
use crate::parser::util::start_of_line;
|
|
use crate::types::Paragraph;
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
pub(crate) fn paragraph<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
|
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
|
let contexts = [
|
|
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner {
|
|
start_after_affiliated_keywords: remaining,
|
|
keywords: &affiliated_keywords,
|
|
}),
|
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
class: ExitClass::Gamma,
|
|
exit_matcher: ¶graph_end,
|
|
}),
|
|
];
|
|
let parser_context = context.with_additional_node(&contexts[0]);
|
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
|
|
|
let (remaining, (children, _exit_contents)) = verify(
|
|
many_till(standard_set_object_matcher, exit_matcher),
|
|
|(children, _exit_contents)| !children.is_empty(),
|
|
)(remaining)?;
|
|
|
|
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
|
|
|
|
let (remaining, _trailing_ws) =
|
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
let source = get_consumed(input, remaining);
|
|
|
|
Ok((
|
|
remaining,
|
|
Paragraph {
|
|
source: source.into(),
|
|
name: get_name(&affiliated_keywords),
|
|
children,
|
|
},
|
|
))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn paragraph_end<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
let regular_end = recognize(tuple((start_of_line, many1(blank_line))))(input);
|
|
if regular_end.is_ok() {
|
|
return regular_end;
|
|
}
|
|
match get_has_affiliated_keyword(context) {
|
|
Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => {
|
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
"No exit due to affiliated keywords.",
|
|
))));
|
|
}
|
|
_ => {}
|
|
}
|
|
// Check to see if input is the start of a HasAffiliatedKeyword
|
|
alt((
|
|
recognize(parser_with_context!(detect_element(false))(context)),
|
|
eof,
|
|
))(input)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::context::parser_with_context;
|
|
use crate::context::Context;
|
|
use crate::context::ContextElement;
|
|
use crate::context::GlobalSettings;
|
|
use crate::context::List;
|
|
use crate::parser::element_parser::element;
|
|
use crate::parser::org_source::OrgSource;
|
|
use crate::types::GetStandardProperties;
|
|
|
|
#[test]
|
|
fn two_paragraphs() {
|
|
let input = OrgSource::new("foo bar baz\n\nlorem ipsum");
|
|
let global_settings = GlobalSettings::default();
|
|
let initial_context = ContextElement::document_context();
|
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
|
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
|
let (remaining, second_paragraph) =
|
|
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
|
assert_eq!(Into::<&str>::into(remaining), "");
|
|
assert_eq!(
|
|
first_paragraph.get_standard_properties().get_source(),
|
|
"foo bar baz\n\n"
|
|
);
|
|
assert_eq!(
|
|
second_paragraph.get_standard_properties().get_source(),
|
|
"lorem ipsum"
|
|
);
|
|
}
|
|
}
|