#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LineKind {
Context,
Add,
Del,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Line {
pub kind: LineKind,
pub text: Vec<u8>,
pub no_newline: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hunk {
pub old_start: u32,
pub old_lines: u32,
pub new_start: u32,
pub new_lines: u32,
pub section: Vec<u8>,
pub lines: Vec<Line>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FileContent {
Text(Vec<Hunk>),
Binary(Vec<Vec<u8>>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FileDiff {
pub headers: Vec<Vec<u8>>,
pub trailer: Vec<(usize, Vec<u8>)>,
pub old_path: Option<Vec<u8>>,
pub new_path: Option<Vec<u8>>,
pub content: FileContent,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Patch {
pub preamble: Vec<Vec<u8>>,
pub files: Vec<FileDiff>,
pub no_trailing_newline: bool,
}
impl FileDiff {
pub fn display_path(&self) -> String {
let real = |p: &Option<Vec<u8>>| match p.as_deref() {
Some(b"/dev/null") | None => None,
Some(b) => Some(b.to_vec()),
};
real(&self.new_path)
.or_else(|| real(&self.old_path))
.map(|b| String::from_utf8_lossy(&b).into_owned())
.unwrap_or_default()
}
pub fn hunk_count(&self) -> usize {
self.content.hunk_count()
}
}
impl FileContent {
pub fn hunk_count(&self) -> usize {
match self {
FileContent::Text(hunks) => hunks.len(),
FileContent::Binary(_) => 0,
}
}
}
pub(crate) fn count_kinds(lines: &[Line]) -> (u32, u32, u32) {
let mut ctx = 0;
let mut add = 0;
let mut del = 0;
for l in lines {
match l.kind {
LineKind::Context => ctx += 1,
LineKind::Add => add += 1,
LineKind::Del => del += 1,
}
}
(ctx, add, del)
}
impl Hunk {
pub fn change_counts(&self) -> (u32, u32) {
let (_, add, del) = count_kinds(&self.lines);
(add, del)
}
pub fn changed_lines(&self) -> impl Iterator<Item = (usize, &Line)> {
self.lines
.iter()
.filter(|l| !matches!(l.kind, LineKind::Context))
.enumerate()
.map(|(idx, l)| (idx + 1, l))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_path_of_a_deletion_is_the_old_path() {
let f = FileDiff {
headers: Vec::new(),
trailer: Vec::new(),
old_path: Some(b"f2".to_vec()),
new_path: Some(b"/dev/null".to_vec()),
content: FileContent::Text(Vec::new()),
};
assert_eq!(f.display_path(), "f2");
}
#[test]
fn change_counts_counts_add_and_del() {
let h = Hunk {
old_start: 1,
old_lines: 2,
new_start: 1,
new_lines: 2,
section: Vec::new(),
lines: vec![
Line {
kind: LineKind::Context,
text: b"a".to_vec(),
no_newline: None,
},
Line {
kind: LineKind::Del,
text: b"b".to_vec(),
no_newline: None,
},
Line {
kind: LineKind::Add,
text: b"c".to_vec(),
no_newline: None,
},
],
};
assert_eq!(h.change_counts(), (1, 1));
}
}