summaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/ro/rollup/src/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ro/rollup/src/parse.rs')
-rw-r--r--pkgs/by-name/ro/rollup/src/parse.rs437
1 files changed, 437 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..ac30eaa4
--- /dev/null
+++ b/pkgs/by-name/ro/rollup/src/parse.rs
@@ -0,0 +1,437 @@
+#![allow(clippy::no_effect)]
+
+use core::fmt;
+use std::{
+ fs,
+ path::{Path, PathBuf},
+};
+
+use anyhow::{Result, bail};
+use pretty_assertions::assert_eq;
+
+#[derive(Clone)]
+pub(crate) struct Patch {
+ pub(crate) files: Vec<FileDiff>,
+ pub(crate) commit_message: String,
+ pub(crate) date: String,
+ pub(crate) final_whitespace: &'static str,
+ pub(crate) generating_version: 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)?;
+ }
+ writeln!(f, "--{}", self.final_whitespace)?;
+ writeln!(f, "{}", self.generating_version)?;
+
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+pub(crate) enum FileDiff {
+ Add {
+ path: PathBuf,
+ diff: String,
+ index: String,
+ new_file_mode: String,
+ },
+ Remove {
+ path: PathBuf,
+ diff: String,
+ index: String,
+ deleted_mode: String,
+ },
+ Change {
+ path: PathBuf,
+ diff: String,
+ index: String,
+ },
+ RenameNoChange {
+ from: PathBuf,
+ to: PathBuf,
+ },
+ RenameWithChange {
+ from: PathBuf,
+ to: PathBuf,
+ similarity: String,
+ diff: String,
+ index: String,
+ },
+}
+
+impl fmt::Display for FileDiff {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ FileDiff::Add {
+ path,
+ diff,
+ index,
+ new_file_mode,
+ } => {
+ writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?;
+ writeln!(f, "new file mode {}", new_file_mode)?;
+ writeln!(f, "index {}", index)?;
+ writeln!(f, "--- /dev/null")?;
+ writeln!(f, "+++ b/{}", path.display())?;
+ writeln!(f, "{}", diff)?;
+ }
+ FileDiff::Remove {
+ path,
+ diff,
+ index,
+ deleted_mode,
+ } => {
+ writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?;
+ writeln!(f, "deleted file mode {}", deleted_mode)?;
+ writeln!(f, "index {}", index)?;
+ writeln!(f, "--- a/{}", path.display())?;
+ writeln!(f, "+++ /dev/null")?;
+ writeln!(f, "{}", diff)?;
+ }
+ FileDiff::Change { path, diff, index } => {
+ writeln!(f, "diff --git a/{} b/{}", path.display(), path.display())?;
+ writeln!(f, "index {}", index)?;
+ writeln!(f, "--- a/{}", path.display())?;
+ writeln!(f, "+++ b/{}", path.display())?;
+ writeln!(f, "{}", diff)?;
+ }
+ FileDiff::RenameNoChange { from, to } => {
+ writeln!(f, "diff --git a/{} b/{}", from.display(), to.display())?;
+ writeln!(f, "similarity index 100%")?;
+ writeln!(f, "rename from {}", from.display())?;
+ writeln!(f, "rename to {}", to.display())?;
+ }
+ FileDiff::RenameWithChange {
+ from,
+ to,
+ similarity,
+ diff,
+ index,
+ } => {
+ writeln!(f, "diff --git a/{} b/{}", from.display(), to.display())?;
+ writeln!(f, "similarity index {}", similarity)?;
+ writeln!(f, "rename from {}", from.display())?;
+ writeln!(f, "rename to {}", 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;
+ }
+ };
+}
+
+macro_rules! parser {
+ (
+ $iterator:ident,
+ $debug:ident,
+ $($prefix:literal @ $prefix_var:ident => {$($expr:tt)*};)* $(;)?
+ .. $diff:ident
+ ) => {
+ $(
+ let $prefix_var = {
+ let next = $iterator.next().unwrap();
+ if $debug {
+ eprintln!(" {}", next);
+ }
+ next
+ }.strip_prefix($prefix).unwrap().to_owned();
+ $(
+ $expr
+ )*;
+ )*
+ let $diff = {
+ let mut diff = vec![];
+ let mut iter = $iterator.clone();
+ while let Some(next) = iter.next() {
+ if next.starts_with("diff --git") {
+ break;
+ }
+ if (next == "-- " || next == "--")
+ && iter.peek().map(|v| v.starts_with("2")) == Some(true)
+ {
+ break;
+ }
+ diff.push(next);
+ }
+ diff
+ };
+
+ for _ in 0..$diff.len() {
+ let next = $iterator.next().unwrap();
+ if $debug {
+ eprintln!(" {}", next);
+ }
+ }
+ let $diff = $diff.join("\n");
+
+ };
+}
+
+pub(crate) fn parse_file(file: &Path, debug: bool) -> Result<Patch> {
+ if debug {
+ eprintln!("Started parsing `{}`..", file.display());
+ }
+ let patch_text = fs::read_to_string(file)?;
+
+ let (commit_message, date, mut cut_idx) = {
+ let mut commit = None;
+ let mut date = None;
+ let mut author = None;
+ let mut final_message = None;
+ let mut finished_headers = false;
+ let mut cut_idx = None;
+ for (idx, line) in patch_text.lines().enumerate() {
+ 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()+2..].to_owned()
+ } else {
+ unreachable!("Email subjects always start with [PATCH x/y]");
+ }
+ });
+ if line.starts_with("MIME-Version: ")
+ || line.starts_with("Content-Type: ")
+ || line.starts_with("Content-Transfer-Encoding: ")
+ {
+ continue;
+ }
+
+ if line.starts_with(" ") {
+ // this line is a continuation of the previous header. We just assume
+ // it's the subject header.
+ final_message.as_mut().unwrap().push_str(line);
+ continue;
+ }
+
+ if line.is_empty() {
+ // add the newline to end the subject message
+ final_message.as_mut().unwrap().push('\n');
+ // and one newline to mark the end of the header and the start for more text.
+ final_message.as_mut().unwrap().push('\n');
+ finished_headers = true;
+ continue;
+ }
+
+ if line == "---" {
+ cut_idx = Some(idx);
+ break;
+ }
+
+ if finished_headers {
+ // If we finished all the headers, the rest must be commit message stuff.
+ // One per line.
+
+ final_message.as_mut().unwrap().push_str(line);
+ final_message.as_mut().unwrap().push('\n');
+ } else {
+ bail!("Failed to recognize commit message header: `{line}`, while parsing patch.")
+ }
+ }
+
+ if debug {
+ eprintln!("Patch data:");
+ eprintln!(" Author: {}", author.unwrap());
+ eprintln!(" Commit: {}", commit.unwrap());
+ eprintln!(" Date: {}", date.as_ref().unwrap());
+ for line in final_message.as_ref().unwrap().lines() {
+ eprintln!(" {}", line);
+ }
+ eprintln!("****************************");
+ }
+
+ (
+ final_message.unwrap().trim().to_owned(),
+ date.unwrap().trim().to_owned(),
+ cut_idx.unwrap(),
+ )
+ };
+
+ let mut patch_files = vec![];
+ let mut final_whitespace = None;
+ let mut generating_version = None;
+ let mut skipping_frontmatter = true;
+ let mut iterator = patch_text
+ .lines()
+ .skip(cut_idx)
+ // skip one more to go over the `---` line
+ .skip(1)
+ .peekable();
+ while let Some(line) = iterator.peek() {
+ if debug {
+ eprintln!("{}", line);
+ }
+
+ if (line.starts_with(" ") || line.is_empty()) && skipping_frontmatter {
+ iterator.next().unwrap();
+ cut_idx += 1;
+ continue;
+ }
+
+ if line.starts_with("diff --git") {
+ if skipping_frontmatter {
+ skipping_frontmatter = false;
+ }
+ iterator.next().unwrap();
+ continue;
+ }
+
+ match line {
+ line if line.starts_with("new file mode") => {
+ parser! {
+ iterator, debug,
+ "new file mode " @ mode => {let new_file_mode = mode.to_owned()};
+ "index " @ index => {let index = index.to_owned()};
+ "--- " @ from => {assert_eq!(from, "/dev/null")};
+ "+++ b/" @ to => {let path = PathBuf::from(to)};
+ ..diff
+ }
+ patch_files.push(FileDiff::Add {
+ path,
+ diff,
+ index,
+ new_file_mode,
+ })
+ }
+ line if line.starts_with("index") => {
+ parser! {
+ iterator, debug,
+ "index " @ index => {()};
+ "--- a/" @ from => {()};
+ "+++ b/" @ to => {assert_eq!(from, to); let path = PathBuf::from(to)};
+ ..diff
+ }
+ patch_files.push(FileDiff::Change { path, diff, index })
+ }
+ line if line.starts_with("similarity index 100%") => {
+ parser! {
+ iterator, debug,
+ "similarity index 100%" @ none => {assert!(none.is_empty())};
+ "rename from " @ from => {let from = PathBuf::from(from)};
+ "rename to " @ to => {let to = PathBuf::from(to)};
+ .. no_diff
+ }
+ assert!(no_diff.is_empty());
+
+ patch_files.push(FileDiff::RenameNoChange { from, to })
+ }
+ line if line.starts_with("similarity index ") => {
+ parser! {
+ iterator, debug,
+ "similarity index " @ similarity => {()};
+ "rename from " @ from_1 => {let from = PathBuf::from(&from_1)};
+ "rename to " @ to_1 => {let to = PathBuf::from(&to_1)};
+ "index " @ index => {()};
+ "--- a/" @ from_2 => {assert_eq!(from_1, from_2)};
+ "+++ b/" @ to_2 => {assert_eq!(to_1, to_2)};
+ .. diff
+ }
+
+ patch_files.push(FileDiff::RenameWithChange {
+ from,
+ to,
+ similarity,
+ diff,
+ index,
+ })
+ }
+ line if line.starts_with("deleted") => {
+ parser! {
+ iterator, debug,
+ "deleted file mode " @ deleted_mode => {()};
+ "index " @ index => {()};
+ "--- a/" @ from => {let path = PathBuf::from(from)};
+ "+++ /dev/null" @ rest => {assert!(rest.is_empty())};
+ ..diff
+ }
+ patch_files.push(FileDiff::Remove {
+ path,
+ diff,
+ index,
+ deleted_mode,
+ })
+ }
+ line if *line == "--" || *line == "-- " => {
+ final_whitespace = Some( match *line {
+ "-- " => " ",
+ "--" => "",
+ _ => unreachable!(),
+ });
+ iterator.next().unwrap();
+ generating_version = Some(iterator.next().unwrap().to_owned());
+ assert_eq!(iterator.next(), Some(""));
+ assert_eq!(iterator.next(), None);
+ break;
+ }
+ _ => {
+ unreachable!("All possible line starts should be covered, but: `{line}` is not.")
+ }
+ }
+ }
+
+ let patch = Patch {
+ files: patch_files,
+ commit_message,
+ date,
+ final_whitespace: final_whitespace.unwrap(),
+ generating_version: generating_version.unwrap(),
+ };
+
+ let patch_text = patch_text
+ .lines()
+ .skip(cut_idx)
+ // skip one more to go over the `---` line
+ .skip(1)
+ .collect::<Vec<_>>()
+ .join("\n");
+ let check_content = patch.to_string();
+
+ assert_eq!(
+ check_content, patch_text,
+ "Expected parsed patchtext to be the same as our the original patch"
+ );
+
+ Ok(patch)
+}