tessellate 0.3.0

Command-line interface for the Tess rule language
use anstyle::{AnsiColor, Style};
use std::fmt::Write as _;
use tess::analysis::AnalysisReport;

const SUCCESS_STYLE: Style = AnsiColor::Green.on_default().bold();
const FAILURE_STYLE: Style = AnsiColor::Red.on_default().bold();
const INCONCLUSIVE_STYLE: Style = AnsiColor::Yellow.on_default().bold();
const WARNING_STYLE: Style = AnsiColor::Yellow.on_default().bold();
const COUNTEREXAMPLE_STYLE: Style = AnsiColor::Red.on_default().bold();
const DETAIL_STYLE: Style = AnsiColor::BrightBlack.on_default();

pub(crate) fn render_report(report: &AnalysisReport, color: bool) -> String {
    let plain = report.render_text();
    if !color {
        return plain;
    }

    let mut output = String::with_capacity(plain.len());
    for line in plain.split_inclusive('\n') {
        let (content, newline) = line
            .strip_suffix('\n')
            .map_or((line, ""), |content| (content, "\n"));
        if let Some(style) = line_style(content) {
            let _ = write!(output, "{style}{content}{style:#}");
        } else {
            output.push_str(content);
        }
        output.push_str(newline);
    }
    output
}

fn line_style(line: &str) -> Option<Style> {
    if line.starts_with("") {
        Some(SUCCESS_STYLE)
    } else if line.starts_with("") {
        Some(FAILURE_STYLE)
    } else if line.starts_with("? ") {
        Some(INCONCLUSIVE_STYLE)
    } else if line.starts_with("! ") {
        Some(WARNING_STYLE)
    } else if line.starts_with("  counterexample ·") {
        Some(COUNTEREXAMPLE_STYLE)
    } else if line.starts_with("  proved for all inputs")
        || line.starts_with("  satisfying input found")
        || line.starts_with("  no satisfying input exists")
        || line.starts_with("  bounded trace proved")
        || line.starts_with("  trace counterexample found")
        || line.starts_with("  trace analysis inconclusive")
        || line.starts_with("  counterexample found")
        || line.starts_with("  failed")
        || line.starts_with("  inconclusive")
        || line.starts_with("  reason ·")
        || line.starts_with("    reason ·")
    {
        Some(DETAIL_STYLE)
    } else {
        None
    }
}

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

    #[test]
    fn existential_summaries_use_detail_styling() {
        assert_eq!(
            line_style("  satisfying input found · after 2 evaluations"),
            Some(DETAIL_STYLE)
        );
        assert_eq!(
            line_style("  no satisfying input exists · all 3 combinations checked"),
            Some(DETAIL_STYLE)
        );
    }
}