Implement comparison for object tree.
This commit is contained in:
parent
33800c4a88
commit
0aa746fb1e
@ -16,7 +16,6 @@ use super::util::get_property_unquoted_atom;
|
|||||||
use crate::types::AstNode;
|
use crate::types::AstNode;
|
||||||
use crate::types::CharOffsetInLine;
|
use crate::types::CharOffsetInLine;
|
||||||
use crate::types::LineNumber;
|
use crate::types::LineNumber;
|
||||||
use crate::types::Object;
|
|
||||||
use crate::types::RetainLabels;
|
use crate::types::RetainLabels;
|
||||||
use crate::types::SwitchNumberLines;
|
use crate::types::SwitchNumberLines;
|
||||||
|
|
||||||
@ -513,118 +512,133 @@ where
|
|||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Special compare used for affiliate keywords that are parsed as objects.
|
pub(crate) fn compare_property_object_tree<
|
||||||
///
|
|
||||||
/// Org-mode seems to store these as a 3-deep list:
|
|
||||||
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
|
|
||||||
/// - Middle list which has:
|
|
||||||
/// - first element is a list of objects representing the value after the colon.
|
|
||||||
/// - every additional element is a list of objects from inside the square brackets (the optional value).
|
|
||||||
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
|
|
||||||
'b,
|
'b,
|
||||||
's,
|
's,
|
||||||
'x,
|
'x,
|
||||||
R,
|
R,
|
||||||
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
|
RV: std::fmt::Debug + 'b,
|
||||||
|
ROV: std::fmt::Debug + 'b,
|
||||||
|
RI: Iterator<Item = &'b (Option<Vec<ROV>>, Vec<RV>)> + ExactSizeIterator + std::fmt::Debug,
|
||||||
|
RG: Fn(R) -> Option<RI>,
|
||||||
>(
|
>(
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_node: R,
|
rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &'x str,
|
||||||
rust_value_getter: RG,
|
rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
|
||||||
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
|
where
|
||||||
let rust_value = rust_value_getter(rust_node);
|
AstNode<'b, 's>: From<&'b RV>,
|
||||||
|
AstNode<'b, 's>: From<&'b ROV>,
|
||||||
|
{
|
||||||
let value = get_property(emacs, emacs_field)?
|
let value = get_property(emacs, emacs_field)?
|
||||||
.map(Token::as_list)
|
.map(Token::as_list)
|
||||||
.map_or(Ok(None), |r| r.map(Some))?;
|
.map_or(Ok(None), |r| r.map(Some))?;
|
||||||
let (value, rust_value) = match (value, rust_value) {
|
let rust_value = rust_value_getter(rust_node);
|
||||||
|
let (outer_emacs_list, outer_rust_list) = match (value, rust_value) {
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
return Ok(ComparePropertiesResult::NoChange);
|
return Ok(ComparePropertiesResult::NoChange);
|
||||||
}
|
}
|
||||||
(None, Some(_)) | (Some(_), None) => {
|
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, value, rv
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
(Some(value), Some(rust_value)) if value.len() != rust_value.len() => {
|
(Some(el), Some(rl)) if el.len() != rl.len() => {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, el, rl
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
(Some(value), Some(rust_value)) => (value, rust_value),
|
(Some(el), Some(rl)) => (el, rl),
|
||||||
};
|
};
|
||||||
|
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
|
||||||
|
|
||||||
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
for (kw_e, kw_r) in outer_emacs_list.into_iter().zip(outer_rust_list) {
|
||||||
|
let kw_e = kw_e.as_list()?;
|
||||||
// Iterate the outer lists
|
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
|
||||||
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
|
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
||||||
let mut middle_value = value.as_list()?.iter();
|
if let Some(or) = &kw_r.0 {
|
||||||
// First element of middle list is the mandatory value (the value past the colon).
|
// if optional value
|
||||||
let mandatory_value = middle_value.next();
|
let mut kw_e = kw_e.into_iter();
|
||||||
let mandatory_value = match mandatory_value {
|
// First element is a list representing the mandatory value.
|
||||||
Some(mandatory_value) => mandatory_value,
|
if let Some(val_e) = kw_e.next() {
|
||||||
None => {
|
let el = val_e.as_list()?;
|
||||||
|
if el.len() != kw_r.1.len() {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
for (e, r) in el.into_iter().zip(kw_r.1.iter()) {
|
||||||
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, kw_e, kw_r
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
};
|
// Remaining elements are the optional value.
|
||||||
|
if kw_e.len() != or.len() {
|
||||||
// Compare optional value
|
|
||||||
if let Some(rust_optional) = rust_optional {
|
|
||||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
|
||||||
if rust_optional.len() != middle_value.len() {
|
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field,
|
emacs_field, kw_e, kw_r
|
||||||
middle_value.len(),
|
|
||||||
rust_optional.len(),
|
|
||||||
rust_optional
|
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
for (e, r) in middle_value.zip(rust_optional) {
|
for (e, r) in kw_e.zip(or.iter()) {
|
||||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
}
|
}
|
||||||
if !child_status.is_empty() {
|
} else {
|
||||||
let diff_scope = artificial_diff_scope("optional value", child_status)?;
|
// if no optional value
|
||||||
full_status.push(diff_scope);
|
if !kw_e.len() == 1 {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Compare mandatory value
|
let e = kw_e
|
||||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
.first()
|
||||||
let mandatory_value = mandatory_value.as_list()?;
|
.map(Token::as_list)
|
||||||
if rust_value.len() != mandatory_value.len() {
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
let this_status = DiffStatus::Bad;
|
.expect("The above if-statement proves this will be Some.")
|
||||||
let message = Some(format!(
|
.iter();
|
||||||
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
|
let r = kw_r.1.iter();
|
||||||
emacs_field,
|
|
||||||
mandatory_value.len(),
|
if e.len() != r.len() {
|
||||||
rust_value.len(),
|
let this_status = DiffStatus::Bad;
|
||||||
rust_value
|
let message = Some(format!(
|
||||||
));
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
emacs_field, kw_e, kw_r
|
||||||
}
|
));
|
||||||
for (e, r) in mandatory_value.iter().zip(rust_value) {
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
}
|
||||||
|
|
||||||
|
for (e, r) in e.zip(r) {
|
||||||
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !child_status.is_empty() {
|
if !child_status.is_empty() {
|
||||||
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
|
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
|
||||||
full_status.push(diff_scope);
|
full_status.push(diff_scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if full_status.is_empty() {
|
if full_status.is_empty() {
|
||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes;
|
|
||||||
use super::compare_field::compare_property_list_of_quoted_string;
|
use super::compare_field::compare_property_list_of_quoted_string;
|
||||||
|
use super::compare_field::compare_property_object_tree;
|
||||||
use super::compare_field::compare_property_optional_pair;
|
use super::compare_field::compare_property_optional_pair;
|
||||||
use super::compare_field::compare_property_quoted_string;
|
use super::compare_field::compare_property_quoted_string;
|
||||||
use super::compare_field::ComparePropertiesResult;
|
use super::compare_field::ComparePropertiesResult;
|
||||||
@ -386,20 +386,17 @@ where
|
|||||||
|_| Some((*optval, *val)),
|
|_| Some((*optval, *val)),
|
||||||
)?;
|
)?;
|
||||||
ret.push(diff);
|
ret.push(diff);
|
||||||
// todo
|
|
||||||
}
|
}
|
||||||
AffiliatedKeywordValue::ObjectTree(_) => {
|
AffiliatedKeywordValue::ObjectTree(rust_value) => {
|
||||||
// todo
|
let diff = compare_property_object_tree(
|
||||||
} // AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
|
source,
|
||||||
// let diff = compare_property_list_of_list_of_list_of_ast_nodes(
|
emacs,
|
||||||
// source,
|
rust,
|
||||||
// emacs,
|
emacs_property_name.as_str(),
|
||||||
// rust,
|
|_| Some(rust_value.iter()),
|
||||||
// emacs_property_name.as_str(),
|
)?;
|
||||||
// |_| Some(rust_value),
|
ret.push(diff);
|
||||||
// )?;
|
}
|
||||||
// ret.push(diff);
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
@ -171,41 +171,3 @@ fn identify_keyword_type<'g, 's>(
|
|||||||
(false, false, false) => AffiliatedKeywordType::SingleString,
|
(false, false, false) => AffiliatedKeywordType::SingleString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_single_string_keyword<'g, 's>(
|
|
||||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
|
||||||
name: &'s str,
|
|
||||||
) -> bool {
|
|
||||||
// TODO: Is this defined by an elisp variable?
|
|
||||||
for single_string_name in ["plot", "name"] {
|
|
||||||
if name.eq_ignore_ascii_case(single_string_name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_list_of_single_string_keyword<'g, 's>(
|
|
||||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
|
||||||
name: &'s str,
|
|
||||||
) -> bool {
|
|
||||||
// TODO: Is this defined by an elisp variable?
|
|
||||||
for single_string_name in ["results"] {
|
|
||||||
if name.eq_ignore_ascii_case(single_string_name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_list_of_objects_keyword<'g, 's>(
|
|
||||||
global_settings: &'g GlobalSettings<'g, 's>,
|
|
||||||
name: &'s str,
|
|
||||||
) -> bool {
|
|
||||||
for parsed_keyword in global_settings.element_parsed_keywords {
|
|
||||||
if name.eq_ignore_ascii_case(parsed_keyword) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user