diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-04 20:09:47 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-05 22:47:43 +0100 |
| commit | a5c07e033b4660e1aabf45ec42eb1e4b4c07db8f (patch) | |
| tree | ce362e2f9d35c40792c2d1bad17a4f50d8b3605d /pkgs/by-name/ro/rollup/src/parse.rs | |
| parent | pkgs/rollup: Init (diff) | |
| download | nixos-config-a5c07e033b4660e1aabf45ec42eb1e4b4c07db8f.zip | |
pkgs/rollup: Finish first mvp
Diffstat (limited to 'pkgs/by-name/ro/rollup/src/parse.rs')
| -rw-r--r-- | pkgs/by-name/ro/rollup/src/parse.rs | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/pkgs/by-name/ro/rollup/src/parse.rs b/pkgs/by-name/ro/rollup/src/parse.rs new file mode 100644 index 00000000..ceb9d44f --- /dev/null +++ b/pkgs/by-name/ro/rollup/src/parse.rs @@ -0,0 +1,289 @@ +use core::fmt; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{Context, Result}; +use pretty_assertions::assert_eq; + +pub(crate) struct Patch { + pub(crate) files: Vec<FileDiff>, + pub(crate) commit_message: String, +} + +impl fmt::Display for Patch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for file in &self.files { + write!(f, "{}", file)?; + } + + Ok(()) + } +} + +#[derive(Clone)] +pub(crate) enum FileDiff { + Add { + path: PathBuf, + diff: String, + }, + Remove { + path: PathBuf, + diff: String, + index: String, + deleted: String, + }, + Change { + from: PathBuf, + to: PathBuf, + diff: String, + index: String, + }, +} +impl FileDiff { + fn from_parts( + from: PathBuf, + to: PathBuf, + diff: String, + index: Option<String>, + deleted: Option<String>, + ) -> Self { + let diff = diff.trim().to_owned(); + + if from.display().to_string() == "/dev/null" { + Self::Add { path: to, diff } + } else if to.display().to_string() == "/dev/null" { + Self::Remove { + path: from, + diff, + index: index.unwrap(), + deleted: deleted.unwrap(), + } + } else { + Self::Change { + from, + to, + diff, + index: index.unwrap(), + } + } + } +} + +impl fmt::Display for FileDiff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FileDiff::Add { path, diff } => { + // writeln!(f, "diff --git /dev/null b/{}", path.display())?; + writeln!(f, "--- /dev/null")?; + writeln!(f, "+++ b/{}", path.display())?; + writeln!(f, "{}", diff)?; + } + FileDiff::Remove { + path, + diff, + index, + deleted, + } => { + writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?; + writeln!(f, "deleted {}", deleted)?; + writeln!(f, "index {}", index)?; + writeln!(f, "+++ /dev/null")?; + writeln!(f, "{}", diff)?; + } + FileDiff::Change { + from, + to, + diff, + index, + } => { + writeln!(f, "diff --git a/{} b/{}", from.display(), to.display())?; + writeln!(f, "index {}", index)?; + writeln!(f, "--- a/{}", from.display())?; + writeln!(f, "+++ b/{}", to.display())?; + writeln!(f, "{}", diff)?; + } + } + + Ok(()) + } +} + +macro_rules! expect { + (if $gate:ident -> $prefix:literal from $line:expr; assign |$strp_line:ident| $callback:block) => { + expect!(@base continue; if $gate -> $prefix from $line; assign |$strp_line| $callback) + }; + (@no_wrap if $gate:ident -> $prefix:literal from $line:expr; assign |$strp_line:ident| $callback:block) => { + expect!(@base (); if $gate -> $prefix from $line; assign |$strp_line| $callback) + }; + (@base $continue:expr; if $gate:ident -> $prefix:literal from $line:expr; assign |$strp_line:ident| $callback:block) => { + if $gate.is_none() { + let $strp_line: &str = $line + .strip_prefix($prefix) + .expect(concat!( + "Expectected '", + $prefix, + "' prefix, while trying to fill '", + stringify!($gate), + "'" + )) + .trim(); + $gate = Some((|$strp_line: &str| $callback)($strp_line)); + $continue; + } + }; +} + +pub(crate) fn parse_file(file: &Path, debug: bool) -> Result<Patch> { + let data = Command::new("filterdiff") + .arg(&file) + .output() + .context("Failed to run `filterdiff` for patch analysis")?; + + let filterdiff_output = String::from_utf8(data.stdout).unwrap(); + if debug { + eprintln!("{}", filterdiff_output); + eprintln!("****************************"); + } + + let commit_message = { + let raw_message = fs::read_to_string(file)?; + + let mut commit = None; + let mut date = None; + let mut author = None; + let mut final_message = None; + for line in raw_message.lines() { + expect!(if commit -> "From " from line; assign |strp_line| { + let (id, fake_date) = strp_line.split_once(' ').unwrap(); + assert_eq!(fake_date, "Mon Sep 17 00:00:00 2001"); + id.to_owned() + }); + expect!(if author -> "From: " from line; assign |strp_line| { + strp_line.to_owned() + }); + expect!(if date -> "Date: " from line; assign |strp_line| { + strp_line.to_owned() + }); + expect!(if final_message -> "Subject: " from line; assign |strp_line| { + if strp_line.starts_with('[') { + let chars = strp_line.chars().take_while(|ch| *ch != ']').collect::<String>(); + assert_eq!(&chars[1..=5], "PATCH"); + strp_line[chars.len()+1..].to_owned() + } else { + unreachable!("Email subjects always start with [PATCH x/y]"); + } + }); + if line == "---" { + break; + } + final_message.as_mut().unwrap().push_str(line); + final_message.as_mut().unwrap().push('\n'); + } + + if debug { + eprintln!("Patch data:"); + eprintln!(" Author: {}", author.unwrap()); + eprintln!(" Commit: {}", commit.unwrap()); + eprintln!(" Date: {}", date.unwrap()); + for line in final_message.as_ref().unwrap().lines() { + eprintln!(" {}", line); + } + eprintln!("****************************"); + } + + final_message.unwrap().trim().to_owned() + }; + + let mut diff = Patch { + files: vec![], + commit_message, + }; + let mut fd_from = None; + let mut fd_to = None; + let mut fd_diff: Option<String> = None; + let mut fd_index: Option<String> = None; + let mut fd_deleted: Option<String> = None; + let mut git_diff = None; + for (idx, line) in filterdiff_output.lines().enumerate() { + if debug { + eprintln!("{}", line); + } + + if line.starts_with("diff --git") { + if idx != 0 { + diff.files.push(FileDiff::from_parts( + fd_from.take().unwrap(), + fd_to.take().unwrap(), + fd_diff.take().unwrap(), + fd_index.take(), + fd_deleted.take(), + )); + } + git_diff = Some(line.to_owned()); + continue; + } + + if line.starts_with("deleted") { + expect!(if fd_deleted -> "deleted" from line; assign |strp_line| { + strp_line.to_owned() + }); + } + + if line.starts_with("index") { + expect!(if fd_index -> "index" from line; assign |strp_line| { + strp_line.to_owned() + }); + } + + if fd_deleted.is_some() { + let git_diff = git_diff.clone().unwrap(); + expect!(@no_wrap if fd_from -> "diff --git a/" from git_diff; assign |strp_line| { + let (from, _to) = strp_line.split_once(' ').unwrap(); + PathBuf::from(from) + }); + } else { + expect!(if fd_from -> "---" from line; assign |strp_line| { + if strp_line != "/dev/null" { + PathBuf::from(strp_line.strip_prefix("a/").unwrap()) + } else { + PathBuf::from(strp_line) + } + }); + } + expect!(if fd_to -> "+++" from line; assign |strp_line| { + if strp_line != "/dev/null" { + PathBuf::from(strp_line.strip_prefix("b/").unwrap()) + } else { + PathBuf::from(strp_line) + } + }); + + match fd_diff.as_mut() { + Some(str) => { + str.push_str(line); + str.push('\n'); + } + None => { + let mut base = line.to_owned(); + base.push('\n'); + fd_diff = Some(base); + } + } + } + diff.files.push(FileDiff::from_parts( + fd_from.take().unwrap(), + fd_to.take().unwrap(), + fd_diff.take().unwrap(), + fd_index.take(), + fd_deleted.take(), + )); + + let check_content = diff.to_string(); + + assert_eq!(filterdiff_output, check_content); + + Ok(diff) +} |
