diff --git a/Cargo.toml b/Cargo.toml index 13fce5e..2edf6df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "difflib" -version = "0.3.1" +version = "0.4.0" authors = ["Dima Kudosh "] description = "Port of Python's difflib library to Rust." documentation = "https://github.com/DimaKudosh/difflib/wiki" diff --git a/README.md b/README.md index 37334a6..45f8be7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Simply add difflib to your dependencies block in Cargo.toml ```rust [dependencies] -difflib = "0.3.0" +difflib = "0.4.0" ``` ## Documentation @@ -65,7 +65,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); + let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -76,7 +76,7 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs("aaaaa", "aaaab"); + matcher.set_seqs(b"aaaaa", b"aaaab"); println!("{:?}", matcher.ratio()); } ``` diff --git a/examples/example.rs b/examples/example.rs index 3f06d0a..d643be1 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -47,7 +47,7 @@ fn main() { } //SequenceMatcher examples - let mut matcher = SequenceMatcher::new("one two three four", "zero one tree four"); + let mut matcher = SequenceMatcher::new(b"one two three four", b"zero one tree four"); let m = matcher.find_longest_match(0, 18, 0, 18); println!("{:?}", m); let all_matches = matcher.get_matching_blocks(); @@ -58,6 +58,6 @@ fn main() { println!("{:?}", grouped_opcodes); let ratio = matcher.ratio(); println!("{:?}", ratio); - matcher.set_seqs("aaaaa", "aaaab"); + matcher.set_seqs(b"aaaaa", b"aaaab"); println!("{:?}", matcher.ratio()); } diff --git a/src/differ.rs b/src/differ.rs index 6b87e01..5caef9a 100644 --- a/src/differ.rs +++ b/src/differ.rs @@ -4,8 +4,8 @@ use utils::{count_leading, str_with_similar_chars}; #[derive(Default)] pub struct Differ { - pub line_junk: Option bool>, - pub char_junk: Option bool>, + pub line_junk: Option bool>, + pub char_junk: Option bool>, } impl Differ { @@ -123,7 +123,12 @@ impl Differ { } continue; } - let mut cruncher = SequenceMatcher::new(*first_sequence_str, *second_sequence_str); + let (first_sequence_chars, second_sequence_chars) = ( + first_sequence_str.chars().collect::>(), + second_sequence_str.chars().collect::>(), + ); + let mut cruncher = + SequenceMatcher::new(&first_sequence_chars, &second_sequence_chars); cruncher.set_is_junk(self.char_junk); if cruncher.ratio() > best_ratio { best_ratio = cruncher.ratio(); @@ -167,7 +172,9 @@ impl Differ { let second_element = &second_sequence[best_j]; if eqi.is_none() { let (mut first_tag, mut second_tag) = (String::new(), String::new()); - let mut cruncher = SequenceMatcher::new(*first_element, second_element); + let first_element_chars: Vec = first_element.chars().collect(); + let second_element_chars: Vec = second_element.chars().collect(); + let mut cruncher = SequenceMatcher::new(&first_element_chars, &second_element_chars); cruncher.set_is_junk(self.char_junk); for opcode in &cruncher.get_opcodes() { let (first_length, second_length) = ( diff --git a/src/lib.rs b/src/lib.rs index 40801fe..2c25f15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod utils; use sequencematcher::{Sequence, SequenceMatcher}; use std::collections::HashMap; +use std::fmt::Display; use utils::{format_range_context, format_range_unified}; pub fn get_close_matches<'a>( @@ -16,9 +17,9 @@ pub fn get_close_matches<'a>( panic!("Cutoff must be greater than 0.0 and lower than 1.0"); } let mut res: Vec<(f32, &str)> = Vec::new(); - let mut matcher = SequenceMatcher::new("", word); + let mut matcher = SequenceMatcher::new(b"", word.as_bytes()); for i in &possibilities { - matcher.set_first_seq(i); + matcher.set_first_seq(i.as_bytes()); let ratio = matcher.ratio(); if ratio >= cutoff { res.push((ratio, i)); @@ -29,9 +30,9 @@ pub fn get_close_matches<'a>( res.iter().map(|x| x.1).collect() } -pub fn unified_diff( - first_sequence: &T, - second_sequence: &T, +pub fn unified_diff( + first_sequence: &[T], + second_sequence: &[T], from_file: &str, to_file: &str, from_file_date: &str, @@ -59,19 +60,31 @@ pub fn unified_diff( )); for code in group { if code.tag == "equal" { - for i in code.first_start..code.first_end { - res.push(format!(" {}", first_sequence.at_index(i).unwrap())); + for item in first_sequence + .iter() + .take(code.first_end) + .skip(code.first_start) + { + res.push(format!(" {}", item)); } continue; } if code.tag == "replace" || code.tag == "delete" { - for i in code.first_start..code.first_end { - res.push(format!("-{}", first_sequence.at_index(i).unwrap())); + for item in first_sequence + .iter() + .take(code.first_end) + .skip(code.first_start) + { + res.push(format!("-{}", item)); } } if code.tag == "replace" || code.tag == "insert" { - for i in code.second_start..code.second_end { - res.push(format!("+{}", second_sequence.at_index(i).unwrap())); + for item in second_sequence + .iter() + .take(code.second_end) + .skip(code.second_start) + { + res.push(format!("+{}", item)); } } } @@ -79,9 +92,9 @@ pub fn unified_diff( res } -pub fn context_diff( - first_sequence: &T, - second_sequence: &T, +pub fn context_diff( + first_sequence: &[T], + second_sequence: &[T], from_file: &str, to_file: &str, from_file_date: &str, @@ -119,12 +132,12 @@ pub fn context_diff( if any { for opcode in group { if opcode.tag != "insert" { - for i in opcode.first_start..opcode.first_end { - res.push(format!( - "{}{}", - &prefix[&opcode.tag], - first_sequence.at_index(i).unwrap() - )); + for item in first_sequence + .iter() + .take(opcode.first_end) + .skip(opcode.first_start) + { + res.push(format!("{}{}", &prefix[&opcode.tag], item)); } } } @@ -141,12 +154,12 @@ pub fn context_diff( if any { for opcode in group { if opcode.tag != "delete" { - for i in opcode.second_start..opcode.second_end { - res.push(format!( - "{}{}", - &prefix[&opcode.tag], - second_sequence.at_index(i).unwrap() - )); + for item in second_sequence + .iter() + .take(opcode.second_end) + .skip(opcode.second_start) + { + res.push(format!("{}{}", prefix[&opcode.tag], item)); } } } diff --git a/src/sequencematcher.rs b/src/sequencematcher.rs index a0416df..4cfc8b8 100644 --- a/src/sequencematcher.rs +++ b/src/sequencematcher.rs @@ -1,6 +1,6 @@ use std::cmp::{max, min}; use std::collections::HashMap; -use std::fmt::Debug; +use std::hash::Hash; use utils::calculate_ratio; #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] @@ -47,87 +47,20 @@ impl Opcode { } } -pub trait Sequence: Debug { - fn len(&self) -> usize; - fn at_index(&self, index: usize) -> Option<&str>; -} +pub trait Sequence: Eq + Hash {} +impl Sequence for T {} -impl Sequence for str { - 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)) } - } -} - -impl<'a> Sequence for Vec<&'a str> { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(self[index]); - } - None - } -} - -impl Sequence for String { - 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)) } - } -} - -impl<'a> Sequence for Vec { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(&self[index]); - } - None - } -} - -impl<'a> Sequence for [&'a str] { - fn len(&self) -> usize { - self.len() - } - - fn at_index(&self, index: usize) -> Option<&str> { - if index < self.len() { - return Some(self[index]); - } - None - } -} - -pub struct SequenceMatcher<'a, T: 'a + ?Sized + Sequence> { - first_sequence: &'a T, - second_sequence: &'a T, +pub struct SequenceMatcher<'a, T: 'a + Sequence> { + first_sequence: &'a [T], + second_sequence: &'a [T], matching_blocks: Option>, opcodes: Option>, - is_junk: Option bool>, - second_sequence_elements: HashMap<&'a str, Vec>, + is_junk: Option bool>, + second_sequence_elements: HashMap<&'a T, Vec>, } -impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { - pub fn new(first_sequence: &'a T, second_sequence: &'a T) -> SequenceMatcher<'a, T> { +impl<'a, T: Sequence> SequenceMatcher<'a, T> { + pub fn new(first_sequence: &'a [T], second_sequence: &'a [T]) -> SequenceMatcher<'a, T> { let mut matcher = SequenceMatcher { first_sequence, second_sequence, @@ -140,23 +73,23 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { matcher } - pub fn set_is_junk(&mut self, is_junk: Option bool>) { + pub fn set_is_junk(&mut self, is_junk: Option bool>) { self.is_junk = is_junk; 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_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.matching_blocks = 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.matching_blocks = None; self.opcodes = None; @@ -166,9 +99,9 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { 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() { + for (i, item) in second_sequence.iter().enumerate() { let mut counter = second_sequence_elements - .entry(second_sequence.at_index(i).unwrap()) + .entry(item) .or_insert_with(Vec::new); counter.push(i); } @@ -202,10 +135,14 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { let second_sequence_elements = &self.second_sequence_elements; let (mut best_i, mut best_j, mut best_size) = (first_start, second_start, 0); let mut j2len: HashMap = HashMap::new(); - for i in first_start..first_end { + for (i, item) in first_sequence + .iter() + .enumerate() + .take(first_end) + .skip(first_start) + { let mut new_j2len: HashMap = HashMap::new(); - if let Some(indexes) = second_sequence_elements.get(first_sequence.at_index(i).unwrap()) - { + if let Some(indexes) = second_sequence_elements.get(item) { for j in indexes { let j = *j; if j < second_start { @@ -234,15 +171,15 @@ impl<'a, T: ?Sized + Sequence> SequenceMatcher<'a, T> { for _ in 0..2 { while best_i > first_start && best_j > second_start - && first_sequence.at_index(best_i - 1) == second_sequence.at_index(best_j - 1) + && first_sequence.get(best_i - 1) == second_sequence.get(best_j - 1) { best_i -= 1; best_j -= 1; best_size += 1; } - 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) + while best_i + best_size < first_end + && best_j + best_size < second_end + && first_sequence.get(best_i + best_size) == second_sequence.get(best_j + best_size) { best_size += 1; } diff --git a/tests/tests.rs b/tests/tests.rs index e58132f..3de23df 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,7 +5,7 @@ use difflib::sequencematcher::{Match, Opcode, SequenceMatcher}; #[test] fn test_longest_match() { - let matcher = SequenceMatcher::new(" abcd", "abcd abcd"); + let matcher = SequenceMatcher::new(b" abcd", b"abcd abcd"); let m = matcher.find_longest_match(0, 5, 0, 9); assert_eq!(m.first_start, 0); assert_eq!(m.second_start, 4); @@ -14,7 +14,7 @@ fn test_longest_match() { #[test] fn test_all_matches() { - let mut matcher = SequenceMatcher::new("abxcd", "abcd"); + let mut matcher = SequenceMatcher::new(b"abxcd", b"abcd"); let result = matcher.get_matching_blocks(); let mut expected_result = Vec::new(); expected_result.push(Match { @@ -37,7 +37,7 @@ fn test_all_matches() { #[test] fn test_get_opcodes() { - let mut matcher = SequenceMatcher::new("qabxcd", "abycdf"); + let mut matcher = SequenceMatcher::new(b"qabxcd", b"abycdf"); let result = matcher.get_opcodes(); let mut expected_result = Vec::new(); expected_result.push(Opcode { @@ -80,7 +80,7 @@ fn test_get_opcodes() { #[test] fn test_ratio() { - let mut matcher = SequenceMatcher::new("abcd", "bcde"); + let mut matcher = SequenceMatcher::new(b"abcd", b"bcde"); assert_eq!(matcher.ratio(), 0.75); } @@ -103,8 +103,8 @@ fn test_differ_compare() { ); } -fn is_junk_char(ch: &str) -> bool { - if ch == " " || ch == "\t" { +fn is_junk_char(ch: &char) -> bool { + if *ch == ' ' || *ch == '\t' { return true; } false