diffy_imara/
apply.rs

1use crate::{
2    patch::{Hunk, Line, Patch},
3    utils::LineIter,
4};
5use std::{fmt, iter};
6
7/// An error returned when [`apply`]ing a `Patch` fails
8///
9/// [`apply`]: fn.apply.html
10#[derive(Debug)]
11pub struct ApplyError(usize);
12
13impl fmt::Display for ApplyError {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        write!(f, "error applying hunk #{}", self.0)
16    }
17}
18
19impl std::error::Error for ApplyError {}
20
21#[derive(Debug)]
22enum ImageLine<'a, T: ?Sized> {
23    Unpatched(&'a T),
24    Patched(&'a T),
25}
26
27impl<'a, T: ?Sized> ImageLine<'a, T> {
28    fn inner(&self) -> &'a T {
29        match self {
30            ImageLine::Unpatched(inner) | ImageLine::Patched(inner) => inner,
31        }
32    }
33
34    fn into_inner(self) -> &'a T {
35        self.inner()
36    }
37
38    fn is_patched(&self) -> bool {
39        match self {
40            ImageLine::Unpatched(_) => false,
41            ImageLine::Patched(_) => true,
42        }
43    }
44}
45
46impl<T: ?Sized> Copy for ImageLine<'_, T> {}
47
48impl<T: ?Sized> Clone for ImageLine<'_, T> {
49    fn clone(&self) -> Self {
50        *self
51    }
52}
53
54/// Apply a `Patch` to a base image
55///
56/// ```
57/// use diffy_imara::{apply, Patch};
58///
59/// let s = "\
60/// --- a/ideals
61/// +++ b/ideals
62/// @@ -1,4 +1,6 @@
63///  First:
64///      Life before death,
65///      strength before weakness,
66///      journey before destination.
67/// +Second:
68/// +    I will protect those who cannot protect themselves.
69/// ";
70///
71/// let patch = Patch::from_str(s).unwrap();
72///
73/// let base_image = "\
74/// First:
75///     Life before death,
76///     strength before weakness,
77///     journey before destination.
78/// ";
79///
80/// let expected = "\
81/// First:
82///     Life before death,
83///     strength before weakness,
84///     journey before destination.
85/// Second:
86///     I will protect those who cannot protect themselves.
87/// ";
88///
89/// assert_eq!(apply(base_image, &patch).unwrap(), expected);
90/// ```
91pub fn apply(base_image: &str, patch: &Patch<'_, str>) -> Result<String, ApplyError> {
92    let mut image: Vec<_> = LineIter::new(base_image)
93        .map(ImageLine::Unpatched)
94        .collect();
95
96    for (i, hunk) in patch.hunks().iter().enumerate() {
97        apply_hunk(&mut image, hunk).map_err(|_| ApplyError(i + 1))?;
98    }
99
100    Ok(image.into_iter().map(ImageLine::into_inner).collect())
101}
102
103/// Apply a non-utf8 `Patch` to a base image
104pub fn apply_bytes(base_image: &[u8], patch: &Patch<'_, [u8]>) -> Result<Vec<u8>, ApplyError> {
105    let mut image: Vec<_> = LineIter::new(base_image)
106        .map(ImageLine::Unpatched)
107        .collect();
108
109    for (i, hunk) in patch.hunks().iter().enumerate() {
110        apply_hunk(&mut image, hunk).map_err(|_| ApplyError(i + 1))?;
111    }
112
113    Ok(image
114        .into_iter()
115        .flat_map(ImageLine::into_inner)
116        .copied()
117        .collect())
118}
119
120fn apply_hunk<'a, T: PartialEq + ?Sized>(
121    image: &mut Vec<ImageLine<'a, T>>,
122    hunk: &Hunk<'a, T>,
123) -> Result<(), ()> {
124    // Find position
125    let pos = find_position(image, hunk).ok_or(())?;
126
127    // update image
128    image.splice(
129        pos..pos + pre_image_line_count(hunk.lines()),
130        post_image(hunk.lines()).map(ImageLine::Patched),
131    );
132
133    Ok(())
134}
135
136// Search in `image` for a palce to apply hunk.
137// This follows the general algorithm (minus fuzzy-matching context lines) described in GNU patch's
138// man page.
139//
140// It might be worth looking into other possible positions to apply the hunk to as described here:
141// https://neil.fraser.name/writing/patch/
142fn find_position<T: PartialEq + ?Sized>(
143    image: &[ImageLine<T>],
144    hunk: &Hunk<'_, T>,
145) -> Option<usize> {
146    // In order to avoid searching through positions which are out of bounds of the image,
147    // clamp the starting position based on the length of the image
148    let pos = std::cmp::min(hunk.new_range().start().saturating_sub(1), image.len());
149
150    // Create an iterator that starts with 'pos' and then interleaves
151    // moving pos backward/foward by one.
152    let backward = (0..pos).rev();
153    let forward = pos + 1..image.len();
154
155    iter::once(pos)
156        .chain(interleave(backward, forward))
157        .find(|&pos| match_fragment(image, hunk.lines(), pos))
158}
159
160fn pre_image_line_count<T: ?Sized>(lines: &[Line<'_, T>]) -> usize {
161    pre_image(lines).count()
162}
163
164fn post_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
165    lines.iter().filter_map(|line| match line {
166        Line::Context(l) | Line::Insert(l) => Some(*l),
167        Line::Delete(_) => None,
168    })
169}
170
171fn pre_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
172    lines.iter().filter_map(|line| match line {
173        Line::Context(l) | Line::Delete(l) => Some(*l),
174        Line::Insert(_) => None,
175    })
176}
177
178fn match_fragment<T: PartialEq + ?Sized>(
179    image: &[ImageLine<T>],
180    lines: &[Line<'_, T>],
181    pos: usize,
182) -> bool {
183    let len = pre_image_line_count(lines);
184
185    let image = if let Some(image) = image.get(pos..pos + len) {
186        image
187    } else {
188        return false;
189    };
190
191    // If any of these lines have already been patched then we can't match at this position
192    if image.iter().any(ImageLine::is_patched) {
193        return false;
194    }
195
196    pre_image(lines).eq(image.iter().map(ImageLine::inner))
197}
198
199#[derive(Debug)]
200struct Interleave<I, J> {
201    a: iter::Fuse<I>,
202    b: iter::Fuse<J>,
203    flag: bool,
204}
205
206fn interleave<I, J>(
207    i: I,
208    j: J,
209) -> Interleave<<I as IntoIterator>::IntoIter, <J as IntoIterator>::IntoIter>
210where
211    I: IntoIterator,
212    J: IntoIterator<Item = I::Item>,
213{
214    Interleave {
215        a: i.into_iter().fuse(),
216        b: j.into_iter().fuse(),
217        flag: false,
218    }
219}
220
221impl<I, J> Iterator for Interleave<I, J>
222where
223    I: Iterator,
224    J: Iterator<Item = I::Item>,
225{
226    type Item = I::Item;
227
228    fn next(&mut self) -> Option<I::Item> {
229        self.flag = !self.flag;
230        if self.flag {
231            match self.a.next() {
232                None => self.b.next(),
233                item => item,
234            }
235        } else {
236            match self.b.next() {
237                None => self.a.next(),
238                item => item,
239            }
240        }
241    }
242}