oxihuman_core/
patience_diff.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq)]
12pub struct PatienceHunk {
13 pub old_start: usize,
14 pub old_len: usize,
15 pub new_start: usize,
16 pub new_len: usize,
17 pub removed: Vec<String>,
18 pub added: Vec<String>,
19}
20
21#[derive(Debug, Clone)]
23pub struct PatienceDiff {
24 pub hunks: Vec<PatienceHunk>,
25}
26
27impl PatienceDiff {
28 pub fn new() -> Self {
29 Self { hunks: Vec::new() }
30 }
31
32 pub fn hunk_count(&self) -> usize {
33 self.hunks.len()
34 }
35
36 pub fn is_identical(&self) -> bool {
37 self.hunks.is_empty()
38 }
39
40 pub fn total_removed(&self) -> usize {
41 self.hunks.iter().map(|h| h.old_len).sum()
42 }
43
44 pub fn total_added(&self) -> usize {
45 self.hunks.iter().map(|h| h.new_len).sum()
46 }
47}
48
49impl Default for PatienceDiff {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55pub fn unique_common_lines<'a>(old: &[&'a str], new: &[&'a str]) -> Vec<&'a str> {
57 use std::collections::HashMap;
58 let mut old_counts: HashMap<&str, usize> = HashMap::new();
59 let mut new_counts: HashMap<&str, usize> = HashMap::new();
60 for &l in old {
61 *old_counts.entry(l).or_insert(0) += 1;
62 }
63 for &l in new {
64 *new_counts.entry(l).or_insert(0) += 1;
65 }
66 old.iter()
67 .filter(|&&l| old_counts.get(l) == Some(&1) && new_counts.get(l) == Some(&1))
68 .copied()
69 .collect()
70}
71
72pub fn patience_diff(old: &[&str], new: &[&str]) -> PatienceDiff {
74 let mut diff = PatienceDiff::new();
75 let n = old.len().max(new.len());
77 let mut oi = 0usize;
78 let mut ni = 0usize;
79 while oi < old.len() || ni < new.len() {
80 let old_line = old.get(oi).copied();
81 let new_line = new.get(ni).copied();
82 if old_line == new_line {
83 oi += 1;
84 ni += 1;
85 continue;
86 }
87 let mut hunk = PatienceHunk {
88 old_start: oi,
89 new_start: ni,
90 old_len: 0,
91 new_len: 0,
92 removed: Vec::new(),
93 added: Vec::new(),
94 };
95 if let Some(ol) = old_line {
96 hunk.removed.push(ol.to_string());
97 hunk.old_len = 1;
98 }
99 if let Some(nl) = new_line {
100 hunk.added.push(nl.to_string());
101 hunk.new_len = 1;
102 }
103 diff.hunks.push(hunk);
104 if oi < old.len() {
105 oi += 1;
106 }
107 if ni < new.len() {
108 ni += 1;
109 }
110 }
111 let _ = n; diff
113}
114
115pub fn patience_diff_to_string(diff: &PatienceDiff) -> String {
117 let mut out = String::new();
118 for h in &diff.hunks {
119 out.push_str(&format!(
120 "@@ -{},{} +{},{} @@\n",
121 h.old_start, h.old_len, h.new_start, h.new_len
122 ));
123 for l in &h.removed {
124 out.push('-');
125 out.push_str(l);
126 out.push('\n');
127 }
128 for l in &h.added {
129 out.push('+');
130 out.push_str(l);
131 out.push('\n');
132 }
133 }
134 out
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_identical_is_empty() {
143 let lines = ["a", "b", "c"];
144 let diff = patience_diff(&lines, &lines);
145 assert!(diff.is_identical());
146 }
147
148 #[test]
149 fn test_one_change() {
150 let old = ["a", "b"];
151 let new = ["a", "c"];
152 let diff = patience_diff(&old, &new);
153 assert!(!diff.is_identical());
154 }
155
156 #[test]
157 fn test_hunk_count() {
158 let old = ["x"];
159 let new = ["y"];
160 let diff = patience_diff(&old, &new);
161 assert_eq!(diff.hunk_count(), 1);
162 }
163
164 #[test]
165 fn test_total_removed_added() {
166 let old = ["a", "b"];
167 let new = ["c", "d"];
168 let diff = patience_diff(&old, &new);
169 assert_eq!(diff.total_removed(), diff.total_added());
170 }
171
172 #[test]
173 fn test_unique_common_lines() {
174 let old = ["a", "b", "c"];
175 let new = ["b", "d", "e"];
176 let common = unique_common_lines(&old, &new);
177 assert_eq!(common, vec!["b"]);
178 }
179
180 #[test]
181 fn test_unique_common_lines_empty_when_duplicate() {
182 let old = ["a", "a"];
183 let new = ["a"];
184 let common = unique_common_lines(&old, &new);
185 assert!(common.is_empty());
186 }
187
188 #[test]
189 fn test_to_string_contains_at() {
190 let old = ["x"];
191 let new = ["y"];
192 let diff = patience_diff(&old, &new);
193 let s = patience_diff_to_string(&diff);
194 assert!(s.contains("@@"));
195 }
196
197 #[test]
198 fn test_default() {
199 let d = PatienceDiff::default();
200 assert!(d.is_identical());
201 }
202
203 #[test]
204 fn test_added_lines_tracked() {
205 let old: &[&str] = &[];
206 let new = ["a", "b"];
207 let diff = patience_diff(old, &new);
208 assert!(diff.total_added() > 0);
209 }
210}