use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static RESTORE_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^ Determining projects to restore\.\.\.\n?").unwrap());
static PACKAGE_DOWNLOAD_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^ Downloading [^\n]+\n?").unwrap());
static BUILD_PROGRESS_RE: Lazy<Regex> = Lazy::new(||
Regex::new(r"(?m)^ [^\n]+ -> [^\n]+\.dll\n?").unwrap());
static TEST_PASS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+Passed [^\n]+\n?").unwrap());
pub fn compress_build(raw: &str, exit_code: i32) -> String {
let cleaned = compactor::normalise(raw);
let s = RESTORE_RE.replace_all(&cleaned, "");
let s = PACKAGE_DOWNLOAD_RE.replace_all(&s, "");
if exit_code == 0 {
let s = BUILD_PROGRESS_RE.replace_all(&s, "");
return compactor::collapse_blanks(&s);
}
let kept: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
!t.is_empty()
&& (t.contains("error")
|| t.contains("Error")
|| t.contains("warning")
|| t.contains("Build FAILED")
|| t.contains("Build succeeded"))
})
.collect();
if kept.is_empty() {
return compactor::collapse_blanks(&s);
}
kept.join("\n")
}
pub fn compress_test(raw: &str, exit_code: i32) -> String {
let cleaned = compactor::normalise(raw);
let s = RESTORE_RE.replace_all(&cleaned, "");
if exit_code == 0 {
let s = TEST_PASS_RE.replace_all(&s, "");
let kept: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
t.is_empty()
|| t.contains("Test Run Successful")
|| t.contains("Passed!")
|| t.contains("Total:")
|| t.contains("Duration:")
|| t.starts_with("Build succeeded")
})
.collect();
if !kept.is_empty() {
return kept.join("\n");
}
}
let s = TEST_PASS_RE.replace_all(&s, "");
compactor::collapse_blanks(&s)
}
pub fn compress_restore(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = PACKAGE_DOWNLOAD_RE.replace_all(&cleaned, "");
compactor::collapse_blanks(&s)
}
pub fn compress_dotnet(subcmd: &str, raw: &str, exit_code: i32) -> String {
let sub = subcmd.trim();
match sub {
"build" | "publish" | "run" | "pack" => compress_build(raw, exit_code),
"test" => compress_test(raw, exit_code),
"restore" | "add" | "remove" => compress_restore(raw),
_ => {
let cleaned = compactor::normalise(raw);
let s = RESTORE_RE.replace_all(&cleaned, "");
compactor::collapse_blanks(&s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_success_strips_dll_lines() {
let raw = " Determining projects to restore...\n myproject -> /home/user/project/bin/Debug/net8.0/myproject.dll\nBuild succeeded.\n 0 Warning(s)\n 0 Error(s)\n";
let out = compress_build(raw, 0);
assert!(out.contains("Build succeeded"), "{out}");
assert!(!out.contains("Determining projects"), "{out}");
}
#[test]
fn build_failure_keeps_errors() {
let raw = "Program.cs(10,5): error CS0103: The name 'Foo' does not exist\nBuild FAILED.\n";
let out = compress_build(raw, 1);
assert!(out.contains("error CS0103"), "{out}");
assert!(out.contains("FAILED"), "{out}");
}
#[test]
fn test_success_strips_passed_lines() {
let raw = " Passed TestFoo [< 1 ms]\n Passed TestBar [2 ms]\nTest Run Successful.\n Total: 2, Passed: 2, Duration: 0.05s\n";
let out = compress_test(raw, 0);
assert!(out.contains("Test Run Successful"), "{out}");
assert!(!out.contains("Passed TestFoo"), "{out}");
}
}