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,
mask_passwords: 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,
pub mask_passwords: bool,
}
impl<'a, T: DiffResultFormat> std::fmt::Display for DiffDisplay<'a, T> {
fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let result = self.inner.diff_result_format(
&mut f,
&self.path,
self.use_color,
self.use_verbose,
self.mask_passwords,
);
if self.use_color {
crate::reset_color();
}
result
}
}
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,
mask_passwords: 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_verbose {
if use_color {
crate::set_fg(Some(Color::Yellow));
}
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,
mask_passwords,
)?;
}
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("[", ", ", "]")
)
}
}
};
Ok(())
}
}
pub fn diff_entry<'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)
}
pub fn diff_hashmap<'a, A>(
a: &'a HashMap<String, Vec<A>>,
b: &'a HashMap<String, Vec<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<&Vec<A>> = a.get(*key);
let el_b: Option<&Vec<A>> = b.get(*key);
match (el_a, el_b) {
(Some(v_a), Some(v_b)) => {
v_a.into_iter()
.enumerate()
.for_each(|(index, value_a)| match v_b.get(index) {
Some(value_b) => {
let dr: DiffResult<A> = value_a.diff(value_b);
if let DiffResult::Identical { .. } = dr {
} else {
has_differences = true;
}
acc.push(dr);
}
None => {
has_differences = true;
acc.push(DiffResult::OnlyLeft { left: value_a })
}
});
if v_a.len() < v_b.len() {
has_differences = true;
v_b[v_a.len()..]
.into_iter()
.for_each(|value_b| acc.push(DiffResult::OnlyRight { right: value_b }));
}
}
(Some(v_a), None) => {
has_differences = true;
v_a.into_iter()
.for_each(|e| acc.push(DiffResult::OnlyLeft { left: e }));
}
(None, Some(v_b)) => {
has_differences = true;
v_b.into_iter()
.for_each(|e| acc.push(DiffResult::OnlyRight { right: e }));
}
(None, None) => {}
}
}
(has_differences, acc)
}
#[cfg(test)]
mod test {
use super::*;
use diff::group::Group;
#[test]
fn diff_empty_groups() {
let a = HashMap::<String, Vec<Group>>::new();
let b = HashMap::<String, Vec<Group>>::new();
let (has_differences, _) = diff_hashmap(&a, &b);
assert_eq!(false, has_differences);
}
}