my fork of dmp
WIP: a compatible version
| -rw-r--r-- | src/dmp.rs | 196 | ||||
| -rw-r--r-- | src/traits.rs | 93 | ||||
| -rw-r--r-- | tests/test.rs | 216 |
3 files changed, 289 insertions, 216 deletions
@@ -2,25 +2,10 @@ use core::str; use std::{char, collections::HashMap, fmt::Display}; use chrono::{NaiveTime, TimeDelta, Utc}; -use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS}; +use percent_encoding::{percent_decode, percent_encode}; use crate::{errors::Error, DType}; -// Appending controls to ensure exact same encoding as cpp variant -pub const ENCODE_SET: &AsciiSet = &CONTROLS - .add(b'"') - .add(b'<') - .add(b'>') - .add(b'`') - .add(b'{') - .add(b'}') - .add(b'%') - .add(b'[') - .add(b'\\') - .add(b']') - .add(b'^') - .add(b'|'); - /// Enum representing the different ops of diff #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Ops { @@ -1420,25 +1405,21 @@ impl DiffMatchPatch { } } - pub fn to_delta(diffs: &[Diff<u8>]) -> Vec<u8> { + pub fn to_delta<T: DType>(diffs: &[Diff<T>]) -> Vec<T> { let mut data = diffs .iter() .map(|diff| { match diff.op() { Ops::Insert => { - let encoded = percent_encode(diff.data(), ENCODE_SET) - .map(|v| v.as_bytes()) - .collect::<Vec<_>>() - .concat(); + let encoded = T::percent_encode(diff.data()); // format!("+{encoded}") - ["+".as_bytes(), &encoded, "\t".as_bytes()].concat() + [&[T::from_char('+')], &encoded[..], &[T::from_char('\t')]].concat() } Ops::Delete => { - [b"-", diff.size().to_string().as_bytes(), "\t".as_bytes()].concat() + [&[T::from_char('-')], &T::from_str(diff.size().to_string().as_str())[..], &[T::from_char('\t')]].concat() } Ops::Equal => { - // format!("={}", diff.size()) - [b"=", diff.size().to_string().as_bytes(), "\t".as_bytes()].concat() + [&[T::from_char('=')], &T::from_str(diff.size().to_string().as_str())[..], &[T::from_char('\t')]].concat() } } }) @@ -1679,7 +1660,7 @@ impl DiffMatchPatch { } #[inline] - pub fn diff_text_old(diffs: &[Diff<u8>]) -> Vec<u8> { + pub fn diff_text_old<T: DType>(diffs: &[Diff<T>]) -> Vec<T> { diffs .iter() .filter_map(|diff| { @@ -1694,7 +1675,7 @@ impl DiffMatchPatch { } - pub fn diff_text_new(diffs: &[Diff<u8>]) -> Vec<u8> { + pub fn diff_text_new<T: DType>(diffs: &[Diff<T>]) -> Vec<T> { diffs .iter() .filter_map(|diff| { @@ -1712,7 +1693,7 @@ impl DiffMatchPatch { // limit of the match algorithm. // Intended to be called only from within patch_apply. #[inline] - fn split_max(&self, patches: &mut Patches) { + fn split_max<T: DType>(&self, patches: &mut Patches<T>) { let max_bit = self.match_max_bits(); let patch_margin = self.patch_margin() as usize; @@ -1949,7 +1930,7 @@ impl DiffMatchPatch { // Match methods impl DiffMatchPatch { - fn match_internal(&self, text: &[u8], pattern: &[u8], loc: usize) -> Option<usize> { + fn match_internal<T: DType>(&self, text: &[T], pattern: &[T], loc: usize) -> Option<usize> { // Check for null inputs. // Nothing to match. if text.is_empty() { @@ -1972,7 +1953,7 @@ impl DiffMatchPatch { } } - fn match_bitap(&self, text: &[u8], pattern: &[u8], loc: usize) -> Option<usize> { + fn match_bitap<T: DType>(&self, text: &[T], pattern: &[T], loc: usize) -> Option<usize> { if pattern.len() > self.match_max_bits() { todo!("Throw error"); } @@ -2098,7 +2079,7 @@ impl DiffMatchPatch { best_loc } - fn match_alphabet(pattern: &[u8]) -> HashMap<u8, usize> { + fn match_alphabet<T: DType>(pattern: &[T]) -> HashMap<T, usize> { let mut map = HashMap::with_capacity(pattern.len()); pattern.iter().enumerate().for_each(|(i, &p)| { @@ -2128,16 +2109,22 @@ impl DiffMatchPatch { } // Patch Methods -#[derive(Debug, Default, Clone)] -pub struct Patch { - diffs: Vec<Diff<u8>>, +#[derive(Debug, Clone)] +pub struct Patch<T: DType> { + diffs: Vec<Diff<T>>, start1: usize, start2: usize, length1: usize, length2: usize, } -impl Display for Patch { +impl <T: DType>Default for Patch<T> { + fn default() -> Self { + Self { diffs: Vec::new(), start1: 0, start2: 0, length1: 0, length2: 0 } + } +} + +impl <T: DType>Display for Patch<T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let coord1 = if self.length1 == 0 { format!("{},0", self.start1) @@ -2174,37 +2161,38 @@ impl Display for Patch { coord2, " @@\n".to_string(), ]; - self.diffs.iter().for_each(|diff| { + for diff in self.diffs.iter() { let sign = match diff.op() { Ops::Insert => '+', Ops::Delete => '-', Ops::Equal => ' ', }; - let segment = format!("{sign}{}\n", percent_encode(diff.data(), ENCODE_SET)); + let enc = T::percent_encode(diff.data()); + let segment = format!("{sign}{}\n", T::to_string(&enc).map_err(|_| std::fmt::Error)?); segments.push(segment) - }); + }; write!(f, "{}", segments.join("")) } } -impl Patch { - pub fn diffs(&self) -> &[Diff<u8>] { +impl <T: DType>Patch<T> { + pub fn diffs(&self) -> &[Diff<T>] { &self.diffs[..] } } -pub enum PatchInput<'a> { +pub enum PatchInput<'a, T: DType> { Texts(&'a str, &'a str), - Diffs(&'a [Diff<u8>]), - TextDiffs(&'a str, &'a [Diff<u8>]), + Diffs(&'a [Diff<T>]), + TextDiffs(&'a str, &'a [Diff<T>]), } -pub type Patches = Vec<Patch>; +pub type Patches<T> = Vec<Patch<T>>; impl DiffMatchPatch { - fn parse_patch_header(s: &[u8]) -> Option<(usize, Option<usize>, usize, Option<usize>)> { + fn parse_patch_header<T: DType>(s: &[T]) -> Option<(usize, Option<usize>, usize, Option<usize>)> { let mut section = Vec::with_capacity(64); let mut current_sect = 0; @@ -2214,10 +2202,10 @@ impl DiffMatchPatch { let mut new_cols = None; for &c in s.iter() { - if c == b' ' { + if c == T::from_char(' ') { match current_sect { 0 => { - if §ion != b"@@" { + if section != T::from_str("@@") { return None; } } @@ -2226,23 +2214,23 @@ impl DiffMatchPatch { return None; } - let splits = section[1..].split(|&p| p == b',').collect::<Vec<_>>(); + let splits = section[1..].split(|&p| p == T::from_char(',')).collect::<Vec<_>>(); let ol = splits.first()?; - old_line = std::str::from_utf8(ol).ok()?.parse::<usize>().ok()?; + old_line = T::to_string(ol).ok()?.parse::<usize>().ok()?; if let Some(&oc) = splits.get(1) { - old_cols = Some(std::str::from_utf8(oc).ok()?.parse::<usize>().ok()?); + old_cols = Some(T::to_string(oc).ok()?.parse::<usize>().ok()?); } } 2 => { - let splits = section[if *section.first()? == b'+' { 1 } else { 0 }..] - .split(|&p| p == b',') + let splits = section[if *section.first()? == T::from_char('+') { 1 } else { 0 }..] + .split(|&p| p == T::from_char(',')) .collect::<Vec<_>>(); let nl = splits.first()?; - new_line = std::str::from_utf8(nl).ok()?.parse::<usize>().ok()?; + new_line = T::to_string(nl).ok()?.parse::<usize>().ok()?; if let Some(&nc) = splits.get(1) { - new_cols = Some(std::str::from_utf8(nc).ok()?.parse::<usize>().ok()?); + new_cols = Some(T::to_string(nc).ok()?.parse::<usize>().ok()?); } } _ => { @@ -2256,25 +2244,25 @@ impl DiffMatchPatch { continue; } - if current_sect == 1 && section.is_empty() && c != b'-' { + if current_sect == 1 && section.is_empty() && c != T::from_char('-') { return None; } section.push(c); } - if §ion != b"@@" { + if section != T::from_str("@@") { return None; } Some((old_line, old_cols, new_line, new_cols)) } - fn patch_make_internal( + fn patch_make_internal<T: DType>( &self, - txt: &[u8], - diffs: &[Diff<u8>], - ) -> Result<Patches, crate::errors::Error> { + txt: &[T], + diffs: &[Diff<T>], + ) -> Result<Patches<T>, crate::errors::Error> { // No diffs -> no patches if diffs.is_empty() { return Ok(Vec::new()); @@ -2288,8 +2276,8 @@ impl DiffMatchPatch { let mut char_n1 = 0; let mut char_n2 = 0; - let mut prepatch: Vec<u8> = txt.to_vec(); - let mut postpatch: Vec<u8> = prepatch.clone(); + let mut prepatch: Vec<T> = txt.to_vec(); + let mut postpatch: Vec<T> = prepatch.clone(); diffs.iter().enumerate().for_each(|(idx, diff)| { // a new patch starts here @@ -2355,7 +2343,7 @@ impl DiffMatchPatch { Ok(patches) } - fn patch_add_context(&self, patch: &mut Patch, text: &[u8]) { + fn patch_add_context<T: DType>(&self, patch: &mut Patch<T>, text: &[T]) { if text.is_empty() { return; } @@ -2422,11 +2410,11 @@ impl DiffMatchPatch { patch.length2 += prefix.len() + suffix.len(); } - fn patch_apply_internal( + fn patch_apply_internal<T: DType>( &self, - patches: &Patches, - source: &[u8], - ) -> Result<(Vec<u8>, Vec<bool>), crate::errors::Error> { + patches: &Patches<T>, + source: &[T], + ) -> Result<(Vec<T>, Vec<bool>), crate::errors::Error> { if patches.is_empty() { return Ok((source.to_vec(), vec![])); } @@ -2546,11 +2534,11 @@ impl DiffMatchPatch { Ok((source, results)) } - fn patch_add_padding(&self, patches: &mut Patches) -> Vec<u8> { + fn patch_add_padding<T: DType>(&self, patches: &mut Patches<T>) -> Vec<T> { let pad_len = self.patch_margin() as usize; let null_pad = (1..pad_len + 1) - .filter_map(|c| char::from_u32(c as u32).map(|c_| c_ as u8)) + .filter_map(|c| c.as_char().map(|c| T::from_char(c))) .collect::<Vec<_>>(); // Bump all the patches forward. @@ -2703,7 +2691,7 @@ impl DiffMatchPatch { /// Given a diff, measure its Levenshtein distance in terms of the number of inserted, deleted or substituted characters. /// The minimum distance is 0 which means equality, the maximum distance is the length of the longer string. - pub fn diff_levenshtein(&self, diffs: &[Diff<u8>]) -> usize { + pub fn diff_levenshtein<T: DType>(&self, diffs: &[Diff<T>]) -> usize { let mut levenshtein = 0; let mut insert = 0; let mut delete = 0; @@ -2882,46 +2870,48 @@ impl DiffMatchPatch { /// Given two texts, or an already computed list of differences, return an array of patch objects. /// The third form PatchInput::TextDiffs(...) is preferred, use it if you happen to have that data available, otherwise this function will compute the missing pieces. /// TODO: add example - pub fn patch_make(&self, input: PatchInput) -> Result<Patches, crate::errors::Error> { + pub fn patch_make<T: DType>(&self, input: PatchInput<T>) -> Result<Patches<T>, crate::errors::Error> { let mut diff_input; let txt_old; let (txt, diffs) = match input { // No diffs provided, lets make our own PatchInput::Texts(txt1, txt2) => { - let dmp = DiffMatchPatch::default(); - diff_input = dmp.diff_main(txt1, txt2)?; + diff_input = T::differ(self, txt1, txt2)?; if diff_input.len() > 2 { Self::cleanup_semantic(&mut diff_input); } - (txt1.as_bytes(), &diff_input[..]) + (T::from_str(txt1), &diff_input[..]) } PatchInput::Diffs(diffs) => { // No origin string provided, compute our own. - txt_old = Self::diff_text_old(diffs); - (&txt_old[..], diffs) + (Self::diff_text_old(diffs), diffs) } - PatchInput::TextDiffs(txt, diffs) => (txt.as_bytes(), diffs), + PatchInput::TextDiffs(txt, diffs) => { + txt_old = T::from_str(txt); + (txt_old, diffs) + }, }; - self.patch_make_internal(txt, diffs) + self.patch_make_internal(&txt, diffs) } /// Reduces an array of patch objects to a block of text which looks extremely similar to the standard GNU diff/patch format. This text may be stored or transmitted. /// TODO: add example - pub fn patch_to_text(&self, patches: &Patches) -> String { + pub fn patch_to_text<T: DType>(&self, patches: &Patches<T>) -> String { patches.iter().map(|p| p.to_string()).collect::<String>() } /// Parses a block of text (which was presumably created by the patch_toText function) and returns an array of patch objects. /// TODO: add example - pub fn patch_from_text(&self, text: &str) -> Result<Patches, Error> { + pub fn patch_from_text<T: DType>(&self, text: &str) -> Result<Patches<T>, Error> { if text.is_empty() { return Ok(vec![]); } - let mut text = text.as_bytes().split(|&p| p == b'\n').collect::<Vec<_>>(); + let txt_t = T::from_str(text); + let mut text = txt_t.split(|&p| p == T::from_char('\n')).collect::<Vec<_>>(); let mut patches = vec![]; @@ -2975,27 +2965,21 @@ impl DiffMatchPatch { }; // Should never panic, already checked for `empty` - let sign = txt.first().unwrap(); - - let line = percent_decode(&txt[1..]).collect::<Vec<_>>(); - - match sign { - b'-' => { - patch.diffs.push(Diff::delete(&line)); - } - b'+' => { - patch.diffs.push(Diff::insert(&line)); - } - b' ' => { - patch.diffs.push(Diff::equal(&line)); - } - b'@' => { - // next patch, break - break; - } - _ => { - return Err(Error::InvalidInput); - } + let &sign = txt.first().unwrap(); + + let line = T::percent_decode(&txt[1..]); + + if sign == T::from_char('-') { + patch.diffs.push(Diff::delete(&line)); + } else if sign == T::from_char('+') { + patch.diffs.push(Diff::insert(&line)); + } else if sign == T::from_char(' ') { + patch.diffs.push(Diff::equal(&line)); + } else if sign == T::from_char('@') { + // next patch, break + break; + } else { + return Err(Error::InvalidInput); } text.remove(0); @@ -3018,15 +3002,15 @@ impl DiffMatchPatch { /// If patch_delete_threshold is closer to 1, then the deleted text may contain anything. /// In most use cases Patch_DeleteThreshold should just be set to the same value as match_threshold. /// TODO: add example - pub fn patch_apply( + pub fn patch_apply<T: DType>( &self, - patches: &Patches, + patches: &Patches<T>, source_txt: &str, ) -> Result<(String, Vec<bool>), crate::errors::Error> { - let (str_bytes, results) = self.patch_apply_internal(patches, source_txt.as_bytes())?; + let (str_data, results) = self.patch_apply_internal(patches, &T::from_str(source_txt))?; Ok(( - String::from_utf8(str_bytes).map_err(|_| crate::errors::Error::Utf8Error)?, + T::to_string(&str_data).map_err(|_| crate::errors::Error::Utf8Error)?, results, )) } diff --git a/src/traits.rs b/src/traits.rs index 20e0025..73635f8 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,11 +1,28 @@ use std::hash::Hash; use chrono::NaiveTime; +use percent_encoding::{AsciiSet, CONTROLS}; use crate::dmp::{Diff, DiffMatchPatch}; +// Appending controls to ensure exact same encoding as cpp variant +const ENCODE_SET: &AsciiSet = &CONTROLS + .add(b'"') + .add(b'<') + .add(b'>') + .add(b'`') + .add(b'{') + .add(b'}') + .add(b'%') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + pub trait DType: Copy + Ord + Eq + Hash { + fn differ(dmp: &DiffMatchPatch, txt_old: &str, txt_new: &str) -> Result<Vec<Diff<Self>>, crate::errors::Error>; fn bisect_split( dmp: &DiffMatchPatch, old: &[Self], @@ -16,16 +33,22 @@ pub trait DType: Copy + Ord + Eq + Hash { ) -> Result<Vec<Diff<Self>>, crate::errors::Error>; fn from_char(c: char) -> Self; - fn as_char(&self) -> Option<char>; - fn from_str(str: &str) -> Vec<Self>; + fn to_string(data: &[Self]) -> Result<String, crate::Error>; fn is_linebreak_end(input: &[Self]) -> bool; fn is_linebreak_start(input: &[Self]) -> bool; + + fn percent_encode(input: &[Self]) -> Vec<Self>; + fn percent_decode(input: &[Self]) -> Vec<Self>; } impl DType for u8 { + fn differ(dmp: &DiffMatchPatch, txt_old: &str, txt_new: &str) -> Result<Vec<Diff<Self>>, crate::errors::Error> { + dmp.diff_main(txt_old, txt_new) + } + fn bisect_split( dmp: &DiffMatchPatch, old: &[u8], @@ -60,6 +83,11 @@ impl DType for u8 { } #[inline] + fn to_string(data: &[Self]) -> Result<String, crate::Error> { + std::str::from_utf8(data).map_err(|_| crate::Error::Utf8Error).map(|s| s.to_string()) + } + + #[inline] fn is_linebreak_end(input: &[Self]) -> bool { input.ends_with(b"\n\n") || input.ends_with(b"\n\r\n") } @@ -71,9 +99,24 @@ impl DType for u8 { || input.starts_with(b"\n\r\n") || input.starts_with(b"\n\n") } + + #[inline] + fn percent_encode(input: &[Self]) -> Vec<Self> { + percent_encoding::percent_encode(input, ENCODE_SET).collect::<String>().as_bytes().to_vec() + } + + #[inline] + fn percent_decode(input: &[Self]) -> Vec<Self> { + // percent_encoding::percent_encode(input, ENCODE_SET).collect::<String>().as_bytes().to_vec() + todo!() + } } impl DType for char { + fn differ(dmp: &DiffMatchPatch, txt_old: &str, txt_new: &str) -> Result<Vec<Diff<Self>>, crate::errors::Error> { + dmp.diff_main_compat(txt_old, txt_new) + } + fn bisect_split( dmp: &DiffMatchPatch, old: &[char], @@ -108,6 +151,11 @@ impl DType for char { } #[inline] + fn to_string(data: &[Self]) -> Result<String, crate::Error> { + Ok(data.iter().collect::<String>()) + } + + #[inline] fn is_linebreak_end(input: &[Self]) -> bool { input.ends_with(&['\n', '\n']) || input.ends_with(&['\n', '\r', '\n']) } @@ -119,9 +167,36 @@ impl DType for char { || input.starts_with(&['\n', '\r', '\n']) || input.starts_with(&['\n', '\n']) } + + #[inline] + fn percent_encode(input: &[Self]) -> Vec<Self> { + let d = input + .iter() + .map(|c| { + let mut b = vec![0; c.len_utf8()]; + c.encode_utf8(&mut b); + + b + }).collect::<Vec<_>>() + .concat(); + + + let encoded = percent_encoding::percent_encode(&d[..], ENCODE_SET).collect::<String>(); + + Self::from_str(&encoded) + } + + #[inline] + fn percent_decode(input: &[Self]) -> Vec<Self> { + todo!() + } } impl DType for usize { + fn differ(_: &DiffMatchPatch, _: &str, _: &str) -> Result<Vec<Diff<Self>>, crate::errors::Error> { + unimplemented!() + } + fn bisect_split( dmp: &DiffMatchPatch, old: &[usize], @@ -155,6 +230,10 @@ impl DType for usize { unimplemented!() } + fn to_string(_: &[Self]) -> Result<String, crate::Error> { + unimplemented!() + } + fn is_linebreak_end(_: &[Self]) -> bool { unimplemented!() } @@ -163,4 +242,14 @@ impl DType for usize { fn is_linebreak_start(_: &[Self]) -> bool { unimplemented!() } + + #[inline] + fn percent_encode(_: &[Self]) -> Vec<Self> { + unimplemented!() + } + + #[inline] + fn percent_decode(_: &[Self]) -> Vec<Self> { + unimplemented!() + } }
\ No newline at end of file diff --git a/tests/test.rs b/tests/test.rs index ce839cd..f32ff24 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -365,114 +365,114 @@ fn test_diff_main_compat() -> Result<(), Error> { assert!(dmp.diff_main_compat("", "")?.is_empty()); // Equality - // assert_eq!(vec![Diff::equal(b"abc")], dmp.diff_main_compat("abc", "abc")?); - - // // Simple insert - // assert_eq!( - // vec![Diff::equal(b"ab"), Diff::insert(b"123"), Diff::equal(b"c")], - // dmp.diff_main_compat("abc", "ab123c")? - // ); - - // // Simple delete - // assert_eq!( - // vec![Diff::equal(b"a"), Diff::delete(b"123"), Diff::equal(b"bc")], - // dmp.diff_main_compat("a123bc", "abc")? - // ); - - // // Two insertions - // assert_eq!( - // vec![ - // Diff::equal(b"a"), - // Diff::insert(b"123"), - // Diff::equal(b"b"), - // Diff::insert(b"456"), - // Diff::equal(b"c"), - // ], - // dmp.diff_main_compat("abc", "a123b456c")? - // ); - - // // Two deletions. - // assert_eq!( - // vec![ - // Diff::equal(b"a"), - // Diff::delete(b"123"), - // Diff::equal(b"b"), - // Diff::delete(b"456"), - // Diff::equal(b"c"), - // ], - // dmp.diff_main_compat("a123b456c", "abc")? - // ); - - // // Perform a real diff. - // // Switch off the timeout. - // dmp.set_timeout(None); - // // Simple cases. - // assert_eq!( - // vec![Diff::delete(b"a"), Diff::insert(b"b"),], - // dmp.diff_main_compat("a", "b")? - // ); - - // assert_eq!( - // vec![ - // Diff::delete(b"Apple"), - // Diff::insert(b"Banana"), - // Diff::equal(b"s are a"), - // Diff::insert(b"lso"), - // Diff::equal(b" fruit.") - // ], - // dmp.diff_main_compat("Apples are a fruit.", "Bananas are also fruit.")? - // ); - - // assert_eq!( - // vec![ - // Diff::delete(b"a"), - // Diff::insert("\u{0680}".as_bytes()), - // Diff::equal(b"x"), - // Diff::delete(b"\t"), - // Diff::insert(b"\0") - // ], - // dmp.diff_main_compat("ax\t", "\u{0680}x\0")? - // ); - - // // Overlaps. - // assert_eq!( - // vec![ - // Diff::delete(b"1"), - // Diff::equal(b"a"), - // Diff::delete(b"y"), - // Diff::equal(b"b"), - // Diff::delete(b"2"), - // Diff::insert(b"xab"), - // ], - // dmp.diff_main_compat("1ayb2", "abxab")? - // ); - - // assert_eq!( - // vec![ - // Diff::insert(b"xaxcx"), - // Diff::equal(b"abc"), - // Diff::delete(b"y"), - // ], - // dmp.diff_main_compat("abcy", "xaxcxabc")? - // ); - - // assert_eq!( - // vec![ - // Diff::delete(b"ABCD"), - // Diff::equal(b"a"), - // Diff::delete(b"="), - // Diff::insert(b"-"), - // Diff::equal(b"bcd"), - // Diff::delete(b"="), - // Diff::insert(b"-"), - // Diff::equal(b"efghijklmnopqrs"), - // Diff::delete(b"EFGHIJKLMNOefg"), - // ], - // dmp.diff_main_compat( - // "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", - // "a-bcd-efghijklmnopqrs" - // )? - // ); + assert_eq!(vec![Diff::equal(&"abc".chars().collect::<Vec<_>>()[..])], dmp.diff_main_compat("abc", "abc")?); + + // Simple insert + assert_eq!( + vec![Diff::equal(&"ab".chars().collect::<Vec<_>>()[..]), Diff::insert(&"123".chars().collect::<Vec<_>>()[..]), Diff::equal(&['c'])], + dmp.diff_main_compat("abc", "ab123c")? + ); + + // Simple delete + assert_eq!( + vec![Diff::equal(&['a']), Diff::delete(&"123".chars().collect::<Vec<_>>()[..]), Diff::equal(&['b','c'])], + dmp.diff_main_compat("a123bc", "abc")? + ); + + // Two insertions + assert_eq!( + vec![ + Diff::equal(&['a']), + Diff::insert(&"123".chars().collect::<Vec<_>>()[..]), + Diff::equal(&['b']), + Diff::insert(&['4','5','6']), + Diff::equal(&['c']), + ], + dmp.diff_main_compat("abc", "a123b456c")? + ); + + // Two deletions. + assert_eq!( + vec![ + Diff::equal(&['a']), + Diff::delete(&"123".chars().collect::<Vec<_>>()[..]), + Diff::equal(&['b']), + Diff::delete(&"456".chars().collect::<Vec<_>>()[..]), + Diff::equal(&['c']), + ], + dmp.diff_main_compat("a123b456c", "abc")? + ); + + // Perform a real diff. + // Switch off the timeout. + dmp.set_timeout(None); + // Simple cases. + assert_eq!( + vec![Diff::delete(&['a']), Diff::insert(&['b']),], + dmp.diff_main_compat("a", "b")? + ); + + assert_eq!( + vec![ + Diff::delete(&"Apple".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"Banana".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"s are a".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"lso".chars().collect::<Vec<_>>()[..]), + Diff::equal(&" fruit.".chars().collect::<Vec<_>>()[..]) + ], + dmp.diff_main_compat("Apples are a fruit.", "Bananas are also fruit.")? + ); + + assert_eq!( + vec![ + Diff::delete(&['a']), + Diff::insert(&"\u{0680}".chars().collect::<Vec<_>>()[..]), + Diff::equal(&['x']), + Diff::delete(&['\t']), + Diff::insert(&['\0']) + ], + dmp.diff_main_compat("ax\t", "\u{0680}x\0")? + ); + + // Overlaps. + assert_eq!( + vec![ + Diff::delete(&['1']), + Diff::equal(&['a']), + Diff::delete(&['y']), + Diff::equal(&['b']), + Diff::delete(&['2']), + Diff::insert(&"xab".chars().collect::<Vec<_>>()[..]), + ], + dmp.diff_main_compat("1ayb2", "abxab")? + ); + + assert_eq!( + vec![ + Diff::insert(&"xaxcx".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"abc".chars().collect::<Vec<_>>()[..]), + Diff::delete(&['y']), + ], + dmp.diff_main_compat("abcy", "xaxcxabc")? + ); + + assert_eq!( + vec![ + Diff::delete(&"ABCD".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"a".chars().collect::<Vec<_>>()[..]), + Diff::delete(&"=".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"-".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"bcd".chars().collect::<Vec<_>>()[..]), + Diff::delete(&"=".chars().collect::<Vec<_>>()[..]), + Diff::insert(&"-".chars().collect::<Vec<_>>()[..]), + Diff::equal(&"efghijklmnopqrs".chars().collect::<Vec<_>>()[..]), + Diff::delete(&"EFGHIJKLMNOefg".chars().collect::<Vec<_>>()[..]), + ], + dmp.diff_main_compat( + "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", + "a-bcd-efghijklmnopqrs" + )? + ); // Large equality. assert_eq!( |