diff --git a/src/compare/compare_field.rs b/src/compare/compare_field.rs index f6af4dc..3df2d7e 100644 --- a/src/compare/compare_field.rs +++ b/src/compare/compare_field.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::fmt::Debug; use std::str::FromStr; @@ -208,6 +209,59 @@ pub(crate) fn compare_property_list_of_quoted_string< Ok(ComparePropertiesResult::NoChange) } +pub(crate) fn compare_property_set_of_quoted_string< + 'a, + 'b, + 's, + 'x, + R, + RV: AsRef + std::fmt::Debug + Ord + 'a + ?Sized, + RI: Iterator, + RG: Fn(R) -> Option, +>( + _source: &'s str, + emacs: &'b Token<'s>, + rust_node: R, + emacs_field: &'x str, + rust_value_getter: RG, +) -> Result, Box> { + let value = get_property(emacs, emacs_field)? + .map(Token::as_list) + .map_or(Ok(None), |r| r.map(Some))?; + let empty = Vec::new(); + let value = value.unwrap_or(&empty); + let rust_value = rust_value_getter(rust_node); + let rust_value = if let Some(rust_value) = rust_value { + let slices: BTreeSet<&str> = rust_value.map(|rv| rv.as_ref()).collect(); + slices + } else { + BTreeSet::new() + }; + let value: Vec<&str> = value + .iter() + .map(|e| e.as_atom()) + .collect::, _>>()?; + let value: Vec = value + .into_iter() + .map(unquote) + .collect::, _>>()?; + let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect(); + let mismatched: Vec<_> = value + .symmetric_difference(&rust_value) + .map(|val| *val) + .collect(); + if !mismatched.is_empty() { + let this_status = DiffStatus::Bad; + let message = Some(format!( + "{} mismatch. Mismatched values: {}", + emacs_field, + mismatched.join(", ") + )); + return Ok(ComparePropertiesResult::SelfChange(this_status, message)); + } + Ok(ComparePropertiesResult::NoChange) +} + pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>( _source: &'s str, emacs: &'b Token<'s>, diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 2857483..3159bcf 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -12,6 +12,7 @@ use super::compare_field::compare_property_list_of_ast_nodes; use super::compare_field::compare_property_list_of_quoted_string; use super::compare_field::compare_property_numeric; use super::compare_field::compare_property_quoted_string; +use super::compare_field::compare_property_set_of_quoted_string; use super::compare_field::compare_property_single_ast_node; use super::compare_field::compare_property_unquoted_atom; use super::elisp_fact::ElispFact; @@ -1676,80 +1677,61 @@ fn compare_table<'b, 's>( 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 child_status = Vec::new(); let mut message = None; - // TODO: Compare :caption - // Compare name - let name = get_property_quoted_string(emacs, ":name")?; - if name.as_ref().map(String::as_str) != rust.name { - this_status = DiffStatus::Bad; - message = Some(format!( - "Name mismatch (emacs != rust) {:?} != {:?}", - name, rust.name - )); - } + compare_children( + source, + emacs, + &rust.children, + &mut child_status, + &mut this_status, + &mut message, + )?; - // 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)) - } + for diff in compare_properties!( + source, + emacs, + rust, + ( + EmacsField::Optional(":name"), + |r| r.name, + compare_property_quoted_string + ), + ( + EmacsField::Optional(":caption"), + compare_identity, + compare_noop + ), + ( + EmacsField::Required(":tblfm"), + |r| if r.formulas.is_empty() { + None + } else { + Some(r.formulas.iter().map(|kw| kw.value)) + }, + compare_property_set_of_quoted_string + ), + ( + EmacsField::Optional(":type"), + |_| Some("org"), + compare_property_unquoted_atom + ), + ( + EmacsField::Required(":value"), + compare_identity, + compare_property_always_nil + ) + ) { + match diff { + ComparePropertiesResult::NoChange => {} + ComparePropertiesResult::SelfChange(new_status, new_message) => { + this_status = new_status; + message = new_message } + ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry), } - } else { - if !rust.formulas.is_empty() { - this_status = DiffStatus::Bad; - message = Some(format!( - "Formulas do not match (emacs != rust): {:?} != {:?}", - emacs_formulas, rust.formulas - )) - } - } - - // Compare type - let table_type = get_property_unquoted_atom(emacs, ":type")?.expect("Table should have a type"); - if table_type != "org" { - this_status = DiffStatus::Bad; - message = Some(format!( - "Table type mismatch (emacs != rust) {:?} != {:?}", - table_type, "org" - )); - } - - // Compare value - let value = get_property(emacs, ":value")?; - if value.is_some() { - // I don't know what :value is for, but it seems to always be nil. This is here to alert me to value being non-nil so I can investigate. - this_status = DiffStatus::Bad; - message = Some(format!("Non-nil value {:?}", 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 {