const SECTION_PREFIX: &str = "diff --git ";
const HUNK_PREFIX: &str = "@@ ";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NewSpan {
pub first: u64,
pub last: u64,
}
fn section_is_for(header: &str, extended: &[&str], name: &str) -> bool {
let rename: String = format!("rename to {name}");
return header == format!("{SECTION_PREFIX}a/{name} b/{name}") || extended.iter().any(|line| return *line == rename);
}
fn parse_new_start(line: &str) -> Option<u64> {
let mut sides = line.strip_prefix("@@ -")?.split(' ');
let _old = sides.next()?;
let new: &str = sides.next()?.strip_prefix('+')?;
let (start_text, length_text) = new.split_once(',').unwrap_or((new, "1"));
let start: u64 = start_text.parse::<u64>().ok()?;
let length: u64 = length_text.parse::<u64>().ok()?;
return Some(if length == 0 { start + 1 } else { start });
}
pub fn new_spans(patch_lines: &[&str], name: &str) -> Vec<NewSpan> {
let mut spans: Vec<NewSpan> = Vec::new();
let mut index: usize = 0;
while index < patch_lines.len() {
let header: &str = patch_lines[index];
index += 1;
if !header.starts_with(SECTION_PREFIX) {
continue;
}
let first_hunk: usize = patch_lines[index..]
.iter()
.position(|line| return line.starts_with(HUNK_PREFIX) || line.starts_with(SECTION_PREFIX))
.map_or(patch_lines.len(), |offset| return index + offset);
let matches: bool = section_is_for(header, &patch_lines[index..first_hunk], name);
index = first_hunk;
if matches {
read_hunks(patch_lines, &mut index, &mut spans);
return spans;
}
}
return spans;
}
fn read_hunks(patch_lines: &[&str], index: &mut usize, spans: &mut Vec<NewSpan>) {
let mut new_line: u64 = 0;
let mut open: Option<NewSpan> = None;
while *index < patch_lines.len() {
let line: &str = patch_lines[*index];
if line.starts_with(SECTION_PREFIX) {
break;
}
*index += 1;
if line.starts_with(HUNK_PREFIX) {
close_run(&mut open, spans);
new_line = parse_new_start(line).unwrap_or(new_line);
continue;
}
match line.as_bytes().first() {
Some(b'-') => {
open = Some(open.unwrap_or(NewSpan { first: new_line, last: new_line }));
}
Some(b'+') => {
let run: NewSpan = open.unwrap_or(NewSpan { first: new_line, last: new_line });
open = Some(NewSpan { first: run.first, last: new_line });
new_line += 1;
}
Some(b' ') => {
close_run(&mut open, spans);
new_line += 1;
}
_ => {}
}
}
close_run(&mut open, spans);
}
fn close_run(open: &mut Option<NewSpan>, spans: &mut Vec<NewSpan>) {
if let Some(run) = open.take() {
spans.push(run);
}
}
#[cfg(test)]
#[path = "standards_review_patch_tests.rs"]
mod tests;