use std::collections::{HashMap, HashSet};
use termcolor::Color;
use stack::Stack;
pub mod entry;
pub mod field;
pub mod group;
#[derive(Debug)]
pub enum DiffResult<'a, T> {
Identical { left: &'a T, right: &'a T },
Changed { left: &'a T, right: &'a T },
InnerDifferences {
left: &'a T,
right: &'a T,
inner_differences: Vec<Box<dyn DiffResultFormat + 'a>>,
},
OnlyLeft { left: &'a T },
OnlyRight { right: &'a T },
}
pub trait Diff
where
Self: Sized,
{
fn diff<'a>(&'a self, other: &'a Self) -> DiffResult<'a, Self>;
}
pub trait DiffResultFormat: std::fmt::Debug {
fn diff_result_format(
&self,
f: &mut std::fmt::Formatter<'_>,
path: &Stack<&String>,
use_color: bool,
use_verbose: bool,
) -> std::fmt::Result;
}
pub struct DiffDisplay<'a, T: DiffResultFormat> {
pub inner: T,
pub path: Stack<&'a String>,
pub use_color: bool,
pub use_verbose: bool,
}
impl<'a, T: DiffResultFormat> std::fmt::Display for DiffDisplay<'a, T> {
fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner
.diff_result_format(&mut f, &self.path, self.use_color, self.use_verbose)
}
}
impl<'a, E> DiffResultFormat for DiffResult<'a, E>
where
E: std::fmt::Display + std::fmt::Debug,
{
fn diff_result_format(
&self,
mut f: &mut std::fmt::Formatter<'_>,
path: &Stack<&String>,
use_color: bool,
use_verbose: bool,
) -> std::fmt::Result {
let _ = match self {
DiffResult::Identical { .. } => Ok(()),
DiffResult::Changed { left, right } => {
if use_color {
crate::set_fg(Some(Color::Red));
}
if use_verbose {
let indent = " ".repeat(path.len());
write!(f, "- {}{}\n", indent, left)?;
} else {
write!(
f,
"- {}\n",
path.append(&format!("{}", left)).mk_string("[", ", ", "]")
)?;
}
if use_color {
crate::set_fg(Some(Color::Green));
}
if use_verbose {
let indent = " ".repeat(path.len());
write!(f, "+ {}{}\n", indent, right)
} else {
write!(
f,
"+ {}\n",
path.append(&format!("{}", right)).mk_string("[", ", ", "]")
)
}
}
DiffResult::InnerDifferences {
left,
inner_differences,
..
} => {
if use_color {
crate::set_fg(Some(Color::Yellow));
}
if use_verbose {
let indent = " ".repeat(path.len());
write!(f, "~ {}{}\n", indent, left)?;
}
for id in inner_differences {
id.diff_result_format(
&mut f,
&path.append(&format!("{}", left)),
use_color,
use_verbose,
)?;
}
Ok(())
}
DiffResult::OnlyLeft { left } => {
if use_color {
crate::set_fg(Some(Color::Red));
}
if use_verbose {
let indent = " ".repeat(path.len());
write!(f, "- {}{}\n", indent, left)
} else {
write!(
f,
"- {}\n",
path.append(&format!("{}", left)).mk_string("[", ", ", "]")
)
}
}
DiffResult::OnlyRight { right } => {
if use_color {
crate::set_fg(Some(Color::Green));
}
if use_verbose {
let indent = " ".repeat(path.len());
write!(f, "+ {}{}\n", indent, right)
} else {
write!(
f,
"+ {}\n",
path.append(&format!("{}", right)).mk_string("[", ", ", "]")
)
}
}
};
if use_color {
crate::set_fg(None);
}
Ok(())
}
}
pub fn diff_hashmap<'a, A>(
a: &'a HashMap<String, A>,
b: &'a HashMap<String, A>,
) -> (bool, Vec<DiffResult<'a, A>>)
where
A: Diff,
{
let mut keys = HashSet::new();
keys.extend(a.keys());
keys.extend(b.keys());
let mut keys: Vec<_> = keys.iter().collect();
keys.sort();
let mut acc: Vec<DiffResult<A>> = Vec::new();
let mut has_differences = false;
for key in keys {
let el_a: Option<&A> = a.get(*key);
let el_b: Option<&A> = b.get(*key);
match (el_a, el_b) {
(Some(v_a), Some(v_b)) => {
let dr: DiffResult<A> = v_a.diff(v_b);
if let DiffResult::Identical { .. } = dr {
} else {
has_differences = true;
}
acc.push(dr);
}
(Some(v_a), None) => {
has_differences = true;
acc.push(DiffResult::OnlyLeft { left: v_a })
}
(None, Some(v_b)) => {
has_differences = true;
acc.push(DiffResult::OnlyRight { right: v_b })
}
(None, None) => {}
}
}
(has_differences, acc)
}