Small refactor on the api

This commit is contained in:
Armin Ronacher 2021-01-24 02:11:20 +01:00
parent 8aaa934925
commit bf03a57c03

View file

@ -101,20 +101,62 @@ pub struct TextDiff<'old, 'new, 'bufs> {
algorithm: Algorithm, algorithm: Algorithm,
} }
/// The tag of a change.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub enum Change { pub enum ChangeTag {
Equal, Equal,
Delete, Delete,
Insert, Insert,
} }
impl Change { /// Represents the expanded textual change.
///
/// This type is returned from the [`TextDiff::iter_changes`] method. It
/// exists so that it's more convenient to work with textual differences as
/// the underlying [`DiffOp`] does not know anything about strings.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
pub struct Change<'s> {
tag: ChangeTag,
old_index: Option<usize>,
new_index: Option<usize>,
value: &'s str,
}
impl<'s> Change<'s> {
/// Returns the change tag.
pub fn tag(&self) -> ChangeTag {
self.tag
}
/// Returns the old index if available.
pub fn old_index(&self) -> Option<usize> {
self.old_index
}
/// Returns the new index if available.
pub fn new_index(&self) -> Option<usize> {
self.new_index
}
/// Returns the changed value.
pub fn value(&self) -> &'s str {
self.value
}
}
impl ChangeTag {
/// Returns the unified sign of this change. /// Returns the unified sign of this change.
///
/// This is the prefix rendered into a unified diff:
///
/// * `Equal`: an empty space (` `)
/// * `Delete: a minus sign (`-`)
/// * `Insert: a plus sign (`+`)
pub fn unified_sign(self) -> char { pub fn unified_sign(self) -> char {
match self { match self {
Change::Equal => ' ', ChangeTag::Equal => ' ',
Change::Delete => '-', ChangeTag::Delete => '-',
Change::Insert => '+', ChangeTag::Insert => '+',
} }
} }
} }
@ -164,28 +206,18 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
&self.new &self.new
} }
/// Return the old slices for an op.
pub fn old_slices_for_op(&self, op: &DiffOp) -> &[&'old str] {
&self.old_slices()[op.old_range()]
}
/// Return the new slices for an op.
pub fn new_slices_for_op(&self, op: &DiffOp) -> &[&'new str] {
&self.new_slices()[op.new_range()]
}
/// Iterates over the changes the op expands to. /// Iterates over the changes the op expands to.
/// ///
/// The fields are in the form `(change, old_index, new_index, string)`. /// This method is a convenient way to automatically resolve the different
pub fn iter_op( /// ways in which a change could be encoded (insert/delete vs replace), look
&self, /// up the value from the appropriate slice and also handle correct index
op: &DiffOp, /// handling.
) -> impl Iterator<Item = (Change, Option<usize>, Option<usize>, &str)> { pub fn iter_changes(&self, op: &DiffOp) -> impl Iterator<Item = Change> {
let (tag, old_range, new_range) = op.as_tag_tuple(); let (tag, old_range, new_range) = op.as_tag_tuple();
let mut old_index = old_range.start; let mut old_index = old_range.start;
let mut new_index = new_range.start; let mut new_index = new_range.start;
let mut old_slices = self.old_slices_for_op(op); let mut old_slices = &self.old_slices()[op.old_range()];
let mut new_slices = self.new_slices_for_op(op); let mut new_slices = &self.new_slices()[op.new_range()];
std::iter::from_fn(move || match tag { std::iter::from_fn(move || match tag {
DiffTag::Equal => { DiffTag::Equal => {
@ -193,12 +225,12 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
old_slices = rest; old_slices = rest;
old_index += 1; old_index += 1;
new_index += 1; new_index += 1;
Some(( Some(Change {
Change::Equal, tag: ChangeTag::Equal,
Some(old_index - 1), old_index: Some(old_index - 1),
Some(new_index - 1), new_index: Some(new_index - 1),
first, value: first,
)) })
} else { } else {
None None
} }
@ -207,7 +239,12 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
if let Some((&first, rest)) = old_slices.split_first() { if let Some((&first, rest)) = old_slices.split_first() {
old_slices = rest; old_slices = rest;
old_index += 1; old_index += 1;
Some((Change::Delete, Some(old_index - 1), None, first)) Some(Change {
tag: ChangeTag::Delete,
old_index: Some(old_index - 1),
new_index: None,
value: first,
})
} else { } else {
None None
} }
@ -216,7 +253,12 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
if let Some((&first, rest)) = new_slices.split_first() { if let Some((&first, rest)) = new_slices.split_first() {
new_slices = rest; new_slices = rest;
new_index += 1; new_index += 1;
Some((Change::Insert, None, Some(new_index - 1), first)) Some(Change {
tag: ChangeTag::Insert,
old_index: None,
new_index: Some(new_index - 1),
value: first,
})
} else { } else {
None None
} }
@ -225,11 +267,21 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
if let Some((&first, rest)) = old_slices.split_first() { if let Some((&first, rest)) = old_slices.split_first() {
old_slices = rest; old_slices = rest;
old_index += 1; old_index += 1;
Some((Change::Delete, Some(old_index - 1), None, first)) Some(Change {
tag: ChangeTag::Delete,
old_index: Some(old_index - 1),
new_index: None,
value: first,
})
} else if let Some((&first, rest)) = new_slices.split_first() { } else if let Some((&first, rest)) = new_slices.split_first() {
new_slices = rest; new_slices = rest;
new_index += 1; new_index += 1;
Some((Change::Insert, None, Some(new_index - 1), first)) Some(Change {
tag: ChangeTag::Insert,
old_index: None,
new_index: Some(new_index - 1),
value: first,
})
} else { } else {
None None
} }
@ -306,8 +358,14 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
UnifiedRange(group[group.len() - 1].new_range()), UnifiedRange(group[group.len() - 1].new_range()),
)?; )?;
for op in group { for op in group {
for (change, _, _, s) in self.iter_op(&op) { for change in self.iter_changes(&op) {
write!(&mut w, "{}{}{}", change.unified_sign(), s, nl)?; write!(
&mut w,
"{}{}{}",
change.tag().unified_sign(),
change.value(),
nl
)?;
} }
} }
} }
@ -473,46 +531,46 @@ fn test_line_ops() {
let changes = diff let changes = diff
.ops() .ops()
.iter() .iter()
.flat_map(|op| diff.iter_op(op)) .flat_map(|op| diff.iter_changes(op))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
insta::assert_debug_snapshot!(&changes, @r###" insta::assert_debug_snapshot!(&changes, @r###"
[ [
( Change {
Equal, tag: Equal,
Some( old_index: Some(
0, 0,
), ),
Some( new_index: Some(
0, 0,
), ),
"Hello World\n", value: "Hello World\n",
), },
( Change {
Delete, tag: Delete,
Some( old_index: Some(
1, 1,
), ),
None, new_index: None,
"some stuff here\n", value: "some stuff here\n",
), },
( Change {
Insert, tag: Insert,
None, old_index: None,
Some( new_index: Some(
1, 1,
), ),
"some amazing stuff here\n", value: "some amazing stuff here\n",
), },
( Change {
Equal, tag: Equal,
Some( old_index: Some(
2, 2,
), ),
Some( new_index: Some(
2, 2,
), ),
"some more stuff here\n", value: "some more stuff here\n",
), },
] ]
"###); "###);
} }