organic/src/compare/compare_field.rs

162 lines
5.5 KiB
Rust
Raw Normal View History

use std::fmt::Debug;
use super::diff::DiffStatus;
2023-10-08 14:40:01 -04:00
use super::sexp::unquote;
use super::sexp::Token;
use super::util::get_property;
use super::util::get_property_quoted_string;
use super::util::get_property_unquoted_atom;
#[derive(Debug)]
pub(crate) enum EmacsField<'s> {
Required(&'s str),
#[allow(dead_code)]
Optional(&'s str),
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
2023-10-06 16:32:49 -04:00
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
_emacs: &'b Token<'s>,
_rust_node: R,
_emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
Ok(None)
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
pub(crate) fn compare_identity() -> () {
()
}
/// Assert that the emacs value is always nil or absent.
///
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
emacs: &'b Token<'s>,
_rust_node: R,
emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?;
if value.is_some() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} was expected to always be nil: {:?}",
emacs_field, value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}
pub(crate) fn compare_property_quoted_string<
'b,
's,
'x,
R,
RV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<RV>,
>(
emacs: &'b Token<'s>,
rust_node: R,
2023-10-06 16:03:41 -04:00
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property_quoted_string(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}
pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>(
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property_unquoted_atom(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value != value {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}
2023-10-08 14:40:01 -04:00
pub(crate) fn compare_property_list_of_quoted_string<
'b,
's,
'x,
R,
2023-10-08 15:08:21 -04:00
RV: AsRef<str> + std::fmt::Debug,
RI: Iterator<Item = RV>,
RG: Fn(R) -> Option<RI>,
2023-10-08 14:40:01 -04:00
>(
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?;
let rust_value = rust_value_getter(rust_node);
2023-10-08 15:08:21 -04:00
// TODO: Seems we are needlessly coverting to a vec here.
let rust_value: Option<Vec<RV>> = rust_value.map(|it| it.collect());
match (value, &rust_value) {
2023-10-08 14:40:01 -04:00
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(Some((this_status, message)));
}
(Some(el), Some(rl)) if el.len() != rl.len() => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(Some((this_status, message)));
}
(Some(el), Some(rl)) => {
for (e, r) in el.iter().zip(rl) {
let e = unquote(e.as_atom()?)?;
let r = r.as_ref();
if e != r {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
2023-10-08 14:40:01 -04:00
));
return Ok(Some((this_status, message)));
}
}
}
}
Ok(None)
}