yarp-cli 0.3.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);
    if line.is_empty() {
        EvidenceClass::Noise
    } else if is_cli_diagnostic(line) {
        EvidenceClass::Diagnostic
    } else if is_list_outcome(line) {
        EvidenceClass::Outcome
    } else {
        EvidenceClass::Example
    }
}

fn is_cli_diagnostic(line: &[u8]) -> bool {
    let line = trim_start(line);
    if [
        &b"error:"[..],
        &b"fatal:"[..],
        &b"warning:"[..],
        &b"gh: error"[..],
    ]
    .iter()
    .any(|prefix| starts_ascii_insensitive(line, prefix))
    {
        return true;
    }
    let command_prefix = [
        &b"ls:"[..],
        &b"find:"[..],
        &b"fd:"[..],
        &b"fdfind:"[..],
        &b"tree:"[..],
        &b"du:"[..],
        &b"df:"[..],
        &b"ps:"[..],
        &b"lsof:"[..],
        &b"free:"[..],
    ]
    .iter()
    .any(|prefix| starts_ascii_insensitive(line, prefix));
    command_prefix
        && [
            &b"cannot"[..],
            &b"could not"[..],
            &b"denied"[..],
            &b"error"[..],
            &b"failed"[..],
            &b"invalid"[..],
            &b"no such"[..],
            &b"unknown"[..],
            &b"unrecognized"[..],
        ]
        .iter()
        .any(|term| contains_ascii_insensitive(line, term))
}

fn is_list_outcome(line: &[u8]) -> bool {
    [
        &b"\tsuccess\t"[..],
        &b"\tfailure\t"[..],
        &b"\tcancelled\t"[..],
        &b"\tin_progress\t"[..],
        &b"\tqueued\t"[..],
    ]
    .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))
    })
}

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

    #[test]
    fn list_item_words_do_not_become_diagnostics() {
        assert_eq!(
            classify(b"abc123 fix: preserve error details after failures\n"),
            EvidenceClass::Example
        );
        assert_eq!(
            classify(b"completed\tfailure\tCI\tmain\n"),
            EvidenceClass::Outcome
        );
        assert_eq!(
            classify(b"fatal: bad revision\n"),
            EvidenceClass::Diagnostic
        );
        assert_eq!(
            classify(b"find: './private': Permission denied\n"),
            EvidenceClass::Diagnostic
        );
        assert_eq!(
            classify(b"ls: cannot access 'missing': No such file\n"),
            EvidenceClass::Diagnostic
        );
        assert_eq!(classify(b"find: source fixture\n"), EvidenceClass::Example);
    }
}