bdiff 0.1.0

Binary file diff
Documentation
use diff;
pub mod input;
pub mod print;
use crate::input::Settings;

/// The side a byte comes from.
///
/// Represents the file a byte was found in.
/// Either `Left` or `Right` if found in one of the two files, or `Both` if found in both.
#[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
        }
    }
}

/// A single diff line.
///
/// Represents a single line in a diff (batch of bytes aligned to a given width).
#[derive(Debug)]
pub struct Line<'a> {
    /// Starting offset of the line in the left file.
    pub left_offs: usize,
    /// Starting offset of the line in the right file.
    pub right_offs: usize,
    /// Bytes the line consists of.
    pub content: Vec<(&'a u8, Side)>
}

/// A batch of lines.
///
/// Represents a batch of one or more diffs with context before and after.
pub struct Batch<'a> {
    /// The lines in the batch.
    pub lines: Vec<Line<'a>>,
    /// Number of different bytes in the left file in this batch.
    pub left_diff: usize,
    /// Number of different bytes in the right file in this batch.
    pub right_diff: usize
}

impl<'a> Batch<'a> {
    /// Concatenates two batches.
    ///
    /// The batches are assumed to lie right next to each other.
    ///
    /// # Arguments
    ///
    /// * `other`: The other batch to concatenate to this one. Will be consumed.
    ///
    /// # Returns
    ///
    /// Returns `self`.
    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))
}

/// Calculates the difference between two byte vectors.
///
/// Finds differences between the two vectors and returns them with some context. Groups this information into `lines`.
///
/// # Arguments
///
/// * `left`: The first vector to compare.
/// * `right`: The second vector to compare.
/// * `settings`: Additional parameters such as line length, lines to include before and after each difference (for context) and how to align bytes.
///
/// # Returns
///
/// A vector of batches, each corresponding to one or more adjacent changes.
///
/// # Example
///
/// ```
/// # use bdiff::input::DEFAULT_SETTINGS;
/// # use bdiff::Side::{Both,Left,Right};
/// # use bdiff::diff_file;
/// let left = vec![1,2,3,4,5];
/// let right = vec![1,2,8,4,5];
/// let result = diff_file(&left, &right, &DEFAULT_SETTINGS);
/// assert_eq!(result[0].lines[0].content, vec![
///     (&1, Both),
///     (&2, Both),
///     (&3, Left),
///     (&8, Right),
///     (&4, Both),
///     (&5, Both)
/// ]);
/// ```
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); // 2 changes
    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); // 2 changes
}

#[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); // 2 changes
}

#[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); // 2 removes
    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); // 2 adds
    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);
}