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

View File

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

View File

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