Add new tests, fix bugs with Differ
This commit is contained in:
+42
-19
@@ -16,18 +16,22 @@ impl Differ{
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compare<T: ?Sized + Sequence>(&self, first_sequence: &T, second_sequence: &T) -> Vec<Vec<String>> {
|
||||
pub fn compare<T: ?Sized + Sequence>(&self, first_sequence: &T, second_sequence: &T) -> Vec<String> {
|
||||
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<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize,
|
||||
second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> {
|
||||
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<usize> = None;
|
||||
let mut eqj: Option<usize> = 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<String> {
|
||||
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"]);
|
||||
}
|
||||
+26
-27
@@ -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<Ordering> {
|
||||
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<Opcode>{
|
||||
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);
|
||||
|
||||
+67
-2
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user