use std::collections::HashMap;
use anyhow::{Context, Result};
use itertools::Itertools;
use nom::{bytes::complete::tag, character::complete::not_line_ending, IResult};
pub fn parse_diff(input: &str) -> Result<HashMap<&str, Vec<String>>> {
let mut diffs = HashMap::new();
for diff in input
.lines()
.collect::<Vec<_>>()
.split(|l| l.starts_with("diff"))
.skip(1)
{
diffs.insert(get_path(diff)?, get_hunks(diff)?);
}
Ok(diffs)
}
fn get_path<'a>(diff: &[&'a str]) -> Result<&'a str> {
let diff: IResult<&str, &str> = tag("+++ b/")(diff.get(2).unwrap_or(&""));
let Ok((diff, _)) = diff else { return Ok("") };
let path: IResult<&str, &str> = not_line_ending(diff);
let (_, path) = path
.map_err(|e| e.to_owned())
.context("failed to parse a path from diff")?;
Ok(path)
}
fn get_hunks(diff: &[&str]) -> Result<Vec<String>> {
let mut hunks = Vec::new();
let hunk_groups = diff.iter().group_by(|line| line.starts_with("@@"));
let mut hunk_groups = hunk_groups.into_iter();
loop {
let Some((key, mut lines)) = hunk_groups.next() else {
break;
};
let hunk_head = *lines.next().context("expected another line in diff")?;
if !key {
continue;
}
let (_key, hunk_tail) = hunk_groups
.next()
.context("strange output from `git diff`")?;
hunks.push(
std::iter::once(hunk_head)
.chain(hunk_tail.copied())
.join("\n"),
);
}
Ok(hunks)
}
pub fn parse_hunk_old(header: &str) -> Result<&str> {
let (_, header) = header
.split_once('-')
.with_context(|| format!("tried to parse strange hunk header: {header}"))?;
let (old, _) = header
.split_once(' ')
.with_context(|| format!("tried to parse strange hunk header: {header}"))?;
Ok(old)
}
pub fn parse_hunk_new(header: &str) -> Result<&str> {
let (_, header) = header
.split_once('+')
.with_context(|| format!("tried to parse strange hunk header: {header}"))?;
let (old, _) = header
.split_once(' ')
.with_context(|| format!("tried to parse strange hunk header: {header}"))?;
Ok(old)
}
#[cfg(test)]
mod tests {
use test_case::test_case;
const ISSUE_62: &str = "diff --git a/asteroid-loop/index.html b/asteroid-loop/index.html
index d79df71..e2d1e9f 100644
--- a/asteroid-loop/index.html
+++ b/asteroid-loop/index.html
@@ -14,15 +14,10 @@
var unityInstance = UnityLoader.instantiate(\"unityContainer\", \"Build/thing build.json\", { onProgress: UnityProgress });
</script>
<script src=\"https://code.jquery.com/jquery-1.10.2.js\"></script>
- <script>
- $(function () {
- $(\"#header\").load(\"/assets/header.html\");
- });
- </script>
</head>
<body>
- <div id=\"header\"></div>
+ <#header />
<div class=\"webgl-content\">
<div id=\"unityContainer\" style=\"width: 960px; height: 540px\"></div>
<div class=\"footer\">
@@ -32,4 +27,4 @@
</div>
</body>
-</html>
\\ No newline at end of file
+</html>";
#[test_case(ISSUE_62 ; "issue 62")]
fn parse(diff: &str) {
let parsed = super::parse_diff(diff);
assert!(parsed.is_ok());
let parsed = parsed.unwrap();
assert_eq!(parsed.len(), 1);
}
}