bctx-weave 0.1.14

bctx-weave — FilterMesh lens pipeline, CLI interception, domain compression
Documentation
use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;

// "TASK [role : task name] *****..." lines
static TASK_BANNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^TASK \[[^\]]+\] \*+\n?").unwrap());
// "PLAY [inventory group] *****..." lines
static PLAY_BANNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^PLAY \[[^\]]+\] \*+\n?").unwrap());
// "ok: [hostname]" and "skipping: [hostname]" verbose lines
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 {
        // On success: keep PLAY RECAP + changed/failed lines
        // Strip task banners, ok/skip verbose lines
        let s = TASK_BANNER_RE.replace_all(&cleaned, "");
        let s = PLAY_BANNER_RE.replace_all(&s, "");
        let s = OK_SKIP_RE.replace_all(&s, "");

        // Keep "changed:", "failed:", PLAY RECAP, and summary
        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"));
    }

    // On failure: keep TASK banners for failed tasks, error messages, PLAY RECAP
    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" => {
            // ansible-galaxy: strip download progress, keep install summary
            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}");
    }
}