This adds a deadline to all algorithms which lets one bail in a fixed amount of time to avoid pathological cases.
163 lines
4.6 KiB
Rust
163 lines
4.6 KiB
Rust
use std::hash::Hash;
|
|
use std::ops::{Index, Range};
|
|
use std::time::Instant;
|
|
|
|
use crate::algorithms::{diff_deadline, diff_slices_deadline, Capture, Replace};
|
|
use crate::{Algorithm, DiffOp};
|
|
|
|
/// Creates a diff between old and new with the given algorithm capturing the ops.
|
|
///
|
|
/// This is like [`diff`](crate::algorithms::diff) but instead of using an
|
|
/// arbitrary hook this will always use [`Replace`] + [`Capture`] and return the
|
|
/// captured [`DiffOp`]s.
|
|
pub fn capture_diff<Old, New>(
|
|
alg: Algorithm,
|
|
old: &Old,
|
|
old_range: Range<usize>,
|
|
new: &New,
|
|
new_range: Range<usize>,
|
|
) -> Vec<DiffOp>
|
|
where
|
|
Old: Index<usize> + ?Sized,
|
|
New: Index<usize> + ?Sized,
|
|
Old::Output: Hash + Eq + Ord,
|
|
New::Output: PartialEq<Old::Output> + Hash + Eq + Ord,
|
|
{
|
|
capture_diff_deadline(alg, old, old_range, new, new_range, None)
|
|
}
|
|
|
|
/// Creates a diff between old and new with the given algorithm capturing the ops.
|
|
///
|
|
/// Works like [`capture_diff`] but with an optional deadline.
|
|
pub fn capture_diff_deadline<Old, New>(
|
|
alg: Algorithm,
|
|
old: &Old,
|
|
old_range: Range<usize>,
|
|
new: &New,
|
|
new_range: Range<usize>,
|
|
deadline: Option<Instant>,
|
|
) -> Vec<DiffOp>
|
|
where
|
|
Old: Index<usize> + ?Sized,
|
|
New: Index<usize> + ?Sized,
|
|
Old::Output: Hash + Eq + Ord,
|
|
New::Output: PartialEq<Old::Output> + Hash + Eq + Ord,
|
|
{
|
|
let mut d = Replace::new(Capture::new());
|
|
diff_deadline(alg, &mut d, old, old_range, new, new_range, deadline).unwrap();
|
|
d.into_inner().into_ops()
|
|
}
|
|
|
|
/// Creates a diff between old and new with the given algorithm capturing the ops.
|
|
pub fn capture_diff_slices<T>(alg: Algorithm, old: &[T], new: &[T]) -> Vec<DiffOp>
|
|
where
|
|
T: Eq + Hash + Ord,
|
|
{
|
|
capture_diff_slices_deadline(alg, old, new, None)
|
|
}
|
|
|
|
/// Creates a diff between old and new with the given algorithm capturing the ops.
|
|
///
|
|
/// Works like [`capture_diff_slices`] but with an optional deadline.
|
|
pub fn capture_diff_slices_deadline<T>(
|
|
alg: Algorithm,
|
|
old: &[T],
|
|
new: &[T],
|
|
deadline: Option<Instant>,
|
|
) -> Vec<DiffOp>
|
|
where
|
|
T: Eq + Hash + Ord,
|
|
{
|
|
let mut d = Replace::new(Capture::new());
|
|
diff_slices_deadline(alg, &mut d, old, new, deadline).unwrap();
|
|
d.into_inner().into_ops()
|
|
}
|
|
|
|
/// Return a measure of similarity in the range `0..=1`.
|
|
///
|
|
/// A ratio of `1.0` means the two sequences are a complete match, a
|
|
/// ratio of `0.0` would indicate completely distinct sequences. The input
|
|
/// is the sequence of diff operations and the length of the old and new
|
|
/// sequence.
|
|
pub fn get_diff_ratio(ops: &[DiffOp], old_len: usize, new_len: usize) -> f32 {
|
|
let matches = ops
|
|
.iter()
|
|
.map(|op| {
|
|
if let DiffOp::Equal { len, .. } = *op {
|
|
len
|
|
} else {
|
|
0
|
|
}
|
|
})
|
|
.sum::<usize>();
|
|
let len = old_len + new_len;
|
|
if len == 0 {
|
|
1.0
|
|
} else {
|
|
2.0 * matches as f32 / len as f32
|
|
}
|
|
}
|
|
|
|
/// Isolate change clusters by eliminating ranges with no changes.
|
|
///
|
|
/// This will leave holes behind in long periods of equal ranges so that
|
|
/// you can build things like unified diffs.
|
|
pub fn group_diff_ops(mut ops: Vec<DiffOp>, n: usize) -> Vec<Vec<DiffOp>> {
|
|
if ops.is_empty() {
|
|
return vec![];
|
|
}
|
|
|
|
let mut pending_group = Vec::new();
|
|
let mut rv = Vec::new();
|
|
|
|
if let Some(DiffOp::Equal {
|
|
old_index,
|
|
new_index,
|
|
len,
|
|
}) = ops.first_mut()
|
|
{
|
|
let offset = (*len).saturating_sub(n);
|
|
*old_index += offset;
|
|
*new_index += offset;
|
|
*len -= offset;
|
|
}
|
|
|
|
if let Some(DiffOp::Equal { len, .. }) = ops.last_mut() {
|
|
*len -= (*len).saturating_sub(n);
|
|
}
|
|
|
|
for op in ops.into_iter() {
|
|
if let DiffOp::Equal {
|
|
old_index,
|
|
new_index,
|
|
len,
|
|
} = op
|
|
{
|
|
// End the current group and start a new one whenever
|
|
// there is a large range with no changes.
|
|
if len > n * 2 {
|
|
pending_group.push(DiffOp::Equal {
|
|
old_index,
|
|
new_index,
|
|
len: n,
|
|
});
|
|
rv.push(pending_group);
|
|
let offset = len.saturating_sub(n);
|
|
pending_group = vec![DiffOp::Equal {
|
|
old_index: old_index + offset,
|
|
new_index: new_index + offset,
|
|
len: len - offset,
|
|
}];
|
|
continue;
|
|
}
|
|
}
|
|
pending_group.push(op);
|
|
}
|
|
|
|
match &pending_group[..] {
|
|
&[] | &[DiffOp::Equal { .. }] => {}
|
|
_ => rv.push(pending_group),
|
|
}
|
|
|
|
rv
|
|
}
|