bctx-weave 0.1.28

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

static GEM_FETCH_RE: Lazy<Regex> = Lazy::new(||
    // Matches both "Fetching: foo.gem" and "Fetching gem metadata..."
    Regex::new(r"(?m)^Fetching[:\s][^\n]+\n?").unwrap());
static GEM_DL_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Downloading gem [^\n]+\n?").unwrap());
// "Installing foo (1.2.3)" — strip on success, keep on failure
static GEM_INSTALLING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Installing [^\n]+\n?").unwrap());
// Bundler: "Using foo 1.2.3 (from ...)"
static BUNDLE_USING_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^Using [^\n]+\n?").unwrap());
static BUNDLE_FETCH_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^(Fetching|Fetching gem metadata|Resolving dependencies)\S*[^\n]*\n?").unwrap()
});

// ── gem install ───────────────────────────────────────────────────────────────

pub fn compress_gem_install(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = GEM_FETCH_RE.replace_all(&cleaned, "");
    let s = GEM_DL_RE.replace_all(&s, "");
    let s = GEM_INSTALLING_RE.replace_all(&s, "");
    let out = compactor::collapse_blanks(&s);
    if out.trim().is_empty() {
        cleaned
            .lines()
            .filter(|l| l.starts_with("Successfully installed") || l.contains("gem installed"))
            .collect::<Vec<_>>()
            .join("\n")
    } else {
        out
    }
}

// ── gem update ────────────────────────────────────────────────────────────────

pub fn compress_gem_update(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = GEM_FETCH_RE.replace_all(&cleaned, "");
    let s = GEM_DL_RE.replace_all(&s, "");
    let s = GEM_INSTALLING_RE.replace_all(&s, "");
    let out = compactor::collapse_blanks(&s);
    if out.trim().is_empty() {
        return "gem update: nothing to update".to_string();
    }
    out
}

// ── gem list ──────────────────────────────────────────────────────────────────

pub fn compress_gem_list(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() <= 40 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more gems]",
        lines[..40].join("\n"),
        lines.len() - 40
    )
}

// ── bundle install / update ───────────────────────────────────────────────────

pub fn compress_bundle_install(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BUNDLE_FETCH_RE.replace_all(&cleaned, "");
    let s = BUNDLE_USING_RE.replace_all(&s, "");
    let s = GEM_INSTALLING_RE.replace_all(&s, "");
    let out = compactor::collapse_blanks(&s);
    if out.trim().is_empty() {
        // Try to recover "Bundle complete" summary
        let summary: Vec<&str> = cleaned
            .lines()
            .filter(|l| l.starts_with("Bundle complete") || l.starts_with("Bundled gems"))
            .collect();
        if !summary.is_empty() {
            return summary.join("\n");
        }
    }
    out
}

pub fn compress_bundle_update(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BUNDLE_FETCH_RE.replace_all(&cleaned, "");
    let s = BUNDLE_USING_RE.replace_all(&s, "");
    // Keep "Updating" lines (which gems changed)
    compactor::collapse_blanks(&s)
}

// ── top-level dispatcher ──────────────────────────────────────────────────────

pub fn compress_gem(subcmd: &str, raw: &str) -> String {
    match subcmd.trim() {
        "install" | "i" => compress_gem_install(raw),
        "update" => compress_gem_update(raw),
        "list" => compress_gem_list(raw),
        _ => compactor::normalise(raw),
    }
}

pub fn compress_bundle(subcmd: &str, raw: &str) -> String {
    match subcmd.trim() {
        "install" | "i" => compress_bundle_install(raw),
        "update" => compress_bundle_update(raw),
        "exec" => compactor::normalise(raw),
        _ => compress_bundle_install(raw),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn gem_install_strips_fetch_installing() {
        let raw = "Fetching: foo-1.0.0.gem\nInstalling foo (1.0.0)\nInstalling bar (2.0.0)\nSuccessfully installed foo-1.0.0\n1 gem installed\n";
        let out = compress_gem_install(raw);
        assert!(!out.contains("Fetching"), "{out}");
        assert!(!out.contains("Installing foo"), "{out}");
        assert!(
            out.contains("Successfully installed") || out.contains("gem installed"),
            "{out}"
        );
    }

    #[test]
    fn gem_list_truncates_large_output() {
        let raw = (0..60)
            .map(|i| format!("gem{i} (1.{i}.0)"))
            .collect::<Vec<_>>()
            .join("\n");
        let out = compress_gem_list(&raw);
        assert!(out.contains("more gems"), "{out}");
    }

    #[test]
    fn bundle_install_strips_using_and_fetch() {
        let raw = "Fetching gem metadata from https://rubygems.org/\nUsing rake 13.0.6\nUsing bundler 2.3.0\nInstalling activesupport 7.0.0\nBundle complete! 5 Gemfile dependencies, 42 gems now installed.\n";
        let out = compress_bundle_install(raw);
        assert!(!out.contains("Using rake"), "{out}");
        assert!(!out.contains("Fetching gem metadata"), "{out}");
        assert!(out.contains("Bundle complete"), "{out}");
    }

    #[test]
    fn bundle_update_keeps_updating_lines() {
        let raw = "Fetching gem metadata\nUsing foo 1.0.0\nUpdating foo (1.0.0 => 1.1.0)\nBundle updated!\n";
        let out = compress_bundle_update(raw);
        assert!(!out.contains("Using foo"), "{out}");
        assert!(
            out.contains("Updating foo") || out.contains("Bundle updated"),
            "{out}"
        );
    }
}