1 | use std::hash::Hash; |
2 | use std::ops::{Index, Range}; |
3 | use std::time::Instant; |
4 | |
5 | use crate::algorithms::{diff_deadline, Capture, Compact, Replace}; |
6 | use crate::{Algorithm, DiffOp}; |
7 | |
8 | /// Creates a diff between old and new with the given algorithm capturing the ops. |
9 | /// |
10 | /// This is like [`diff`](crate::algorithms::diff) but instead of using an |
11 | /// arbitrary hook this will always use [`Compact`] + [`Replace`] + [`Capture`] |
12 | /// and return the captured [`DiffOp`]s. |
13 | pub fn capture_diff<Old, New>( |
14 | alg: Algorithm, |
15 | old: &Old, |
16 | old_range: Range<usize>, |
17 | new: &New, |
18 | new_range: Range<usize>, |
19 | ) -> Vec<DiffOp> |
20 | where |
21 | Old: Index<usize> + ?Sized, |
22 | New: Index<usize> + ?Sized, |
23 | Old::Output: Hash + Eq + Ord, |
24 | New::Output: PartialEq<Old::Output> + Hash + Eq + Ord, |
25 | { |
26 | capture_diff_deadline(alg, old, old_range, new, new_range, deadline:None) |
27 | } |
28 | |
29 | /// Creates a diff between old and new with the given algorithm capturing the ops. |
30 | /// |
31 | /// Works like [`capture_diff`] but with an optional deadline. |
32 | pub fn capture_diff_deadline<Old, New>( |
33 | alg: Algorithm, |
34 | old: &Old, |
35 | old_range: Range<usize>, |
36 | new: &New, |
37 | new_range: Range<usize>, |
38 | deadline: Option<Instant>, |
39 | ) -> Vec<DiffOp> |
40 | where |
41 | Old: Index<usize> + ?Sized, |
42 | New: Index<usize> + ?Sized, |
43 | Old::Output: Hash + Eq + Ord, |
44 | New::Output: PartialEq<Old::Output> + Hash + Eq + Ord, |
45 | { |
46 | let mut d: Compact<'_, '_, Old, New, …> = Compact::new(d:Replace::new(Capture::new()), old, new); |
47 | diff_deadline(alg, &mut d, old, old_range, new, new_range, deadline).unwrap(); |
48 | d.into_inner().into_inner().into_ops() |
49 | } |
50 | |
51 | /// Creates a diff between old and new with the given algorithm capturing the ops. |
52 | pub fn capture_diff_slices<T>(alg: Algorithm, old: &[T], new: &[T]) -> Vec<DiffOp> |
53 | where |
54 | T: Eq + Hash + Ord, |
55 | { |
56 | capture_diff_slices_deadline(alg, old, new, deadline:None) |
57 | } |
58 | |
59 | /// Creates a diff between old and new with the given algorithm capturing the ops. |
60 | /// |
61 | /// Works like [`capture_diff_slices`] but with an optional deadline. |
62 | pub fn capture_diff_slices_deadline<T>( |
63 | alg: Algorithm, |
64 | old: &[T], |
65 | new: &[T], |
66 | deadline: Option<Instant>, |
67 | ) -> Vec<DiffOp> |
68 | where |
69 | T: Eq + Hash + Ord, |
70 | { |
71 | capture_diff_deadline(alg, old, old_range:0..old.len(), new, new_range:0..new.len(), deadline) |
72 | } |
73 | |
74 | /// Return a measure of similarity in the range `0..=1`. |
75 | /// |
76 | /// A ratio of `1.0` means the two sequences are a complete match, a |
77 | /// ratio of `0.0` would indicate completely distinct sequences. The input |
78 | /// is the sequence of diff operations and the length of the old and new |
79 | /// sequence. |
80 | pub fn get_diff_ratio(ops: &[DiffOp], old_len: usize, new_len: usize) -> f32 { |
81 | let matches: usize = opsimpl Iterator |
82 | .iter() |
83 | .map(|op: &DiffOp| { |
84 | if let DiffOp::Equal { len: usize, .. } = *op { |
85 | len |
86 | } else { |
87 | 0 |
88 | } |
89 | }) |
90 | .sum::<usize>(); |
91 | let len: usize = old_len + new_len; |
92 | if len == 0 { |
93 | 1.0 |
94 | } else { |
95 | 2.0 * matches as f32 / len as f32 |
96 | } |
97 | } |
98 | |
99 | /// Isolate change clusters by eliminating ranges with no changes. |
100 | /// |
101 | /// This will leave holes behind in long periods of equal ranges so that |
102 | /// you can build things like unified diffs. |
103 | pub fn group_diff_ops(mut ops: Vec<DiffOp>, n: usize) -> Vec<Vec<DiffOp>> { |
104 | if ops.is_empty() { |
105 | return vec![]; |
106 | } |
107 | |
108 | let mut pending_group = Vec::new(); |
109 | let mut rv = Vec::new(); |
110 | |
111 | if let Some(DiffOp::Equal { |
112 | old_index, |
113 | new_index, |
114 | len, |
115 | }) = ops.first_mut() |
116 | { |
117 | let offset = (*len).saturating_sub(n); |
118 | *old_index += offset; |
119 | *new_index += offset; |
120 | *len -= offset; |
121 | } |
122 | |
123 | if let Some(DiffOp::Equal { len, .. }) = ops.last_mut() { |
124 | *len -= (*len).saturating_sub(n); |
125 | } |
126 | |
127 | for op in ops.into_iter() { |
128 | if let DiffOp::Equal { |
129 | old_index, |
130 | new_index, |
131 | len, |
132 | } = op |
133 | { |
134 | // End the current group and start a new one whenever |
135 | // there is a large range with no changes. |
136 | if len > n * 2 { |
137 | pending_group.push(DiffOp::Equal { |
138 | old_index, |
139 | new_index, |
140 | len: n, |
141 | }); |
142 | rv.push(pending_group); |
143 | let offset = len.saturating_sub(n); |
144 | pending_group = vec![DiffOp::Equal { |
145 | old_index: old_index + offset, |
146 | new_index: new_index + offset, |
147 | len: len - offset, |
148 | }]; |
149 | continue; |
150 | } |
151 | } |
152 | pending_group.push(op); |
153 | } |
154 | |
155 | match &pending_group[..] { |
156 | &[] | &[DiffOp::Equal { .. }] => {} |
157 | _ => rv.push(pending_group), |
158 | } |
159 | |
160 | rv |
161 | } |
162 | |
163 | #[test ] |
164 | fn test_non_string_iter_change() { |
165 | use crate::ChangeTag; |
166 | |
167 | let old = vec![1, 2, 3]; |
168 | let new = vec![1, 2, 4]; |
169 | let ops = capture_diff_slices(Algorithm::Myers, &old, &new); |
170 | let changes: Vec<_> = ops |
171 | .iter() |
172 | .flat_map(|x| x.iter_changes(&old, &new)) |
173 | .map(|x| (x.tag(), x.value())) |
174 | .collect(); |
175 | |
176 | assert_eq!( |
177 | changes, |
178 | vec![ |
179 | (ChangeTag::Equal, 1), |
180 | (ChangeTag::Equal, 2), |
181 | (ChangeTag::Delete, 3), |
182 | (ChangeTag::Insert, 4), |
183 | ] |
184 | ); |
185 | } |
186 | |