organic/src/parser/footnote_definition.rs

177 lines
6.0 KiB
Rust
Raw Normal View History

use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while;
use nom::character::complete::digit1;
use nom::character::complete::space0;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::WORD_CONSTITUENT_CHARACTERS;
2023-09-03 16:07:51 +00:00
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
2023-04-21 22:36:01 +00:00
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
2023-04-21 20:10:56 +00:00
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line;
2023-09-03 16:07:51 +00:00
use crate::types::FootnoteDefinition;
2023-08-11 00:04:59 +00:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'r, 's>(
2023-09-03 02:45:46 +00:00
context: RefContext<'_, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(input)?;
// Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
2023-09-03 16:07:51 +00:00
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &footnote_definition_end,
2023-09-03 16:07:51 +00:00
}),
];
2023-09-03 16:28:45 +00:00
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters.
2023-04-22 05:45:38 +00:00
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteDefinition {
source: source.into(),
label: lbl.into(),
children,
},
))
}
2023-08-11 00:04:59 +00:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
))(input)
}
2023-08-11 00:04:59 +00:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>(
2023-09-03 02:45:46 +00:00
context: RefContext<'_, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = alt((
recognize(tuple((
parser_with_context!(maybe_consume_trailing_whitespace)(context),
detect_footnote_definition,
))),
recognize(tuple((
start_of_line,
verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() >= 2
}),
))),
))(input)?;
Ok((remaining, source))
}
2023-04-10 15:54:05 +00:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
2023-04-10 15:54:05 +00:00
#[cfg(test)]
mod tests {
use super::*;
2023-09-03 16:07:51 +00:00
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
2023-09-03 16:28:45 +00:00
use crate::types::Source;
2023-04-10 15:54:05 +00:00
#[test]
fn two_paragraphs() {
2023-08-24 21:15:24 +00:00
let input = OrgSource::new(
"[fn:1] A footnote.
2023-04-10 15:54:05 +00:00
[fn:2] A multi-
2023-08-24 21:15:24 +00:00
line footnote.",
);
2023-09-03 16:07:51 +00:00
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
2023-08-24 21:15:24 +00:00
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
2023-04-10 15:54:05 +00:00
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
let (remaining, second_footnote_definition) =
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
2023-08-24 21:15:24 +00:00
assert_eq!(Into::<&str>::into(remaining), "");
2023-04-10 15:54:05 +00:00
assert_eq!(
first_footnote_definition.get_source(),
2023-04-10 15:54:05 +00:00
"[fn:1] A footnote.
"
);
assert_eq!(
second_footnote_definition.get_source(),
2023-04-10 15:54:05 +00:00
"[fn:2] A multi-
line footnote."
);
}
#[test]
fn multiline_break() {
2023-08-24 21:15:24 +00:00
let input = OrgSource::new(
"[fn:2] A multi-
line footnote.
2023-08-24 21:15:24 +00:00
not in the footnote.",
);
2023-09-03 16:07:51 +00:00
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
2023-08-24 21:15:24 +00:00
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
2023-08-24 21:15:24 +00:00
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!(
first_footnote_definition.get_source(),
"[fn:2] A multi-
line footnote.
"
);
}
2023-04-10 15:54:05 +00:00
}