yarp-cli 0.3.0

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

#[must_use]
pub fn classify(line: &[u8]) -> EvidenceClass {
    let line = body(line);
    if line.is_empty() {
        EvidenceClass::Noise
    } else if is_diagnostic(line) {
        EvidenceClass::Diagnostic
    } else {
        EvidenceClass::Example
    }
}

fn is_diagnostic(line: &[u8]) -> bool {
    [
        &b"##[error]"[..],
        &b"##[warning]"[..],
        &b"[error]"[..],
        &b"[warning]"[..],
        &b"error: "[..],
        &b"warning: "[..],
        &b"fatal: "[..],
        &b"panic: "[..],
    ]
    .iter()
    .any(|prefix| starts_ascii_insensitive(line, prefix))
        || looks_timestamped(line)
            && [
                &b" ERROR "[..],
                &b" WARNING "[..],
                &b" WARN "[..],
                &b" FATAL "[..],
                &b" PANIC "[..],
                &b" FAILED "[..],
                &b"level=error"[..],
                &b"level=warning"[..],
                &b"\"level\":\"error\""[..],
                &b"\"level\":\"warning\""[..],
            ]
            .iter()
            .any(|needle| contains_ascii_insensitive(line, needle))
}

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 looks_timestamped(line: &[u8]) -> bool {
    line.get(..5)
        .is_some_and(|prefix| prefix[..4].iter().all(u8::is_ascii_digit) && prefix[4] == b'-')
        || [
            &b"Jan "[..],
            &b"Feb "[..],
            &b"Mar "[..],
            &b"Apr "[..],
            &b"May "[..],
            &b"Jun "[..],
            &b"Jul "[..],
            &b"Aug "[..],
            &b"Sep "[..],
            &b"Oct "[..],
            &b"Nov "[..],
            &b"Dec "[..],
        ]
        .iter()
        .any(|prefix| line.starts_with(prefix))
}

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

    #[test]
    fn source_code_terms_are_not_log_diagnostics() {
        assert_eq!(
            classify(b"+void main().catch((error: unknown) => {\n"),
            EvidenceClass::Example
        );
        assert_eq!(
            classify(b"2026-08-01T10:00:00Z ERROR request failed\n"),
            EvidenceClass::Diagnostic
        );
        assert_eq!(
            classify(b"2026-08-01T10:00:00Z level=warning message=retrying\n"),
            EvidenceClass::Diagnostic
        );
    }
}