prx 0.5.9

Praxis — agent-native Unix tools. Single binary replacing grep, cat, find, sed, diff for AI coding agents.
use super::{Diagnostic, ParsedResult, define_regex};

define_regex!(TOTAL_RE, r"^TOTAL\s+\d+\s+\d+\s+([\d.]+%)");
define_regex!(FILE_COV_RE, r"^(\S+\.py)\s+\d+\s+\d+\s+([\d.]+%)");
define_regex!(STMTS_MISSED_RE, r"^TOTAL\s+(\d+)\s+(\d+)");

pub fn parse(output: &str) -> ParsedResult {
    let mut total_coverage = String::new();
    let mut total_stmts = 0;
    let mut total_missed = 0;
    let mut warnings = Vec::new();

    for line in output.lines() {
        if let Some(caps) = TOTAL_RE.captures(line) {
            total_coverage = caps[1].to_string();
        }

        if let Some(caps) = STMTS_MISSED_RE.captures(line) {
            total_stmts = caps[1].parse().unwrap_or(0);
            total_missed = caps[2].parse().unwrap_or(0);
        }

        if let Some(caps) = FILE_COV_RE.captures(line) {
            let file = &caps[1];
            let pct_str = &caps[2];
            let pct: f32 = pct_str.trim_end_matches('%').parse().unwrap_or(100.0);
            if pct < 80.0 {
                warnings.push(Diagnostic {
                    name: file.to_string(),
                    location: None,
                    message: format!("{pct_str} coverage"),
                });
            }
        }
    }

    let summary = if total_coverage.is_empty() {
        "no coverage data".to_string()
    } else {
        format!(
            "{} total coverage ({} stmts, {} missed)",
            total_coverage, total_stmts, total_missed
        )
    };

    ParsedResult {
        summary,
        passed: 0,
        failed: 0,
        skipped: 0,
        failures: vec![],
        warnings,
        tail: None,
    }
}

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

    #[test]
    fn parse_pytest_cov_with_header() {
        let output = "\
---------- coverage: platform linux, python 3.11 ----------
Name                      Stmts   Miss  Cover
----------------------------------------------
src/auth.py                  42     12    71%
src/handler.py              100      5    95%
src/__init__.py               2      0   100%
----------------------------------------------
TOTAL                       144     17    88%
";
        let result = parse(output);
        assert!(result.summary.contains("88%"));
        assert!(result.summary.contains("144 stmts"));
        assert!(result.summary.contains("17 missed"));
        assert_eq!(result.warnings.len(), 1);
        assert_eq!(result.warnings[0].name, "src/auth.py");
        assert!(result.warnings[0].message.contains("71%"));
    }

    #[test]
    fn parse_coverage_report_format() {
        let output = "\
Name                      Stmts   Miss  Cover
----------------------------------------------
src/auth.py                  42     12    71%
src/handler.py              100      5    95%
----------------------------------------------
TOTAL                       144     17    88%
";
        let result = parse(output);
        assert!(result.summary.contains("88%"));
        assert_eq!(result.warnings.len(), 1);
    }

    #[test]
    fn parse_all_high_coverage() {
        let output = "\
Name                      Stmts   Miss  Cover
----------------------------------------------
src/auth.py                  42      2    95%
src/handler.py              100      5    95%
----------------------------------------------
TOTAL                       144      7    95%
";
        let result = parse(output);
        assert!(result.summary.contains("95%"));
        assert!(result.warnings.is_empty());
    }
}