heddle-cli-render 0.15.7

Heddle's terminal presentation layer.
Documentation
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use verbs::FsckReport;

use crate::cli::{render::write_stdout, style};

pub fn fsck_json(report: &FsckReport) -> Result<()> {
    let mut text = serde_json::to_string(report)?;
    text.push('\n');
    write_stdout(&text)
}

pub fn fsck_text(report: &FsckReport) -> Result<()> {
    write_stdout(&format_fsck_text(report))
}

fn format_fsck_text(report: &FsckReport) -> String {
    let mut text = String::new();
    if report.valid {
        let counted = style::count(report.objects_checked, "object");
        text.push_str(&format!(
            "{} repository is valid ({counted} checked)\n",
            style::ok_marker(),
        ));
        if report.git_projection_checked {
            text.push_str(&format!(
                "  {}\n",
                style::field("Git projection", "mapping, notes, and checkout checked")
            ));
        }
    } else {
        text.push_str(&format!(
            "{} repository has {}\n",
            style::error_marker(),
            style::count(report.errors.len(), "integrity error")
        ));
        for error in &report.errors {
            if let Some(obj) = &error.object {
                text.push_str(&format!(
                    "  {} {} {}\n",
                    style::error(&format!("[{}]", error.kind)),
                    error.message,
                    style::dim(&format!("({obj})"))
                ));
            } else {
                text.push_str(&format!(
                    "  {} {}\n",
                    style::error(&format!("[{}]", error.kind)),
                    error.message
                ));
            }
        }
    }
    if let Some(target) = &report.repair_target {
        let status = if report.repaired {
            "repaired"
        } else {
            "no changes"
        };
        text.push_str(&format!(
            "  {}\n",
            style::field("Repair", &format!("{target}: {status}"))
        ));
        for repair in &report.repairs {
            if repair.count > 0 || repair.repaired {
                text.push_str(&format!(
                    "    {} {} ({})\n",
                    repair.name, repair.detail, repair.count
                ));
            }
        }
    }
    if let Some(provenance) = &report.provenance {
        text.push_str(&format!(
            "  {}\n",
            style::field("Provenance registry", &provenance.registry_status)
        ));
        for state in &provenance.states {
            let short = state
                .state_id
                .strip_prefix("hs-")
                .unwrap_or(&state.state_id);
            let short = &short[..short.len().min(12)];
            text.push_str(&format!(
                "    {short} {:<24} {}\n",
                state.display_status(),
                state.detail
            ));
        }
    }
    for warning in &report.warnings {
        text.push_str(&format!("{} {}\n", style::warn_marker(), warning));
    }
    text
}

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

    #[test]
    fn text_renderer_consumes_the_typed_fsck_report() {
        let report = FsckReport {
            valid: true,
            errors: Vec::new(),
            warnings: vec!["legacy object retained".to_string()],
            objects_checked: 2,
            git_projection_checked: false,
            provenance: None,
            repair_target: None,
            repaired: false,
            repairs: Vec::new(),
        };

        let text = format_fsck_text(&report);
        assert!(text.contains("repository is valid (2 objects checked)"));
        assert!(text.contains("legacy object retained"));
    }
}