Add support for diffing description lists.

This commit is contained in:
Tom Alexander 2023-08-29 19:22:11 -04:00
parent 2682779534
commit f6c895319f
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
4 changed files with 138 additions and 58 deletions

View File

@ -5,3 +5,4 @@
:: ipsum :: ipsum
- -
lorem :: ipsum lorem :: ipsum
- dolar *bold* foo :: ipsum

View File

@ -182,6 +182,23 @@ impl<'s> DiffResult<'s> {
} }
} }
fn artificial_diff_scope<'s>(
name: String,
message: Option<String>,
children: Vec<DiffResult<'s>>,
emacs_token: &'s Token<'s>,
rust_source: &'s str,
) -> Result<DiffResult<'s>, Box<dyn std::error::Error>> {
Ok(DiffResult {
status: DiffStatus::Good,
name,
message,
children,
rust_source,
emacs_token,
})
}
fn compare_element<'s>( fn compare_element<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
@ -385,18 +402,10 @@ fn compare_heading<'s>(
} }
// Compare todo-keyword // Compare todo-keyword
let todo_keyword = { let todo_keyword = get_property(emacs, ":todo-keyword")?
let children = emacs.as_list()?; .map(Token::as_atom)
let attributes_child = children .map_or(Ok(None), |r| r.map(Some))?
.iter() .unwrap_or("nil");
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let todo_keyword = attributes_map
.get(":todo-keyword")
.ok_or("Missing :todo-keyword attribute.");
todo_keyword?.as_atom()?
};
match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) { match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) {
("nil", None, _) => {} ("nil", None, _) => {}
(_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {} (_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {}
@ -410,18 +419,7 @@ fn compare_heading<'s>(
}; };
// Compare title // Compare title
let title = { let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let title = attributes_map
.get(":title")
.ok_or("Missing :title attribute.");
*title?
};
for (emacs_child, rust_child) in title.as_list()?.iter().zip(rust.title.iter()) { for (emacs_child, rust_child) in title.as_list()?.iter().zip(rust.title.iter()) {
child_status.push(compare_object(source, emacs_child, rust_child)?); child_status.push(compare_object(source, emacs_child, rust_child)?);
} }
@ -453,19 +451,12 @@ fn compare_heading<'s>(
fn get_tags_from_heading<'s>( fn get_tags_from_heading<'s>(
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
) -> Result<HashSet<String>, Box<dyn std::error::Error>> { ) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?; let tags = match get_property(emacs, ":tags")? {
let attributes_child = children Some(prop) => prop,
.iter() None => return Ok(HashSet::new()),
.nth(1) };
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let tags = attributes_map
.get(":tags")
.ok_or("Missing :tags attribute.")?;
match tags.as_atom() { match tags.as_atom() {
Ok("nil") => {
return Ok(HashSet::new());
}
Ok(val) => panic!("Unexpected value for tags: {:?}", val), Ok(val) => panic!("Unexpected value for tags: {:?}", val),
Err(_) => {} Err(_) => {}
}; };
@ -483,6 +474,26 @@ fn get_tags_from_heading<'s>(
Ok(tags) Ok(tags)
} }
fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let prop = attributes_map
.get(key)
.ok_or(format!("Missing {} attribute.", key))?;
match prop.as_atom() {
Ok("nil") => return Ok(None),
_ => {}
};
Ok(Some(*prop))
}
fn compare_paragraph<'s>( fn compare_paragraph<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
@ -553,6 +564,7 @@ fn compare_plain_list_item<'s>(
let children = emacs.as_list()?; let children = emacs.as_list()?;
let mut child_status = Vec::new(); let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "item"; let emacs_name = "item";
if assert_name(emacs, emacs_name).is_err() { if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
@ -562,14 +574,50 @@ fn compare_plain_list_item<'s>(
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
} }
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { // Compare tag
child_status.push(compare_element(source, emacs_child, rust_child)?); let tag = get_property(emacs, ":tag")?;
} match (tag, rust.tag.is_empty()) {
(None, true) => {}
(None, false) | (Some(_), true) => {
this_status = DiffStatus::Bad;
message = Some("Mismatched tags".to_owned());
}
(Some(tag), false) => {
let tag_status = tag
.as_list()?
.iter()
.zip(rust.tag.iter())
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope(
"tag".to_owned(),
None,
tag_status,
tag,
rust.get_source(),
)?);
}
};
// Compare contents
let contents_status = children
.iter()
.skip(2)
.zip(rust.children.iter())
.map(|(emacs_child, rust_child)| compare_element(source, emacs_child, rust_child))
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope(
"contents".to_owned(),
None,
contents_status,
emacs,
rust.get_source(),
)?);
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
message: None, message,
children: child_status, children: child_status,
rust_source: rust.get_source(), rust_source: rust.get_source(),
emacs_token: emacs, emacs_token: emacs,
@ -1167,6 +1215,7 @@ fn compare_plain_text<'s>(
) -> Result<DiffResult<'s>, Box<dyn std::error::Error>> { ) -> Result<DiffResult<'s>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None; let mut message = None;
let rust_source = rust.get_source();
let text = emacs.as_text()?; let text = emacs.as_text()?;
let start_ind: usize = text let start_ind: usize = text
.properties .properties
@ -1181,20 +1230,20 @@ fn compare_plain_text<'s>(
.as_atom()? .as_atom()?
.parse()?; .parse()?;
let emacs_text_length = end_ind - start_ind; let emacs_text_length = end_ind - start_ind;
if rust.source.len() != emacs_text_length { if rust_source.len() != emacs_text_length {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"(emacs len != rust len) {:?} != {:?}", "(emacs len != rust len) {:?} != {:?}",
emacs_text_length, emacs_text_length,
rust.source.len() rust_source.len()
)); ));
} }
let unquoted_text = unquote(text.text)?; let unquoted_text = unquote(text.text)?;
if unquoted_text != rust.source { if unquoted_text != rust_source {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"(emacs != rust) {:?} != {:?}", "(emacs != rust) {:?} != {:?}",
unquoted_text, rust.source unquoted_text, rust_source
)); ));
} }
@ -1203,7 +1252,7 @@ fn compare_plain_text<'s>(
name: "plain-text".to_owned(), name: "plain-text".to_owned(),
message, message,
children: Vec::new(), children: Vec::new(),
rust_source: rust.get_source(), rust_source,
emacs_token: emacs, emacs_token: emacs,
}) })
} }

