diagprint 0.6.0

Pretty, structured diagnostics and reports for Rust applications.
Documentation
use super::Renderer;
use crate::{Diagnostic, Label, LabelKind};

#[derive(Debug, Default, Clone, Copy)]
pub struct MarkdownRenderer;

fn label_kind_name(kind: LabelKind) -> &'static str {
    match kind {
        LabelKind::Primary => "Primary",
        LabelKind::Secondary => "Secondary",
    }
}

fn label_location(label: &Label) -> String {
    let location = &label.location;

    format!(
        "{}:{}{}",
        location.file,
        location.line,
        location
            .column
            .map(|column| format!(":{column}"))
            .unwrap_or_default()
    )
}

impl Renderer for MarkdownRenderer {
    fn render(&self, d: &Diagnostic) -> String {
        let mut o = format!(
            "# {}{}\n\n**Application:** `{}`  \n**Timestamp:** `{}`  \n**PID:** `{}`  \n**Host:** `{}`  \n**Session:** `{}`  \n**Report:** `{}`\n\n## Message\n\n{}\n",
            d.severity,
            d.code
                .as_ref()
                .map(|c| format!(" — `{c}`"))
                .unwrap_or_default(),
            d.application,
            d.timestamp.to_rfc3339(),
            d.pid,
            d.hostname,
            d.session_id,
            d.report_id,
            d.message
        );

        if !d.labels.is_empty() {
            o.push_str("\n## Labels\n\n");

            for label in &d.labels {
                o.push_str(&format!(
                    "- **{}:** `{}`",
                    label_kind_name(label.kind),
                    label_location(label)
                ));

                if let Some(length) = label.length {
                    o.push_str(&format!(" (length: `{length}`)"));
                }

                if let Some(message) = &label.message {
                    o.push_str(" — ");
                    o.push_str(message);
                }

                o.push('\n');
            }
        }

        if let Some(c) = &d.cause {
            o.push_str("\n## Causes\n\n");

            for (i, x) in c.iter().enumerate() {
                o.push_str(&format!("{}- {}\n", "  ".repeat(i), x.message));
            }
        }

        if !d.notes.is_empty() {
            o.push_str("\n## Notes\n\n");

            for n in &d.notes {
                o.push_str(&format!("- {n}\n"));
            }
        }

        if let Some(h) = &d.help {
            o.push_str(&format!("\n## Help\n\n{h}\n"));
        }

        o
    }
}