use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static TASK_LINE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^> Task :[^\n]+\n?").unwrap());
static MAVEN_INFO_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^\[INFO\] (Building |--- |Scanning for |Using base |Compiling)[^\n]+\n?")
.unwrap()
});
static MAVEN_DOWNLOAD_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^(Downloading|Downloaded): https?://[^\n]+\n?").unwrap());
static GRADLE_DOWNLOAD_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^Download https?://[^\n]+\n?").unwrap());
pub fn compress_gradle(raw: &str, exit_code: i32) -> String {
let cleaned = compactor::normalise(raw);
let s = GRADLE_DOWNLOAD_RE.replace_all(&cleaned, "");
if exit_code == 0 {
let kept: Vec<&str> = s
.lines()
.filter(|l| {
l.starts_with("BUILD")
|| l.starts_with("FAILURE")
|| (l.starts_with("> Task") && (l.contains("FAILED") || l.contains("SKIPPED")))
|| l.contains("tests were run")
|| l.contains("test") && l.contains("passed")
})
.collect();
if kept.is_empty() {
let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
let start = lines.len().saturating_sub(5);
return lines[start..].join("\n");
}
return kept.join("\n");
}
let s = TASK_LINE_RE.replace_all(&s, "");
let kept: Vec<&str> = s
.lines()
.filter(|l| {
!l.trim().is_empty()
&& (l.contains("error")
|| l.contains("Error")
|| l.contains("FAILED")
|| l.contains("FAILURE")
|| l.starts_with("e:")
|| l.contains("Exception"))
})
.collect();
if kept.is_empty() {
return compactor::collapse_blanks(&s);
}
kept.join("\n")
}
pub fn compress_maven(raw: &str, exit_code: i32) -> String {
let cleaned = compactor::normalise(raw);
let s = MAVEN_DOWNLOAD_RE.replace_all(&cleaned, "");
let s = MAVEN_INFO_RE.replace_all(&s, "");
if exit_code == 0 {
let kept: Vec<&str> = s
.lines()
.filter(|l| {
l.contains("BUILD SUCCESS")
|| l.contains("BUILD FAILURE")
|| l.contains("Tests run")
|| l.contains("ERROR")
|| l.starts_with("[WARNING]")
})
.collect();
if kept.is_empty() {
let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
let start = lines.len().saturating_sub(5);
return lines[start..].join("\n");
}
return kept.join("\n");
}
let kept: Vec<&str> = s
.lines()
.filter(|l| {
!l.trim().is_empty()
&& (l.contains("[ERROR]")
|| l.contains("BUILD FAILURE")
|| l.contains("Tests run") && l.contains("FAILURE"))
})
.collect();
if kept.is_empty() {
return compactor::collapse_blanks(&s);
}
kept.join("\n")
}
pub fn compress_jvm_build(prog: &str, raw: &str, exit_code: i32) -> String {
match prog {
"gradle" | "gradlew" | "./gradlew" => compress_gradle(raw, exit_code),
"mvn" | "mvnw" | "./mvnw" => compress_maven(raw, exit_code),
_ => compress_gradle(raw, exit_code),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gradle_success_keeps_build_successful() {
let raw =
"> Task :compileJava\n> Task :test\nBUILD SUCCESSFUL in 12s\n4 actionable tasks\n";
let out = compress_gradle(raw, 0);
assert!(out.contains("BUILD SUCCESSFUL"), "{out}");
}
#[test]
fn gradle_strips_downloads() {
let raw = "Download https://jcenter.bintray.com/com/google/guava/guava/30.0/guava-30.0.jar\n> Task :compileJava\nBUILD SUCCESSFUL in 5s\n";
let out = compress_gradle(raw, 0);
assert!(!out.contains("Download https://"), "{out}");
}
#[test]
fn maven_strips_info_and_downloads() {
let raw = "Downloading: https://repo.maven.apache.org/maven2/foo.jar\n[INFO] Building myproject 1.0.0\n[INFO] BUILD SUCCESS\n";
let out = compress_maven(raw, 0);
assert!(!out.contains("Downloading:"), "{out}");
assert!(out.contains("BUILD SUCCESS"), "{out}");
}
#[test]
fn gradle_failure_keeps_errors() {
let raw = "> Task :compileJava FAILED\nerror: incompatible types: String cannot be converted to int\nBUILD FAILED\n";
let out = compress_gradle(raw, 1);
assert!(out.contains("FAILED") || out.contains("error"), "{out}");
}
}