diffy_fork_filenames/
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::{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    for pos in iter::once(pos).chain(interleave(backward, forward)) {
155        if match_fragment(image, hunk.lines(), pos) {
156            return Some(pos);
157        }
158    }
159
160    None
161}
162
163fn pre_image_line_count<T: ?Sized>(lines: &[Line<'_, T>]) -> usize {
164    pre_image(lines).count()
165}
166
167fn post_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
168    lines.iter().filter_map(|line| match line {
169        Line::Context(l) | Line::Insert(l) => Some(*l),
170        Line::Delete(_) => None,
171    })
172}
173
174fn pre_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
175    lines.iter().filter_map(|line| match line {
176        Line::Context(l) | Line::Delete(l) => Some(*l),
177        Line::Insert(_) => None,
178    })
179}
180
181fn match_fragment<T: PartialEq + ?Sized>(
182    image: &[ImageLine<T>],
183    lines: &[Line<'_, T>],
184    pos: usize,
185) -> bool {
186    let len = pre_image_line_count(lines);
187
188    let image = if let Some(image) = image.get(pos..pos + len) {
189        image
190    } else {
191        return false;
192    };
193
194    // If any of these lines have already been patched then we can't match at this position
195    if image.iter().any(ImageLine::is_patched) {
196        return false;
197    }
198
199    pre_image(lines).eq(image.iter().map(ImageLine::inner))
200}
201
202#[derive(Debug)]
203struct Interleave<I, J> {
204    a: iter::Fuse<I>,
205    b: iter::Fuse<J>,
206    flag: bool,
207}
208
209fn interleave<I, J>(
210    i: I,
211    j: J,
212) -> Interleave<<I as IntoIterator>::IntoIter, <J as IntoIterator>::IntoIter>
213where
214    I: IntoIterator,
215    J: IntoIterator<Item = I::Item>,
216{
217    Interleave {
218        a: i.into_iter().fuse(),
219        b: j.into_iter().fuse(),
220        flag: false,
221    }
222}
223
224impl<I, J> Iterator for Interleave<I, J>
225where
226    I: Iterator,
227    J: Iterator<Item = I::Item>,
228{
229    type Item = I::Item;
230
231    fn next(&mut self) -> Option<I::Item> {
232        self.flag = !self.flag;
233        if self.flag {
234            match self.a.next() {
235                None => self.b.next(),
236                item => item,
237            }
238        } else {
239            match self.b.next() {
240                None => self.a.next(),
241                item => item,
242            }
243        }
244    }
245}