use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static PROGRESS_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^\s*[✔✘⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s+(?:Loaded|Cataloged|Indexed|Parsed|Tagged)[^\n]*\n?")
.unwrap()
});
pub fn compress_syft(raw: &str) -> String {
let cleaned = compactor::normalise(raw);
let s = PROGRESS_RE.replace_all(&cleaned, "");
let trimmed = s.trim();
if trimmed.starts_with('{') || trimmed.starts_with("SPDX") || trimmed.starts_with("<?xml") {
return trimmed.to_string();
}
let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
let total = lines
.iter()
.filter(|l| {
let t = l.trim();
!t.starts_with("NAME") && !t.is_empty()
})
.count();
if lines.len() > 30 {
let shown = &lines[..30];
return format!(
"{}\n… [{} total packages — {} more not shown]",
shown.join("\n"),
total,
total.saturating_sub(30)
);
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_spinner_progress() {
let raw = " ✔ Loaded image\n ✔ Cataloged packages\nNAME VERSION TYPE\nopenssl 3.0.2 deb\ncurl 7.81.0 deb\n";
let out = compress_syft(raw);
assert!(!out.contains("Loaded image"), "{out}");
assert!(out.contains("openssl"), "{out}");
}
#[test]
fn truncates_large_sbom() {
let mut lines = vec!["NAME VERSION TYPE".to_string()];
for i in 0..40 {
lines.push(format!("pkg-{i} 1.{i}.0 deb"));
}
let out = compress_syft(&lines.join("\n"));
assert!(out.contains("more not shown"), "{out}");
}
#[test]
fn json_passthrough() {
let raw = " ✔ Cataloged packages\n{\"schema\":\"https://cyclonedx.org\"}\n";
let out = compress_syft(raw);
assert!(out.starts_with('{'), "{out}");
}
}