View File

@ -1,6 +1,7 @@
use super::element::Element; use super::element::Element;
use super::lesser_element::TableCell; use super::lesser_element::TableCell;
use super::source::Source; use super::source::Source;
use super::Object;
#[derive(Debug)] #[derive(Debug)]
pub struct PlainList<'s> { pub struct PlainList<'s> {
@ -13,6 +14,7 @@ pub struct PlainListItem<'s> {
pub source: &'s str, pub source: &'s str,
pub indentation: usize, pub indentation: usize,
pub bullet: &'s str, pub bullet: &'s str,
pub tag: Vec<Object<'s>>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }

View File

@ -1,15 +1,12 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::multispace1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
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::many1; use nom::multi::many1;
@ -18,10 +15,12 @@ use nom::sequence::tuple;
use super::greater_element::PlainList; use super::greater_element::PlainList;
use super::greater_element::PlainListItem; use super::greater_element::PlainListItem;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use super::Context; use super::Context;
use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
@ -151,6 +150,7 @@ pub fn plain_list_item<'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: Vec::new(),
children: Vec::new(), children: Vec::new(),
}, },
)); ));
@ -158,7 +158,11 @@ pub fn plain_list_item<'r, 's>(
Err(_) => {} Err(_) => {}
}; };
let (remaining, _maybe_tag) = opt(tuple((space1, item_tag, tag(" ::"))))(remaining)?; let (remaining, maybe_tag) = opt(tuple((
space1,
parser_with_context!(item_tag)(context),
tag(" ::"),
)))(remaining)?;
let (remaining, _ws) = alt((space1, line_ending))(remaining)?; let (remaining, _ws) = alt((space1, line_ending))(remaining)?;
let exit_matcher = plain_list_item_end(indent_level); let exit_matcher = plain_list_item_end(indent_level);
let parser_context = context let parser_context = context
@ -183,6 +187,9 @@ pub fn plain_list_item<'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()), // TODO
children, children,
}, },
)); ));
@ -266,15 +273,36 @@ fn _line_indented_lte<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn item_tag<'r, 's>(
recognize(many_till( context: Context<'r, 's>,
anychar, input: OrgSource<'s>,
peek(alt(( ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
line_ending, let parser_context =
tag(" :: "), context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
recognize(tuple((tag(" ::"), alt((line_ending, eof))))), class: ExitClass::Gamma,
))), exit_matcher: &item_tag_end,
))(input) }));
let (remaining, (children, _exit_contents)) = verify(
many_till(
// TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(alt((
line_ending,
tag(" :: "),
recognize(tuple((tag(" ::"), alt((line_ending, eof))))),
)))(input)
} }
#[cfg(test)] #[cfg(test)]