Reformat code with rustfmt

This commit is contained in:
Dima Kudosh
2017-02-26 19:32:22 +03:00
parent 7fc731ab99
commit 07de887127
6 changed files with 939 additions and 792 deletions
+47 -37
View File
@@ -5,46 +5,56 @@ use difflib::sequencematcher::SequenceMatcher;
fn main() { fn main() {
// unified_diff // unified_diff
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let diff = difflib::unified_diff(&first_text, &second_text, "Original", "Current", let diff = difflib::unified_diff(&first_text,
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); &second_text,
for line in &diff { "Original",
println!("{:?}", line); "Current",
} "2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3);
for line in &diff {
println!("{:?}", line);
}
//context_diff //context_diff
let diff = difflib::context_diff(&first_text, &second_text, "Original", "Current", let diff = difflib::context_diff(&first_text,
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3); &second_text,
for line in &diff { "Original",
println!("{:?}", line); "Current",
} "2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3);
for line in &diff {
println!("{:?}", line);
}
//get_close_matches //get_close_matches
let words = vec!["ape", "apple", "peach", "puppy"]; let words = vec!["ape", "apple", "peach", "puppy"];
let result = difflib::get_close_matches("appel", words, 3, 0.6); let result = difflib::get_close_matches("appel", words, 3, 0.6);
println!("{:?}", result); println!("{:?}", result);
//Differ examples //Differ examples
let differ = Differ::new(); let differ = Differ::new();
let diff = differ.compare(&first_text, &second_text); let diff = differ.compare(&first_text, &second_text);
for line in &diff { for line in &diff {
println!("{:?}", line); println!("{:?}", line);
} }
//SequenceMatcher examples //SequenceMatcher examples
let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four");
let m = matcher.find_longest_match(0, 18, 0, 18); let m = matcher.find_longest_match(0, 18, 0, 18);
println!("{:?}", m); println!("{:?}", m);
let all_matches = matcher.get_matching_blocks(); let all_matches = matcher.get_matching_blocks();
println!("{:?}", all_matches); println!("{:?}", all_matches);
let opcode = matcher.get_opcodes(); let opcode = matcher.get_opcodes();
println!("{:?}", opcode); println!("{:?}", opcode);
let grouped_opcodes = matcher.get_grouped_opcodes(2); let grouped_opcodes = matcher.get_grouped_opcodes(2);
println!("{:?}", grouped_opcodes); println!("{:?}", grouped_opcodes);
let ratio = matcher.ratio(); let ratio = matcher.ratio();
println!("{:?}", ratio); println!("{:?}", ratio);
matcher.set_seqs("aaaaa", "aaaab"); matcher.set_seqs("aaaaa", "aaaab");
println!("{:?}", matcher.ratio()); println!("{:?}", matcher.ratio());
} }
+276 -195
View File
@@ -5,216 +5,297 @@ use std::cmp;
pub struct Differ { pub struct Differ {
pub line_junk: Option<fn(&str) -> bool>, pub line_junk: Option<fn(&str) -> bool>,
pub char_junk: Option<fn(&str) -> bool> pub char_junk: Option<fn(&str) -> bool>,
} }
impl Differ{ impl Differ {
pub fn new() -> Differ { pub fn new() -> Differ {
Differ{ Differ {
line_junk: None, line_junk: None,
char_junk: None char_junk: None,
} }
} }
pub fn compare<T: ?Sized + Sequence>(&self, first_sequence: &T, second_sequence: &T) -> Vec<String> { pub fn compare<T: ?Sized + Sequence>(&self,
let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); first_sequence: &T,
matcher.set_is_junk(self.line_junk); second_sequence: &T)
let mut res = Vec::new(); -> Vec<String> {
for opcode in matcher.get_opcodes(){ let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
let mut gen = Vec::new(); matcher.set_is_junk(self.line_junk);
match opcode.tag.as_ref() { let mut res = Vec::new();
"replace" => { gen = self.fancy_replace(first_sequence, opcode.first_start, opcode.first_end, for opcode in matcher.get_opcodes() {
second_sequence, opcode.second_start, opcode.second_end) }, let mut gen = Vec::new();
"delete" => { gen = self.dump("-", first_sequence, opcode.first_start, opcode.first_end) }, match opcode.tag.as_ref() {
"insert" => { gen = self.dump("+", second_sequence, opcode.second_start, opcode.second_end) }, "replace" => {
"equal" => { gen = self.dump(" ", first_sequence, opcode.first_start, opcode.first_end) }, gen = self.fancy_replace(first_sequence,
_ => {} opcode.first_start,
} opcode.first_end,
for i in gen { second_sequence,
res.push(i); opcode.second_start,
} opcode.second_end)
} }
res "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
}
fn dump<T: ?Sized + Sequence>(&self, tag: &str, sequence: &T, start: usize, end: usize) -> Vec<String> { fn dump<T: ?Sized + Sequence>(&self,
let mut res = Vec::new(); tag: &str,
for i in start..end { sequence: &T,
match sequence.at_index(i) { start: usize,
Some(s) => res.push(format!("{} {}", tag, s)), end: usize)
None => {}, -> Vec<String> {
} let mut res = Vec::new();
} for i in start..end {
res match sequence.at_index(i) {
} Some(s) => res.push(format!("{} {}", tag, s)),
None => {}
}
}
res
}
fn plain_replace<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize, fn plain_replace<T: ?Sized + Sequence>(&self,
second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> { first_sequence: &T,
if !(first_start < first_end && second_start < second_end){ first_start: usize,
return Vec::new(); first_end: usize,
} second_sequence: &T,
let mut first; second_start: usize,
let second; second_end: usize)
if second_end - second_start < first_end - first_start{ -> Vec<String> {
first = self.dump("+", second_sequence, second_start, second_end); if !(first_start < first_end && second_start < second_end) {
second = self.dump("-", first_sequence, first_start, first_end); return Vec::new();
} else { }
first = self.dump("-", first_sequence, first_start, first_end); let mut first;
second = self.dump("+", second_sequence, second_start, second_end); let second;
} if second_end - second_start < first_end - first_start {
for s in second{ first = self.dump("+", second_sequence, second_start, second_end);
first.push(s); second = self.dump("-", first_sequence, first_start, first_end);
} } else {
first first = self.dump("-", first_sequence, first_start, first_end);
} second = self.dump("+", second_sequence, second_start, second_end);
}
for s in second {
first.push(s);
}
first
}
fn fancy_replace<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize, fn fancy_replace<T: ?Sized + Sequence>(&self,
second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> { first_sequence: &T,
let mut res = Vec::new(); first_start: usize,
let (mut best_ratio, cutoff) = (0.74, 0.75); first_end: usize,
let (mut best_i, mut best_j) = (0, 0); second_sequence: &T,
let (mut second_sequence_str, mut first_sequence_str); second_start: usize,
let mut eqi: Option<usize> = None; second_end: usize)
let mut eqj: Option<usize> = None; -> Vec<String> {
for j in second_start..second_end{ let mut res = Vec::new();
second_sequence_str = second_sequence.at_index(j).unwrap(); let (mut best_ratio, cutoff) = (0.74, 0.75);
for i in first_start..first_end{ let (mut best_i, mut best_j) = (0, 0);
first_sequence_str = first_sequence.at_index(i).unwrap(); let (mut second_sequence_str, mut first_sequence_str);
if first_sequence_str == second_sequence_str{ let mut eqi: Option<usize> = None;
if eqi.is_none(){ let mut eqj: Option<usize> = None;
eqi = Some(i); for j in second_start..second_end {
eqj = Some(j); second_sequence_str = second_sequence.at_index(j).unwrap();
} for i in first_start..first_end {
continue; first_sequence_str = first_sequence.at_index(i).unwrap();
} if first_sequence_str == second_sequence_str {
let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); if eqi.is_none() {
cruncher.set_is_junk(self.char_junk); eqi = Some(i);
if cruncher.ratio() > best_ratio{ eqj = Some(j);
best_ratio = cruncher.ratio(); }
best_i = i; continue;
best_j = j; }
} let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str);
} cruncher.set_is_junk(self.char_junk);
} if cruncher.ratio() > best_ratio {
if best_ratio < cutoff{ best_ratio = cruncher.ratio();
if eqi.is_none(){ best_i = i;
res.extend(self.plain_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end).iter().cloned()); best_j = j;
return res }
} }
best_i = eqi.unwrap(); }
best_j = eqj.unwrap(); if best_ratio < cutoff {
} else { if eqi.is_none() {
eqi = None; res.extend(self.plain_replace(first_sequence,
} first_start,
res.extend(self.fancy_helper(first_sequence, first_start, best_i, second_sequence, second_start, best_j).iter().cloned()); first_end,
let (first_element, second_element) = (first_sequence.at_index(best_i).unwrap(), second_sequence.at_index(best_j).unwrap()); second_sequence,
if eqi.is_none(){ second_start,
let (mut first_tag, mut second_tag) = (String::new(), String::new()); second_end)
let mut cruncher = SequenceMatcher::new(first_element, second_element); .iter()
cruncher.set_is_junk(self.char_junk); .cloned());
for opcode in &cruncher.get_opcodes(){ return res;
let (first_length, second_length) = (opcode.first_end - opcode.first_start, opcode.second_end - opcode.second_start); }
match opcode.tag.as_ref() { best_i = eqi.unwrap();
"replace" => { best_j = eqj.unwrap();
first_tag.push_str(&str_with_similar_chars('^', first_length)); } else {
second_tag.push_str(&str_with_similar_chars('^', second_length)); eqi = None;
}, }
"delete" => { res.extend(self.fancy_helper(first_sequence,
first_tag.push_str(&str_with_similar_chars('-', first_length)); first_start,
}, best_i,
"insert" => { second_sequence,
second_tag.push_str(&str_with_similar_chars('+', second_length)); second_start,
}, best_j)
"equal" => { .iter()
first_tag.push_str(&str_with_similar_chars(' ', first_length)); .cloned());
second_tag.push_str(&str_with_similar_chars(' ', second_length)); 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_element, second_element);
res.extend(self.qformat(&first_element, &second_element, &first_tag, &second_tag).iter().cloned()); cruncher.set_is_junk(self.char_junk);
} else { for opcode in &cruncher.get_opcodes() {
let mut s = String::from(" "); let (first_length, second_length) = (opcode.first_end - opcode.first_start,
s.push_str(&first_element); opcode.second_end - opcode.second_start);
res.push(s); match opcode.tag.as_ref() {
} "replace" => {
res.extend(self.fancy_helper(first_sequence, best_i + 1, first_end, second_sequence, best_j + 1, second_end).iter().cloned()); first_tag.push_str(&str_with_similar_chars('^', first_length));
res second_tag.push_str(&str_with_similar_chars('^', second_length));
} }
"delete" => {
first_tag.push_str(&str_with_similar_chars('-', first_length));
}
"insert" => {
second_tag.push_str(&str_with_similar_chars('+', second_length));
}
"equal" => {
first_tag.push_str(&str_with_similar_chars(' ', first_length));
second_tag.push_str(&str_with_similar_chars(' ', second_length));
}
_ => {}
}
}
res.extend(self.qformat(&first_element, &second_element, &first_tag, &second_tag)
.iter()
.cloned());
} else {
let mut s = String::from(" ");
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
}
fn fancy_helper<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize, fn fancy_helper<T: ?Sized + Sequence>(&self,
second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> { first_sequence: &T,
let mut res = Vec::new(); first_start: usize,
if first_start < first_end{ first_end: usize,
if second_start < second_end{ second_sequence: &T,
res = self.fancy_replace(first_sequence, first_start, first_end, second_sequence, second_start, second_end); second_start: usize,
} else { second_end: usize)
res = self.dump("-", first_sequence, first_start, first_end); -> Vec<String> {
} let mut res = Vec::new();
} else if second_start < second_end { if first_start < first_end {
res = self.dump("+", second_sequence, second_start, second_end); if second_start < second_end {
} res = self.fancy_replace(first_sequence,
res first_start,
} first_end,
second_sequence,
second_start,
second_end);
} else {
res = self.dump("-", first_sequence, first_start, first_end);
}
} else if second_start < second_end {
res = self.dump("+", second_sequence, second_start, second_end);
}
res
}
fn qformat(&self, first_line: &str, second_line: &str, first_tags: &str, second_tags: &str) -> Vec<String> { fn qformat(&self,
let mut res = Vec::new(); first_line: &str,
let mut first_tags = first_tags; second_line: &str,
let mut second_tags = second_tags; first_tags: &str,
let mut common = cmp::min(count_leading(first_line, '\t'), count_leading(second_line, '\t')); second_tags: &str)
common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); -> Vec<String> {
common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' ')); let mut res = Vec::new();
first_tags = first_tags.split_at(common).1.trim_right(); let mut first_tags = first_tags;
second_tags = second_tags.split_at(common).1.trim_right(); let mut second_tags = second_tags;
let mut s = String::from(format!("- {}", first_line)); let mut common = cmp::min(count_leading(first_line, '\t'),
res.push(s); count_leading(second_line, '\t'));
if first_tags != ""{ common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' '));
s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags)); common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' '));
res.push(s); first_tags = first_tags.split_at(common).1.trim_right();
} second_tags = second_tags.split_at(common).1.trim_right();
s = String::from(format!("+ {}", second_line)); let mut s = String::from(format!("- {}", first_line));
res.push(s); res.push(s);
if second_tags != "" { if first_tags != "" {
s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), second_tags)); s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags));
res.push(s); res.push(s);
} }
res s = String::from(format!("+ {}", second_line));
} res.push(s);
if second_tags != "" {
pub fn restore(delta: &Vec<String>, which: usize) -> Vec<String> { s = String::from(format!("? {}{}\n",
if !(which == 1 || which == 2) { str_with_similar_chars('\t', common),
panic!("Second parameter must be 1 or 2"); second_tags));
} res.push(s);
let mut res = Vec::new(); }
let tag; res
if which == 1 { }
tag = "- ".to_string();
} else {
tag = "+ ".to_string();
}
let prefixes = vec![tag, " ".to_string()];
for line in delta {
for prefix in &prefixes {
if line.starts_with(prefix) {
res.push( line.split_at(2).1.to_string() );
}
}
}
res
}
pub fn restore(delta: &Vec<String>, which: usize) -> Vec<String> {
if !(which == 1 || which == 2) {
panic!("Second parameter must be 1 or 2");
}
let mut res = Vec::new();
let tag;
if which == 1 {
tag = "- ".to_string();
} else {
tag = "+ ".to_string();
}
let prefixes = vec![tag, " ".to_string()];
for line in delta {
for prefix in &prefixes {
if line.starts_with(prefix) {
res.push(line.split_at(2).1.to_string());
}
}
}
res
}
} }
#[test] #[test]
fn test_fancy_replace() { fn test_fancy_replace() {
let differ = Differ::new(); let differ = Differ::new();
let result = differ.fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1).join(""); let result = differ.fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1)
assert_eq!(result, "- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n"); .join("");
assert_eq!(result,
"- abcDefghiJkl\n? ^ ^ ^\n+ abcdefGhijkl\n? ^ ^ ^\n");
} }
#[test] #[test]
fn test_qformat() { fn test_qformat() {
let differ = Differ::new(); let differ = Differ::new();
let result = differ.qformat("\tabcDefghiJkl\n", "\tabcdefGhijkl\n", " ^ ^ ^ ", " ^ ^ ^ "); let result = differ.qformat("\tabcDefghiJkl\n",
assert_eq!(result, vec!["- \tabcDefghiJkl\n", "? \t ^ ^ ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^ ^\n"]); "\tabcdefGhijkl\n",
" ^ ^ ^ ",
" ^ ^ ^ ");
assert_eq!(result,
vec!["- \tabcDefghiJkl\n", "? \t ^ ^ ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^ ^\n"]);
} }
+133 -113
View File
@@ -8,122 +8,142 @@ use sequencematcher::{SequenceMatcher, Sequence};
use utils::{format_range_unified, format_range_context}; use utils::{format_range_unified, format_range_context};
pub fn get_close_matches<'a>(word: &str, possibilities: Vec<&'a str>, n: usize, cutoff: f32) -> Vec<&'a str> { pub fn get_close_matches<'a>(word: &str,
if !(0.0 <= cutoff && cutoff <= 1.0) { possibilities: Vec<&'a str>,
panic!("Cutoff must be greater than 0.0 and lower than 1.0"); n: usize,
} cutoff: f32)
let mut res: Vec<(f32, &str)> = Vec::new(); -> Vec<&'a str> {
let mut matcher = SequenceMatcher::new("", word); if !(0.0 <= cutoff && cutoff <= 1.0) {
for i in &possibilities { panic!("Cutoff must be greater than 0.0 and lower than 1.0");
matcher.set_first_seq(i); }
let ratio = matcher.ratio(); let mut res: Vec<(f32, &str)> = Vec::new();
if ratio >= cutoff { let mut matcher = SequenceMatcher::new("", word);
res.push((ratio, i)); for i in &possibilities {
} matcher.set_first_seq(i);
} let ratio = matcher.ratio();
res.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); if ratio >= cutoff {
res.truncate(n); res.push((ratio, i));
res.iter().map(|x| x.1).collect() }
}
res.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
res.truncate(n);
res.iter().map(|x| x.1).collect()
} }
pub fn unified_diff<T: Sequence>(first_sequence: &T, second_sequence: &T, from_file: &str, to_file: &str, pub fn unified_diff<T: Sequence>(first_sequence: &T,
from_file_date: &str, to_file_date: &str, n: usize) -> Vec<String> { second_sequence: &T,
let mut res = Vec::new(); from_file: &str,
let lineterm = '\n'; to_file: &str,
let mut started = false; from_file_date: &str,
let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); to_file_date: &str,
for group in &matcher.get_grouped_opcodes(n) { n: usize)
if !started { -> Vec<String> {
started = true; let mut res = Vec::new();
let from_date = format!("\t{}", from_file_date); let lineterm = '\n';
let to_date = format!("\t{}", to_file_date); let mut started = false;
res.push( format!("--- {}{}{}", from_file, from_date, lineterm) ); let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
res.push( format!("+++ {}{}{}", to_file, to_date, lineterm) ); for group in &matcher.get_grouped_opcodes(n) {
} if !started {
let (first, last) = (group.first().unwrap(), group.last().unwrap()); started = true;
let file1_range = format_range_unified(first.first_start, last.first_end); let from_date = format!("\t{}", from_file_date);
let file2_range = format_range_unified(first.second_start, last.second_end); let to_date = format!("\t{}", to_file_date);
res.push( format!("@@ -{} +{} @@{}", file1_range, file2_range, lineterm) ); res.push(format!("--- {}{}{}", from_file, from_date, lineterm));
for code in group { res.push(format!("+++ {}{}{}", to_file, to_date, lineterm));
if code.tag == "equal" { }
for i in code.first_start..code.first_end { let (first, last) = (group.first().unwrap(), group.last().unwrap());
res.push( format!(" {}", first_sequence.at_index(i).unwrap()) ); let file1_range = format_range_unified(first.first_start, last.first_end);
} let file2_range = format_range_unified(first.second_start, last.second_end);
continue; res.push(format!("@@ -{} +{} @@{}", file1_range, file2_range, lineterm));
} for code in group {
if code.tag == "replace" || code.tag == "delete" { if code.tag == "equal" {
for i in code.first_start..code.first_end { for i in code.first_start..code.first_end {
res.push( format!("-{}", first_sequence.at_index(i).unwrap()) ); res.push(format!(" {}", first_sequence.at_index(i).unwrap()));
} }
} continue;
if code.tag == "replace" || code.tag == "insert" { }
for i in code.second_start..code.second_end { if code.tag == "replace" || code.tag == "delete" {
res.push( format!("+{}", second_sequence.at_index(i).unwrap()) ); for i in code.first_start..code.first_end {
} res.push(format!("-{}", first_sequence.at_index(i).unwrap()));
} }
} }
} if code.tag == "replace" || code.tag == "insert" {
res for i in code.second_start..code.second_end {
res.push(format!("+{}", second_sequence.at_index(i).unwrap()));
}
}
}
}
res
} }
pub fn context_diff<T: Sequence>(first_sequence: &T, second_sequence: &T, from_file: &str, to_file: &str, pub fn context_diff<T: Sequence>(first_sequence: &T,
from_file_date: &str, to_file_date: &str, n: usize) -> Vec<String> { second_sequence: &T,
let mut res = Vec::new(); from_file: &str,
let lineterm = '\n'; to_file: &str,
let mut prefix: HashMap<String, String> = HashMap::new(); from_file_date: &str,
prefix.insert(String::from("insert"), String::from("+ ")); to_file_date: &str,
prefix.insert(String::from("delete"), String::from("- ")); n: usize)
prefix.insert(String::from("replace"), String::from("! ")); -> Vec<String> {
prefix.insert(String::from("equal"), String::from(" ")); let mut res = Vec::new();
let mut started = false; let lineterm = '\n';
let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); let mut prefix: HashMap<String, String> = HashMap::new();
for group in &matcher.get_grouped_opcodes(n) { prefix.insert(String::from("insert"), String::from("+ "));
if !started { prefix.insert(String::from("delete"), String::from("- "));
started = true; prefix.insert(String::from("replace"), String::from("! "));
let from_date = format!("\t{}", from_file_date); prefix.insert(String::from("equal"), String::from(" "));
let to_date = format!("\t{}", to_file_date); let mut started = false;
res.push( format!("*** {}{}{}", from_file, from_date, lineterm) ); let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
res.push( format!("--- {}{}{}", to_file, to_date, lineterm) ); for group in &matcher.get_grouped_opcodes(n) {
} if !started {
let (first, last) = (group.first().unwrap(), group.last().unwrap()); started = true;
res.push(format!("***************{}", lineterm)); let from_date = format!("\t{}", from_file_date);
let file1_range = format_range_context(first.first_start, last.first_end); let to_date = format!("\t{}", to_file_date);
res.push(format!("*** {} ****{}", file1_range, lineterm)); res.push(format!("*** {}{}{}", from_file, from_date, lineterm));
let mut any = false; res.push(format!("--- {}{}{}", to_file, to_date, lineterm));
for opcode in group { }
if opcode.tag == "replace" || opcode.tag == "delete" { let (first, last) = (group.first().unwrap(), group.last().unwrap());
any = true; res.push(format!("***************{}", lineterm));
break; let file1_range = format_range_context(first.first_start, last.first_end);
} res.push(format!("*** {} ****{}", file1_range, lineterm));
} let mut any = false;
if any { for opcode in group {
for opcode in group { if opcode.tag == "replace" || opcode.tag == "delete" {
if opcode.tag != "insert" { any = true;
for i in opcode.first_start..opcode.first_end { break;
res.push( format!("{}{}", prefix.get(&opcode.tag).unwrap(), first_sequence.at_index(i).unwrap()) ); }
} }
} if any {
} for opcode in group {
} if opcode.tag != "insert" {
let file2_range = format_range_context(first.second_start, last.second_end); for i in opcode.first_start..opcode.first_end {
res.push(format!("--- {} ----{}", file2_range, lineterm)); res.push(format!("{}{}",
any = false; prefix.get(&opcode.tag).unwrap(),
for opcode in group { first_sequence.at_index(i).unwrap()));
if opcode.tag == "replace" || opcode.tag == "insert" { }
any = true; }
break; }
} }
} let file2_range = format_range_context(first.second_start, last.second_end);
if any { res.push(format!("--- {} ----{}", file2_range, lineterm));
for opcode in group { any = false;
if opcode.tag != "delete" { for opcode in group {
for i in opcode.second_start..opcode.second_end { if opcode.tag == "replace" || opcode.tag == "insert" {
res.push( format!("{}{}", prefix.get(&opcode.tag).unwrap(), second_sequence.at_index(i).unwrap()) ); any = true;
} break;
} }
} }
} if any {
} for opcode in group {
res if opcode.tag != "delete" {
for i in opcode.second_start..opcode.second_end {
res.push(format!("{}{}",
prefix.get(&opcode.tag).unwrap(),
second_sequence.at_index(i).unwrap()));
}
}
}
}
}
res
} }
+313 -294
View File
@@ -4,74 +4,77 @@ use utils::calculate_ratio;
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct Match{ pub struct Match {
pub first_start: usize, pub first_start: usize,
pub second_start: usize, pub second_start: usize,
pub size: usize pub size: usize,
} }
impl Match{ impl Match {
fn new(first_start: usize, second_start: usize, size: usize) -> Match { fn new(first_start: usize, second_start: usize, size: usize) -> Match {
Match{ Match {
first_start: first_start, first_start: first_start,
second_start: second_start, second_start: second_start,
size: size size: size,
} }
} }
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Opcode{ pub struct Opcode {
pub tag: String, pub tag: String,
pub first_start: usize, pub first_start: usize,
pub first_end: usize, pub first_end: usize,
pub second_start: usize, pub second_start: usize,
pub second_end: usize pub second_end: usize,
} }
impl Opcode{ impl Opcode {
fn new(tag: String, first_start: usize, first_end: usize, second_start: usize, second_end: usize) -> Opcode { fn new(tag: String,
Opcode{ first_start: usize,
tag: tag, first_end: usize,
first_start: first_start, second_start: usize,
first_end: first_end, second_end: usize)
second_start: second_start, -> Opcode {
second_end: second_end Opcode {
} tag: tag,
} first_start: first_start,
first_end: first_end,
second_start: second_start,
second_end: second_end,
}
}
} }
pub trait Sequence: Debug { pub trait Sequence: Debug {
fn len(&self) -> usize; fn len(&self) -> usize;
fn at_index(&self, index: usize) -> Option<&str>; fn at_index(&self, index: usize) -> Option<&str>;
} }
impl Sequence for str { impl Sequence for str {
fn len(&self) -> usize { fn len(&self) -> usize {
self.len() self.len()
} }
fn at_index(&self, index: usize) -> Option<&str> { fn at_index(&self, index: usize) -> Option<&str> {
if index > self.len(){ if index > self.len() {
return None return None;
} }
unsafe{ unsafe { Some(self.slice_unchecked(index, index + 1)) }
Some(self.slice_unchecked(index, index + 1)) }
}
}
} }
impl<'a> Sequence for Vec<&'a str> { impl<'a> Sequence for Vec<&'a str> {
fn len(&self) -> usize { fn len(&self) -> usize {
self.len() self.len()
} }
fn at_index(&self, index: usize) -> Option<&str> { fn at_index(&self, index: usize) -> Option<&str> {
if index < self.len() { if index < self.len() {
return Some(self[index]) return Some(self[index]);
} }
None None
} }
} }
impl Sequence for String { impl Sequence for String {
@@ -100,262 +103,278 @@ impl<'a> Sequence for Vec<String> {
} }
} }
pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence>{ pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> {
first_sequence: &'a T, first_sequence: &'a T,
second_sequence: &'a T, second_sequence: &'a T,
matching_blocks: Option<Vec<Match>>, matching_blocks: Option<Vec<Match>>,
opcodes: Option<Vec<Opcode>>, opcodes: Option<Vec<Opcode>>,
is_junk: Option<fn(&str) -> bool>, is_junk: Option<fn(&str) -> bool>,
second_sequence_elements: HashMap<&'a str, Vec<usize>>, second_sequence_elements: HashMap<&'a str, Vec<usize>>,
second_sequence_popular: Vec<&'a str> second_sequence_popular: Vec<&'a str>,
} }
impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> {
pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> {
let mut matcher = let mut matcher = SequenceMatcher {
SequenceMatcher{ first_sequence: first_sequence,
first_sequence: first_sequence, second_sequence: second_sequence,
second_sequence: second_sequence, matching_blocks: None,
matching_blocks: None, opcodes: None,
opcodes: None, is_junk: None,
is_junk: None, second_sequence_elements: HashMap::new(),
second_sequence_elements: HashMap::new(), second_sequence_popular: Vec::new(),
second_sequence_popular: Vec::new() };
}; matcher.set_seqs(first_sequence, second_sequence);
matcher.set_seqs(first_sequence, second_sequence); matcher
matcher }
}
pub fn set_is_junk(&mut self, is_junk: Option<fn(&str) -> bool>) { pub fn set_is_junk(&mut self, is_junk: Option<fn(&str) -> bool>) {
self.is_junk = is_junk; self.is_junk = is_junk;
self.set_second_seq(self.second_sequence); self.set_second_seq(self.second_sequence);
} }
pub fn set_seqs(&mut self, first_sequence: &'a T, second_sequence: &'a T) { pub fn set_seqs(&mut self, first_sequence: &'a T, second_sequence: &'a T) {
self.set_first_seq(first_sequence); self.set_first_seq(first_sequence);
self.set_second_seq(second_sequence); self.set_second_seq(second_sequence);
} }
pub fn set_first_seq(&mut self, sequence: &'a T) { pub fn set_first_seq(&mut self, sequence: &'a T) {
self.first_sequence = sequence; self.first_sequence = sequence;
self.matching_blocks = None; self.matching_blocks = None;
self.opcodes = None; self.opcodes = None;
} }
pub fn set_second_seq(&mut self, sequence: &'a T) { pub fn set_second_seq(&mut self, sequence: &'a T) {
self.second_sequence = sequence; self.second_sequence = sequence;
self.matching_blocks = None; self.matching_blocks = None;
self.opcodes = None; self.opcodes = None;
self.chain_second_seq(); self.chain_second_seq();
} }
fn chain_second_seq(&mut self) { fn chain_second_seq(&mut self) {
let second_sequence = self.second_sequence; let second_sequence = self.second_sequence;
let mut second_sequence_elements = HashMap::new(); let mut second_sequence_elements = HashMap::new();
for i in 0..second_sequence.len() { for i in 0..second_sequence.len() {
let mut counter = second_sequence_elements.entry(second_sequence.at_index(i).unwrap()).or_insert(Vec::new()); let mut counter = second_sequence_elements.entry(second_sequence.at_index(i).unwrap())
counter.push(i); .or_insert(Vec::new());
} counter.push(i);
if self.is_junk.is_some() { }
let mut junk = Vec::new(); if self.is_junk.is_some() {
for element in second_sequence_elements.keys() { let mut junk = Vec::new();
if (self.is_junk.unwrap())(element) { for element in second_sequence_elements.keys() {
junk.push(element.clone()); if (self.is_junk.unwrap())(element) {
} junk.push(element.clone());
} }
for element in &junk { }
second_sequence_elements.remove(element); for element in &junk {
} second_sequence_elements.remove(element);
} }
let mut popular = Vec::new(); }
let len = second_sequence.len(); let mut popular = Vec::new();
if len >= 200 { let len = second_sequence.len();
let test_len = (len as f32 / 100.0).floor() as usize + 1; if len >= 200 {
for (element, indexes) in second_sequence_elements.iter() { let test_len = (len as f32 / 100.0).floor() as usize + 1;
if indexes.len() > test_len { for (element, indexes) in second_sequence_elements.iter() {
popular.push(element.clone()); if indexes.len() > test_len {
} popular.push(element.clone());
} }
for element in &popular { }
second_sequence_elements.remove(element); for element in &popular {
} second_sequence_elements.remove(element);
} }
self.second_sequence_elements = second_sequence_elements; }
self.second_sequence_popular = popular; self.second_sequence_elements = second_sequence_elements;
} self.second_sequence_popular = popular;
}
pub fn find_longest_match(&self, first_start: usize, first_end: usize, second_start: usize, second_end: usize) -> Match { pub fn find_longest_match(&self,
let first_sequence = &self.first_sequence; first_start: usize,
let second_sequence = &self.second_sequence; first_end: usize,
let second_sequence_elements = &self.second_sequence_elements; second_start: usize,
let (mut best_i, mut best_j, mut best_size) = (first_start, second_start, 0); second_end: usize)
let mut j2len: HashMap<usize, usize> = HashMap::new(); -> Match {
for i in first_start..first_end { let first_sequence = &self.first_sequence;
let mut new_j2len: HashMap<usize, usize> = HashMap::new(); let second_sequence = &self.second_sequence;
match second_sequence_elements.get(first_sequence.at_index(i).unwrap()) { let second_sequence_elements = &self.second_sequence_elements;
Some(indexes) => { let (mut best_i, mut best_j, mut best_size) = (first_start, second_start, 0);
for j in indexes { let mut j2len: HashMap<usize, usize> = HashMap::new();
let j = j.clone(); for i in first_start..first_end {
if j < second_start { let mut new_j2len: HashMap<usize, usize> = HashMap::new();
continue; match second_sequence_elements.get(first_sequence.at_index(i).unwrap()) {
}; Some(indexes) => {
if j >= second_end { for j in indexes {
break; let j = j.clone();
}; if j < second_start {
let mut size = 0; continue;
if j > 0 { };
match j2len.get(&(j-1)){ if j >= second_end {
Some(k) => { break;
size = k.clone(); };
}, let mut size = 0;
None => {} if j > 0 {
} match j2len.get(&(j - 1)) {
} Some(k) => {
size += 1; size = k.clone();
new_j2len.insert(j, size); }
if size > best_size { None => {}
best_i = i + 1 - size; }
best_j = j + 1 - size; }
best_size = size; size += 1;
} new_j2len.insert(j, size);
} if size > best_size {
}, best_i = i + 1 - size;
None => {}, best_j = j + 1 - size;
} best_size = size;
j2len = new_j2len; }
} }
for _ in 0..2 { }
while best_i > first_start && best_j > second_start && None => {}
first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j-1){ }
best_i = best_i - 1; j2len = new_j2len;
best_j = best_j - 1; }
best_size = best_size + 1; for _ in 0..2 {
} while best_i > first_start && best_j > second_start &&
while best_i + best_size < first_end && best_j + best_size < second_end && first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) {
first_sequence.at_index(best_i + best_size) == second_sequence.at_index(best_j + best_size) { best_i = best_i - 1;
best_size += 1; best_j = best_j - 1;
} best_size = best_size + 1;
} }
Match::new(best_i, best_j, best_size) while best_i + best_size < first_end && best_j + best_size < second_end &&
} first_sequence.at_index(best_i + best_size) ==
second_sequence.at_index(best_j + best_size) {
best_size += 1;
}
}
Match::new(best_i, best_j, best_size)
}
pub fn get_matching_blocks(&mut self) -> Vec<Match> { pub fn get_matching_blocks(&mut self) -> Vec<Match> {
if self.matching_blocks.as_ref().is_some(){ if self.matching_blocks.as_ref().is_some() {
return self.matching_blocks.as_ref().unwrap().clone() return self.matching_blocks.as_ref().unwrap().clone();
} }
let (first_length, second_length) = (self.first_sequence.len(), self.second_sequence.len()); let (first_length, second_length) = (self.first_sequence.len(), self.second_sequence.len());
let mut matches = Vec::new(); let mut matches = Vec::new();
let mut queue = vec![(0, first_length, 0, second_length)]; let mut queue = vec![(0, first_length, 0, second_length)];
while !queue.is_empty(){ while !queue.is_empty() {
let (first_start, first_end, second_start, second_end) = queue.pop().unwrap(); let (first_start, first_end, second_start, second_end) = queue.pop().unwrap();
let m = self.find_longest_match(first_start, first_end, second_start, second_end); let m = self.find_longest_match(first_start, first_end, second_start, second_end);
match m.size { match m.size {
0 => {}, 0 => {}
_ => { _ => {
if first_start < m.first_start && second_start < m.second_start{ if first_start < m.first_start && second_start < m.second_start {
queue.push((first_start, m.first_start, second_start, m.second_start)); queue.push((first_start, m.first_start, second_start, m.second_start));
} }
if m.first_start + m.size < first_end && m.second_start + m.size < second_end{ if m.first_start + m.size < first_end && m.second_start + m.size < second_end {
queue.push((m.first_start + m.size, first_end, m.second_start + m.size, second_end)); queue.push((m.first_start + m.size,
} first_end,
matches.push(m); m.second_start + m.size,
}, second_end));
} }
} matches.push(m);
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{ matches.sort_by(|a, b| a.cmp(b));
if first_start + size == m.first_start && second_start + size == m.second_start{ let (mut first_start, mut second_start, mut size) = (0, 0, 0);
size += m.size let mut non_adjacent = Vec::new();
} for m in &matches {
else { if first_start + size == m.first_start && second_start + size == m.second_start {
if size != 0{ size += m.size
non_adjacent.push(Match::new(first_start, second_start, size)); } else {
} if size != 0 {
first_start = m.first_start; non_adjacent.push(Match::new(first_start, second_start, size));
second_start = m.second_start; }
size = m.size; first_start = m.first_start;
} second_start = m.second_start;
} size = m.size;
if size != 0{ }
non_adjacent.push(Match::new(first_start, second_start, size)); }
} if size != 0 {
non_adjacent.push(Match::new(first_length, second_length, 0)); non_adjacent.push(Match::new(first_start, second_start, size));
self.matching_blocks = Some(non_adjacent); }
self.matching_blocks.as_ref().unwrap().clone() non_adjacent.push(Match::new(first_length, second_length, 0));
} self.matching_blocks = Some(non_adjacent);
self.matching_blocks.as_ref().unwrap().clone()
}
pub fn get_opcodes(&mut self) -> Vec<Opcode>{ pub fn get_opcodes(&mut self) -> Vec<Opcode> {
if self.opcodes.as_ref().is_some(){ if self.opcodes.as_ref().is_some() {
return self.opcodes.as_ref().unwrap().clone() return self.opcodes.as_ref().unwrap().clone();
} }
let mut opcodes = Vec::new(); let mut opcodes = Vec::new();
let (mut i, mut j) = (0, 0); let (mut i, mut j) = (0, 0);
for m in self.get_matching_blocks(){ for m in self.get_matching_blocks() {
let mut tag = String::new(); let mut tag = String::new();
if i < m.first_start && j < m.second_start{ if i < m.first_start && j < m.second_start {
tag = String::from("replace"); tag = String::from("replace");
} } else if i < m.first_start {
else if i < m.first_start{ tag = String::from("delete");
tag = String::from("delete"); } else if j < m.second_start {
} tag = String::from("insert");
else if j < m.second_start{ }
tag = String::from("insert"); if !tag.is_empty() {
} opcodes.push(Opcode::new(tag, i, m.first_start, j, m.second_start));
if !tag.is_empty(){ }
opcodes.push( Opcode::new(tag, i, m.first_start, j, m.second_start) ); i = m.first_start + m.size;
} j = m.second_start + m.size;
i = m.first_start + m.size; if m.size != 0 {
j = m.second_start + m.size; opcodes.push(
if m.size != 0{ Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j)
opcodes.push( Opcode::new(String::from("equal"), m.first_start, i, m.second_start, j) ); );
} }
} }
self.opcodes = Some(opcodes); self.opcodes = Some(opcodes);
return self.opcodes.as_ref().unwrap().clone() return self.opcodes.as_ref().unwrap().clone();
} }
pub fn get_grouped_opcodes(&mut self, n: usize) -> Vec<Vec<Opcode>> { pub fn get_grouped_opcodes(&mut self, n: usize) -> Vec<Vec<Opcode>> {
let mut res = Vec::new(); let mut res = Vec::new();
let mut codes = self.get_opcodes(); let mut codes = self.get_opcodes();
if codes.is_empty() { if codes.is_empty() {
codes.push(Opcode::new("equal".to_string(), 0, 1, 0, 1)); codes.push(Opcode::new("equal".to_string(), 0, 1, 0, 1));
} }
if codes.first().unwrap().tag == "equal" { if codes.first().unwrap().tag == "equal" {
let mut opcode = codes.first_mut().unwrap(); let mut opcode = codes.first_mut().unwrap();
opcode.first_start = max(opcode.first_start, opcode.first_end - n); opcode.first_start = max(opcode.first_start, opcode.first_end - n);
opcode.second_start = max(opcode.second_start, opcode.second_end - n); opcode.second_start = max(opcode.second_start, opcode.second_end - n);
} }
if codes.last().unwrap().tag == "equal" { if codes.last().unwrap().tag == "equal" {
let mut opcode = codes.last_mut().unwrap(); let mut opcode = codes.last_mut().unwrap();
opcode.first_end = min(opcode.first_start + n, opcode.first_end); opcode.first_end = min(opcode.first_start + n, opcode.first_end);
opcode.second_end = min(opcode.second_start + n, opcode.second_end); opcode.second_end = min(opcode.second_start + n, opcode.second_end);
} }
let nn = n + n; let nn = n + n;
let mut group = Vec::new(); let mut group = Vec::new();
for code in &codes { for code in &codes {
let (mut first_start, mut second_start) = (code.first_start, code.second_start); let (mut first_start, mut second_start) = (code.first_start, code.second_start);
if code.tag == "equal" && code.first_end-code.first_start > nn { if code.tag == "equal" && code.first_end - code.first_start > nn {
group.push( Opcode::new(code.tag.clone(), code.first_start, min(code.first_end, code.first_start+n), group.push(Opcode::new(code.tag.clone(),
code.second_start, min(code.second_end, code.second_start+n)) ); code.first_start,
res.push(group.clone()); min(code.first_end, code.first_start + n),
group.clear(); code.second_start,
first_start = max(first_start, code.first_end-n); min(code.second_end, code.second_start + n)));
second_start = max(second_start, code.second_end - n); res.push(group.clone());
} group.clear();
group.push( Opcode::new(code.tag.clone(), first_start, code.first_end, second_start, code.second_end) ); first_start = max(first_start, code.first_end - n);
} second_start = max(second_start, code.second_end - n);
if !group.is_empty() && !(group.len()==1 && group.first().unwrap().tag == "equal") { }
res.push(group.clone()); group.push(Opcode::new(code.tag.clone(),
} first_start,
res code.first_end,
} second_start,
code.second_end));
}
if !group.is_empty() && !(group.len() == 1 && group.first().unwrap().tag == "equal") {
res.push(group.clone());
}
res
}
pub fn ratio(&mut self) -> f32{ pub fn ratio(&mut self) -> f32 {
let matches = self.get_matching_blocks().iter().fold(0, |res, &m| res + m.size); let matches = self.get_matching_blocks().iter().fold(0, |res, &m| res + m.size);
calculate_ratio(matches, self.first_sequence.len() + self.second_sequence.len()) calculate_ratio(matches,
} self.first_sequence.len() + self.second_sequence.len())
}
} }
+35 -35
View File
@@ -1,51 +1,51 @@
pub fn calculate_ratio(matches: usize, length: usize) -> f32{ pub fn calculate_ratio(matches: usize, length: usize) -> f32 {
if length != 0{ if length != 0 {
return 2.0 * matches as f32 / length as f32 return 2.0 * matches as f32 / length as f32;
} }
return 1.0 return 1.0;
} }
pub fn str_with_similar_chars(c: char, length: usize) -> String { pub fn str_with_similar_chars(c: char, length: usize) -> String {
let mut s = String::new(); let mut s = String::new();
for _ in 0..length{ for _ in 0..length {
s.push_str(&c.to_string()); s.push_str(&c.to_string());
} }
s s
} }
pub fn count_leading(line: &str, c: char) -> usize { pub fn count_leading(line: &str, c: char) -> usize {
let (mut i, n) = (0, line.len()); let (mut i, n) = (0, line.len());
let line: Vec<char> = line.chars().collect(); let line: Vec<char> = line.chars().collect();
while (i < n) && line[i] == c { while (i < n) && line[i] == c {
i += 1; i += 1;
} }
return i return i;
} }
pub fn format_range_unified(start:usize, end: usize) -> String { pub fn format_range_unified(start: usize, end: usize) -> String {
let mut beginning = start + 1; let mut beginning = start + 1;
let length = end - start; let length = end - start;
if length == 1 { if length == 1 {
return beginning.to_string() return beginning.to_string();
} }
if length == 0 { if length == 0 {
beginning -= 1; beginning -= 1;
} }
format!("{},{}", beginning, length) format!("{},{}", beginning, length)
} }
pub fn format_range_context(start: usize, end: usize) -> String { pub fn format_range_context(start: usize, end: usize) -> String {
let mut beginning = start + 1; let mut beginning = start + 1;
let length = end - start; let length = end - start;
if length == 0 { if length == 0 {
beginning -= 1 beginning -= 1
} }
if length <= 1 { if length <= 1 {
return beginning.to_string() return beginning.to_string();
} }
format!("{},{}", beginning, beginning + length - 1) format!("{},{}", beginning, beginning + length - 1)
} }
+120 -103
View File
@@ -4,145 +4,162 @@ use difflib::sequencematcher::{SequenceMatcher, Match, Opcode};
use difflib::differ::Differ; use difflib::differ::Differ;
#[test] #[test]
fn test_longest_match() fn test_longest_match() {
{ let matcher = SequenceMatcher::new(" abcd", "abcd abcd");
let matcher = SequenceMatcher::new(" abcd", "abcd abcd"); let m = matcher.find_longest_match(0, 5, 0, 9);
let m = matcher.find_longest_match(0, 5, 0, 9); assert_eq!(m.first_start, 0);
assert_eq!(m.first_start, 0); assert_eq!(m.second_start, 4);
assert_eq!(m.second_start, 4); assert_eq!(m.size, 5);
assert_eq!(m.size, 5);
} }
#[test] #[test]
fn test_all_matches() fn test_all_matches() {
{ let mut matcher = SequenceMatcher::new("abxcd", "abcd");
let mut matcher = SequenceMatcher::new("abxcd", "abcd"); let result = matcher.get_matching_blocks();
let result = matcher.get_matching_blocks(); let mut expected_result = Vec::new();
let mut expected_result = Vec::new(); expected_result.push(Match {
expected_result.push(Match{ first_start: 0,
first_start: 0, second_start: 0,
second_start: 0, size: 2,
size: 2 });
}); expected_result.push(Match {
expected_result.push(Match{ first_start: 3,
first_start: 3, second_start: 2,
second_start: 2, size: 2,
size: 2 });
}); expected_result.push(Match {
expected_result.push(Match{ first_start: 5,
first_start: 5, second_start: 4,
second_start: 4, size: 0,
size: 0 });
}); assert_eq!(result, expected_result);
assert_eq!(result, expected_result);
} }
#[test] #[test]
fn test_get_opcodes() { fn test_get_opcodes() {
let mut matcher = SequenceMatcher::new("qabxcd", "abycdf"); let mut matcher = SequenceMatcher::new("qabxcd", "abycdf");
let result = matcher.get_opcodes(); let result = matcher.get_opcodes();
let mut expected_result = Vec::new(); let mut expected_result = Vec::new();
expected_result.push(Opcode{ expected_result.push(Opcode {
tag: "delete".to_string(), tag: "delete".to_string(),
first_start: 0, first_start: 0,
first_end: 1, first_end: 1,
second_start: 0, second_start: 0,
second_end: 0 second_end: 0,
}); });
expected_result.push(Opcode{ expected_result.push(Opcode {
tag: "equal".to_string(), tag: "equal".to_string(),
first_start: 1, first_start: 1,
first_end: 3, first_end: 3,
second_start: 0, second_start: 0,
second_end: 2 second_end: 2,
}); });
expected_result.push(Opcode{ expected_result.push(Opcode {
tag: "replace".to_string(), tag: "replace".to_string(),
first_start: 3, first_start: 3,
first_end: 4, first_end: 4,
second_start: 2, second_start: 2,
second_end: 3 second_end: 3,
}); });
expected_result.push(Opcode{ expected_result.push(Opcode {
tag: "equal".to_string(), tag: "equal".to_string(),
first_start: 4, first_start: 4,
first_end: 6, first_end: 6,
second_start: 3, second_start: 3,
second_end: 5 second_end: 5,
}); });
expected_result.push(Opcode{ expected_result.push(Opcode {
tag: "insert".to_string(), tag: "insert".to_string(),
first_start: 6, first_start: 6,
first_end: 6, first_end: 6,
second_start: 5, second_start: 5,
second_end: 6 second_end: 6,
}); });
assert_eq!(result, expected_result); assert_eq!(result, expected_result);
} }
#[test] #[test]
fn test_ratio() { fn test_ratio() {
let mut matcher = SequenceMatcher::new("abcd", "bcde"); let mut matcher = SequenceMatcher::new("abcd", "bcde");
assert_eq!(matcher.ratio(), 0.75); assert_eq!(matcher.ratio(), 0.75);
} }
#[test] #[test]
fn test_get_close_matches() { fn test_get_close_matches() {
let words = vec!["ape", "apple", "peach", "puppy"]; let words = vec!["ape", "apple", "peach", "puppy"];
let result = difflib::get_close_matches("appel", words, 3, 0.6); let result = difflib::get_close_matches("appel", words, 3, 0.6);
assert_eq!(result, vec!["apple", "ape"]); assert_eq!(result, vec!["apple", "ape"]);
} }
#[test] #[test]
fn test_differ_compare() { fn test_differ_compare() {
let first_text = vec!["one\n", "two\n", "three\n"]; let first_text = vec!["one\n", "two\n", "three\n"];
let second_text = vec!["ore\n", "tree\n", "emu\n"]; let second_text = vec!["ore\n", "tree\n", "emu\n"];
let differ = Differ::new(); let differ = Differ::new();
let result = differ.compare(&first_text, &second_text).join(""); 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"); assert_eq!(result,
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n");
} }
fn is_junk_char(ch: &str) -> bool { fn is_junk_char(ch: &str) -> bool {
if ch == " " || ch == "\t" { if ch == " " || ch == "\t" {
return true return true;
} }
false false
} }
#[test] #[test]
fn test_differ_compare_with_func() { fn test_differ_compare_with_func() {
let first_text = vec!["one\n", "two\n", "three\n"]; let first_text = vec!["one\n", "two\n", "three\n"];
let second_text = vec!["ore\n", "tree\n", "emu\n"]; let second_text = vec!["ore\n", "tree\n", "emu\n"];
let mut differ = Differ::new(); let mut differ = Differ::new();
differ.char_junk = Some(is_junk_char); differ.char_junk = Some(is_junk_char);
let result = differ.compare(&first_text, &second_text).join(""); 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"); assert_eq!(result,
"- one\n? ^\n+ ore\n? ^\n- two\n- three\n? -\n+ tree\n+ emu\n");
} }
#[test] #[test]
fn test_differ_restore() { fn test_differ_restore() {
let first_text = vec!["one\n", " two\n", "three\n"]; let first_text = vec!["one\n", " two\n", "three\n"];
let second_text = vec!["ore\n", "tree\n", "emu\n"]; let second_text = vec!["ore\n", "tree\n", "emu\n"];
let differ = Differ::new(); let differ = Differ::new();
let diff = differ.compare(&first_text, &second_text); let diff = differ.compare(&first_text, &second_text);
assert_eq!(first_text, Differ::restore(&diff, 1)); assert_eq!(first_text, Differ::restore(&diff, 1));
assert_eq!(second_text, Differ::restore(&diff, 2)); assert_eq!(second_text, Differ::restore(&diff, 2));
} }
#[test] #[test]
fn test_unified_diff() { fn test_unified_diff() {
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let result = difflib::unified_diff(&first_text, &second_text, "Original", "Current", let result = difflib::unified_diff(&first_text,
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3).join(""); &second_text,
assert_eq!(result, "--- Original\t2005-01-26 23:30:50\n+++ Current\t2010-04-02 10:20:52\n@@ -1,4 +1,4 @@\n+zero one-two-three+tree four"); "Original",
"Current",
"2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3)
.join("");
assert_eq!(result,
"--- Original\t2005-01-26 23:30:50\n+++ Current\t2010-04-02 10:20:52\n@@ -1,4 \
+1,4 @@\n+zero one-two-three+tree four");
} }
#[test] #[test]
fn test_context_diff() { fn test_context_diff() {
let first_text = "one two three four".split(" ").collect::<Vec<&str>>(); let first_text = "one two three four".split(" ").collect::<Vec<&str>>();
let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>(); let second_text = "zero one tree four".split(" ").collect::<Vec<&str>>();
let result = difflib::context_diff(&first_text, &second_text, "Original", "Current", let result = difflib::context_diff(&first_text,
"2005-01-26 23:30:50", "2010-04-02 10:20:52", 3).join(""); &second_text,
assert_eq!(result, "*** Original\t2005-01-26 23:30:50\n--- Current\t2010-04-02 10:20:52\n***************\n*** 1,4 ****\n one! two! three four--- 1,4 ----\n+ zero one! tree four"); "Original",
"Current",
"2005-01-26 23:30:50",
"2010-04-02 10:20:52",
3)
.join("");
assert_eq!(result,
"*** Original\t2005-01-26 23:30:50\n--- Current\t2010-04-02 \
10:20:52\n***************\n*** 1,4 ****\n one! two! three four--- 1,4 ----\n+ \
zero one! tree four");
} }