//! Basic diff functions use crate::lcs; use ansi_term::Colour; use std::fmt; /// Single change in original slice needed to get new slice #[derive(Debug, PartialEq, Eq)] pub enum DiffOp<'a, T: 'a> { /// Appears only in second slice Insert(&'a [T]), /// Appears in both slices, but changed Replace(&'a [T], &'a [T]), /// Appears only in first slice Remove(&'a [T]), /// Appears on both slices Equal(&'a [T]), } /// Diffs any slices which implements PartialEq pub fn diff<'a, T: PartialEq>(x: &'a [T], y: &'a [T]) -> Vec> { let mut ops: Vec> = Vec::new(); let table = lcs::Table::new(x, y); let mut i = 0; let mut j = 0; for m in table.matches_zero() { let x_seq = &x[i..m.x]; let y_seq = &y[j..m.y]; if i < m.x && j < m.y { ops.push(DiffOp::Replace(x_seq, y_seq)); } else if i < m.x { ops.push(DiffOp::Remove(x_seq)); } else if j < m.y { ops.push(DiffOp::Insert(y_seq)); } i = m.x + m.len; j = m.y + m.len; if m.len > 0 { ops.push(DiffOp::Equal(&x[m.x..i])); } } ops } /// Container for slice diff result. Can be pretty-printed by Display trait. #[derive(Debug, PartialEq, Eq)] pub struct SliceChangeset<'a, T> { pub diff: Vec>, } impl<'a, T: fmt::Display> SliceChangeset<'a, T> { pub fn format(&self, skip_same: bool) -> String { let mut out: Vec = Vec::with_capacity(self.diff.len()); for op in &self.diff { match op { DiffOp::Equal(a) => { if !skip_same || a.len() == 1 { for i in a.iter() { out.push(format!(" {}", i)) } } else if a.len() > 1 { out.push(format!(" ... skip({}) ...", a.len())); } } DiffOp::Insert(a) => { for i in a.iter() { out.push(Colour::Green.paint(format!("+ {}", i)).to_string()); } } DiffOp::Remove(a) => { for i in a.iter() { out.push(Colour::Red.paint(format!("- {}", i)).to_string()); } } DiffOp::Replace(a, b) => { let min_len = std::cmp::min(a.len(), b.len()); let max_len = std::cmp::max(a.len(), b.len()); for i in 0..min_len { out.push( Colour::Yellow .paint(format!("~ {} -> {}", a[i], b[i])) .to_string(), ); } for i in min_len..max_len { if max_len == a.len() { out.push(Colour::Red.paint(format!("- {}", a[i])).to_string()); } else { out.push(Colour::Green.paint(format!("+ {}", b[i])).to_string()); } } } } } format!("[\n{}\n]", out.join(",\n")) } } impl<'a, T: fmt::Display> fmt::Display for SliceChangeset<'a, T> { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "{}", self.format(true)) } } /// Diff two arbitary slices with elements that support Display trait pub fn diff_slice<'a, T: PartialEq + std::fmt::Display>( x: &'a [T], y: &'a [T], ) -> SliceChangeset<'a, T> { let diff = diff(x, y); SliceChangeset { diff } } #[test] fn test_basic() { assert_eq!( diff(&[1, 2, 3, 4, 5, 6], &[2, 3, 5, 7]), vec![ DiffOp::Remove(&[1]), DiffOp::Equal(&[2, 3]), DiffOp::Remove(&[4]), DiffOp::Equal(&[5]), DiffOp::Replace(&[6], &[7]), ] ); assert_eq!( diff_slice( &["q", "a", "b", "x", "c", "d"], &["a", "b", "y", "c", "d", "f"], ) .diff, vec![ DiffOp::Remove(&["q"]), DiffOp::Equal(&["a", "b"]), DiffOp::Replace(&["x"], &["y"]), DiffOp::Equal(&["c", "d"]), DiffOp::Insert(&["f"]), ] ); assert_eq!( diff(&["a", "c", "d", "b"], &["a", "e", "b"]), vec![ DiffOp::Equal(&["a"]), DiffOp::Replace(&["c", "d"], &["e"]), DiffOp::Equal(&["b"]), ] ); println!("Diff: {}", diff_slice(&[1, 2, 3, 4, 5, 6], &[2, 3, 5, 7])); println!( "Diff: {}", diff_slice( &["q", "a", "b", "x", "c", "d"], &["a", "b", "y", "c", "d", "f"] ) ); println!( "Diff: {}", diff_slice(&["a", "c", "d", "b"], &["a", "e", "b"]) ); }