Handle the possibility of a title-less headline.

This commit is contained in:
Tom Alexander 2023-09-14 01:41:09 -04:00
parent fc4ff97c14
commit 44e9f708c9
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
4 changed files with 108 additions and 42 deletions

View File

@ -1 +1,2 @@
* DONE * DONE
*

View File

@ -546,14 +546,26 @@ fn compare_heading<'s>(
}; };
// Compare title // Compare title
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?; let title = get_property(emacs, ":title")?;
let title_status = title match (title, rust.title.len()) {
.as_list()? (None, 0) => {}
.iter() (None, _) => {
.zip(rust.title.iter()) this_status = DiffStatus::Bad;
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child)) message = Some(format!(
.collect::<Result<Vec<_>, _>>()?; "Titles do not match (emacs != rust): {:?} != {:?}",
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?); title, rust.title
))
}
(Some(title), _) => {
let title_status = title
.as_list()?
.iter()
.zip(rust.title.iter())
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
}
};
// Compare priority // Compare priority
let priority = get_property(emacs, ":priority")?; let priority = get_property(emacs, ":priority")?;

View File

@ -8,6 +8,7 @@ use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
@ -19,6 +20,11 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::section; use super::section::section;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::org_line_ending;
use super::util::org_space;
use super::util::org_space_or_line_ending;
use super::util::org_spaces0;
use super::util::org_spaces1;
use super::util::start_of_line; use super::util::start_of_line;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@ -81,10 +87,10 @@ fn _heading<'b, 'g, 'r, 's>(
Heading { Heading {
source: source.into(), source: source.into(),
stars: star_count, stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| { todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword)) (todo_keyword_type, Into::<&str>::into(todo_keyword))
}), }),
priority_cookie: maybe_priority.map(|(priority, _)| priority), priority_cookie: maybe_priority.map(|(_, priority)| priority),
title, title,
tags: heading_tags, tags: heading_tags,
children, children,
@ -109,9 +115,9 @@ fn headline<'b, 'g, 'r, 's>(
OrgSource<'s>, OrgSource<'s>,
( (
usize, usize,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>, Option<(TodoKeywordType, OrgSource<'s>)>,
Option<(PriorityCookie, OrgSource<'s>)>, Option<(OrgSource<'s>, PriorityCookie)>,
Option<(OrgSource<'s>, OrgSource<'s>)>, Option<OrgSource<'s>>,
Vec<Object<'s>>, Vec<Object<'s>>,
Vec<&'s str>, Vec<&'s str>,
), ),
@ -122,45 +128,45 @@ fn headline<'b, 'g, 'r, 's>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let ( let (remaining, (_, star_count, _)) = tuple((
remaining,
(
_,
star_count,
_,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags,
_,
_,
),
) = tuple((
start_of_line, start_of_line,
verify(many1_count(tag("*")), |star_count| { verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars *star_count > parent_stars
}), }),
space1, peek(org_space),
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
opt(tuple((priority_cookie, space1))),
opt(tuple((tag("COMMENT"), space1))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?; ))(input)?;
let (remaining, maybe_todo_keyword) = opt(tuple((
org_spaces1,
parser_with_context!(heading_keyword)(&parser_context),
peek(org_space_or_line_ending),
)))(remaining)?;
let (remaining, maybe_priority) = opt(tuple((org_spaces1, priority_cookie)))(remaining)?;
let (remaining, maybe_comment) = opt(tuple((
org_spaces1,
tag("COMMENT"),
peek(org_space_or_line_ending),
)))(remaining)?;
let (remaining, maybe_title) = opt(tuple((
org_spaces1,
many1(parser_with_context!(standard_set_object)(&parser_context)),
)))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((org_spaces0, tags)))(remaining)?;
let (remaining, _) = tuple((org_spaces0, org_line_ending))(remaining)?;
Ok(( Ok((
remaining, remaining,
( (
star_count, star_count,
maybe_todo_keyword, maybe_todo_keyword.map(|(_, todo, _)| todo),
maybe_priority, maybe_priority,
maybe_comment, maybe_comment.map(|(_, comment, _)| comment),
title, maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
maybe_tags maybe_tags
.map(|(_ws, tags)| { .map(|(_ws, tags)| {
tags.into_iter() tags.into_iter()

View File

@ -1,4 +1,5 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::is_a;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::none_of; use nom::character::complete::none_of;
@ -9,9 +10,11 @@ use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use nom::Slice;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use crate::context::parser_with_context;
@ -212,6 +215,9 @@ fn text_until_eol<'r, 's>(
Ok(line.trim()) Ok(line.trim())
} }
/// Return a tuple of (input, output) from a nom parser.
///
/// This is similar to recognize except it returns the input instead of the portion of the input that was consumed.
pub(crate) fn include_input<'s, F, O>( pub(crate) fn include_input<'s, F, O>(
mut inner: F, mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)> ) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
@ -223,3 +229,44 @@ where
Ok((remaining, (input, output))) Ok((remaining, (input, output)))
} }
} }
/// Match single space or tab.
///
/// In org-mode syntax, spaces and tabs are interchangeable.
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
one_of(" \t")(input)
}
/// Matches a single space, tab, line ending, or end of file.
///
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
pub(crate) fn org_space_or_line_ending<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((recognize(one_of(" \t")), org_line_ending))(input)
}
/// Match as many spaces and tabs as possible. No minimum match.
///
/// In org-mode syntax, spaces and tabs are interchangeable.
pub(crate) fn org_spaces0<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let found = is_a(" \t")(input);
if found.is_ok() {
return found;
}
Ok((input, input.slice(..0)))
}
/// Match as many spaces and tabs as possible. Minimum 1 character.
///
/// In org-mode syntax, spaces and tabs are interchangeable.
pub(crate) fn org_spaces1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(is_a(" \t"), |res: &OrgSource<'_>| res.len() > 0)(input)
}
/// Match a line break or the end of the file.
///
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
pub(crate) fn org_line_ending<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((line_ending, eof))(input)
}