use diff;
pub mod input;
pub mod print;
use crate::input::Settings;
#[derive(Debug,PartialEq,Eq,Clone,Copy)]
pub enum Side {
Left,
Right,
Both
}
impl Side {
fn from_result<A>(result: &diff::Result<A>) -> Side {
match result {
diff::Result::Left(_) => Side::Left,
diff::Result::Right(_) => Side::Right,
diff::Result::Both(_, _) => Side::Both
}
}
}
#[derive(Debug)]
pub struct Line<'a> {
pub left_offs: usize,
pub right_offs: usize,
pub content: Vec<(&'a u8, Side)>
}
pub struct Batch<'a> {
pub lines: Vec<Line<'a>>,
pub left_diff: usize,
pub right_diff: usize
}
impl<'a> Batch<'a> {
fn extend(&mut self, mut other: Batch<'a>) -> &Batch {
self.lines.append(&mut other.lines);
self.left_diff += other.left_diff;
self.right_diff += other.right_diff;
self
}
}
fn get_byte<'a, A>(res: &diff::Result<&'a A>) -> &'a A {
match res {
diff::Result::Both(b, _) => b,
diff::Result::Left(b) => b,
diff::Result::Right(b) => b
}
}
fn split_apply<A, B, C>(fn1: fn(&A) -> B, fn2: fn(&A) -> C) -> impl Fn(A) -> (B, C) {
move |inp| (fn1(&inp), fn2(&inp))
}
pub fn diff_file<'a>(left: &'a Vec<u8>, right: &'a Vec<u8>, settings: &'a Settings) -> Vec<Batch<'a>> {
let mut out = vec![];
let mut left_offs = 0;
let mut right_offs = 0;
let mut left_start_offs = 0;
let mut right_start_offs = 0;
let mut curr = vec![];
let mut backbuffer: Vec<Line> = vec![];
let mut must_print = 0;
let mut left_diff = 0;
let mut right_diff = 0;
let mut preliminary_entry = None;
let mut has_either = 0;
for bd in diff::slice(left, right) {
match bd {
diff::Result::Both(_, _) => {
left_offs += 1;
right_offs += 1;
has_either = 1;
},
diff::Result::Left(_) => {
left_offs += 1;
left_diff += 1;
must_print = settings.after + 1;
has_either = (settings.align)(1, has_either);
},
diff::Result::Right(_) => {
right_offs += 1;
right_diff += 1;
must_print = settings.after + 1;
has_either = (settings.align)(has_either, 1);
},
}
curr.push(bd);
if (settings.align)(left_offs, right_offs) % settings.width == 0 && has_either == 1 {
backbuffer.push(Line { left_offs: left_start_offs, right_offs: right_start_offs, content: curr.into_iter().map(split_apply(get_byte, Side::from_result)).collect() });
has_either = 0;
if must_print > 0 {
must_print -= 1;
if must_print == 0 {
let mut batch = preliminary_entry.unwrap_or(Batch { lines: vec![], left_diff: 0, right_diff: 0 });
batch.extend(Batch { lines: backbuffer, left_diff, right_diff });
preliminary_entry = Some(batch);
left_diff = 0;
right_diff = 0;
backbuffer = vec![];
}
} else {
if backbuffer.len() > settings.before {
backbuffer.drain(0..1);
if preliminary_entry.is_some() {
let mut batch = preliminary_entry.unwrap();
batch.extend(Batch { lines: vec![], left_diff, right_diff });
out.push(batch);
preliminary_entry = None;
}
}
}
left_start_offs = left_offs;
right_start_offs = right_offs;
curr = vec![];
}
}
if must_print > 0 {
let mut batch = preliminary_entry.unwrap_or(Batch { lines: vec![], left_diff: 0, right_diff: 0});
if curr.len() > 0 {
backbuffer.push(Line { left_offs: left_start_offs, right_offs: right_start_offs, content: curr.into_iter().map(split_apply(get_byte, Side::from_result)).collect() });
}
batch.extend(Batch { lines: backbuffer, left_diff, right_diff });
out.push(batch);
} else if preliminary_entry.is_some() {
let batch = preliminary_entry.unwrap();
out.push(batch);
}
out
}
#[cfg(test)]
use input::{DEFAULT_SETTINGS, ALIGN_LEFT, ALIGN_RIGHT};
#[test]
fn test_bdiff_equiv_files() {
let left = vec![1,2,3,4,5,6,7,8];
let right = vec![1,2,3,4,5,6,7,8];
let diff = diff_file(&left, &right, &DEFAULT_SETTINGS);
assert_eq!(diff.len(), 0);
}
#[test]
fn test_bdiff_returns_diff_batch() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,3,4,3,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 3);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10); assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Left && *b.0 == 4).count(), 2);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Right && *b.0 == 3).count(), 2);
}
#[test]
fn test_bdiff_handles_diff_at_file_start() {
let left = vec![4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![4,4,3,4,3,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 2);
assert_eq!(diff[0].lines[1].content.len(), 8);
assert_eq!(diff[0].lines[0].content.len(), 10); }
#[test]
fn test_bdiff_handles_diff_at_file_end() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,3,4,3,4,4,4];
let settings = Settings { before: 1, after: 1, width: 8, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 2);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10); }
#[test]
fn test_bdiff_aligns_removes_correctly() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, align: ALIGN_LEFT, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 3);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 8);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Left && *b.0 == 4).count(), 2);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Right).count(), 0);
}
#[test]
fn test_bdiff_aligns_removes_correctly_right() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, align: ALIGN_RIGHT, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 3);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10); assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Left && *b.0 == 4).count(), 2);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Right).count(), 0);
}
#[test]
fn test_bdiff_aligns_adds_correctly() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, align: ALIGN_LEFT, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 3);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10); assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Left).count(), 0);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Right && *b.0 == 4).count(), 2);
}
#[test]
fn test_bdiff_aligns_adds_correctly_right() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, align: ALIGN_RIGHT, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 3);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 8);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Left).count(), 0);
assert_eq!(diff[0].lines[1].content.iter().filter(|b| b.1 == Side::Right && *b.0 == 4).count(), 2);
}
#[test]
fn test_bdiff_concats_adjacent_batches() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,3,4,4,3,4,
5,7,5,5,7,5,5,5,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 4);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[3].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10);
assert_eq!(diff[0].lines[2].content.len(), 10);
}
#[test]
fn test_bdiff_concats_adjacent_batches_with_before_and_after_between() {
let left = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,4,4,4,4,4,
9,9,9,9,9,9,9,9,
9,9,9,9,9,9,9,9,
5,5,5,5,5,5,5,5,
0,8,7,6,5,4,3,2,1,2,3];
let right = vec![1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,
4,4,4,3,4,4,3,4,
9,9,9,9,9,9,9,9,
9,9,9,9,9,9,9,9,
5,7,5,5,7,5,5,5,
0,8,7,6,5,4,3,2,1,2,3];
let settings = Settings { before: 1, after: 1, width: 8, ..DEFAULT_SETTINGS };
let diff = diff_file(&left, &right, &settings);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].lines.len(), 6);
assert_eq!(diff[0].lines[0].content.len(), 8);
assert_eq!(diff[0].lines[2].content.len(), 8);
assert_eq!(diff[0].lines[3].content.len(), 8);
assert_eq!(diff[0].lines[5].content.len(), 8);
assert_eq!(diff[0].lines[1].content.len(), 10);
assert_eq!(diff[0].lines[4].content.len(), 10);
}