difflib 0.1.0

Port of Python's difflib library to Rust.
Documentation
pub mod sequencematcher;
pub mod differ;
mod utils;


use std::collections::HashMap;
use sequencematcher::{SequenceMatcher, Sequence};
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> {
	if !(0.0 <= cutoff && cutoff <= 1.0) {
		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);
	for i in &possibilities {
		matcher.set_first_seq(i);
		let ratio = matcher.ratio();
		if ratio >= cutoff {
			res.push((ratio, i));
		}
	}
	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, 
	from_file_date: &str, to_file_date: &str, n: usize) -> Vec<String> {
	let mut res = Vec::new();
	let lineterm = '\n';
	let mut started = false;
	let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
	for group in &matcher.get_grouped_opcodes(n) {
		if !started {
			started = true;
			let from_date = format!("\t{}", from_file_date);
			let to_date = format!("\t{}", to_file_date);
			res.push( format!("--- {}{}{}", from_file, from_date, lineterm) );
			res.push( format!("+++ {}{}{}", to_file, to_date, lineterm) );
		}
		let (first, last) = (group.first().unwrap(), group.last().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);
		res.push( format!("@@ -{} +{} @@{}", file1_range, file2_range, lineterm) );
		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()) );
				}
				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()) );
				}
			}
			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()) );
				}
			}
		}
	}
	res
}


pub fn context_diff<T: Sequence>(first_sequence: &T, second_sequence: &T, from_file: &str, to_file: &str, 
	from_file_date: &str, to_file_date: &str, n: usize) -> Vec<String> {
	let mut res = Vec::new();
	let lineterm = '\n';
	let mut prefix: HashMap<String, String> = HashMap::new();
	prefix.insert(String::from("insert"), String::from("+ "));
	prefix.insert(String::from("delete"), String::from("- "));
	prefix.insert(String::from("replace"), String::from("! "));
	prefix.insert(String::from("equal"), String::from("  "));
	let mut started = false;
	let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
	for group in &matcher.get_grouped_opcodes(n) {
		if !started {
			started = true;
			let from_date = format!("\t{}", from_file_date);
			let to_date = format!("\t{}", to_file_date);
			res.push( format!("*** {}{}{}", from_file, from_date, lineterm) );
			res.push( format!("--- {}{}{}", to_file, to_date, lineterm) );
		}
		let (first, last) = (group.first().unwrap(), group.last().unwrap());
		res.push(format!("***************{}", lineterm));
		let file1_range = format_range_context(first.first_start, last.first_end); 
		res.push(format!("*** {} ****{}", file1_range, lineterm));
		let mut any = false;
		for opcode in group {
			if opcode.tag == "replace" || opcode.tag == "delete" {
				any = true;
				break;
			}
		}
		if any {
			for opcode in group {
				if opcode.tag != "insert" {
					for i in opcode.first_start..opcode.first_end {
						res.push( format!("{}{}", prefix.get(&opcode.tag).unwrap(), first_sequence.at_index(i).unwrap()) );
					}
				}
			}
		}
		let file2_range = format_range_context(first.second_start, last.second_end);
		res.push(format!("--- {} ----{}", file2_range, lineterm));
		any = false;
		for opcode in group {
			if opcode.tag == "replace" || opcode.tag == "insert" {
				any = true;
				break;
			}
		}
		if any {
			for opcode in group {
				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
}