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:
parent
0b8e237280
commit
a3e10af892
7 changed files with 75 additions and 37 deletions
|
|
@ -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
13
examples/nonstring.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
37
src/iter.rs
37
src/iter.rs
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
24
src/types.rs
24
src/types.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue