yarp-cli 0.2.0

Prune Pi command output and archive tool calls locally
Documentation
use super::classify::{body, contains_ascii_insensitive, trim_start};
use super::evidence::EvidenceClass;

#[must_use]
pub fn classify(line: &[u8]) -> EvidenceClass {
    let line = body(line);
    let trimmed = trim_start(line);
    if is_outcome(trimmed) {
        EvidenceClass::Outcome
    } else if is_routine(trimmed) {
        EvidenceClass::Noise
    } else if is_diagnostic(trimmed) {
        EvidenceClass::Diagnostic
    } else if trimmed.starts_with(b"running ")
        || trimmed.starts_with(b"collected ")
        || trimmed.starts_with(b"test session starts")
    {
        EvidenceClass::Structure
    } else if trimmed.is_empty() {
        EvidenceClass::Noise
    } else {
        EvidenceClass::Example
    }
}

fn is_outcome(line: &[u8]) -> bool {
    [
        &b"test result:"[..],
        &b"failures:"[..],
        &b"Test Files  "[..],
        &b"Tests  "[..],
    ]
    .iter()
    .any(|prefix| starts_ascii_insensitive(line, prefix))
        || contains_ascii_insensitive(line, b" passed in ")
        || contains_ascii_insensitive(line, b" failed, ")
            && contains_ascii_insensitive(line, b" passed")
}

fn is_diagnostic(line: &[u8]) -> bool {
    [
        &b"FAIL "[..],
        &b"FAILED "[..],
        &b"--- FAIL:"[..],
        &b"not ok "[..],
        &b"AssertionError"[..],
        &b"ZodError"[..],
        &b"Caused by:"[..],
        &b"Traceback"[..],
        &b"error:"[..],
        &b"error["[..],
        &b"npm error"[..],
        &b"thread '"[..],
        &b"E "[..],
    ]
    .iter()
    .any(|prefix| starts_ascii_insensitive(line, prefix))
        || line.starts_with("❯".as_bytes()) && contains_ascii_insensitive(line, b" failed")
        || contains_ascii_insensitive(line, b"testCodeFailure")
        || contains_ascii_insensitive(line, b"stopping after ")
            && contains_ascii_insensitive(line, b" failure")
}

fn starts_ascii_insensitive(line: &[u8], prefix: &[u8]) -> bool {
    line.get(..prefix.len()).is_some_and(|candidate| {
        candidate
            .iter()
            .zip(prefix)
            .all(|(left, right)| left.eq_ignore_ascii_case(right))
    })
}

fn is_routine(line: &[u8]) -> bool {
    line.starts_with(b"test ") && (line.ends_with(b" ... ok") || line.ends_with(b" ok"))
        || line.starts_with(b"   Compiling ")
        || line.starts_with(b"Compiling ")
        || line.starts_with(b"Checking ")
        || line.starts_with(b"Fresh ")
        || contains_ascii_insensitive(line, b" passed in ")
}

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

    #[test]
    fn successful_test_names_and_fixture_values_are_not_diagnostics() {
        assert_eq!(
            classify(b"test handles_error_without_failure ... ok\n"),
            EvidenceClass::Noise
        );
        assert_eq!(
            classify(b"      \"code\": \"space.variable.ENRICH_MAX_ERROR_RATE\",\n"),
            EvidenceClass::Example
        );
        assert_eq!(
            classify(b"E       AssertionError: assert 1 == 2\n"),
            EvidenceClass::Diagnostic
        );
        assert_eq!(
            classify(b"error[E0308]: type mismatch\n"),
            EvidenceClass::Diagnostic
        );
    }
}