diffus-0.10.0/.cargo_vcs_info.json0000644000000001120000000000000123630ustar { "git": { "sha1": "0b205781c46319d5cfe709d5aead0a3a9b4a9ff7" } } diffus-0.10.0/.gitignore000064400000000000000000000000360000000000000131260ustar 00000000000000/target **/*.rs.bk Cargo.lock diffus-0.10.0/Cargo.toml0000644000000035110000000000000103670ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "diffus" version = "0.10.0" authors = ["Jim Holmström ", "Johan Gardell <736172+gardell@users.noreply.github.com>"] publish = ["crates-io"] description = "Finds the difference between two instances of any data structure. Supports: collections, Strings, Maps etc. Uses LCS where applicable. Also supports derive via `diffus-derive`." homepage = "https://github.com/distil/diffus" documentation = "https://docs.rs/diffus" readme = "../README.md" keywords = ["algorithm", "diff", "difference", "data", "data-structure"] categories = ["algorithms", "data-structures"] license = "Apache-2.0" repository = "https://github.com/distil/diffus" [lib] name = "diffus" path = "src/lib.rs" [dependencies.diffus-derive] version = "=0.10.0" optional = true [dependencies.indexmap] version = "1" optional = true [dependencies.itertools] version = "0.10" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.snake_case] version = "0.3" optional = true [dependencies.uuid] version = ">=0.5" optional = true [features] default = [] derive = ["diffus-derive"] indexmap-impl = ["indexmap"] serialize-impl = ["diffus-derive/serialize-impl", "serde", "indexmap/serde-1", "uuid/serde", "snake_case/serde"] snake_case-impl = ["snake_case"] uuid-impl = ["uuid"] diffus-0.10.0/Cargo.toml.orig000064400000000000000000000025440000000000000140330ustar 00000000000000[package] name = "diffus" version = "0.10.0" authors = [ "Jim Holmström ", "Johan Gardell <736172+gardell@users.noreply.github.com>", ] description = "Finds the difference between two instances of any data structure. Supports: collections, Strings, Maps etc. Uses LCS where applicable. Also supports derive via `diffus-derive`." homepage = "https://github.com/distil/diffus" repository = "https://github.com/distil/diffus" documentation = "https://docs.rs/diffus" readme = "../README.md" keywords = [ "algorithm", "diff", "difference", "data", "data-structure" ] categories = [ "algorithms", "data-structures" ] publish = [ "crates-io" ] license = "Apache-2.0" edition = "2018" [lib] name = "diffus" path = "src/lib.rs" [dependencies] itertools = "0.10" indexmap = { version = "1", optional = true } uuid = { version = ">=0.5", optional = true } snake_case = { version = "0.3", optional = true } serde = { version = "1.0", features = [ "derive" ], optional = true } diffus-derive = { version = "=0.10.0", path = "../diffus-derive", optional = true } [features] default = [] derive = [ "diffus-derive" ] indexmap-impl = [ "indexmap" ] uuid-impl = [ "uuid" ] snake_case-impl = [ "snake_case" ] serialize-impl = [ "diffus-derive/serialize-impl", "serde", "indexmap/serde-1", "uuid/serde", "snake_case/serde" ] diffus-0.10.0/src/diffable_impls/borrow.rs000064400000000000000000000037440000000000000165560ustar 00000000000000use crate::{edit, Diffable}; use std::borrow::Borrow; fn diff_borrowable<'a, T, C, D>(left: &'a C, right: &'a C) -> edit::Edit<'a, C> where T: Diffable<'a> + ?Sized + 'a, C: Borrow + Diffable<'a, Diff = D> + ?Sized, D: From, { match left.borrow().diff(right.borrow()) { edit::Edit::Copy(_) => edit::Edit::Copy(left), edit::Edit::Change(diff) => edit::Edit::Change(diff.into()), } } macro_rules! borrow_impl { ($($typ:ident),*) => { $( impl<'a, T: Diffable<'a> + ?Sized + 'a> Diffable<'a> for $typ { type Diff = $typ; fn diff(&'a self, other: &'a Self) -> edit::Edit<'a, Self> { diff_borrowable::(self, other) } } )* } } use std::{rc::Rc, sync::Arc}; borrow_impl! { Box, Rc, Arc } impl<'a, T: Diffable<'a> + ?Sized + 'a> Diffable<'a> for &'a T { type Diff = T::Diff; fn diff(&'a self, other: &'a Self) -> edit::Edit<'a, Self> { diff_borrowable::(self, other) } } #[cfg(test)] mod tests { use super::*; #[test] fn box_example() { let left = 13; let right = 37; if let edit::Edit::Change(diff) = Box::new(left).diff(&Box::new(right)) { assert_eq!(*diff, (&13, &37)); } } #[test] fn rc_example() { let left = 13; let right = 37; if let edit::Edit::Change(diff) = Rc::new(left).diff(&Rc::new(right)) { assert_eq!(*diff, (&13, &37)); } } #[test] fn arc_example() { let left = 13; let right = 37; if let edit::Edit::Change(diff) = Arc::new(left).diff(&Arc::new(right)) { assert_eq!(*diff, (&13, &37)); } } #[test] fn reference_example() { let left = 13; let right = 37; if let edit::Edit::Change(diff) = (&left).diff(&(&right)) { assert_eq!(diff, (&13, &37)); } } } diffus-0.10.0/src/diffable_impls/collection.rs000064400000000000000000000036320000000000000173730ustar 00000000000000use crate::{ edit::{self, collection}, Diffable, Same, }; macro_rules! collection_impl { ($($typ:ident),*) => { $( impl<'a, T: Same + Diffable<'a> + 'a> Diffable<'a> for $typ { type Diff = Vec>; fn diff(&'a self, other: &'a Self) -> edit::Edit { let s = crate::lcs::lcs_post_change( crate::lcs::lcs( || self.iter(), || other.iter(), self.len(), other.len(), ) ) .collect::>(); if s.iter().all(collection::Edit::is_copy) { edit::Edit::Copy(self) } else { edit::Edit::Change(s) } } } )* } } use std::collections::{BinaryHeap, LinkedList, VecDeque}; collection_impl! { BinaryHeap, LinkedList, Vec, VecDeque } #[cfg(test)] mod tests { use super::*; #[test] fn diff() { use super::Diffable; let left = b"XMJYAUZ".to_vec(); let right = b"MZJAWXU".to_vec(); let diff = left.diff(&right); if let edit::Edit::Change(diff) = diff { use collection::Edit::*; assert_eq!( diff.into_iter().collect::>(), vec![ Remove(&b'X'), Copy(&b'M'), Insert(&b'Z'), Copy(&b'J'), Remove(&b'Y'), Copy(&b'A'), Insert(&b'W'), Insert(&b'X'), Copy(&b'U'), Remove(&b'Z') ] ); } else { unreachable!() } } } diffus-0.10.0/src/diffable_impls/map.rs000064400000000000000000000042550000000000000160170ustar 00000000000000use crate::{ edit::{map, Edit}, Diffable, }; macro_rules! map_impl { ($(($typ:ident, $key_constraint:ident)),*) => { $( impl<'a, K: Eq + $key_constraint + 'a, V: Diffable<'a> + 'a> Diffable<'a> for $typ { type Diff = $typ<&'a K, map::Edit<'a, V>>; fn diff(&'a self, other: &'a Self) -> Edit { let intersection = self .iter() .filter_map(|(k, v)| Some((k, (v, other.get(k)?)))); let unique_self = self.iter().filter(|(k, _)| !other.contains_key(*k)); let unique_other = other.iter().filter(|(k, _)| !self.contains_key(*k)); let value_diffs = unique_other .map(|(k, v)| (k, map::Edit::Insert(v))) .chain(unique_self.map(|(k, v)| (k, map::Edit::Remove(v)))) .chain(intersection.map(|(k, (self_v, other_v))| (k, self_v.diff(other_v).into()))) .collect::<$typ<_, _>>(); if value_diffs.values().any(|v| !v.is_copy()) { Edit::Change(value_diffs) } else { Edit::Copy(self) } } } )* } } use std::{ collections::{BTreeMap, HashMap}, hash::Hash, }; map_impl! { (BTreeMap, Ord), (HashMap, Hash) } #[cfg(feature = "indexmap-impl")] use indexmap::IndexMap; #[cfg(feature = "indexmap-impl")] map_impl! { (IndexMap, Hash) } #[cfg(test)] mod tests { use super::*; #[test] fn example() { let unity: std::collections::HashMap<_, _> = [(1, 1), (2, 2), (3, 3)].iter().cloned().collect(); let not_unity: std::collections::HashMap<_, _> = [(1, 1), (2, 3), (4, 4)].iter().cloned().collect(); if let Edit::Change(diff) = unity.diff(¬_unity) { assert!(diff[&1].is_copy()); assert_eq!(diff[&2].change().unwrap(), &(&2, &3)); assert!(diff[&3].is_remove()); assert_eq!(diff[&4].insert().unwrap(), &4); } else { unreachable!() } } } diffus-0.10.0/src/diffable_impls/mod.rs000064400000000000000000000001620000000000000160120ustar 00000000000000pub mod borrow; pub mod collection; pub mod map; pub mod option; pub mod primitives; pub mod set; pub mod string; diffus-0.10.0/src/diffable_impls/option.rs000064400000000000000000000023070000000000000165460ustar 00000000000000use crate::{ edit::{self, enm}, Diffable, }; impl<'a, T: Diffable<'a> + 'a> Diffable<'a> for Option { type Diff = enm::Edit<'a, Self, T::Diff>; fn diff(&'a self, other: &'a Self) -> edit::Edit { match (self, other) { (None, None) => edit::Edit::Copy(self), (Some(a), Some(b)) => match a.diff(&b) { edit::Edit::Copy(_) => edit::Edit::Copy(self), edit::Edit::Change(diff) => edit::Edit::Change(enm::Edit::AssociatedChanged(diff)), }, _ => edit::Edit::Change(enm::Edit::VariantChanged(self, other)), } } } #[cfg(test)] mod tests { use super::*; #[test] fn is_copy() { assert!((None as Option).diff(&None).is_copy()); assert!(Some(3).diff(&Some(3)).is_copy()); } #[test] fn variant_changed() { if let Some(enm::Edit::VariantChanged(&None, &Some(3))) = None.diff(&Some(3)).change() { } else { unreachable!(); } } #[test] fn associate_change() { if let Some(enm::Edit::AssociatedChanged((&1, &2))) = Some(1).diff(&Some(2)).change() { } else { unreachable!(); } } } diffus-0.10.0/src/diffable_impls/primitives.rs000064400000000000000000000014250000000000000174310ustar 00000000000000use crate::{edit, Diffable}; macro_rules! primitive_impl { ($($typ:ty),*) => { $( impl<'a> Diffable<'a> for $typ { type Diff = (&'a $typ, &'a $typ); fn diff(&'a self, other: &'a Self) -> edit::Edit { use crate::Same; if self.same(other) { edit::Edit::Copy(self) } else { edit::Edit::Change((self, other)) } } } )* } } primitive_impl! { i64, i32, i16, i8, u64, u32, u16, u8, char, bool, isize, usize, f32, f64, () } #[cfg(feature = "uuid-impl")] primitive_impl! { uuid::Uuid } #[cfg(feature = "snake_case-impl")] primitive_impl! { snake_case::SnakeCase } diffus-0.10.0/src/diffable_impls/set.rs000064400000000000000000000041540000000000000160330ustar 00000000000000use crate::{ edit::{set, Edit}, Diffable, }; macro_rules! set_impl { ($(($typ:ident, $key_constraint:ident, $diff_type:ident)),*) => { $( impl<'a, K: Diffable<'a> + Eq + $key_constraint + 'a> Diffable<'a> for $typ { type Diff = $diff_type<&'a K, set::Edit<'a, K>>; fn diff(&'a self, other: &'a Self) -> Edit { let intersection = self .iter() .filter(|k| other.contains(*k)); let unique_self = self.iter().filter(|k| !other.contains(*k)); let unique_other = other.iter().filter(|k| !self.contains(*k)); let value_diffs = unique_other .map(|k| (k, set::Edit::Insert(k))) .chain(unique_self.map(|k| (k, set::Edit::Remove(k)))) .chain(intersection.map(|k| (k, set::Edit::Copy(k)))) .collect::<$diff_type<_, _>>(); if value_diffs.iter().any(|(_, edit)| !edit.is_copy()) { Edit::Change(value_diffs) } else { Edit::Copy(self) } } } )* } } use std::{ collections::{BTreeMap, BTreeSet, HashMap, HashSet}, hash::Hash, }; set_impl! { (BTreeSet, Ord, BTreeMap), (HashSet, Hash, HashMap) } #[cfg(feature = "indexmap-impl")] use indexmap::{IndexMap, IndexSet}; #[cfg(feature = "indexmap-impl")] set_impl! { (IndexSet, Hash, IndexMap) } #[cfg(test)] mod tests { use super::*; #[test] fn example() { let unity: std::collections::HashSet<_, _> = [1, 2, 3].iter().cloned().collect(); let not_unity: std::collections::HashSet<_, _> = [1, 2, 4].iter().cloned().collect(); if let Edit::Change(diff) = unity.diff(¬_unity) { assert!(diff[&1].is_copy()); assert!(diff[&2].is_copy()); assert!(diff[&3].is_remove()); assert_eq!(diff[&4].insert().unwrap(), &4); } else { unreachable!() } } } diffus-0.10.0/src/diffable_impls/string.rs000064400000000000000000000052310000000000000165430ustar 00000000000000use crate::{ edit::{self, string}, lcs, Diffable, }; impl<'a> Diffable<'a> for str { type Diff = Vec; fn diff(&'a self, other: &'a Self) -> edit::Edit { let s = lcs::lcs( || self.chars(), || other.chars(), self.chars().count(), other.chars().count(), ) .map(Into::into) .collect::>(); if s.iter().all(string::Edit::is_copy) { edit::Edit::Copy(self) } else { edit::Edit::Change(s) } } } impl<'a> Diffable<'a> for String { type Diff = >::Diff; fn diff(&'a self, other: &'a Self) -> edit::Edit { match self.as_str().diff(other.as_str()) { edit::Edit::Change(diff) => edit::Edit::Change(diff), edit::Edit::Copy(_) => edit::Edit::Copy(self), } } } #[cfg(test)] mod tests { use crate::edit::{self, string}; #[test] fn string() { use super::Diffable; let left = "XMJYAUZ".to_owned(); let right = "MZJAWXU".to_owned(); let diff = left.diff(&right); if let edit::Edit::Change(diff) = diff { assert_eq!( diff.into_iter().collect::>(), vec![ string::Edit::Remove('X'), string::Edit::Copy('M'), string::Edit::Insert('Z'), string::Edit::Copy('J'), string::Edit::Remove('Y'), string::Edit::Copy('A'), string::Edit::Insert('W'), string::Edit::Insert('X'), string::Edit::Copy('U'), string::Edit::Remove('Z') ] ); } else { unreachable!() } } #[test] fn str() { use super::Diffable; let left = "XMJYAUZ"; let right = "MZJAWXU"; let diff = left.diff(&right); if let edit::Edit::Change(diff) = diff { assert_eq!( diff.into_iter().collect::>(), vec![ string::Edit::Remove('X'), string::Edit::Copy('M'), string::Edit::Insert('Z'), string::Edit::Copy('J'), string::Edit::Remove('Y'), string::Edit::Copy('A'), string::Edit::Insert('W'), string::Edit::Insert('X'), string::Edit::Copy('U'), string::Edit::Remove('Z') ] ); } else { unreachable!() } } } diffus-0.10.0/src/edit/collection.rs000064400000000000000000000026340000000000000153610ustar 00000000000000use crate::Same; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq)] pub enum Edit<'a, T: ?Sized, Diff> { Copy(&'a T), Insert(&'a T), Remove(&'a T), Change(Diff), } impl<'a, T: Same + ?Sized, Diff> Edit<'a, T, Diff> { pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } pub fn is_insert(&self) -> bool { if let Self::Insert(_) = self { true } else { false } } pub fn is_remove(&self) -> bool { if let Self::Remove(_) = self { true } else { false } } pub fn is_change(&self) -> bool { self.change().is_some() } pub fn copy(&self) -> Option<&T> { if let Self::Copy(value) = self { Some(value) } else { None } } pub fn insert(&self) -> Option<&T> { if let Self::Insert(value) = self { Some(value) } else { None } } pub fn remove(&self) -> Option<&T> { if let Self::Remove(value) = self { Some(value) } else { None } } pub fn change(&self) -> Option<&Diff> { if let Self::Change(value) = self { Some(value) } else { None } } } diffus-0.10.0/src/edit/enm.rs000064400000000000000000000021550000000000000140030ustar 00000000000000#[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, Eq, PartialEq)] pub enum Edit<'a, T: ?Sized, Diff> { Copy(&'a T), VariantChanged(&'a T, &'a T), AssociatedChanged(Diff), } impl<'a, T: ?Sized, Diff> Edit<'a, T, Diff> { pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } pub fn is_variant_changed(&self) -> bool { if let Self::VariantChanged(_, _) = self { true } else { false } } pub fn is_associated_changed(&self) -> bool { if let Self::AssociatedChanged(_) = self { true } else { false } } pub fn variant_changed(&self) -> Option<(&'a T, &'a T)> { if let Self::VariantChanged(left, right) = self { Some((left, right)) } else { None } } pub fn associated_change(&self) -> Option<&Diff> { if let Self::AssociatedChanged(value) = self { Some(value) } else { None } } } diffus-0.10.0/src/edit/map.rs000064400000000000000000000033110000000000000137740ustar 00000000000000use crate::Diffable; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, PartialEq)] pub enum Edit<'a, T: Diffable<'a> + ?Sized> { Copy(&'a T), Insert(&'a T), Remove(&'a T), Change(T::Diff), } impl<'a, T: Diffable<'a> + ?Sized> Edit<'a, T> { // // Checks if the edit is an insert. // // # Examples // // ``` // assert_eq!(Edit::Insert(&2).is_insert(), true); // assert_eq!(Edit::Remove.is_insert(), false); // ``` pub fn is_insert(&self) -> bool { if let Self::Insert(_) = self { true } else { false } } pub fn is_remove(&self) -> bool { if let Self::Remove(_) = self { true } else { false } } pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } pub fn is_change(&self) -> bool { if let Self::Change(_) = self { true } else { false } } pub fn insert(&self) -> Option<&'a T> { if let Self::Insert(value) = self { Some(value) } else { None } } pub fn remove(&self) -> Option<&'a T> { if let Self::Remove(value) = self { Some(value) } else { None } } pub fn change(&self) -> Option<&T::Diff> { if let Self::Change(value_diff) = self { Some(value_diff) } else { None } } pub fn copy(&self) -> Option<&'a T> { if let Self::Copy(value) = self { Some(value) } else { None } } } diffus-0.10.0/src/edit/mod.rs000064400000000000000000000023350000000000000140030ustar 00000000000000pub mod collection; pub mod enm; pub mod map; pub mod set; pub mod string; use crate::Diffable; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq)] pub enum Edit<'a, T: Diffable<'a> + ?Sized> { Copy(&'a T), Change(T::Diff), } impl<'a, T: Diffable<'a> + ?Sized> Edit<'a, T> { pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } pub fn copy(&self) -> Option<&'a T> { if let Self::Copy(value) = self { Some(value) } else { None } } pub fn is_change(&self) -> bool { if let Self::Change(_) = self { true } else { false } } pub fn change(&self) -> Option<&T::Diff> { if let Self::Change(value_diff) = self { Some(value_diff) } else { None } } } impl<'a, Diff, T: Diffable<'a, Diff = Diff> + 'a> Into> for Edit<'a, T> { fn into(self) -> map::Edit<'a, T> { match self { Self::Copy(value) => map::Edit::Copy(value), Self::Change(diff) => map::Edit::Change(diff), } } } diffus-0.10.0/src/edit/set.rs000064400000000000000000000025630000000000000140220ustar 00000000000000use crate::Diffable; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, PartialEq)] pub enum Edit<'a, T: Diffable<'a> + ?Sized> { Copy(&'a T), Insert(&'a T), Remove(&'a T), } impl<'a, T: Diffable<'a> + ?Sized> Edit<'a, T> { pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } // // Checks if the edit is an insert. // // # Examples // // ``` // assert_eq!(Edit::Insert(&2).is_insert(), true); // assert_eq!(Edit::Remove.is_insert(), false); // ``` pub fn is_insert(&self) -> bool { if let Self::Insert(_) = self { true } else { false } } pub fn is_remove(&self) -> bool { if let Self::Remove(_) = self { true } else { false } } pub fn copy(&self) -> Option<&'a T> { if let Self::Copy(value) = self { Some(value) } else { None } } pub fn insert(&self) -> Option<&'a T> { if let Self::Insert(value) = self { Some(value) } else { None } } pub fn remove(&self) -> Option<&'a T> { if let Self::Remove(value) = self { Some(value) } else { None } } } diffus-0.10.0/src/edit/string.rs000064400000000000000000000026170000000000000145350ustar 00000000000000use crate::lcs; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Edit { Copy(char), Insert(char), Remove(char), } impl From> for Edit { fn from(edit: lcs::Edit) -> Self { use lcs::Edit::*; match edit { Same(left, _) => Self::Copy(left), Insert(value) => Self::Insert(value), Remove(value) => Self::Remove(value), } } } impl Edit { pub fn is_copy(&self) -> bool { if let Self::Copy(_) = self { true } else { false } } pub fn is_insert(&self) -> bool { if let Self::Insert(_) = self { true } else { false } } pub fn is_remove(&self) -> bool { if let Self::Remove(_) = self { true } else { false } } pub fn copy(self) -> Option { if let Self::Copy(value) = self { Some(value) } else { None } } pub fn insert(self) -> Option { if let Self::Insert(value) = self { Some(value) } else { None } } pub fn remove(self) -> Option { if let Self::Remove(value) = self { Some(value) } else { None } } } diffus-0.10.0/src/lcs.rs000064400000000000000000000146410000000000000130630ustar 00000000000000use crate::{edit, Diffable, Same}; #[cfg_attr(feature = "serialize-impl", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq)] pub enum Edit { Same(T, T), Insert(T), Remove(T), } impl Edit { pub fn is_same(&self) -> bool { if let Edit::Same(_, _) = self { true } else { false } } } fn c_matrix( x: impl Fn() -> I, y: impl Fn() -> J, x_len: usize, y_len: usize, ) -> (usize, crate::twodvec::TwoDVec, usize) where I: DoubleEndedIterator, J: DoubleEndedIterator, { let mut x_iter = x(); let mut y_iter = y(); let prefix_eq = x_iter .by_ref() .zip(y_iter.by_ref()) .take_while(|(x, y)| x.same(y)) .count(); // Only check the suffix if we did not consume the entirety of either of the iterators // (If one of them are consumed, we would double count elements) let check_suffix = x_len.min(y_len) != prefix_eq; let suffix_eq = if check_suffix { x_iter .rev() .zip(y_iter.rev()) .take_while(|(x, y)| x.same(y)) .count() } else { 0 }; let width = x_len.saturating_sub(prefix_eq + suffix_eq) + 1; let height = y_len.saturating_sub(prefix_eq + suffix_eq) + 1; let mut c = crate::twodvec::TwoDVec::new(0, width, height); for (i, x) in x().skip(prefix_eq).take(width - 1).enumerate() { for (j, y) in y().skip(prefix_eq).take(height - 1).enumerate() { c[j + 1][i + 1] = if x.same(&y) { c[j][i] + 1 } else { c[j][i + 1].max(c[j + 1][i]) }; } } (prefix_eq, c, suffix_eq) } fn lcs_base( c: crate::twodvec::TwoDVec, mut x: itertools::PutBack>, mut y: itertools::PutBack>, ) -> impl Iterator> { let mut i = c.width() - 1; let mut j = c.height() - 1; std::iter::from_fn(move || { let current_x = x.next(); let current_y = y.next(); let left = j.checked_sub(1).map(|j_minus| c[j_minus][i]); let above = i.checked_sub(1).map(|i_minus| c[j][i_minus]); if current_x.is_some() && current_y.is_some() && current_x .as_ref() .unwrap() .same(current_y.as_ref().unwrap()) { i = i - 1; j = j - 1; match (current_x, current_y) { (Some(current_x), Some(current_y)) => Some(Edit::Same(current_x, current_y)), _ => unreachable!(), } } else if current_y.is_some() && (current_x.is_none() || left >= above) { current_x.map(|c| x.put_back(c)); j = j - 1; current_y.map(|value| Edit::Insert(value)) } else if current_x.is_some() && (current_y.is_none() || left < above) { current_y.map(|c| y.put_back(c)); i = i - 1; current_x.map(|value| Edit::Remove(value)) } else { None } }) .collect::>() .into_iter() .rev() } pub(crate) fn lcs< 'a, T: Same, I: DoubleEndedIterator, J: DoubleEndedIterator, >( x: impl Fn() -> I, y: impl Fn() -> J, x_len: usize, y_len: usize, ) -> impl Iterator> { let (prefix_eq, c, suffix_eq) = c_matrix(&x, &y, x_len, y_len); x().zip(y()) .take(prefix_eq) .map(|(x, y)| Edit::Same(x, y)) .chain(lcs_base( c, itertools::put_back( x().rev() .skip(suffix_eq) .take(x_len.saturating_sub(prefix_eq + suffix_eq)), ), itertools::put_back( y().rev() .skip(suffix_eq) .take(y_len.saturating_sub(prefix_eq + suffix_eq)), ), )) .chain( x().skip(x_len - suffix_eq) .zip(y().skip(y_len - suffix_eq)) .map(|(x, y)| Edit::Same(x, y)), ) } // FIXME move out from lcs pub(crate) fn lcs_post_change<'a, T: Same + Diffable<'a> + ?Sized + 'a>( result: impl Iterator>, ) -> impl Iterator>::Diff>> { result.map(|edit| match edit { Edit::Same(left, right) => match left.diff(right) { edit::Edit::Copy(t) => edit::collection::Edit::Copy(t), edit::Edit::Change(diff) => edit::collection::Edit::Change(diff), }, Edit::Insert(value) => edit::collection::Edit::Insert(value), Edit::Remove(value) => edit::collection::Edit::Remove(value), }) } #[cfg(test)] mod tests { use super::*; #[test] fn characters() { let left = "XMJYAUZ"; let right = "MZJAWXU"; let s = lcs( || left.chars(), || right.chars(), left.chars().count(), right.chars().count(), ); assert_eq!( s.collect::>(), vec![ Edit::Remove('X'), Edit::Same('M', 'M'), Edit::Insert('Z'), Edit::Same('J', 'J'), Edit::Remove('Y'), Edit::Same('A', 'A'), Edit::Insert('W'), Edit::Insert('X'), Edit::Same('U', 'U'), Edit::Remove('Z') ] ); } #[test] fn words() { let left = "The quick brown fox jumps over the lazy dog"; let right = "The quick brown dog leaps over the lazy cat"; let s = lcs( || left.split_whitespace(), || right.split_whitespace(), left.split_whitespace().count(), right.split_whitespace().count(), ); assert_eq!( s.collect::>(), vec![ Edit::Same("The", "The"), Edit::Same("quick", "quick"), Edit::Same("brown", "brown"), Edit::Remove("fox"), Edit::Remove("jumps"), Edit::Insert("dog"), Edit::Insert("leaps"), Edit::Same("over", "over"), Edit::Same("the", "the"), Edit::Same("lazy", "lazy"), Edit::Remove("dog"), Edit::Insert("cat") ] ); } } diffus-0.10.0/src/lib.rs000064400000000000000000000004750000000000000130500ustar 00000000000000pub mod diffable_impls; pub mod edit; mod lcs; pub mod same; mod twodvec; pub trait Diffable<'a> { type Diff: 'a; fn diff(&'a self, other: &'a Self) -> edit::Edit<'a, Self>; } pub trait Same { fn same(&self, other: &Self) -> bool; } #[cfg(feature = "derive")] #[doc(hidden)] pub use diffus_derive::*; diffus-0.10.0/src/same.rs000064400000000000000000000021430000000000000132210ustar 00000000000000use crate::Same; impl Same for Option { fn same(&self, other: &Self) -> bool { match (self, other) { (Some(a), Some(b)) => a.same(b), (None, None) => true, _ => false, } } } macro_rules! same_for_eq { ($($typ:ty),*) => { $( impl Same for $typ { fn same(&self, other: &Self) -> bool { self == other } } )* } } same_for_eq! { i64, i32, i16, i8, u64, u32, u16, u8, char, str, bool, isize, usize, () } macro_rules! same_for_float { ($($typ:ty),*) => { $( impl Same for $typ { fn same(&self, other: &Self) -> bool { self.to_ne_bytes() == other.to_ne_bytes() } } )* } } same_for_float! { f32, f64 } #[cfg(feature = "snake_case-impl")] same_for_eq! { snake_case::SnakeCase } #[cfg(feature = "uuid-impl")] same_for_eq! { uuid::Uuid } impl Same for &T { fn same(&self, other: &Self) -> bool { (*self).same(*other) } } diffus-0.10.0/src/twodvec.rs000064400000000000000000000015120000000000000137460ustar 00000000000000pub(crate) struct TwoDVec { storage: Vec, width: usize, } impl TwoDVec { pub fn new(initial: T, width: usize, height: usize) -> Self { Self { storage: vec![initial; width * height], width, } } } impl TwoDVec { pub fn height(&self) -> usize { self.storage.len() / self.width } pub fn width(&self) -> usize { self.width } } impl std::ops::Index for TwoDVec { type Output = [T]; fn index(&self, index: usize) -> &Self::Output { &self.storage.as_slice()[self.width * index..][..self.width] } } impl std::ops::IndexMut for TwoDVec { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.storage.as_mut_slice()[self.width * index..][..self.width] } }