Make the change type be generic over any T rather &T (#31)

This makes the interface of this crate more flexible as the utility
methods such as `iter_changes` now also work if a container does not
contain references.
This commit is contained in:
Armin Ronacher 2021-09-11 11:20:51 +02:00 committed by GitHub
parent 0b8e237280
commit a3e10af892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 37 deletions

View file

@ -2,6 +2,12 @@
All notable changes to similar are documented here.
## Unreleased
* Change the `Change` type and associated methods to work on any `T: Clone` instead
of `&T`. This makes the `iter_changes` method also work on slices of integers
or other values.
## 1.3.0
* Performance improvements for the LCS algorithm.

13
examples/nonstring.rs Normal file
View file

@ -0,0 +1,13 @@
use similar::{capture_diff_slices, Algorithm};
fn main() {
let old = vec![1, 2, 3];
let new = vec![1, 2, 4];
let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
for op in ops {
for change in op.iter_changes(&old, &new) {
println!("{:?}", change);
}
}
}

View file

@ -159,3 +159,27 @@ pub fn group_diff_ops(mut ops: Vec<DiffOp>, n: usize) -> Vec<Vec<DiffOp>> {
rv
}
#[test]
fn test_non_string_iter_change() {
use crate::ChangeTag;
let old = vec![1, 2, 3];
let new = vec![1, 2, 4];
let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
let changes: Vec<_> = ops
.iter()
.flat_map(|x| x.iter_changes(&old, &new))
.map(|x| (x.tag(), x.value()))
.collect();
assert_eq!(
changes,
vec![
(ChangeTag::Equal, 1),
(ChangeTag::Equal, 2),
(ChangeTag::Delete, 3),
(ChangeTag::Insert, 4),
]
);
}

View file

@ -11,7 +11,7 @@ use std::ops::{Index, Range};
use crate::{Change, ChangeTag, DiffOp, DiffTag};
/// Iterator for [`DiffOp::iter_changes`].
pub struct ChangesIter<'lookup, 'data, Old: ?Sized, New: ?Sized, T: ?Sized> {
pub struct ChangesIter<'lookup, Old: ?Sized, New: ?Sized, T> {
old: &'lookup Old,
new: &'lookup New,
old_range: Range<usize>,
@ -21,15 +21,13 @@ pub struct ChangesIter<'lookup, 'data, Old: ?Sized, New: ?Sized, T: ?Sized> {
old_i: usize,
new_i: usize,
tag: DiffTag,
_marker: PhantomData<&'data T>,
_marker: PhantomData<T>,
}
impl<'lookup, 'data, Old, New, T> ChangesIter<'lookup, 'data, Old, New, T>
impl<'lookup, Old, New, T> ChangesIter<'lookup, Old, New, T>
where
Old: Index<usize, Output = &'data T> + ?Sized,
New: Index<usize, Output = &'data T> + ?Sized,
T: 'data + ?Sized,
'data: 'lookup,
Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = T> + ?Sized,
{
pub(crate) fn new(old: &'lookup Old, new: &'lookup New, op: DiffOp) -> Self {
let (tag, old_range, new_range) = op.as_tag_tuple();
@ -52,20 +50,19 @@ where
}
}
impl<'lookup, 'data, Old, New, T> Iterator for ChangesIter<'lookup, 'data, Old, New, T>
impl<'lookup, Old, New, T> Iterator for ChangesIter<'lookup, Old, New, T>
where
Old: Index<usize, Output = &'data T> + ?Sized,
New: Index<usize, Output = &'data T> + ?Sized,
T: 'data + ?Sized,
'data: 'lookup,
Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = T> + ?Sized,
T: Clone,
{
type Item = Change<'data, T>;
type Item = Change<T>;
fn next(&mut self) -> Option<Self::Item> {
match self.tag {
DiffTag::Equal => {
if self.old_i < self.old_range.end {
let value = self.old[self.old_i];
let value = self.old[self.old_i].clone();
self.old_i += 1;
self.old_index += 1;
self.new_index += 1;
@ -81,7 +78,7 @@ where
}
DiffTag::Delete => {
if self.old_i < self.old_range.end {
let value = self.old[self.old_i];
let value = self.old[self.old_i].clone();
self.old_i += 1;
self.old_index += 1;
Some(Change {
@ -96,7 +93,7 @@ where
}
DiffTag::Insert => {
if self.new_i < self.new_range.end {
let value = self.new[self.new_i];
let value = self.new[self.new_i].clone();
self.new_i += 1;
self.new_index += 1;
Some(Change {
@ -111,7 +108,7 @@ where
}
DiffTag::Replace => {
if self.old_i < self.old_range.end {
let value = self.old[self.old_i];
let value = self.old[self.old_i].clone();
self.old_i += 1;
self.old_index += 1;
Some(Change {
@ -121,7 +118,7 @@ where
value,
})
} else if self.new_i < self.new_range.end {
let value = self.new[self.new_i];
let value = self.new[self.new_i].clone();
self.new_i += 1;
self.new_index += 1;
Some(Change {
@ -147,7 +144,7 @@ mod text {
old: &'slf [&'data T],
new: &'slf [&'data T],
ops: &'slf [DiffOp],
current_iter: Option<ChangesIter<'slf, 'data, [&'data T], [&'data T], T>>,
current_iter: Option<ChangesIter<'slf, [&'data T], [&'data T], &'data T>>,
}
impl<'slf, 'data, T> AllChangesIter<'slf, 'data, T>
@ -173,7 +170,7 @@ mod text {
T: PartialEq + 'data + ?Sized,
'data: 'slf,
{
type Item = Change<'data, T>;
type Item = Change<&'data T>;
fn next(&mut self) -> Option<Self::Item> {
loop {

View file

@ -159,8 +159,8 @@ impl<'s, T: DiffableStr + ?Sized> InlineChange<'s, T> {
}
}
impl<'s, T: DiffableStr + ?Sized> From<Change<'s, T>> for InlineChange<'s, T> {
fn from(change: Change<'s, T>) -> InlineChange<'s, T> {
impl<'s, T: DiffableStr + ?Sized> From<Change<&'s T>> for InlineChange<'s, T> {
fn from(change: Change<&'s T>) -> InlineChange<'s, T> {
InlineChange {
tag: change.tag(),
old_index: change.old_index(),

View file

@ -495,7 +495,7 @@ impl<'old, 'new, 'bufs, T: DiffableStr + ?Sized + 'old + 'new> TextDiff<'old, 'n
pub fn iter_changes<'x, 'slf>(
&'slf self,
op: &DiffOp,
) -> ChangesIter<'slf, 'x, [&'x T], [&'x T], T>
) -> ChangesIter<'slf, [&'x T], [&'x T], &'x T>
where
'x: 'slf,
'old: 'x,
@ -728,7 +728,7 @@ fn test_get_close_matches() {
fn test_lifetimes_on_iter() {
use crate::Change;
fn diff_lines<'x, T>(old: &'x T, new: &'x T) -> Vec<Change<'x, T::Output>>
fn diff_lines<'x, T>(old: &'x T, new: &'x T) -> Vec<Change<&'x T::Output>>
where
T: DiffableStrRef + ?Sized,
{

View file

@ -59,15 +59,15 @@ impl fmt::Display for ChangeTag {
/// This type has additional methods that are only available for types
/// implementing [`DiffableStr`](crate::text::DiffableStr).
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub struct Change<'s, T: ?Sized> {
pub struct Change<T> {
pub(crate) tag: ChangeTag,
pub(crate) old_index: Option<usize>,
pub(crate) new_index: Option<usize>,
pub(crate) value: &'s T,
pub(crate) value: T,
}
/// These methods are available for all change types.
impl<'s, T: ?Sized> Change<'s, T> {
impl<T: Clone> Change<T> {
/// Returns the change tag.
pub fn tag(&self) -> ChangeTag {
self.tag
@ -89,8 +89,8 @@ impl<'s, T: ?Sized> Change<'s, T> {
/// this value is more or less useful. If you always want to have a utf-8
/// string it's best to use the [`Change::as_str`] and
/// [`Change::to_string_lossy`] methods.
pub fn value(&self) -> &'s T {
self.value
pub fn value(&self) -> T {
self.value.clone()
}
}
@ -270,16 +270,14 @@ impl DiffOp {
/// (ChangeTag::Insert, "blah"),
/// ]);
/// ```
pub fn iter_changes<'x, 'lookup, Old, New, T>(
pub fn iter_changes<'lookup, Old, New, T>(
&self,
old: &'lookup Old,
new: &'lookup New,
) -> ChangesIter<'lookup, 'x, Old, New, T>
) -> ChangesIter<'lookup, Old, New, T>
where
Old: Index<usize, Output = &'x T> + ?Sized,
New: Index<usize, Output = &'x T> + ?Sized,
T: 'x + ?Sized,
'x: 'lookup,
Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = T> + ?Sized,
{
ChangesIter::new(old, new, *self)
}
@ -436,7 +434,7 @@ mod text_additions {
/// The text interface can produce changes over [`DiffableStr`] implementing
/// values. As those are generic interfaces for different types of strings
/// utility methods to make working with standard rust strings more enjoyable.
impl<'s, T: DiffableStr + ?Sized> Change<'s, T> {
impl<'s, T: DiffableStr + ?Sized> Change<&'s T> {
/// Returns the value as string if it is utf-8.
pub fn as_str(&self) -> Option<&'s str> {
T::as_str(self.value)
@ -457,7 +455,7 @@ mod text_additions {
}
}
impl<'s, T: DiffableStr + ?Sized> fmt::Display for Change<'s, T> {
impl<'s, T: DiffableStr + ?Sized> fmt::Display for Change<&'s T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,