use std::borrow::Cow; // TODO: Add a check for unexpected keys in the properties use std::collections::BTreeSet; use std::collections::HashSet; use super::elisp_fact::ElispFact; use super::elisp_fact::GetElispFact; use super::sexp::unquote; use super::sexp::Token; use super::util::compare_standard_properties; use super::util::get_property; use super::util::get_property_boolean; use super::util::get_property_numeric; use super::util::get_property_quoted_string; use super::util::get_property_unquoted_atom; use crate::types::AngleLink; use crate::types::AstNode; use crate::types::BabelCall; use crate::types::Bold; use crate::types::CheckboxType; use crate::types::Citation; use crate::types::CitationReference; use crate::types::Clock; use crate::types::Code; use crate::types::Comment; use crate::types::CommentBlock; use crate::types::Date; use crate::types::DayOfMonth; use crate::types::DayOfMonthInner; use crate::types::DiarySexp; use crate::types::Document; use crate::types::DocumentElement; use crate::types::Drawer; use crate::types::DynamicBlock; use crate::types::Entity; use crate::types::ExampleBlock; use crate::types::ExportBlock; use crate::types::ExportSnippet; use crate::types::FixedWidthArea; use crate::types::FootnoteDefinition; use crate::types::FootnoteReference; use crate::types::GetStandardProperties; use crate::types::GreaterBlock; use crate::types::Heading; use crate::types::HorizontalRule; use crate::types::Hour; use crate::types::HourInner; use crate::types::InlineBabelCall; use crate::types::InlineSourceBlock; use crate::types::Italic; use crate::types::Keyword; use crate::types::LatexEnvironment; use crate::types::LatexFragment; use crate::types::LineBreak; use crate::types::Minute; use crate::types::MinuteInner; use crate::types::Month; use crate::types::MonthInner; use crate::types::NodeProperty; use crate::types::OrgMacro; use crate::types::Paragraph; use crate::types::PlainLink; use crate::types::PlainList; use crate::types::PlainListItem; use crate::types::PlainListItemCounter; use crate::types::PlainListItemPreBlank; use crate::types::PlainListType; use crate::types::PlainText; use crate::types::Planning; use crate::types::PriorityCookie; use crate::types::PropertyDrawer; use crate::types::RadioLink; use crate::types::RadioTarget; use crate::types::RegularLink; use crate::types::RepeaterType; use crate::types::RepeaterWarningDelayValueType; use crate::types::Section; use crate::types::SrcBlock; use crate::types::StandardProperties; use crate::types::StatisticsCookie; use crate::types::StrikeThrough; use crate::types::Subscript; use crate::types::Superscript; use crate::types::Table; use crate::types::TableCell; use crate::types::TableRow; use crate::types::Target; use crate::types::Time; use crate::types::TimeUnit; use crate::types::Timestamp; use crate::types::TimestampRangeType; use crate::types::TimestampType; use crate::types::TodoKeywordType; use crate::types::Underline; use crate::types::Verbatim; use crate::types::VerseBlock; use crate::types::WarningDelayType; use crate::types::Year; use crate::types::YearInner; #[derive(Debug)] pub enum DiffEntry<'b, 's> { DiffResult(DiffResult<'b, 's>), DiffLayer(DiffLayer<'b, 's>), } #[derive(Debug)] pub struct DiffResult<'b, 's> { status: DiffStatus, name: Cow<'s, str>, message: Option, children: Vec>, rust_source: &'s str, #[allow(dead_code)] emacs_token: &'b Token<'s>, } #[derive(Debug, PartialEq)] enum DiffStatus { Good, Bad, } #[derive(Debug)] pub struct DiffLayer<'b, 's> { name: Cow<'s, str>, children: Vec>, } impl<'b, 's> From> for DiffEntry<'b, 's> { fn from(value: DiffResult<'b, 's>) -> Self { DiffEntry::DiffResult(value) } } impl<'b, 's> From> for DiffEntry<'b, 's> { fn from(value: DiffLayer<'b, 's>) -> Self { DiffEntry::DiffLayer(value) } } impl<'b, 's> DiffEntry<'b, 's> { fn has_bad_children(&self) -> bool { match self { DiffEntry::DiffResult(diff) => &diff.children, DiffEntry::DiffLayer(diff) => &diff.children, } .iter() .any(|child| child.is_immediately_bad() || child.has_bad_children()) } fn is_immediately_bad(&self) -> bool { match self { DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad, DiffEntry::DiffLayer(_) => false, } } pub fn is_bad(&self) -> bool { self.is_immediately_bad() || self.has_bad_children() } pub fn print(&self, original_document: &str) -> Result<(), Box> { self.print_indented(0, original_document) } fn print_indented( &self, indentation: usize, original_document: &str, ) -> Result<(), Box> { match self { DiffEntry::DiffResult(diff) => diff.print_indented(indentation, original_document), DiffEntry::DiffLayer(diff) => diff.print_indented(indentation, original_document), } } } impl<'b, 's> DiffResult<'b, 's> { fn print_indented( &self, indentation: usize, original_document: &str, ) -> Result<(), Box> { let status_text = { match self.status { DiffStatus::Good => { if self.has_bad_children() { format!( "{color}BADCHILD{reset}", color = DiffResult::foreground_color(255, 255, 0), reset = DiffResult::reset_color(), ) } else { format!( "{color}GOOD{reset}", color = DiffResult::foreground_color(0, 255, 0), reset = DiffResult::reset_color(), ) } } DiffStatus::Bad => format!( "{color}BAD{reset}", color = DiffResult::foreground_color(255, 0, 0), reset = DiffResult::reset_color(), ), } }; let rust_offset = self.rust_source.as_ptr() as usize - original_document.as_ptr() as usize; let preceding_text = &original_document[..rust_offset]; println!( "{indentation}{status_text} {name} char({char_offset}) {message}", indentation = " ".repeat(indentation), status_text = status_text, name = self.name, char_offset = preceding_text.chars().count() + 1, message = self.message.as_ref().map(|m| m.as_str()).unwrap_or("") ); for child in self.children.iter() { child.print_indented(indentation + 1, original_document)?; } Ok(()) } fn has_bad_children(&self) -> bool { self.children .iter() .any(|child| child.is_immediately_bad() || child.has_bad_children()) } fn foreground_color(red: u8, green: u8, blue: u8) -> String { if DiffResult::should_use_color() { format!( "\x1b[38;2;{red};{green};{blue}m", red = red, green = green, blue = blue ) } else { String::new() } } #[allow(dead_code)] fn background_color(red: u8, green: u8, blue: u8) -> String { if DiffResult::should_use_color() { format!( "\x1b[48;2;{red};{green};{blue}m", red = red, green = green, blue = blue ) } else { String::new() } } fn reset_color() -> &'static str { if DiffResult::should_use_color() { "\x1b[0m" } else { "" } } fn should_use_color() -> bool { !std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty()) } } impl<'b, 's> DiffLayer<'b, 's> { fn has_bad_children(&self) -> bool { self.children .iter() .any(|child| child.is_immediately_bad() || child.has_bad_children()) } fn print_indented( &self, indentation: usize, original_document: &str, ) -> Result<(), Box> { let status_text = if self.has_bad_children() { format!( "{color}BADCHILD{reset}", color = DiffResult::foreground_color(255, 255, 0), reset = DiffResult::reset_color(), ) } else { format!( "{color}GOOD{reset}", color = DiffResult::foreground_color(0, 255, 0), reset = DiffResult::reset_color(), ) }; println!( "{indentation}{status_text} {name}", indentation = " ".repeat(indentation), status_text = status_text, name = self.name, ); for child in self.children.iter() { child.print_indented(indentation + 1, original_document)?; } Ok(()) } } fn artificial_diff_scope<'b, 's>( name: &'static str, children: Vec>, ) -> Result, Box> { Ok(DiffLayer { name: name.into(), children, } .into()) } fn compare_ast_node<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: AstNode<'b, 's>, ) -> Result, Box> { let compare_result: Result, Box> = match rust { AstNode::Document(node) => _compare_document(source, emacs, node), AstNode::Heading(node) => compare_heading(source, emacs, node), AstNode::Section(node) => compare_section(source, emacs, node), AstNode::Paragraph(node) => compare_paragraph(source, emacs, node), AstNode::PlainList(node) => compare_plain_list(source, emacs, node), AstNode::PlainListItem(node) => compare_plain_list_item(source, emacs, node), AstNode::GreaterBlock(node) => compare_greater_block(source, emacs, node), AstNode::DynamicBlock(node) => compare_dynamic_block(source, emacs, node), AstNode::FootnoteDefinition(node) => compare_footnote_definition(source, emacs, node), AstNode::Comment(node) => compare_comment(source, emacs, node), AstNode::Drawer(node) => compare_drawer(source, emacs, node), AstNode::PropertyDrawer(node) => compare_property_drawer(source, emacs, node), AstNode::NodeProperty(node) => compare_node_property(source, emacs, node), AstNode::Table(node) => compare_table(source, emacs, node), AstNode::TableRow(node) => compare_table_row(source, emacs, node), AstNode::VerseBlock(node) => compare_verse_block(source, emacs, node), AstNode::CommentBlock(node) => compare_comment_block(source, emacs, node), AstNode::ExampleBlock(node) => compare_example_block(source, emacs, node), AstNode::ExportBlock(node) => compare_export_block(source, emacs, node), AstNode::SrcBlock(node) => compare_src_block(source, emacs, node), AstNode::Clock(node) => compare_clock(source, emacs, node), AstNode::DiarySexp(node) => compare_diary_sexp(source, emacs, node), AstNode::Planning(node) => compare_planning(source, emacs, node), AstNode::FixedWidthArea(node) => compare_fixed_width_area(source, emacs, node), AstNode::HorizontalRule(node) => compare_horizontal_rule(source, emacs, node), AstNode::Keyword(node) => compare_keyword(source, emacs, node), AstNode::BabelCall(node) => compare_babel_call(source, emacs, node), AstNode::LatexEnvironment(node) => compare_latex_environment(source, emacs, node), AstNode::Bold(node) => compare_bold(source, emacs, node), AstNode::Italic(node) => compare_italic(source, emacs, node), AstNode::Underline(node) => compare_underline(source, emacs, node), AstNode::StrikeThrough(node) => compare_strike_through(source, emacs, node), AstNode::Code(node) => compare_code(source, emacs, node), AstNode::Verbatim(node) => compare_verbatim(source, emacs, node), AstNode::PlainText(node) => compare_plain_text(source, emacs, node), AstNode::RegularLink(node) => compare_regular_link(source, emacs, node), AstNode::RadioLink(node) => compare_radio_link(source, emacs, node), AstNode::RadioTarget(node) => compare_radio_target(source, emacs, node), AstNode::PlainLink(node) => compare_plain_link(source, emacs, node), AstNode::AngleLink(node) => compare_angle_link(source, emacs, node), AstNode::OrgMacro(node) => compare_org_macro(source, emacs, node), AstNode::Entity(node) => compare_entity(source, emacs, node), AstNode::LatexFragment(node) => compare_latex_fragment(source, emacs, node), AstNode::ExportSnippet(node) => compare_export_snippet(source, emacs, node), AstNode::FootnoteReference(node) => compare_footnote_reference(source, emacs, node), AstNode::Citation(node) => compare_citation(source, emacs, node), AstNode::CitationReference(node) => compare_citation_reference(source, emacs, node), AstNode::InlineBabelCall(node) => compare_inline_babel_call(source, emacs, node), AstNode::InlineSourceBlock(node) => compare_inline_source_block(source, emacs, node), AstNode::LineBreak(node) => compare_line_break(source, emacs, node), AstNode::Target(node) => compare_target(source, emacs, node), AstNode::StatisticsCookie(node) => compare_statistics_cookie(source, emacs, node), AstNode::Subscript(node) => compare_subscript(source, emacs, node), AstNode::Superscript(node) => compare_superscript(source, emacs, node), AstNode::TableCell(node) => compare_table_cell(source, emacs, node), AstNode::Timestamp(node) => compare_timestamp(source, emacs, node), }; let mut compare_result = match compare_result.unwrap_or_else(|e| { DiffResult { status: DiffStatus::Bad, name: rust.get_elisp_fact().get_elisp_name(), message: Some(e.to_string()), children: Vec::new(), rust_source: rust.get_standard_properties().get_source(), emacs_token: emacs, } .into() }) { DiffEntry::DiffResult(inner) => inner, DiffEntry::DiffLayer(_) => { unreachable!("Layers are only interior to DiffResults of AST nodes.") } }; // PlainText is a special case because upstream Org-Mode uses relative values for the bounds in plaintext rather than absolute so the below checks do not account for that. if let AstNode::PlainText(_) = rust { } else { match compare_standard_properties(source, emacs, &rust) { Err(err) => { compare_result.status = DiffStatus::Bad; compare_result.message = Some(err.to_string()) } Ok(_) => {} } } Ok(compare_result.into()) } pub fn compare_document<'b, 's>( emacs: &'b Token<'s>, rust: &'b Document<'s>, ) -> Result, Box> { compare_ast_node(rust.source, emacs, rust.into()) } fn _compare_document<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Document<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // Compare :path // :path is a quoted string to the absolute path of the document. let document_path = get_property_quoted_string(emacs, ":path")?; let rust_document_path = rust.path.as_ref().map(|p| p.to_str()).flatten(); match ( document_path.as_ref().map(|s| s.as_str()), rust_document_path, ) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Path mismatch (emacs != rust) {:?} != {:?}", document_path, rust_document_path )); } (Some(e), Some(r)) if e != r => { this_status = DiffStatus::Bad; message = Some(format!( "Path mismatch (emacs != rust) {:?} != {:?}", document_path, rust_document_path )); } (Some(_), Some(_)) => {} }; // Compare category // :CATEGORY is specified either from "#+CATEGORY:" or it is the file name without the ".org" extension. let category = get_property_quoted_string(emacs, ":CATEGORY")?; match (category.as_ref(), rust.category.as_ref()) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Category mismatch (emacs != rust) {:?} != {:?}", category, rust.category )); } (Some(e), Some(r)) if e != r => { this_status = DiffStatus::Bad; message = Some(format!( "Category mismatch (emacs != rust) {:?} != {:?}", category, rust.category )); } (Some(_), Some(_)) => {} }; // Skipping "org-data" and its properties for (i, token) in children.iter().skip(2).enumerate() { let section_or_headline = token.as_list()?; let first_cell = section_or_headline .first() .ok_or("Should have at least one child.")? .as_atom()?; if first_cell == "section" { if i != 0 { return Err("Section cannot be after the first child of document.".into()); } child_status.push(compare_ast_node( rust.source, token, rust.zeroth_section .as_ref() .ok_or("No corresponding zeroth-section")? .into(), )?); } else if first_cell == "headline" { let corresponding_heading = rust .children .iter() .nth(i - rust.zeroth_section.as_ref().map(|_| 1).unwrap_or(0)) .ok_or("Should have a corresponding heading.")?; child_status.push(compare_ast_node( rust.source, token, corresponding_heading.into(), )?); } else { return Err(format!( "Document should only contain sections and headlines, found: {}", first_cell ) .into()); } } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_section<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b Section<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let this_status = DiffStatus::Good; let mut child_status = Vec::new(); let message = None; for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_heading<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b Heading<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // Compare level let level = get_property(emacs, ":level")? .ok_or("Level should not be nil")? .as_atom()?; if rust.level.to_string() != level { this_status = DiffStatus::Bad; message = Some(format!( "Headline level do not match (emacs != rust): {} != {}", level, rust.level )) } // Compare tags let emacs_tags = get_tags_from_heading(emacs)?; let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect(); let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect(); let difference: Vec<&str> = emacs_tags .symmetric_difference(&rust_tags) .map(|val| *val) .collect(); if !difference.is_empty() { this_status = DiffStatus::Bad; message = Some(format!("Mismatched tags: {}", difference.join(", "))); } // Compare todo-keyword 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_type, rust_todo)), Ok(emacs_todo)) if emacs_todo == *rust_todo => {} (emacs_todo, rust_todo, _) => { this_status = DiffStatus::Bad; message = Some(format!( "(emacs != rust) {:?} != {:?}", emacs_todo, rust_todo )); } }; // Compare todo-type let todo_type = get_property(emacs, ":todo-type")? .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .unwrap_or("nil"); // todo-type is an unquoted string either todo, done, or nil match (todo_type, &rust.todo_keyword) { ("nil", None) => {} ("todo", Some((TodoKeywordType::Todo, _))) => {} ("done", Some((TodoKeywordType::Done, _))) => {} (emacs_todo, rust_todo) => { this_status = DiffStatus::Bad; message = Some(format!( "(emacs != rust) {:?} != {:?}", emacs_todo, rust_todo )); } }; // Compare title let title = get_property(emacs, ":title")?; match (title, rust.title.len()) { (None, 0) => {} (None, _) => { this_status = DiffStatus::Bad; message = Some(format!( "Titles do not match (emacs != rust): {:?} != {:?}", title, rust.title )) } (Some(title), _) => { let title_status = title .as_list()? .iter() .zip(rust.title.iter()) .map(|(emacs_child, rust_child)| { compare_ast_node(source, emacs_child, rust_child.into()) }) .collect::, _>>()?; child_status.push(artificial_diff_scope("title", title_status)?); } }; // Compare priority let priority = get_property(emacs, ":priority")?; match (priority, rust.priority_cookie) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Priority cookie mismatch (emacs != rust) {:?} != {:?}", priority, rust.priority_cookie )); } (Some(emacs_priority_cookie), Some(rust_priority_cookie)) => { let emacs_priority_cookie = emacs_priority_cookie.as_atom()?.parse::()?; if emacs_priority_cookie != rust_priority_cookie { this_status = DiffStatus::Bad; message = Some(format!( "Priority cookie mismatch (emacs != rust) {:?} != {:?}", emacs_priority_cookie, rust_priority_cookie )); } } } // Compare archived let archived = get_property(emacs, ":archivedp")?; match (archived, rust.is_archived) { (None, true) | (Some(_), false) => { this_status = DiffStatus::Bad; message = Some(format!( "archived mismatch (emacs != rust) {:?} != {:?}", archived, rust.is_archived )); } (None, false) | (Some(_), true) => {} } // Compare commented let commented = get_property(emacs, ":commentedp")?; match (commented, rust.is_comment) { (None, true) | (Some(_), false) => { this_status = DiffStatus::Bad; message = Some(format!( "commented mismatch (emacs != rust) {:?} != {:?}", commented, rust.is_comment )); } (None, false) | (Some(_), true) => {} } // Compare raw-value let raw_value = get_property_quoted_string(emacs, ":raw-value")? .ok_or("Headlines should have :raw-value.")?; let rust_raw_value = rust.get_raw_value(); if raw_value != rust_raw_value { this_status = DiffStatus::Bad; message = Some(format!( "raw-value mismatch (emacs != rust) {:?} != {:?}", raw_value, rust_raw_value )); } // Compare footnote-section-p let footnote_section = get_property_boolean(emacs, ":footnote-section-p")?; if footnote_section != rust.is_footnote_section { this_status = DiffStatus::Bad; message = Some(format!( "footnote section mismatch (emacs != rust) {:?} != {:?}", footnote_section, rust.is_footnote_section )); } // TODO: Compare :pre-blank :scheduled :closed // // :scheduled and :closed seem to only appear when the headline has a planning // Compare section let section_status = children .iter() .skip(2) .zip(rust.children.iter()) .map(|(emacs_child, rust_child)| match rust_child { DocumentElement::Heading(rust_heading) => { compare_ast_node(source, emacs_child, rust_heading.into()) } DocumentElement::Section(rust_section) => { compare_ast_node(source, emacs_child, rust_section.into()) } }) .collect::, _>>()?; child_status.push(artificial_diff_scope("section", section_status)?); Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn get_tags_from_heading<'b, 's>( emacs: &'b Token<'s>, ) -> Result, Box> { let tags = match get_property(emacs, ":tags")? { Some(prop) => prop, None => return Ok(HashSet::new()), }; match tags.as_atom() { Ok(val) => panic!("Unexpected value for tags: {:?}", val), Err(_) => {} }; let tags = { let tags = tags.as_list()?; let strings = tags .iter() .map(Token::as_atom) .collect::, _>>()?; strings .into_iter() .map(unquote) .collect::, _>>()? }; Ok(tags) } fn compare_paragraph<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b Paragraph<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_plain_list<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b PlainList<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // Compare type // :type is an unquoted atom of either descriptive, ordered, or unordered let list_type = get_property_unquoted_atom(emacs, ":type")?; match (list_type, &rust.list_type) { (None, _) => panic!("Emacs returned a list with no type."), (Some("unordered"), PlainListType::Unordered) => {} (Some("ordered"), PlainListType::Ordered) => {} (Some("descriptive"), PlainListType::Descriptive) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "List type mismatch (emacs != rust) {:?} != {:?}", list_type, rust.list_type )); } } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_plain_list_item<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b PlainListItem<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // 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_ast_node(source, emacs_child, rust_child.into()) }) .collect::, _>>()?; child_status.push(artificial_diff_scope("tag", tag_status)?); } }; // Compare contents let contents_status = children .iter() .skip(2) .zip(rust.children.iter()) .map(|(emacs_child, rust_child)| compare_ast_node(source, emacs_child, rust_child.into())) .collect::, _>>()?; child_status.push(artificial_diff_scope("contents", contents_status)?); // Compare bullet let bullet = get_property_quoted_string(emacs, ":bullet")? .ok_or("Plain list items must have a :bullet.")?; if bullet != rust.bullet { this_status = DiffStatus::Bad; message = Some(format!( "Bullet mismatch (emacs != rust) {:?} != {:?}", bullet, rust.bullet )); } // Compare counter let counter = get_property_unquoted_atom(emacs, ":counter")?; let counter: Option = counter .map(|val| val.parse()) .map_or(Ok(None), |r| r.map(Some))?; match (counter, rust.counter) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Counter mismatch (emacs != rust) {:?} != {:?}", counter, rust.counter )); } (Some(e), Some(r)) if e != r => { this_status = DiffStatus::Bad; message = Some(format!( "Counter mismatch (emacs != rust) {:?} != {:?}", counter, rust.counter )); } (Some(_), Some(_)) => {} }; // Compare checkbox let checkbox = get_property(emacs, ":checkbox")? .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .unwrap_or("nil"); match (checkbox, &rust.checkbox) { ("nil", None) => {} ("off", Some((CheckboxType::Off, _))) => {} ("trans", Some((CheckboxType::Trans, _))) => {} ("on", Some((CheckboxType::On, _))) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Checkbox mismatch (emacs != rust) {:?} != {:?}", checkbox, rust.checkbox )); } }; // Compare pre-blank // :pre-blank appears to count the line breaks between "::" and the contents in a descriptive list. Oddly enough it does not count the spaces so I'm not quite sure what the value is. let pre_blank = get_property_unquoted_atom(emacs, ":pre-blank")?; let pre_blank: Option = pre_blank .map(|val| val.parse()) .map_or(Ok(None), |r| r.map(Some))?; if pre_blank.unwrap_or(0) != rust.pre_blank { this_status = DiffStatus::Bad; message = Some(format!( "Pre-blank mismatch (emacs != rust) {:?} != {:?}", pre_blank, rust.pre_blank )); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_greater_block<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b GreaterBlock<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Special block compare :type :parameters // Center and quote block has no additional properties for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_dynamic_block<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b DynamicBlock<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :block-name :arguments for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_footnote_definition<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b FootnoteDefinition<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :label :pre-blank for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_comment<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Comment<'s>, ) -> Result, Box> { let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_drawer<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b Drawer<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :drawer-name for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_property_drawer<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b PropertyDrawer<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_node_property<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b NodeProperty<'s>, ) -> Result, Box> { let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :key :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_table<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b Table<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // Compare formulas // // :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table. let emacs_formulas = get_property(emacs, ":tblfm")?; if let Some(emacs_formulas) = emacs_formulas { let emacs_formulas = emacs_formulas.as_list()?; if emacs_formulas.len() != rust.formulas.len() { this_status = DiffStatus::Bad; message = Some(format!( "Formulas do not match (emacs != rust): {:?} != {:?}", emacs_formulas, rust.formulas )) } else { let atoms = emacs_formulas .into_iter() .map(Token::as_atom) .collect::, _>>()?; let unquoted = atoms .into_iter() .map(unquote) .collect::, _>>()?; for kw in &rust.formulas { if !unquoted.contains(kw.value) { this_status = DiffStatus::Bad; message = Some(format!("Could not find formula in emacs: {}", kw.value)) } } } } else { if !rust.formulas.is_empty() { this_status = DiffStatus::Bad; message = Some(format!( "Formulas do not match (emacs != rust): {:?} != {:?}", emacs_formulas, rust.formulas )) } } // TODO: Compare :type :value for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_table_row<'b, 's>( source: &'s str, emacs: &'b Token<'s>, rust: &'b TableRow<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type // // :type is an unquoted atom of either standard or rule for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_table_cell<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b TableCell<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_verse_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b VerseBlock<'s>, ) -> Result, Box> { let children = emacs.as_list()?; let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_comment_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b CommentBlock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_example_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b ExampleBlock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value :switches :number-lines :preserve-indent :retain-labels :use-labels :label-fmt Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_export_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b ExportBlock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_src_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b SrcBlock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_clock<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Clock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :status :value :duration Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_diary_sexp<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b DiarySexp<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_planning<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Planning<'s>, ) -> Result, Box> { let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // Compare scheduled let scheduled = get_property(emacs, ":scheduled")?; match (scheduled, &rust.scheduled) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Scheduled mismatch (emacs != rust) {:?} != {:?}", scheduled, rust.scheduled )); } (Some(emacs_child), Some(rust_child)) => { let result = compare_ast_node(_source, emacs_child, rust_child.into())?; child_status.push(artificial_diff_scope("scheduled", vec![result])?); } } // Compare deadline let deadline = get_property(emacs, ":deadline")?; match (deadline, &rust.deadline) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Deadline mismatch (emacs != rust) {:?} != {:?}", deadline, rust.deadline )); } (Some(emacs_child), Some(rust_child)) => { let result = compare_ast_node(_source, emacs_child, rust_child.into())?; child_status.push(artificial_diff_scope("deadline", vec![result])?); } } // Compare closed let closed = get_property(emacs, ":closed")?; match (closed, &rust.closed) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { this_status = DiffStatus::Bad; message = Some(format!( "Closed mismatch (emacs != rust) {:?} != {:?}", closed, rust.closed )); } (Some(emacs_child), Some(rust_child)) => { let result = compare_ast_node(_source, emacs_child, rust_child.into())?; child_status.push(artificial_diff_scope("closed", vec![result])?); } } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_fixed_width_area<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b FixedWidthArea<'s>, ) -> Result, Box> { let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_horizontal_rule<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b HorizontalRule<'s>, ) -> Result, Box> { let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_keyword<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Keyword<'s>, ) -> Result, Box> { let child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; let key = unquote( get_property(emacs, ":key")? .ok_or("Emacs keywords should have a :key")? .as_atom()?, )?; if key != rust.key.to_uppercase() { this_status = DiffStatus::Bad; message = Some(format!( "Mismatchs keyword keys (emacs != rust) {:?} != {:?}", key, rust.key )) } let value = unquote( get_property(emacs, ":value")? .ok_or("Emacs keywords should have a :value")? .as_atom()?, )?; if value != rust.value { this_status = DiffStatus::Bad; message = Some(format!( "Mismatchs keyword values (emacs != rust) {:?} != {:?}", value, rust.value )) } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_babel_call<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b BabelCall<'s>, ) -> Result, Box> { let child_status = Vec::new(); let mut this_status = DiffStatus::Good; let mut message = None; // TODO: Compare :call :inside-header :arguments :end-header let value = unquote( get_property(emacs, ":value")? .ok_or("Emacs keywords should have a :value")? .as_atom()?, )?; if value != rust.value { this_status = DiffStatus::Bad; message = Some(format!( "Mismatchs keyword values (emacs != rust) {:?} != {:?}", value, rust.value )) } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_latex_environment<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b LatexEnvironment<'s>, ) -> Result, Box> { let child_status = Vec::new(); let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: child_status, rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_plain_text<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b PlainText<'s>, ) -> Result, Box> { 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 .get(0) .expect("Should have start index.") .as_atom()? .parse()?; let end_ind: usize = text .properties .get(1) .expect("Should have end index.") .as_atom()? .parse()?; let emacs_text_length = end_ind - start_ind; if rust_source.chars().count() != emacs_text_length { this_status = DiffStatus::Bad; message = Some(format!( "(emacs len != rust len) {:?} != {:?}", emacs_text_length, rust_source.len() )); } let unquoted_text = unquote(text.text)?; if unquoted_text != rust_source { this_status = DiffStatus::Bad; message = Some(format!( "(emacs != rust) {:?} != {:?}", unquoted_text, rust_source )); } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source, emacs_token: emacs, } .into()) } fn compare_bold<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Bold<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_italic<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Italic<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_underline<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Underline<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_verbatim<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Verbatim<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_code<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Code<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_strike_through<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b StrikeThrough<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_regular_link<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b RegularLink<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type :path :format :raw-link :application :search-option Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_radio_link<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b RadioLink<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type :path :format :raw-link :application :search-option Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_radio_target<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b RadioTarget<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_plain_link<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b PlainLink<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type :path :format :raw-link :application :search-option Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_angle_link<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b AngleLink<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :type :path :format :raw-link :application :search-option Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_org_macro<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b OrgMacro<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :key :value :args Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_entity<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Entity<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :name :latex :latex-math-p :html :ascii :latin1 :utf-8 :use-brackets-p Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_latex_fragment<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b LatexFragment<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_export_snippet<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b ExportSnippet<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :back-end :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_footnote_reference<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b FootnoteReference<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :label :type Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_citation<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Citation<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :style :prefix :suffix Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_citation_reference<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b CitationReference<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :key :prefix :suffix Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_inline_babel_call<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b InlineBabelCall<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :call :inside-header :arguments :end-header :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_inline_source_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b InlineSourceBlock<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :language :value :parameters Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_line_break<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b LineBreak<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_target<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Target<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_statistics_cookie<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b StatisticsCookie<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :value Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_subscript<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Subscript<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :use-brackets-p Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_superscript<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Superscript<'s>, ) -> Result, Box> { let this_status = DiffStatus::Good; let message = None; // TODO: Compare :use-brackets-p Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) } fn compare_timestamp<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b Timestamp<'s>, ) -> Result, Box> { let mut this_status = DiffStatus::Good; let mut message = None; // Compare type let timestamp_type = get_property_unquoted_atom(emacs, ":type")?; match (timestamp_type, &rust.timestamp_type) { (Some("diary"), TimestampType::Diary) => {} (Some("active"), TimestampType::Active) => {} (Some("inactive"), TimestampType::Inactive) => {} (Some("active-range"), TimestampType::ActiveRange) => {} (Some("inactive-range"), TimestampType::InactiveRange) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Timestamp type mismatch (emacs != rust) {:?} != {:?}", timestamp_type, rust.timestamp_type )); } } // Compare range-type let range_type = get_property_unquoted_atom(emacs, ":range-type")?; match (range_type, &rust.range_type) { (Some("daterange"), TimestampRangeType::DateRange) => {} (Some("timerange"), TimestampRangeType::TimeRange) => {} (None, TimestampRangeType::None) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Range type mismatch (emacs != rust) {:?} != {:?}", range_type, rust.range_type )); } } // Compare raw-value let raw_value = get_property_quoted_string(emacs, ":raw-value")? .ok_or("Timestamps should have a :raw-value.")?; if raw_value != rust.get_raw_value() { this_status = DiffStatus::Bad; message = Some(format!( "Raw value mismatch (emacs != rust) {:?} != {:?}", raw_value, rust.get_raw_value() )); } // Compare start let year_start: Option = get_property_numeric(emacs, ":year-start")?; let month_start: Option = get_property_numeric(emacs, ":month-start")?; let day_of_month_start: Option = get_property_numeric(emacs, ":day-start")?; let rust_year_start = rust.start.as_ref().map(Date::get_year).map(Year::get_value); let rust_month_start = rust .start .as_ref() .map(Date::get_month) .map(Month::get_value); let rust_day_of_month_start = rust .start .as_ref() .map(Date::get_day_of_month) .map(DayOfMonth::get_value); if year_start != rust_year_start { this_status = DiffStatus::Bad; message = Some(format!( "year start mismatch (emacs != rust) {:?} != {:?}", year_start, rust_year_start )); } if month_start != rust_month_start { this_status = DiffStatus::Bad; message = Some(format!( "month start mismatch (emacs != rust) {:?} != {:?}", month_start, rust_month_start )); } if day_of_month_start != rust_day_of_month_start { this_status = DiffStatus::Bad; message = Some(format!( "day of month start mismatch (emacs != rust) {:?} != {:?}", day_of_month_start, rust_day_of_month_start )); } // Compare end let year_end: Option = get_property_numeric(emacs, ":year-end")?; let month_end: Option = get_property_numeric(emacs, ":month-end")?; let day_of_month_end: Option = get_property_numeric(emacs, ":day-end")?; let rust_year_end = rust.end.as_ref().map(Date::get_year).map(Year::get_value); let rust_month_end = rust.end.as_ref().map(Date::get_month).map(Month::get_value); let rust_day_of_month_end = rust .end .as_ref() .map(Date::get_day_of_month) .map(DayOfMonth::get_value); if year_end != rust_year_end { this_status = DiffStatus::Bad; message = Some(format!( "year end mismatch (emacs != rust) {:?} != {:?}", year_end, rust_year_end )); } if month_end != rust_month_end { this_status = DiffStatus::Bad; message = Some(format!( "month end mismatch (emacs != rust) {:?} != {:?}", month_end, rust_month_end )); } if day_of_month_end != rust_day_of_month_end { this_status = DiffStatus::Bad; message = Some(format!( "day of month end mismatch (emacs != rust) {:?} != {:?}", day_of_month_end, rust_day_of_month_end )); } // Compare time start let hour_start: Option = get_property_numeric(emacs, ":hour-start")?; let minute_start: Option = get_property_numeric(emacs, ":minute-start")?; let rust_hour_start = rust .start_time .as_ref() .map(Time::get_hour) .map(Hour::get_value); let rust_minute_start = rust .start_time .as_ref() .map(Time::get_minute) .map(Minute::get_value); if hour_start != rust_hour_start { this_status = DiffStatus::Bad; message = Some(format!( "hour start mismatch (emacs != rust) {:?} != {:?}", hour_start, rust_hour_start )); } if minute_start != rust_minute_start { this_status = DiffStatus::Bad; message = Some(format!( "minute start mismatch (emacs != rust) {:?} != {:?}", minute_start, rust_minute_start )); } // Compare time end let hour_end: Option = get_property_numeric(emacs, ":hour-end")?; let minute_end: Option = get_property_numeric(emacs, ":minute-end")?; let rust_hour_end = rust .end_time .as_ref() .map(Time::get_hour) .map(Hour::get_value); let rust_minute_end = rust .end_time .as_ref() .map(Time::get_minute) .map(Minute::get_value); if hour_end != rust_hour_end { this_status = DiffStatus::Bad; message = Some(format!( "hour end mismatch (emacs != rust) {:?} != {:?}", hour_end, rust_hour_end )); } if minute_end != rust_minute_end { this_status = DiffStatus::Bad; message = Some(format!( "minute end mismatch (emacs != rust) {:?} != {:?}", minute_end, rust_minute_end )); } // Compare repeater let repeater_type = get_property_unquoted_atom(emacs, ":repeater-type")?; let repeater_value: Option = get_property_numeric(emacs, ":repeater-value")?; let repeater_unit = get_property_unquoted_atom(emacs, ":repeater-unit")?; let rust_repeater_type = rust .repeater .as_ref() .map(|repeater| &repeater.repeater_type); let rust_repeater_value = rust.repeater.as_ref().map(|repeater| repeater.value); let rust_repeater_unit = rust.repeater.as_ref().map(|repeater| &repeater.unit); match (repeater_type, rust_repeater_type) { (Some("cumulate"), Some(RepeaterType::Cumulative)) => {} (Some("catch-up"), Some(RepeaterType::CatchUp)) => {} (Some("restart"), Some(RepeaterType::Restart)) => {} (None, None) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Repeater type mismatch (emacs != rust) {:?} != {:?}", repeater_type, rust_repeater_type )); } } if repeater_value != rust_repeater_value { this_status = DiffStatus::Bad; message = Some(format!( "Repeater value mismatch (emacs != rust) {:?} != {:?}", repeater_value, rust_repeater_value )); } match (repeater_unit, rust_repeater_unit) { (Some("hour"), Some(TimeUnit::Hour)) => {} (Some("day"), Some(TimeUnit::Day)) => {} (Some("week"), Some(TimeUnit::Week)) => {} (Some("month"), Some(TimeUnit::Month)) => {} (Some("year"), Some(TimeUnit::Year)) => {} (None, None) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Repeater unit mismatch (emacs != rust) {:?} != {:?}", repeater_unit, rust_repeater_unit )); } } // Compare warning_delay let warning_delay_type = get_property_unquoted_atom(emacs, ":warning-type")?; let warning_delay_value: Option = get_property_numeric(emacs, ":warning-value")?; let warning_delay_unit = get_property_unquoted_atom(emacs, ":warning-unit")?; let rust_warning_delay_type = rust .warning_delay .as_ref() .map(|warning_delay| &warning_delay.warning_delay_type); let rust_warning_delay_value = rust .warning_delay .as_ref() .map(|warning_delay| warning_delay.value); let rust_warning_delay_unit = rust .warning_delay .as_ref() .map(|warning_delay| &warning_delay.unit); match (warning_delay_type, rust_warning_delay_type) { (Some("all"), Some(WarningDelayType::All)) => {} (Some("first"), Some(WarningDelayType::First)) => {} (None, None) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Warning delay type mismatch (emacs != rust) {:?} != {:?}", warning_delay_type, rust_warning_delay_type )); } } if warning_delay_value != rust_warning_delay_value { this_status = DiffStatus::Bad; message = Some(format!( "Warning delay value mismatch (emacs != rust) {:?} != {:?}", warning_delay_value, rust_warning_delay_value )); } match (warning_delay_unit, rust_warning_delay_unit) { (Some("hour"), Some(TimeUnit::Hour)) => {} (Some("day"), Some(TimeUnit::Day)) => {} (Some("week"), Some(TimeUnit::Week)) => {} (Some("month"), Some(TimeUnit::Month)) => {} (Some("year"), Some(TimeUnit::Year)) => {} (None, None) => {} _ => { this_status = DiffStatus::Bad; message = Some(format!( "Warning delay unit mismatch (emacs != rust) {:?} != {:?}", warning_delay_unit, rust_warning_delay_unit )); } } Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), message, children: Vec::new(), rust_source: rust.get_source(), emacs_token: emacs, } .into()) }