Skip to main content

heddle_cli_render/cli/render/
fsck.rs

1// SPDX-License-Identifier: Apache-2.0
2use anyhow::Result;
3use verbs::FsckReport;
4
5use crate::cli::{render::write_stdout, style};
6
7pub fn fsck_json(report: &FsckReport) -> Result<()> {
8    let mut text = serde_json::to_string(report)?;
9    text.push('\n');
10    write_stdout(&text)
11}
12
13pub fn fsck_text(report: &FsckReport) -> Result<()> {
14    write_stdout(&format_fsck_text(report))
15}
16
17fn format_fsck_text(report: &FsckReport) -> String {
18    let mut text = String::new();
19    if report.valid {
20        let counted = style::count(report.objects_checked, "object");
21        text.push_str(&format!(
22            "{} repository is valid ({counted} checked)\n",
23            style::ok_marker(),
24        ));
25        if report.git_projection_checked {
26            text.push_str(&format!(
27                "  {}\n",
28                style::field("Git projection", "mapping, notes, and checkout checked")
29            ));
30        }
31    } else {
32        text.push_str(&format!(
33            "{} repository has {}\n",
34            style::error_marker(),
35            style::count(report.errors.len(), "integrity error")
36        ));
37        for error in &report.errors {
38            if let Some(obj) = &error.object {
39                text.push_str(&format!(
40                    "  {} {} {}\n",
41                    style::error(&format!("[{}]", error.kind)),
42                    error.message,
43                    style::dim(&format!("({obj})"))
44                ));
45            } else {
46                text.push_str(&format!(
47                    "  {} {}\n",
48                    style::error(&format!("[{}]", error.kind)),
49                    error.message
50                ));
51            }
52        }
53    }
54    if let Some(target) = &report.repair_target {
55        let status = if report.repaired {
56            "repaired"
57        } else {
58            "no changes"
59        };
60        text.push_str(&format!(
61            "  {}\n",
62            style::field("Repair", &format!("{target}: {status}"))
63        ));
64        for repair in &report.repairs {
65            if repair.count > 0 || repair.repaired {
66                text.push_str(&format!(
67                    "    {} {} ({})\n",
68                    repair.name, repair.detail, repair.count
69                ));
70            }
71        }
72    }
73    if let Some(provenance) = &report.provenance {
74        text.push_str(&format!(
75            "  {}\n",
76            style::field("Provenance registry", &provenance.registry_status)
77        ));
78        for state in &provenance.states {
79            let short = state
80                .state_id
81                .strip_prefix("hs-")
82                .unwrap_or(&state.state_id);
83            let short = &short[..short.len().min(12)];
84            text.push_str(&format!(
85                "    {short} {:<24} {}\n",
86                state.display_status(),
87                state.detail
88            ));
89        }
90    }
91    for warning in &report.warnings {
92        text.push_str(&format!("{} {}\n", style::warn_marker(), warning));
93    }
94    text
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn text_renderer_consumes_the_typed_fsck_report() {
103        let report = FsckReport {
104            valid: true,
105            errors: Vec::new(),
106            warnings: vec!["legacy object retained".to_string()],
107            objects_checked: 2,
108            git_projection_checked: false,
109            provenance: None,
110            repair_target: None,
111            repaired: false,
112            repairs: Vec::new(),
113        };
114
115        let text = format_fsck_text(&report);
116        assert!(text.contains("repository is valid (2 objects checked)"));
117        assert!(text.contains("legacy object retained"));
118    }
119}