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 lists_rename_to(extended: &[&str], rename: &str) -> bool {
for line in extended {
if *line == rename {
return true;
}
}
return false;
}
fn is_hunk_or_section_line(line: &&str) -> bool {
return line.starts_with(HUNK_PREFIX) || line.starts_with(SECTION_PREFIX);
}
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}") || lists_rename_to(extended, &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 = match patch_lines[index..].iter().position(is_hunk_or_section_line) {
Some(offset) => index + offset,
None => patch_lines.len(),
};
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;