1pub mod differ;
2pub mod sequencematcher;
3mod utils;
4
5use sequencematcher::{Sequence, SequenceMatcher};
6use std::collections::HashMap;
7use std::fmt::Display;
8use utils::{format_range_context, format_range_unified};
9
10pub fn get_close_matches<'a>(
11 word: &str,
12 possibilities: Vec<&'a str>,
13 n: usize,
14 cutoff: f32,
15) -> Vec<&'a str> {
16 if !(0.0 <= cutoff && cutoff <= 1.0) {
17 panic!("Cutoff must be greater than 0.0 and lower than 1.0");
18 }
19 let mut res: Vec<(f32, &str)> = Vec::new();
20 let mut matcher = SequenceMatcher::new("", word);
21 for i in &possibilities {
22 matcher.set_first_seq(i);
23 let ratio = matcher.ratio();
24 if ratio >= cutoff {
25 res.push((ratio, i));
26 }
27 }
28 res.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
29 res.truncate(n);
30 res.iter().map(|x| x.1).collect()
31}
32
33pub fn unified_diff<T: Sequence + Display>(
34 first_sequence: &[T],
35 second_sequence: &[T],
36 from_file: &str,
37 to_file: &str,
38 from_file_date: &str,
39 to_file_date: &str,
40 n: usize,
41) -> Vec<String> {
42 let mut res = Vec::new();
43 let lineterm = '\n';
44 let mut started = false;
45 let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
46 for group in &matcher.get_grouped_opcodes(n) {
47 if !started {
48 started = true;
49 let from_date = format!("\t{}", from_file_date);
50 let to_date = format!("\t{}", to_file_date);
51 res.push(format!("--- {}{}{}", from_file, from_date, lineterm));
52 res.push(format!("+++ {}{}{}", to_file, to_date, lineterm));
53 }
54 let (first, last) = (group.first().unwrap(), group.last().unwrap());
55 let file1_range = format_range_unified(first.first_start, last.first_end);
56 let file2_range = format_range_unified(first.second_start, last.second_end);
57 res.push(format!(
58 "@@ -{} +{} @@{}",
59 file1_range, file2_range, lineterm
60 ));
61 for code in group {
62 if code.tag == "equal" {
63 for item in first_sequence
64 .iter()
65 .take(code.first_end)
66 .skip(code.first_start)
67 {
68 res.push(format!(" {}", item));
69 }
70 continue;
71 }
72 if code.tag == "replace" || code.tag == "delete" {
73 for item in first_sequence
74 .iter()
75 .take(code.first_end)
76 .skip(code.first_start)
77 {
78 res.push(format!("-{}", item));
79 }
80 }
81 if code.tag == "replace" || code.tag == "insert" {
82 for item in second_sequence
83 .iter()
84 .take(code.second_end)
85 .skip(code.second_start)
86 {
87 res.push(format!("+{}", item));
88 }
89 }
90 }
91 }
92 res
93}
94
95pub fn context_diff<T: Sequence + Display>(
96 first_sequence: &[T],
97 second_sequence: &[T],
98 from_file: &str,
99 to_file: &str,
100 from_file_date: &str,
101 to_file_date: &str,
102 n: usize,
103) -> Vec<String> {
104 let mut res = Vec::new();
105 let lineterm = '\n';
106 let mut prefix: HashMap<String, String> = HashMap::new();
107 prefix.insert(String::from("insert"), String::from("+ "));
108 prefix.insert(String::from("delete"), String::from("- "));
109 prefix.insert(String::from("replace"), String::from("! "));
110 prefix.insert(String::from("equal"), String::from(" "));
111 let mut started = false;
112 let mut matcher = SequenceMatcher::new(first_sequence, second_sequence);
113 for group in &matcher.get_grouped_opcodes(n) {
114 if !started {
115 started = true;
116 let from_date = format!("\t{}", from_file_date);
117 let to_date = format!("\t{}", to_file_date);
118 res.push(format!("*** {}{}{}", from_file, from_date, lineterm));
119 res.push(format!("--- {}{}{}", to_file, to_date, lineterm));
120 }
121 let (first, last) = (group.first().unwrap(), group.last().unwrap());
122 res.push(format!("***************{}", lineterm));
123 let file1_range = format_range_context(first.first_start, last.first_end);
124 res.push(format!("*** {} ****{}", file1_range, lineterm));
125 let mut any = false;
126 for opcode in group {
127 if opcode.tag == "replace" || opcode.tag == "delete" {
128 any = true;
129 break;
130 }
131 }
132 if any {
133 for opcode in group {
134 if opcode.tag != "insert" {
135 for item in first_sequence
136 .iter()
137 .take(opcode.first_end)
138 .skip(opcode.first_start)
139 {
140 res.push(format!("{}{}", &prefix[&opcode.tag], item));
141 }
142 }
143 }
144 }
145 let file2_range = format_range_context(first.second_start, last.second_end);
146 res.push(format!("--- {} ----{}", file2_range, lineterm));
147 any = false;
148 for opcode in group {
149 if opcode.tag == "replace" || opcode.tag == "insert" {
150 any = true;
151 break;
152 }
153 }
154 if any {
155 for opcode in group {
156 if opcode.tag != "delete" {
157 for item in second_sequence
158 .iter()
159 .take(opcode.second_end)
160 .skip(opcode.second_start)
161 {
162 res.push(format!("{}{}", prefix[&opcode.tag], item));
163 }
164 }
165 }
166 }
167 }
168 res
169}
170