diff --git a/examples/example.rs b/examples/example.rs index 289a6dd..c4a8aff 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -5,46 +5,46 @@ use difflib::sequencematcher::SequenceMatcher; fn main() { - // unified_diff - let first_text = "one two three four".split(" ").collect::>(); - let second_text = "zero one tree four".split(" ").collect::>(); - let diff = difflib::unified_diff(&first_text, &second_text, "Original", "Current", - "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3, ""); - for line in &diff { - println!("{:?}", line); - } + // unified_diff + let first_text = "one two three four".split(" ").collect::>(); + let second_text = "zero one tree four".split(" ").collect::>(); + let diff = difflib::unified_diff(&first_text, &second_text, "Original", "Current", + "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3, ""); + for line in &diff { + println!("{:?}", line); + } - //context_diff - let diff = difflib::context_diff(&first_text, &second_text, "Original", "Current", - "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3, ""); - for line in &diff { - println!("{:?}", line); - } + //context_diff + let diff = difflib::context_diff(&first_text, &second_text, "Original", "Current", + "2005-01-26 23:30:50", "2010-04-02 10:20:52", 3, ""); + for line in &diff { + println!("{:?}", line); + } //get_close_matches - let words = vec!["ape", "apple", "peach", "puppy"]; - let result = difflib::get_close_matches("appel", words, 3, 0.6); - println!("{:?}", result); + let words = vec!["ape", "apple", "peach", "puppy"]; + let result = difflib::get_close_matches("appel", words, 3, 0.6); + println!("{:?}", result); - //Differ examples - let differ = Differ::new(); - let diff = differ.compare(&first_text, &second_text); - for line in &diff { - println!("{:?}", line); - } + //Differ examples + let differ = Differ::new(); + let diff = differ.compare(&first_text, &second_text); + for line in &diff { + println!("{:?}", line); + } - //SequenceMatcher examples - let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); - let m = matcher.find_longest_match(0, 18, 0, 18); - println!("{:?}", m); - let all_matches = matcher.get_matching_blocks(); - println!("{:?}", all_matches); - let opcode = matcher.get_opcodes(); - println!("{:?}", opcode); - let grouped_opcodes = matcher.get_grouped_opcodes(2); - println!("{:?}", grouped_opcodes); - let ratio = matcher.ratio(); - println!("{:?}", ratio); - matcher.set_seqs("aaaaa", "aaaab"); - println!("{:?}", matcher.ratio()); + //SequenceMatcher examples + let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); + let m = matcher.find_longest_match(0, 18, 0, 18); + println!("{:?}", m); + let all_matches = matcher.get_matching_blocks(); + println!("{:?}", all_matches); + let opcode = matcher.get_opcodes(); + println!("{:?}", opcode); + let grouped_opcodes = matcher.get_grouped_opcodes(2); + println!("{:?}", grouped_opcodes); + let ratio = matcher.ratio(); + println!("{:?}", ratio); + matcher.set_seqs("aaaaa", "aaaab"); + println!("{:?}", matcher.ratio()); } diff --git a/src/differ.rs b/src/differ.rs index b051168..8721d76 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -18,204 +18,204 @@ impl Differ{ pub fn compare(&self, first_sequence: &T, second_sequence: &T) -> Vec { let mut matcher = SequenceMatcher::new(first_sequence, second_sequence); - matcher.set_is_junk(self.line_junk); + matcher.set_is_junk(self.line_junk); let mut res = Vec::new(); for opcode in matcher.get_opcodes(){ - let mut gen = Vec::new(); + let mut gen = Vec::new(); match opcode.tag.as_ref() { - "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) }, - _ => {} + "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); } - for i in gen { - res.push(i); - } } res } fn dump(&self, tag: &str, sequence: &T, start: usize, end: usize) -> Vec { let mut res = Vec::new(); - for i in start..end { - match sequence.at_index(i) { - Some(s) => res.push(format!("{} {}", tag, s)), - None => {}, - } - } + for i in start..end { + match sequence.at_index(i) { + Some(s) => res.push(format!("{} {}", tag, s)), + None => {}, + } + } res } - fn plain_replace(&self, first_sequence: &T, first_start: usize, first_end: usize, - second_sequence: &T, second_start: usize, second_end: usize) -> Vec { - if !(first_start < first_end && second_start < second_end){ - return Vec::new(); - } - let mut first = Vec::new(); - let mut second = Vec::new(); - if second_end - second_start < first_end - first_start{ - first = self.dump("+", second_sequence, second_start, second_end); - second = self.dump("-", first_sequence, first_start, first_end); - } else { - 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 plain_replace(&self, first_sequence: &T, first_start: usize, first_end: usize, + second_sequence: &T, second_start: usize, second_end: usize) -> Vec { + if !(first_start < first_end && second_start < second_end){ + return Vec::new(); + } + let mut first = Vec::new(); + let mut second = Vec::new(); + if second_end - second_start < first_end - first_start{ + first = self.dump("+", second_sequence, second_start, second_end); + second = self.dump("-", first_sequence, first_start, first_end); + } else { + 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(&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 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) = ("", ""); - let mut eqi: Option = None; - let mut eqj: Option = None; - for j in second_start..second_end{ - second_sequence_str = second_sequence.at_index(j).unwrap(); - for i in first_start..first_end{ - first_sequence_str = first_sequence.at_index(i).unwrap(); - if first_sequence_str == second_sequence_str{ - if eqi.is_none(){ - eqi = Some(i); - eqj = Some(j); - } - continue; - } - let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); - cruncher.set_is_junk(self.char_junk); - if cruncher.ratio() > best_ratio{ - best_ratio = cruncher.ratio(); - best_i = i; - best_j = j; - } - } - } - 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 - } - best_i = eqi.unwrap(); - best_j = eqj.unwrap(); - best_ratio = 1.0; - } else { - eqi = None; - } - res.extend(self.fancy_helper(first_sequence, first_start, best_i, second_sequence, second_start, best_j).iter().cloned()); - 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); - cruncher.set_is_junk(self.char_junk); - 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() { - "replace" => { - first_tag.push_str(&str_with_similar_chars('^', first_length)); - 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_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 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) = ("", ""); + let mut eqi: Option = None; + let mut eqj: Option = None; + for j in second_start..second_end{ + second_sequence_str = second_sequence.at_index(j).unwrap(); + for i in first_start..first_end{ + first_sequence_str = first_sequence.at_index(i).unwrap(); + if first_sequence_str == second_sequence_str{ + if eqi.is_none(){ + eqi = Some(i); + eqj = Some(j); + } + continue; + } + let mut cruncher = SequenceMatcher::new(first_sequence_str, second_sequence_str); + cruncher.set_is_junk(self.char_junk); + if cruncher.ratio() > best_ratio{ + best_ratio = cruncher.ratio(); + best_i = i; + best_j = j; + } + } + } + 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 + } + best_i = eqi.unwrap(); + best_j = eqj.unwrap(); + best_ratio = 1.0; + } else { + eqi = None; + } + res.extend(self.fancy_helper(first_sequence, first_start, best_i, second_sequence, second_start, best_j).iter().cloned()); + 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); + cruncher.set_is_junk(self.char_junk); + 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() { + "replace" => { + first_tag.push_str(&str_with_similar_chars('^', first_length)); + 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(&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(); - if first_start < first_end{ - if second_start < second_end{ - res = self.fancy_replace(first_sequence, 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 fancy_helper(&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(); + if first_start < first_end{ + if second_start < second_end{ + res = self.fancy_replace(first_sequence, 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 { - 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 != ""{ - s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags)); - res.push(s); - } - s = String::from(format!("+ {}", second_line)); - res.push(s); - if second_tags != "" { - s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), second_tags)); - res.push(s); - } - res - } + 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 != ""{ + s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), first_tags)); + res.push(s); + } + s = String::from(format!("+ {}", second_line)); + res.push(s); + if second_tags != "" { + s = String::from(format!("? {}{}\n", str_with_similar_chars('\t', common), second_tags)); + res.push(s); + } + res + } - pub fn restore(delta: &Vec, which: usize) -> Vec { - if !(which == 1 || which == 2) { - panic!("Second parameter must be 1 or 2"); - } - let mut res = Vec::new(); - let mut tag = String::new(); - 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, which: usize) -> Vec { + if !(which == 1 || which == 2) { + panic!("Second parameter must be 1 or 2"); + } + let mut res = Vec::new(); + let mut tag = String::new(); + 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] 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"); + 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"]); + 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 e6e6ee1..1030890 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -42,49 +42,49 @@ impl Opcode{ } pub trait Sequence: Debug { - fn len(&self) -> usize; - fn at_index(&self, index: usize) -> Option<&str>; + fn len(&self) -> usize; + fn at_index(&self, index: usize) -> Option<&str>; } impl Sequence for str { - fn len(&self) -> usize { - self.len() - } + fn len(&self) -> usize { + self.len() + } - fn at_index(&self, index: usize) -> Option<&str> { - if index > self.len(){ - return None - } - unsafe{ - Some(self.slice_unchecked(index, index + 1)) - } - } + fn at_index(&self, index: usize) -> Option<&str> { + if index > self.len(){ + return None + } + unsafe{ + Some(self.slice_unchecked(index, index + 1)) + } + } } impl<'a> Sequence for Vec<&'a str> { - fn len(&self) -> usize { - self.len() - } + fn len(&self) -> usize { + self.len() + } - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() && index >= 0 { - return Some(self[index]) - } - None - } + fn at_index(&self, index: usize) -> Option<&str> { + if index < self.len() && index >= 0 { + return Some(self[index]) + } + None + } } impl<'a> Sequence for [&'a str] { fn len(&self) -> usize { - self.len() - } + self.len() + } - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() && index >= 0 { - return Some(self[index]) - } - None - } + fn at_index(&self, index: usize) -> Option<&str> { + if index < self.len() && index >= 0 { + return Some(self[index]) + } + None + } } pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence>{ @@ -134,43 +134,43 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ self.second_sequence = sequence; self.matching_blocks = None; self.opcodes = None; - self.chain_second_seq(); + self.chain_second_seq(); } - fn chain_second_seq(&mut self) { - let second_sequence = self.second_sequence; - let mut second_sequence_elements = HashMap::new(); - for i in 0..second_sequence.len() { - let mut counter = second_sequence_elements.entry(second_sequence.at_index(i).unwrap()).or_insert(Vec::new()); - counter.push(i); - } - if self.is_junk.is_some() { - let mut junk = Vec::new(); - for element in second_sequence_elements.keys() { - if (self.is_junk.unwrap())(element) { - junk.push(element.clone()); - } - } - for element in &junk { - second_sequence_elements.remove(element); - } - } - let mut popular = Vec::new(); - let len = second_sequence.len(); - if len >= 200 { - let test_len = (len as f32 / 100.0).floor() as usize + 1; - for (element, indexes) in second_sequence_elements.iter() { - if indexes.len() > test_len { - popular.push(element.clone()); - } - } - for element in &popular { - second_sequence_elements.remove(element); - } - } - self.second_sequence_elements = second_sequence_elements; - self.second_sequence_popular = popular; - } + fn chain_second_seq(&mut self) { + let second_sequence = self.second_sequence; + let mut second_sequence_elements = HashMap::new(); + for i in 0..second_sequence.len() { + let mut counter = second_sequence_elements.entry(second_sequence.at_index(i).unwrap()).or_insert(Vec::new()); + counter.push(i); + } + if self.is_junk.is_some() { + let mut junk = Vec::new(); + for element in second_sequence_elements.keys() { + if (self.is_junk.unwrap())(element) { + junk.push(element.clone()); + } + } + for element in &junk { + second_sequence_elements.remove(element); + } + } + let mut popular = Vec::new(); + let len = second_sequence.len(); + if len >= 200 { + let test_len = (len as f32 / 100.0).floor() as usize + 1; + for (element, indexes) in second_sequence_elements.iter() { + if indexes.len() > test_len { + popular.push(element.clone()); + } + } + for element in &popular { + second_sequence_elements.remove(element); + } + } + 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 { let first_sequence = &self.first_sequence; @@ -181,34 +181,34 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ for i in first_start..first_end { let mut new_j2len: HashMap = HashMap::new(); match second_sequence_elements.get(first_sequence.at_index(i).unwrap()) { - Some(indexes) => { - for j in indexes { - let j = j.clone(); - if j < second_start { - continue; - }; - if j >= second_end { - break; - }; - let mut size = 0; - if j > 0 { - match j2len.get(&(j-1)){ - Some(k) => { - size = k.clone(); - }, - None => {} - } - } - size += 1; - new_j2len.insert(j, size); - if size > best_size { - best_i = i + 1 - size; - best_j = j + 1 - size; - best_size = size; - } - } - }, - None => {}, + Some(indexes) => { + for j in indexes { + let j = j.clone(); + if j < second_start { + continue; + }; + if j >= second_end { + break; + }; + let mut size = 0; + if j > 0 { + match j2len.get(&(j-1)){ + Some(k) => { + size = k.clone(); + }, + None => {} + } + } + size += 1; + new_j2len.insert(j, size); + if size > best_size { + best_i = i + 1 - size; + best_j = j + 1 - size; + best_size = size; + } + } + }, + None => {}, } j2len = new_j2len; } @@ -223,13 +223,13 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ 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) + } + Match::new(best_i, best_j, best_size) } pub fn get_matching_blocks(&mut self) -> Vec { 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 mut matches = Vec::new(); @@ -239,15 +239,15 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ let m = self.find_longest_match(first_start, first_end, second_start, second_end); match m.size { 0 => {}, - _ => { - if 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{ - queue.push((m.first_start + m.size, first_end, m.second_start + m.size, second_end)); - } - matches.push(m); - }, + _ => { + if 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{ + queue.push((m.first_start + m.size, first_end, m.second_start + m.size, second_end)); + } + matches.push(m); + }, } } matches.sort_by(|a, b| a.cmp(b)); @@ -258,12 +258,12 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ size += m.size } else { - if size != 0{ - non_adjacent.push(Match::new(first_start, second_start, 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)); + } + first_start = m.first_start; + second_start = m.second_start; + size = m.size; } } if size != 0{ @@ -276,7 +276,7 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T>{ pub fn get_opcodes(&mut self) -> Vec{ 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 i, mut j) = (0, 0); diff --git a/src/utils.rs b/src/utils.rs index 535aee3..4286ac5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,45 +7,45 @@ pub fn calculate_ratio(matches: usize, length: usize) -> f32{ pub fn str_with_similar_chars(c: char, length: usize) -> String { - let mut s = String::new(); - for _ in 0..length{ - s.push_str(&c.to_string()); - } - s + let mut s = String::new(); + for _ in 0..length{ + s.push_str(&c.to_string()); + } + s } pub fn count_leading(line: &str, c: char) -> usize { - let (mut i, n) = (0, line.len()); - let line: Vec = line.chars().collect(); - while (i < n) && line[i] == c { - i += 1; - } - return i + let (mut i, n) = (0, line.len()); + let line: Vec = line.chars().collect(); + while (i < n) && line[i] == c { + i += 1; + } + return i } pub fn format_range_unified(start:usize, end: usize) -> String { - let mut beginning = start + 1; - let length = end - start; - if length == 1 { - return beginning.to_string() - } - if length == 0 { - beginning -= 1; - } - format!("{},{}", beginning, length) + let mut beginning = start + 1; + let length = end - start; + if length == 1 { + return beginning.to_string() + } + if length == 0 { + beginning -= 1; + } + format!("{},{}", beginning, length) } pub fn format_range_context(start: usize, end: usize) -> String { - let mut beginning = start + 1; - let length = end - start; - if length == 0 { - beginning -= 1 - } - if length <= 1 { - return beginning.to_string() - } - format!("{},{}", beginning, beginning + length - 1) + let mut beginning = start + 1; + let length = end - start; + if length == 0 { + beginning -= 1 + } + if length <= 1 { + return beginning.to_string() + } + format!("{},{}", beginning, beginning + length - 1) } \ No newline at end of file diff --git a/tests/test.rs b/tests/test.rs index fb70e69..7904eb6 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -7,10 +7,10 @@ use difflib::differ::Differ; fn test_longest_match() { let matcher = SequenceMatcher::new(" abcd", "abcd abcd"); - let m = matcher.find_longest_match(0, 5, 0, 9); - assert_eq!(m.first_start, 0); - assert_eq!(m.second_start, 4); - assert_eq!(m.size, 5); + let m = matcher.find_longest_match(0, 5, 0, 9); + assert_eq!(m.first_start, 0); + assert_eq!(m.second_start, 4); + assert_eq!(m.size, 5); } #[test] @@ -18,131 +18,131 @@ fn test_all_matches() { let mut matcher = SequenceMatcher::new("abxcd", "abcd"); let result = matcher.get_matching_blocks(); - let mut expected_result = Vec::new(); - expected_result.push(Match{ - first_start: 0, - second_start: 0, - size: 2 - }); - expected_result.push(Match{ - first_start: 3, - second_start: 2, - size: 2 - }); - expected_result.push(Match{ - first_start: 5, - second_start: 4, - size: 0 - }); - assert_eq!(result, expected_result); + let mut expected_result = Vec::new(); + expected_result.push(Match{ + first_start: 0, + second_start: 0, + size: 2 + }); + expected_result.push(Match{ + first_start: 3, + second_start: 2, + size: 2 + }); + expected_result.push(Match{ + first_start: 5, + second_start: 4, + 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); + 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); + 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"]); + 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"); + 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"); } fn is_junk_char(ch: &str) -> bool { - if ch == " " || ch == "\t" { - return true - } - false + if ch == " " || ch == "\t" { + return true + } + false } #[test] fn test_differ_compare_with_func() { - let first_text = vec!["one\n", "two\n", "three\n"]; - let second_text = vec!["ore\n", "tree\n", "emu\n"]; - let mut differ = Differ::new(); - differ.char_junk = Some(is_junk_char); - 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"); + let first_text = vec!["one\n", "two\n", "three\n"]; + let second_text = vec!["ore\n", "tree\n", "emu\n"]; + let mut differ = Differ::new(); + differ.char_junk = Some(is_junk_char); + 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"); } #[test] fn test_differ_restore() { - 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 diff = differ.compare(&first_text, &second_text); - assert_eq!(first_text, Differ::restore(&diff, 1)); - assert_eq!(second_text, Differ::restore(&diff, 2)); + 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 diff = differ.compare(&first_text, &second_text); + assert_eq!(first_text, Differ::restore(&diff, 1)); + assert_eq!(second_text, Differ::restore(&diff, 2)); } #[test] fn test_unified_diff() { - let first_text = "one two three four".split(" ").collect::>(); - let second_text = "zero one tree four".split(" ").collect::>(); - let result = difflib::unified_diff(&first_text, &second_text, "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+++ Current\t2010-04-02 10:20:52@@ -1,4 +1,4 @@+zero one-two-three+tree four"); + let first_text = "one two three four".split(" ").collect::>(); + let second_text = "zero one tree four".split(" ").collect::>(); + let result = difflib::unified_diff(&first_text, &second_text, "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+++ Current\t2010-04-02 10:20:52@@ -1,4 +1,4 @@+zero one-two-three+tree four"); } #[test] fn test_context_diff() { - let first_text = "one two three four".split(" ").collect::>(); - let second_text = "zero one tree four".split(" ").collect::>(); - let result = difflib::context_diff(&first_text, &second_text, "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--- Current\t2010-04-02 10:20:52****************** 1,4 **** one! two! three four--- 1,4 ----+ zero one! tree four"); + let first_text = "one two three four".split(" ").collect::>(); + let second_text = "zero one tree four".split(" ").collect::>(); + let result = difflib::context_diff(&first_text, &second_text, "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--- Current\t2010-04-02 10:20:52****************** 1,4 **** one! two! three four--- 1,4 ----+ zero one! tree four"); } \ No newline at end of file