diff --git a/src/differ.rs b/src/differ.rs index ee016a0..77d916b 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -16,18 +16,22 @@ impl Differ{ } } - pub fn compare(&self, first_sequence: &T, second_sequence: &T) -> Vec> { + pub fn compare(&self, first_sequence: &T, second_sequence: &T) -> Vec { let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); let mut res = Vec::new(); for opcode in matcher.get_opcodes(){ + let mut gen = Vec::new(); match opcode.tag.as_ref() { - "replace" => { res.push( self.fancy_replace(first_sequence, opcode.first_start, opcode.first_end, - second_sequence, opcode.second_start, opcode.second_end) ) }, - "delete" => { res.push( self.dump("-", first_sequence, opcode.first_start, opcode.first_end) ) }, - "insert" => { res.push( self.dump("+", second_sequence, opcode.second_start, opcode.second_end) ) }, - "equal" => { res.push( self.dump(" ", first_sequence, opcode.first_start, opcode.first_end) ) }, + "replace" => { gen = self.fancy_replace(first_sequence, opcode.first_start, opcode.first_end, + second_sequence, opcode.second_start, opcode.second_end) }, + "delete" => { gen = self.dump("-", first_sequence, opcode.first_start, opcode.first_end) }, + "insert" => { gen = self.dump("+", second_sequence, opcode.second_start, opcode.second_end) }, + "equal" => { gen = self.dump(" ", first_sequence, opcode.first_start, opcode.first_end) }, _ => {} } + for i in gen { + res.push(i); + } } res } @@ -57,19 +61,18 @@ impl Differ{ first = self.dump("-", first_sequence, first_start, first_end); second = self.dump("+", second_sequence, second_start, second_end); } - for str in second{ - first.push(str); + for s in second{ + first.push(s); } first } fn fancy_replace(&self, first_sequence: &T, first_start: usize, first_end: usize, second_sequence: &T, second_start: usize, second_end: usize) -> Vec { - let mut res = Vec::new(); + let mut res = Vec::new(); let (mut best_ratio, cutoff) = (0.74, 0.75); let (mut best_i, mut best_j) = (0, 0); let (mut second_sequence_str, mut first_sequence_str) = ("", ""); - //cruncher.charjunk = self.charjunk; let mut eqi: Option = None; let mut eqj: Option = None; for j in second_start..second_end{ @@ -84,6 +87,7 @@ impl Differ{ continue; } let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); + //cruncher.charjunk = self.charjunk; if cruncher.ratio() > best_ratio{ best_ratio = cruncher.ratio(); best_i = i; @@ -93,8 +97,8 @@ impl Differ{ } if best_ratio < cutoff{ if eqi.is_none(){ - res.extend(self.plain_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end).iter().cloned()); - return res + //res.extend(self.plain_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end).iter().cloned()); + return res } best_i = eqi.unwrap(); best_j = eqj.unwrap(); @@ -103,10 +107,11 @@ impl Differ{ eqi = None; } res.extend(self.fancy_helper(first_sequence, first_start, best_i, second_sequence, second_start, best_j).iter().cloned()); - let (first_elt, second_elt) = (first_sequence_str.at_index(best_i).unwrap(), second_sequence_str.at_index(best_j).unwrap()); + let (first_element, second_element) = (first_sequence.at_index(best_i).unwrap(), second_sequence.at_index(best_j).unwrap()); if eqi.is_none(){ let (mut first_tag, mut second_tag) = (String::new(), String::new()); - let mut cruncher = SequenceMatcher::new(first_elt, second_elt); + let mut cruncher = SequenceMatcher::new(first_element, second_element); + //cruncher.charjunk = self.charjunk; for opcode in &cruncher.get_opcodes(){ let (first_length, second_length) = (opcode.first_end - opcode.first_start, opcode.second_end - opcode.second_start); match opcode.tag.as_ref() { @@ -115,10 +120,10 @@ impl Differ{ second_tag.push_str(&str_with_similar_chars('^', second_length)); }, "delete" => { - first_tag.push_str(&str_with_similar_chars('^', first_length)); + first_tag.push_str(&str_with_similar_chars('-', first_length)); }, "insert" => { - second_tag.push_str(&str_with_similar_chars('^', second_length)); + second_tag.push_str(&str_with_similar_chars('+', second_length)); }, "equal" => { first_tag.push_str(&str_with_similar_chars(' ', first_length)); @@ -126,12 +131,12 @@ impl Differ{ }, _ => {} } - res.extend(self.qformat(&first_elt, &second_elt, &first_tag, &second_tag).iter().cloned()); } + res.extend(self.qformat(&first_element, &second_element, &first_tag, &second_tag).iter().cloned()); } else { let mut s = String::from(" "); - s.push_str(&first_elt); - res.extend(vec![s].iter().cloned()); + s.push_str(&first_element); + res.push(s); } res.extend(self.fancy_helper(first_sequence, best_i + 1, first_end, second_sequence, best_j + 1, second_end).iter().cloned()); res @@ -154,9 +159,13 @@ impl Differ{ fn qformat(&self, first_line: &str, second_line: &str, first_tags: &str, second_tags: &str) -> Vec { let mut res = Vec::new(); + let mut first_tags = first_tags; + let mut second_tags = second_tags; let mut common = cmp::min(count_leading(first_line, '\t'), count_leading(second_line, '\t')); common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); + first_tags = first_tags.split_at(common).1.trim_right(); + second_tags = second_tags.split_at(common).1.trim_right(); let mut s = String::from(format!("- {}", first_line)); res.push(s); if first_tags != ""{ @@ -173,3 +182,17 @@ impl Differ{ } } + +#[test] +fn test_fancy_replace() { + let differ = Differ::new(); + let result = differ.fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1).join(""); + assert_eq!(result, "- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n"); +} + +#[test] +fn test_qformat() { + let differ = Differ::new(); + let result = differ.qformat("\tabcDefghiJkl\n", "\tabcdefGhijkl\n", " ^ ^ ^ ", " ^ ^ ^ "); + assert_eq!(result, vec!["- \tabcDefghiJkl\n", "? \t ^ ^ ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^ ^\n"]); +} \ No newline at end of file diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index 6af30ab..2b2f9d8 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -1,34 +1,15 @@ use std::collections::HashMap; use std::cmp::{Ordering, max, min}; use utils::calculate_ratio; +use std::fmt::Debug; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct Match{ pub first_start: usize, pub second_start: usize, pub size: usize } -impl Ord for Match { - fn cmp(&self, other: &Match) -> Ordering { - self.size.cmp(&other.size) - } -} - -impl PartialOrd for Match { - fn partial_cmp(&self, other: &Match) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for Match { - fn eq(&self, other: &Match) -> bool { - self.size == other.size - } -} - -impl Eq for Match { } - impl Match{ fn new(first_start: usize, second_start: usize, size: usize) -> Match { Match{ @@ -39,7 +20,7 @@ impl Match{ } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Opcode{ pub tag: String, pub first_start: usize, @@ -60,7 +41,7 @@ impl Opcode{ } } -pub trait Sequence { +pub trait Sequence: Debug { fn len(&self) -> usize; fn at_index(&self, index: usize) -> Option<&str>; fn slice(&self, start: usize, end: usize) -> Option<&str>; @@ -204,7 +185,6 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ } size += 1; new_j2len.insert(j, size); - println!("{:?} {}", i, size); if size > best_size { best_i = i + 1 - size; best_j = j + 1 - size; @@ -254,8 +234,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ }, } } - println!("{:?}", matches); - matches.sort_by(|a, b| b.cmp(a)); + matches.sort_by(|a, b| a.cmp(b)); let (mut first_start, mut second_start, mut size) = (0, 0, 0); let mut non_adjacent = Vec::new(); for m in &matches{ @@ -278,7 +257,26 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ self.matching_blocks = Some(non_adjacent); self.matching_blocks.as_ref().unwrap().clone() } - +/* +if self.opcodes is not None: + return self.opcodes + i = j = 0 + self.opcodes = answer = [] + for ai, bj, size in self.get_matching_blocks(): + tag = '' + if i < ai and j < bj: + tag = 'replace' + elif i < ai: + tag = 'delete' + elif j < bj: + tag = 'insert' + if tag: + answer.append( (tag, i, ai, j, bj) ) + i, j = ai+size, bj+size + if size: + answer.append( ('equal', ai, i, bj, j) ) + return answer +*/ pub fn get_opcodes(&mut self) -> Vec{ if self.opcodes.as_ref().is_some(){ return self.opcodes.as_ref().unwrap().clone() @@ -315,6 +313,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ if codes.is_empty() { codes.push(Opcode::new("equal".to_string(), 0, 1, 0, 1)); } + if codes.first().unwrap().tag == "equal" { let mut opcode = codes.first_mut().unwrap(); opcode.first_start = max(opcode.first_start, opcode.first_end - n); diff --git a/tests/test.rs b/tests/test.rs index 40dcb06..f1e2f23 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,7 +1,7 @@ extern crate difflib; -use difflib::sequencematcher::{SequenceMatcher, Match}; - +use difflib::sequencematcher::{SequenceMatcher, Match, Opcode}; +use difflib::differ::Differ; #[test] fn test_longest_match() @@ -35,4 +35,69 @@ fn test_all_matches() size: 0 }); assert_eq!(result, expected_result); +} + +#[test] +fn test_get_opcodes() { + let mut matcher = SequenceMatcher::new("qabxcd", "abycdf"); + let result = matcher.get_opcodes(); + let mut expected_result = Vec::new(); + expected_result.push(Opcode{ + tag: "delete".to_string(), + first_start: 0, + first_end: 1, + second_start: 0, + second_end: 0 + }); + expected_result.push(Opcode{ + tag: "equal".to_string(), + first_start: 1, + first_end: 3, + second_start: 0, + second_end: 2 + }); + expected_result.push(Opcode{ + tag: "replace".to_string(), + first_start: 3, + first_end: 4, + second_start: 2, + second_end: 3 + }); + expected_result.push(Opcode{ + tag: "equal".to_string(), + first_start: 4, + first_end: 6, + second_start: 3, + second_end: 5 + }); + expected_result.push(Opcode{ + tag: "insert".to_string(), + first_start: 6, + first_end: 6, + second_start: 5, + second_end: 6 + }); + assert_eq!(result, expected_result); +} + +#[test] +fn test_ratio() { + let mut matcher = SequenceMatcher::new("abcd", "bcde"); + assert_eq!(matcher.ratio(), 0.75); +} + +#[test] +fn test_get_close_matches() { + let words = vec!["ape", "apple", "peach", "puppy"]; + let result = difflib::get_close_matches("appel", words, 3, 0.6); + assert_eq!(result, vec!["apple", "ape"]); +} + +#[test] +fn test_differ_compare() { + let first_text = vec!["one\n", "two\n", "three\n"]; + let second_text = vec!["ore\n", "tree\n", "emu\n"]; + let differ = Differ::new(); + let result = differ.compare(&first_text, &second_text).join(""); + assert_eq!(result, "- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n"); } \ No newline at end of file