use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;
static TASK_BANNER_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^TASK \[[^\]]+\] \*+\n?").unwrap());
static PLAY_BANNER_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^PLAY \[[^\]]+\] \*+\n?").unwrap());
static OK_SKIP_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)^(ok|skipping): \[[^\]]+\]\n?").unwrap());
pub fn compress_playbook(raw: &str, exit_code: i32) -> String {
let cleaned = compactor::normalise(raw);
if exit_code == 0 {
let s = TASK_BANNER_RE.replace_all(&cleaned, "");
let s = PLAY_BANNER_RE.replace_all(&s, "");
let s = OK_SKIP_RE.replace_all(&s, "");
let kept: Vec<&str> = s
.lines()
.filter(|l| {
let t = l.trim();
t.is_empty()
|| t.starts_with("changed:")
|| t.starts_with("PLAY RECAP")
|| t.contains("changed=")
|| t.contains("failed=")
|| t.starts_with("RUNNING HANDLER")
})
.collect();
if kept.is_empty() {
return compactor::collapse_blanks(&s);
}
return compactor::collapse_blanks(&kept.join("\n"));
}
let kept: Vec<&str> = cleaned
.lines()
.filter(|l| {
let t = l.trim();
t.is_empty()
|| t.starts_with("TASK")
|| t.starts_with("fatal:")
|| t.starts_with("failed:")
|| t.starts_with("PLAY RECAP")
|| t.contains("failed=")
|| t.contains("FAILED!")
})
.collect();
if kept.is_empty() {
return compactor::collapse_blanks(&cleaned);
}
compactor::collapse_blanks(&kept.join("\n"))
}
pub fn compress_ansible(subcmd: &str, raw: &str, exit_code: i32) -> String {
match subcmd.trim() {
"playbook" | "-playbook" | "ansible-playbook" => compress_playbook(raw, exit_code),
"galaxy" => {
let cleaned = compactor::normalise(raw);
cleaned
.lines()
.filter(|l| !l.contains("Downloading role from") && !l.trim().is_empty())
.collect::<Vec<_>>()
.join("\n")
}
_ => compress_playbook(raw, exit_code),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn playbook_success_keeps_recap() {
let raw = "PLAY [webservers] ****\nTASK [update apt] ****\nok: [host1]\nchanged: [host2]\nPLAY RECAP\nhost1: ok=2 changed=0 failed=0\nhost2: ok=3 changed=1 failed=0\n";
let out = compress_playbook(raw, 0);
assert!(out.contains("PLAY RECAP"), "{out}");
assert!(out.contains("changed="), "{out}");
assert!(!out.contains("ok: [host1]"), "{out}");
}
#[test]
fn playbook_failure_keeps_fatal() {
let raw = "TASK [deploy] ****\nfatal: [host1]: FAILED! => {\"msg\": \"Permission denied\"}\nPLAY RECAP\nhost1: ok=1 changed=0 failed=1\n";
let out = compress_playbook(raw, 1);
assert!(out.contains("fatal:"), "{out}");
assert!(out.contains("PLAY RECAP"), "{out}");
}
}