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. 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 ## 1.3.0
* Performance improvements for the LCS algorithm. * 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 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}; use crate::{Change, ChangeTag, DiffOp, DiffTag};
/// Iterator for [`DiffOp::iter_changes`]. /// 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, old: &'lookup Old,
new: &'lookup New, new: &'lookup New,
old_range: Range<usize>, old_range: Range<usize>,
@ -21,15 +21,13 @@ pub struct ChangesIter<'lookup, 'data, Old: ?Sized, New: ?Sized, T: ?Sized> {
old_i: usize, old_i: usize,
new_i: usize, new_i: usize,
tag: DiffTag, 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 where
Old: Index<usize, Output = &'data T> + ?Sized, Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = &'data T> + ?Sized, New: Index<usize, Output = T> + ?Sized,
T: 'data + ?Sized,
'data: 'lookup,
{ {
pub(crate) fn new(old: &'lookup Old, new: &'lookup New, op: DiffOp) -> Self { pub(crate) fn new(old: &'lookup Old, new: &'lookup New, op: DiffOp) -> Self {
let (tag, old_range, new_range) = op.as_tag_tuple(); 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 where
Old: Index<usize, Output = &'data T> + ?Sized, Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = &'data T> + ?Sized, New: Index<usize, Output = T> + ?Sized,
T: 'data + ?Sized, T: Clone,
'data: 'lookup,
{ {
type Item = Change<'data, T>; type Item = Change<T>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self.tag { match self.tag {
DiffTag::Equal => { DiffTag::Equal => {
if self.old_i < self.old_range.end { 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_i += 1;
self.old_index += 1; self.old_index += 1;
self.new_index += 1; self.new_index += 1;
@ -81,7 +78,7 @@ where
} }
DiffTag::Delete => { DiffTag::Delete => {
if self.old_i < self.old_range.end { 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_i += 1;
self.old_index += 1; self.old_index += 1;
Some(Change { Some(Change {
@ -96,7 +93,7 @@ where
} }
DiffTag::Insert => { DiffTag::Insert => {
if self.new_i < self.new_range.end { 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_i += 1;
self.new_index += 1; self.new_index += 1;
Some(Change { Some(Change {
@ -111,7 +108,7 @@ where
} }
DiffTag::Replace => { DiffTag::Replace => {
if self.old_i < self.old_range.end { 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_i += 1;
self.old_index += 1; self.old_index += 1;
Some(Change { Some(Change {
@ -121,7 +118,7 @@ where
value, value,
}) })
} else if self.new_i < self.new_range.end { } 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_i += 1;
self.new_index += 1; self.new_index += 1;
Some(Change { Some(Change {
@ -147,7 +144,7 @@ mod text {
old: &'slf [&'data T], old: &'slf [&'data T],
new: &'slf [&'data T], new: &'slf [&'data T],
ops: &'slf [DiffOp], 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> impl<'slf, 'data, T> AllChangesIter<'slf, 'data, T>
@ -173,7 +170,7 @@ mod text {
T: PartialEq + 'data + ?Sized, T: PartialEq + 'data + ?Sized,
'data: 'slf, 'data: 'slf,
{ {
type Item = Change<'data, T>; type Item = Change<&'data T>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { 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> { impl<'s, T: DiffableStr + ?Sized> From<Change<&'s T>> for InlineChange<'s, T> {
fn from(change: Change<'s, T>) -> InlineChange<'s, T> { fn from(change: Change<&'s T>) -> InlineChange<'s, T> {
InlineChange { InlineChange {
tag: change.tag(), tag: change.tag(),
old_index: change.old_index(), 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>( pub fn iter_changes<'x, 'slf>(
&'slf self, &'slf self,
op: &DiffOp, op: &DiffOp,
) -> ChangesIter<'slf, 'x, [&'x T], [&'x T], T> ) -> ChangesIter<'slf, [&'x T], [&'x T], &'x T>
where where
'x: 'slf, 'x: 'slf,
'old: 'x, 'old: 'x,
@ -728,7 +728,7 @@ fn test_get_close_matches() {
fn test_lifetimes_on_iter() { fn test_lifetimes_on_iter() {
use crate::Change; 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 where
T: DiffableStrRef + ?Sized, 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 /// This type has additional methods that are only available for types
/// implementing [`DiffableStr`](crate::text::DiffableStr). /// implementing [`DiffableStr`](crate::text::DiffableStr).
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)] #[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) tag: ChangeTag,
pub(crate) old_index: Option<usize>, pub(crate) old_index: Option<usize>,
pub(crate) new_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. /// These methods are available for all change types.
impl<'s, T: ?Sized> Change<'s, T> { impl<T: Clone> Change<T> {
/// Returns the change tag. /// Returns the change tag.
pub fn tag(&self) -> ChangeTag { pub fn tag(&self) -> ChangeTag {
self.tag 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 /// 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 /// string it's best to use the [`Change::as_str`] and
/// [`Change::to_string_lossy`] methods. /// [`Change::to_string_lossy`] methods.
pub fn value(&self) -> &'s T { pub fn value(&self) -> T {
self.value self.value.clone()
} }
} }
@ -270,16 +270,14 @@ impl DiffOp {
/// (ChangeTag::Insert, "blah"), /// (ChangeTag::Insert, "blah"),
/// ]); /// ]);
/// ``` /// ```
pub fn iter_changes<'x, 'lookup, Old, New, T>( pub fn iter_changes<'lookup, Old, New, T>(
&self, &self,
old: &'lookup Old, old: &'lookup Old,
new: &'lookup New, new: &'lookup New,
) -> ChangesIter<'lookup, 'x, Old, New, T> ) -> ChangesIter<'lookup, Old, New, T>
where where
Old: Index<usize, Output = &'x T> + ?Sized, Old: Index<usize, Output = T> + ?Sized,
New: Index<usize, Output = &'x T> + ?Sized, New: Index<usize, Output = T> + ?Sized,
T: 'x + ?Sized,
'x: 'lookup,
{ {
ChangesIter::new(old, new, *self) ChangesIter::new(old, new, *self)
} }
@ -436,7 +434,7 @@ mod text_additions {
/// The text interface can produce changes over [`DiffableStr`] implementing /// The text interface can produce changes over [`DiffableStr`] implementing
/// values. As those are generic interfaces for different types of strings /// values. As those are generic interfaces for different types of strings
/// utility methods to make working with standard rust strings more enjoyable. /// 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. /// Returns the value as string if it is utf-8.
pub fn as_str(&self) -> Option<&'s str> { pub fn as_str(&self) -> Option<&'s str> {
T::as_str(self.value) 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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,