bctx-weave 0.1.17

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

// "Creating temporary archive of 1234 files totalling 1.23 MiB..."
static ARCHIVE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Creating temporary archive of[^\n]*\n?").unwrap());
// "Some files were not included in the source upload:" + following lines starting with "("
static FILE_EXCLUDE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Some files were not included[^\n]*\n(\([^\n]*\n)*").unwrap());
// "Uploading tarball of [.] to [gs://...]"
static UPLOAD_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Uploading tarball of[^\n]*\n?").unwrap());
// Build step relay: "Step #0 - \"name\": some line"
static BUILD_STEP_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"(?m)^Step #\d+ - "[^"]+": (.*)$"#).unwrap());
// "Updated property [core/project]." configuration noise
static PROP_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Updated property \[[^\]]+\]\.\n?").unwrap());
// "Your browser has been opened to visit:" auth noise
static BROWSER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Your browser has been opened[^\n]*\n([^\n]*\n)?").unwrap());

// ── gcloud builds submit ──────────────────────────────────────────────────────

pub fn compress_builds(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = ARCHIVE_RE.replace_all(&cleaned, "");
    let s = FILE_EXCLUDE_RE.replace_all(&s, "");
    let s = UPLOAD_RE.replace_all(&s, "Uploading source…\n");
    // Collapse Step relay lines to just their content
    let s = BUILD_STEP_RE.replace_all(&s, "$1");
    compactor::collapse_blanks(&s)
}

// ── gcloud compute instances list / describe ──────────────────────────────────

pub fn compress_compute(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let cleaned = PROP_RE.replace_all(&cleaned, "");
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    // Table output: keep header + data rows; truncate if large
    if lines.len() > 25 {
        return format!(
            "{}\n… [{} more instances — use --filter to narrow] …",
            lines[..25].join("\n"),
            lines.len() - 25
        );
    }
    lines.join("\n")
}

// ── gcloud container / run / functions ───────────────────────────────────────

pub fn compress_deploy(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Keep lines with URL, status, service name, error
    let useful: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            t.contains("URL:")
                || t.contains("Service URL:")
                || t.contains("serviceUrl")
                || t.contains("uri")
                || t.starts_with("Service [")
                || t.contains("deployed")
                || t.contains("Deployed")
                || t.contains("created")
                || t.contains("updated")
                || t.contains("error")
                || t.contains("Error")
                || t.contains("WARNING")
                || t.contains("httpsTrigger")
                || t.starts_with("OK")
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    useful.join("\n")
}

// ── gcloud iam list-service-accounts ─────────────────────────────────────────

pub fn compress_iam(raw: &str) -> String {
    use once_cell::sync::Lazy;
    use regex::Regex;
    static EMAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*EMAIL:\s*(.+)$").unwrap());

    let cleaned = compactor::normalise(raw);
    let emails: Vec<String> = EMAIL_RE
        .captures_iter(&cleaned)
        .filter_map(|c| c.get(1).map(|m| m.as_str().trim().to_string()))
        .collect();
    if emails.is_empty() {
        return compress_generic(raw);
    }
    format!("{} service accounts:\n{}", emails.len(), emails.join("\n"))
}

// ── gcloud auth ───────────────────────────────────────────────────────────────

pub fn compress_auth(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BROWSER_RE.replace_all(&cleaned, "");
    let useful: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            t.contains("Logged in as")
                || t.contains("ACTIVE")
                || t.contains("ACCOUNT")
                || t.contains("credentialed")
                || t.starts_with("*")
                || t.starts_with("ERROR")
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── generic gcloud (large JSON / unknown subcommand) ──────────────────────────

pub fn compress_generic(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PROP_RE.replace_all(&cleaned, "");
    let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() > 40 {
        return format!(
            "{}\n… [{} more lines — use --format=value(field) to filter] …",
            lines[..40].join("\n"),
            lines.len() - 40
        );
    }
    compactor::collapse_blanks(&s)
}

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

pub fn compress_gcloud(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("builds") {
        return compress_builds(raw);
    }
    if sub.starts_with("compute") {
        return compress_compute(raw);
    }
    if sub.starts_with("run") || sub.starts_with("functions") || sub.starts_with("app") {
        return compress_deploy(raw);
    }
    if sub.starts_with("container") {
        // cluster credentials / get-credentials: already compact
        return compactor::normalise(raw);
    }
    if sub.starts_with("iam") {
        return compress_iam(raw);
    }
    if sub.starts_with("auth") {
        return compress_auth(raw);
    }
    compress_generic(raw)
}

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

    #[test]
    fn builds_strips_archive_noise() {
        let raw = "Creating temporary archive of 1234 files totalling 1.23 MiB before compression...\nSome files were not included in the source upload:\n(this filetype is not allowed by default: .git)\nUploading tarball of [.] to [gs://my-bucket/source.tgz]\nCreated [https://cloudbuild.googleapis.com/v1/projects/my-proj/builds/abc].\nBUILD\nStep #0 - \"build\": Successfully built image.\nDONE\n";
        let out = compress_builds(raw);
        assert!(!out.contains("Creating temporary archive"), "{out}");
        assert!(!out.contains("Some files were not included"), "{out}");
        assert!(out.contains("Uploading source"), "{out}");
        assert!(out.contains("Successfully built image"), "{out}");
        assert!(out.contains("DONE"), "{out}");
    }

    #[test]
    fn compute_truncates_long_list() {
        let rows: Vec<String> = (0..30)
            .map(|i| format!("instance-{i}  us-central1-a  e2-medium  RUNNING"))
            .collect();
        let raw = rows.join("\n");
        let out = compress_compute(&raw);
        assert!(out.contains("more instances"), "{out}");
    }

    #[test]
    fn deploy_keeps_service_url() {
        let raw = "Deploying container to Cloud Run service [my-svc] in project [my-proj]\nOK Deploying... Done.\nOK Creating Revision...\nOK Routing traffic...\nDone.\nService URL: https://my-svc-abc123-uc.a.run.app\n";
        let out = compress_deploy(raw);
        assert!(out.contains("Service URL:"), "{out}");
    }

    #[test]
    fn generic_truncates_large_json() {
        let json_lines: Vec<String> = (0..50)
            .map(|i| format!("  \"key{i}\": \"value{i}\","))
            .collect();
        let out = compress_generic(&json_lines.join("\n"));
        assert!(out.contains("more lines"), "{out}");
    }

    #[test]
    fn generic_strips_property_update_noise() {
        let raw =
            "Updated property [core/project].\nUpdated property [compute/zone].\nListed 0 items.\n";
        let out = compress_generic(raw);
        assert!(!out.contains("Updated property"), "{out}");
        assert!(out.contains("Listed"), "{out}");
    }
}