use crate::{
patch::{Hunk, Line, Patch},
utils::LineIter,
};
use std::{fmt, iter};
#[derive(Debug)]
pub struct ApplyError(usize);
impl fmt::Display for ApplyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "error applying hunk #{}", self.0)
}
}
impl std::error::Error for ApplyError {}
#[derive(Debug)]
enum ImageLine<'a, T: ?Sized> {
Unpatched(&'a T),
Patched(&'a T),
}
impl<'a, T: ?Sized> ImageLine<'a, T> {
fn inner(&self) -> &'a T {
match self {
ImageLine::Unpatched(inner) | ImageLine::Patched(inner) => inner,
}
}
fn into_inner(self) -> &'a T {
self.inner()
}
fn is_patched(&self) -> bool {
match self {
ImageLine::Unpatched(_) => false,
ImageLine::Patched(_) => true,
}
}
}
impl<T: ?Sized> Copy for ImageLine<'_, T> {}
impl<T: ?Sized> Clone for ImageLine<'_, T> {
fn clone(&self) -> Self {
*self
}
}
pub fn apply(base_image: &str, patch: &Patch<'_, str>) -> Result<String, ApplyError> {
let mut image: Vec<_> = LineIter::new(base_image)
.map(ImageLine::Unpatched)
.collect();
for (i, hunk) in patch.hunks().iter().enumerate() {
apply_hunk(&mut image, hunk).map_err(|_| ApplyError(i + 1))?;
}
Ok(image.into_iter().map(ImageLine::into_inner).collect())
}
pub fn apply_bytes(base_image: &[u8], patch: &Patch<'_, [u8]>) -> Result<Vec<u8>, ApplyError> {
let mut image: Vec<_> = LineIter::new(base_image)
.map(ImageLine::Unpatched)
.collect();
for (i, hunk) in patch.hunks().iter().enumerate() {
apply_hunk(&mut image, hunk).map_err(|_| ApplyError(i + 1))?;
}
Ok(image
.into_iter()
.flat_map(ImageLine::into_inner)
.copied()
.collect())
}
fn apply_hunk<'a, T: PartialEq + ?Sized>(
image: &mut Vec<ImageLine<'a, T>>,
hunk: &Hunk<'a, T>,
) -> Result<(), ()> {
let pos = find_position(image, hunk).ok_or(())?;
image.splice(
pos..pos + pre_image_line_count(hunk.lines()),
post_image(hunk.lines()).map(ImageLine::Patched),
);
Ok(())
}
fn find_position<T: PartialEq + ?Sized>(
image: &[ImageLine<T>],
hunk: &Hunk<'_, T>,
) -> Option<usize> {
let pos = std::cmp::min(hunk.new_range().start().saturating_sub(1), image.len());
let backward = (0..pos).rev();
let forward = pos + 1..image.len();
iter::once(pos)
.chain(interleave(backward, forward))
.find(|&pos| match_fragment(image, hunk.lines(), pos))
}
fn pre_image_line_count<T: ?Sized>(lines: &[Line<'_, T>]) -> usize {
pre_image(lines).count()
}
fn post_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
lines.iter().filter_map(|line| match line {
Line::Context(l) | Line::Insert(l) => Some(*l),
Line::Delete(_) => None,
})
}
fn pre_image<'a, 'b, T: ?Sized>(lines: &'b [Line<'a, T>]) -> impl Iterator<Item = &'a T> + 'b {
lines.iter().filter_map(|line| match line {
Line::Context(l) | Line::Delete(l) => Some(*l),
Line::Insert(_) => None,
})
}
fn match_fragment<T: PartialEq + ?Sized>(
image: &[ImageLine<T>],
lines: &[Line<'_, T>],
pos: usize,
) -> bool {
let len = pre_image_line_count(lines);
let image = if let Some(image) = image.get(pos..pos + len) {
image
} else {
return false;
};
if image.iter().any(ImageLine::is_patched) {
return false;
}
pre_image(lines).eq(image.iter().map(ImageLine::inner))
}
#[derive(Debug)]
struct Interleave<I, J> {
a: iter::Fuse<I>,
b: iter::Fuse<J>,
flag: bool,
}
fn interleave<I, J>(
i: I,
j: J,
) -> Interleave<<I as IntoIterator>::IntoIter, <J as IntoIterator>::IntoIter>
where
I: IntoIterator,
J: IntoIterator<Item = I::Item>,
{
Interleave {
a: i.into_iter().fuse(),
b: j.into_iter().fuse(),
flag: false,
}
}
impl<I, J> Iterator for Interleave<I, J>
where
I: Iterator,
J: Iterator<Item = I::Item>,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
self.flag = !self.flag;
if self.flag {
match self.a.next() {
None => self.b.next(),
item => item,
}
} else {
match self.b.next() {
None => self.a.next(),
item => item,
}
}
}
}