From 3cc22943879da4c882fbb67bf410d98d1e68ee9d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Fri, 8 Sep 2023 15:05:42 -0400 Subject: [PATCH] Move headlines into their own file. --- src/parser/document.rs | 207 +------------------------------------- src/parser/headline.rs | 222 +++++++++++++++++++++++++++++++++++++++++ src/parser/mod.rs | 1 + 3 files changed, 226 insertions(+), 204 deletions(-) create mode 100644 src/parser/headline.rs diff --git a/src/parser/document.rs b/src/parser/document.rs index 1c036d58..44aca17f 100644 --- a/src/parser/document.rs +++ b/src/parser/document.rs @@ -1,23 +1,13 @@ -use nom::branch::alt; -use nom::bytes::complete::tag; -use nom::character::complete::anychar; -use nom::character::complete::line_ending; -use nom::character::complete::space0; -use nom::character::complete::space1; use nom::combinator::all_consuming; -use nom::combinator::eof; -use nom::combinator::map; -use nom::combinator::not; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many0; -use nom::multi::many1; -use nom::multi::many1_count; use nom::multi::many_till; -use nom::multi::separated_list1; use nom::sequence::tuple; +use super::headline::detect_headline; +use super::headline::heading; use super::in_buffer_settings::apply_in_buffer_settings; use super::in_buffer_settings::scan_for_in_buffer_settings; use super::org_source::OrgSource; @@ -25,7 +15,6 @@ use super::token::AllTokensIterator; use super::token::Token; use super::util::exit_matcher_parser; use super::util::get_consumed; -use super::util::start_of_line; use crate::context::parser_with_context; use crate::context::Context; use crate::context::ContextElement; @@ -39,19 +28,15 @@ use crate::error::MyError; use crate::error::Res; use crate::parser::comment::comment; use crate::parser::element_parser::element; -use crate::parser::object_parser::standard_set_object; use crate::parser::org_source::convert_error; use crate::parser::planning::planning; use crate::parser::property_drawer::property_drawer; use crate::parser::util::blank_line; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::types::Document; -use crate::types::DocumentElement; use crate::types::Element; -use crate::types::Heading; use crate::types::Object; use crate::types::Section; -use crate::types::TodoKeywordType; /// Parse a full org-mode document. /// @@ -245,7 +230,7 @@ fn zeroth_section<'b, 'g, 'r, 's>( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn section<'b, 'g, 'r, 's>( +pub fn section<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, mut input: OrgSource<'s>, ) -> Res, Section<'s>> { @@ -306,192 +291,6 @@ fn section_end<'b, 'g, 'r, 's>( recognize(detect_headline)(input) } -const fn heading( - parent_stars: usize, -) -> impl for<'b, 'g, 'r, 's> Fn( - RefContext<'b, 'g, 'r, 's>, - OrgSource<'s>, -) -> Res, Heading<'s>> { - move |context, input: OrgSource<'_>| _heading(context, input, parent_stars) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn _heading<'b, 'g, 'r, 's>( - context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, - parent_stars: usize, -) -> Res, Heading<'s>> { - not(|i| context.check_exit_matcher(i))(input)?; - let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) = - headline(context, input, parent_stars)?; - let section_matcher = parser_with_context!(section)(context); - let heading_matcher = parser_with_context!(heading(star_count))(context); - let (remaining, maybe_section) = - opt(map(section_matcher, DocumentElement::Section))(remaining)?; - let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?; - let (remaining, mut children) = - many0(map(heading_matcher, DocumentElement::Heading))(remaining)?; - if let Some(section) = maybe_section { - children.insert(0, section); - } - let remaining = if children.is_empty() { - // Support empty headings - let (remain, _ws) = many0(blank_line)(remaining)?; - remain - } else { - remaining - }; - - let source = get_consumed(input, remaining); - Ok(( - remaining, - Heading { - source: source.into(), - stars: star_count, - todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| { - (todo_keyword_type, Into::<&str>::into(todo_keyword)) - }), - title, - tags: heading_tags, - children, - }, - )) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn detect_headline<'s>(input: OrgSource<'s>) -> Res, ()> { - tuple((start_of_line, many1(tag("*")), space1))(input)?; - Ok((input, ())) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn headline<'b, 'g, 'r, 's>( - context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, - parent_stars: usize, -) -> Res< - OrgSource<'s>, - ( - usize, - OrgSource<'s>, - Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>, - Vec>, - Vec<&'s str>, - ), -> { - let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Document, - exit_matcher: &headline_title_end, - }); - let parser_context = context.with_additional_node(&parser_context); - - let ( - remaining, - (_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending), - ) = tuple(( - start_of_line, - verify(many1_count(tag("*")), |star_count| { - *star_count > parent_stars - }), - space1, - opt(tuple(( - parser_with_context!(heading_keyword)(&parser_context), - space1, - ))), - many1(parser_with_context!(standard_set_object)(&parser_context)), - opt(tuple((space0, tags))), - space0, - alt((line_ending, eof)), - ))(input)?; - Ok(( - remaining, - ( - star_count, - ws, - maybe_todo_keyword, - title, - maybe_tags - .map(|(_ws, tags)| { - tags.into_iter() - .map(|single_tag| Into::<&str>::into(single_tag)) - .collect() - }) - .unwrap_or(Vec::new()), - ), - )) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn headline_title_end<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, -) -> Res, OrgSource<'s>> { - recognize(tuple(( - opt(tuple((space0, tags, space0))), - alt((line_ending, eof)), - )))(input) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn tags<'s>(input: OrgSource<'s>) -> Res, Vec>> { - let (remaining, (_open, tags, _close)) = - tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?; - Ok((remaining, tags)) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { - recognize(many1(verify(anychar, |c| { - c.is_alphanumeric() || "_@#%".contains(*c) - })))(input) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn heading_keyword<'b, 'g, 'r, 's>( - context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, -) -> Res, (TodoKeywordType, OrgSource<'s>)> { - let global_settings = context.get_global_settings(); - if global_settings.in_progress_todo_keywords.is_empty() - && global_settings.complete_todo_keywords.is_empty() - { - alt(( - map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)), - map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)), - ))(input) - } else { - for todo_keyword in global_settings - .in_progress_todo_keywords - .iter() - .map(String::as_str) - { - let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); - match result { - Ok((remaining, ent)) => { - return Ok((remaining, (TodoKeywordType::Todo, ent))); - } - Err(_) => {} - } - } - for todo_keyword in global_settings - .complete_todo_keywords - .iter() - .map(String::as_str) - { - let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); - match result { - Ok((remaining, ent)) => { - return Ok((remaining, (TodoKeywordType::Done, ent))); - } - Err(_) => {} - } - } - Err(nom::Err::Error(CustomError::MyError(MyError( - "NoTodoKeyword".into(), - )))) - } -} - impl<'s> Document<'s> { pub fn iter_tokens<'r>(&'r self) -> impl Iterator> { AllTokensIterator::new(Token::Document(self)) diff --git a/src/parser/headline.rs b/src/parser/headline.rs new file mode 100644 index 00000000..0146badc --- /dev/null +++ b/src/parser/headline.rs @@ -0,0 +1,222 @@ +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::character::complete::anychar; +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::not; +use nom::combinator::opt; +use nom::combinator::recognize; +use nom::combinator::verify; +use nom::multi::many0; +use nom::multi::many1; +use nom::multi::many1_count; +use nom::multi::separated_list1; +use nom::sequence::tuple; + +use super::document::section; +use super::org_source::OrgSource; +use super::util::get_consumed; +use super::util::start_of_line; +use crate::context::parser_with_context; +use crate::context::ContextElement; +use crate::context::ExitClass; +use crate::context::ExitMatcherNode; +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::blank_line; +use crate::types::DocumentElement; +use crate::types::Heading; +use crate::types::Object; +use crate::types::TodoKeywordType; + +pub const fn heading( + parent_stars: usize, +) -> impl for<'b, 'g, 'r, 's> Fn( + RefContext<'b, 'g, 'r, 's>, + OrgSource<'s>, +) -> Res, Heading<'s>> { + move |context, input: OrgSource<'_>| _heading(context, input, parent_stars) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn _heading<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, + parent_stars: usize, +) -> Res, Heading<'s>> { + not(|i| context.check_exit_matcher(i))(input)?; + let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) = + headline(context, input, parent_stars)?; + let section_matcher = parser_with_context!(section)(context); + let heading_matcher = parser_with_context!(heading(star_count))(context); + let (remaining, maybe_section) = + opt(map(section_matcher, DocumentElement::Section))(remaining)?; + let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?; + let (remaining, mut children) = + many0(map(heading_matcher, DocumentElement::Heading))(remaining)?; + if let Some(section) = maybe_section { + children.insert(0, section); + } + let remaining = if children.is_empty() { + // Support empty headings + let (remain, _ws) = many0(blank_line)(remaining)?; + remain + } else { + remaining + }; + + let source = get_consumed(input, remaining); + Ok(( + remaining, + Heading { + source: source.into(), + stars: star_count, + todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| { + (todo_keyword_type, Into::<&str>::into(todo_keyword)) + }), + title, + tags: heading_tags, + children, + }, + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +pub fn detect_headline<'s>(input: OrgSource<'s>) -> Res, ()> { + tuple((start_of_line, many1(tag("*")), space1))(input)?; + Ok((input, ())) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn headline<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, + parent_stars: usize, +) -> Res< + OrgSource<'s>, + ( + usize, + OrgSource<'s>, + Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>, + Vec>, + Vec<&'s str>, + ), +> { + let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Document, + exit_matcher: &headline_title_end, + }); + let parser_context = context.with_additional_node(&parser_context); + + let ( + remaining, + (_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending), + ) = tuple(( + start_of_line, + verify(many1_count(tag("*")), |star_count| { + *star_count > parent_stars + }), + space1, + opt(tuple(( + parser_with_context!(heading_keyword)(&parser_context), + space1, + ))), + many1(parser_with_context!(standard_set_object)(&parser_context)), + opt(tuple((space0, tags))), + space0, + alt((line_ending, eof)), + ))(input)?; + Ok(( + remaining, + ( + star_count, + ws, + maybe_todo_keyword, + title, + maybe_tags + .map(|(_ws, tags)| { + tags.into_iter() + .map(|single_tag| Into::<&str>::into(single_tag)) + .collect() + }) + .unwrap_or(Vec::new()), + ), + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn headline_title_end<'b, 'g, 'r, 's>( + _context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, OrgSource<'s>> { + recognize(tuple(( + opt(tuple((space0, tags, space0))), + alt((line_ending, eof)), + )))(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn tags<'s>(input: OrgSource<'s>) -> Res, Vec>> { + let (remaining, (_open, tags, _close)) = + tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?; + Ok((remaining, tags)) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + recognize(many1(verify(anychar, |c| { + c.is_alphanumeric() || "_@#%".contains(*c) + })))(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn heading_keyword<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, (TodoKeywordType, OrgSource<'s>)> { + let global_settings = context.get_global_settings(); + if global_settings.in_progress_todo_keywords.is_empty() + && global_settings.complete_todo_keywords.is_empty() + { + alt(( + map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)), + map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)), + ))(input) + } else { + for todo_keyword in global_settings + .in_progress_todo_keywords + .iter() + .map(String::as_str) + { + let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); + match result { + Ok((remaining, ent)) => { + return Ok((remaining, (TodoKeywordType::Todo, ent))); + } + Err(_) => {} + } + } + for todo_keyword in global_settings + .complete_todo_keywords + .iter() + .map(String::as_str) + { + let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); + match result { + Ok((remaining, ent)) => { + return Ok((remaining, (TodoKeywordType::Done, ent))); + } + Err(_) => {} + } + } + Err(nom::Err::Error(CustomError::MyError(MyError( + "NoTodoKeyword".into(), + )))) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0b959743..f402d2aa 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -14,6 +14,7 @@ mod fixed_width_area; mod footnote_definition; mod footnote_reference; mod greater_block; +mod headline; mod horizontal_rule; mod in_buffer_settings; mod inline_babel_call;