difflib 0.1.0

Port of Python's difflib library to Rust.
Documentation
use sequencematcher::{SequenceMatcher, Sequence};
use utils::{str_with_similar_chars, count_leading};
use std::cmp;


pub struct Differ {
    pub line_junk: Option<fn(&str) -> bool>,
    pub char_junk: Option<fn(&str) -> bool>
}

impl Differ{
	pub fn new() -> Differ {
		Differ{
			line_junk: None,
			char_junk: None
		}
	}

	pub fn compare<T: ?Sized + Sequence>(&self, first_sequence: &T, second_sequence: &T) -> Vec<String> {
		let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
		matcher.set_is_junk(self.line_junk);
		let mut res = Vec::new();
		for opcode in matcher.get_opcodes(){
			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) },
				_ => {}
			}
			for i in gen {
				res.push(i);
			}
		}
		res
	}

	fn dump<T: ?Sized + Sequence>(&self, tag: &str, sequence: &T, start: usize, end: usize) -> Vec<String> {
		let mut res = Vec::new();
		for i in start..end {
			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, 
		second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> {
		if !(first_start < first_end && second_start < second_end){
			return Vec::new();
		}
		let mut first;
		let second;
		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<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize,
		second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> {
		let mut res = Vec::new();
		let (mut 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<usize> = None;
		let mut eqj: Option<usize> = 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();
		} 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<T: ?Sized + Sequence>(&self, first_sequence: &T, first_start: usize, first_end: usize,
		second_sequence: &T, second_start: usize, second_end: usize) -> Vec<String> {
		let mut res = Vec::new();
		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<String> {
		let mut res = Vec::new();
		let mut first_tags = first_tags;
		let mut second_tags = second_tags;
		let mut common = cmp::min(count_leading(first_line, '\t'), count_leading(second_line, '\t'));
		common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' '));
		common = cmp::min(common, count_leading(first_tags.split_at(common).0, ' '));
		first_tags = first_tags.split_at(common).1.trim_right();
		second_tags = second_tags.split_at(common).1.trim_right();
		let mut s = String::from(format!("- {}", first_line));
		res.push(s);
		if first_tags != ""{
			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<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]
fn test_fancy_replace() {
	let differ = Differ::new();
	let result = differ.fancy_replace(&vec!["abcDefghiJkl\n"], 0, 1, &vec!["abcdefGhijkl\n"], 0, 1).join("");
	assert_eq!(result, "- abcDefghiJkl\n?    ^  ^  ^\n+ abcdefGhijkl\n?    ^  ^  ^\n");
}

#[test]
fn test_qformat() {
	let differ = Differ::new();
	let result = differ.qformat("\tabcDefghiJkl\n", "\tabcdefGhijkl\n", "  ^ ^  ^      ", "  ^ ^  ^      ");
	assert_eq!(result, vec!["- \tabcDefghiJkl\n", "? \t ^ ^  ^\n", "+ \tabcdefGhijkl\n", "? \t ^ ^  ^\n"]);
}