gobby-wiki 0.7.0

Gobby wiki CLI shell
use super::types::SynthesisSource;

pub(super) fn render_frontmatter(
    markdown: &mut String,
    title: &str,
    source_kind: &str,
    handoff_id: &str,
    synthesis_mode: &str,
    degraded_sources: &[&str],
) {
    markdown.push_str("---\n");
    markdown.push_str("title: ");
    markdown.push_str(&yaml_scalar(title));
    markdown.push('\n');
    markdown.push_str("source_kind: ");
    markdown.push_str(&yaml_scalar(source_kind));
    markdown.push('\n');
    markdown.push_str("tags:\n");
    markdown.push_str("  - gwiki\n");
    markdown.push_str("  - compiled\n");
    markdown.push_str("compile_handoff: ");
    markdown.push_str(&yaml_scalar(handoff_id));
    markdown.push('\n');
    markdown.push_str("synthesis_mode: ");
    markdown.push_str(&yaml_scalar(synthesis_mode));
    markdown.push('\n');
    if !degraded_sources.is_empty() {
        markdown.push_str("degraded: true\n");
        markdown.push_str("degraded_sources:\n");
        for source in degraded_sources {
            markdown.push_str("  - ");
            markdown.push_str(source);
            markdown.push('\n');
        }
    }
    markdown.push_str("---\n\n");
}

pub(super) fn render_source_excerpts(markdown: &mut String, sources: &[SynthesisSource]) {
    if sources.is_empty() {
        markdown.push_str("No accepted sources were recorded.\n\n");
        return;
    }

    for source in sources {
        markdown.push_str("- ");
        markdown.push_str(&source.title);
        markdown.push_str(": ");
        if let Some(chunk) = source.chunks.first() {
            markdown.push_str(chunk);
        } else {
            markdown.push_str("Accepted source has no extracted body text.");
        }
        markdown.push('\n');
    }
    markdown.push('\n');
}

pub(super) fn render_list_section(markdown: &mut String, title: &str, values: &[String]) {
    markdown.push_str("## ");
    markdown.push_str(title);
    markdown.push_str("\n\n");
    if values.is_empty() {
        markdown.push_str("- None recorded.\n\n");
        return;
    }
    for value in values {
        markdown.push_str("- ");
        markdown.push_str(value);
        markdown.push('\n');
    }
    markdown.push('\n');
}

pub(super) fn yaml_scalar(value: &str) -> String {
    let mut escaped = String::with_capacity(value.len());
    for ch in value.chars() {
        match ch {
            '\\' => escaped.push_str("\\\\"),
            '"' => escaped.push_str("\\\""),
            '\n' => escaped.push_str("\\n"),
            '\r' => escaped.push_str("\\r"),
            '\t' => escaped.push_str("\\t"),
            '\0' => escaped.push_str("\\u0000"),
            '\u{0008}' => escaped.push_str("\\b"),
            '\u{000C}' => escaped.push_str("\\f"),
            '\u{007F}' => escaped.push_str("\\u007f"),
            ch if ch.is_control() => escaped.push_str(&format!("\\u{:04x}", ch as u32)),
            ch => escaped.push(ch),
        }
    }
    format!("\"{escaped}\"")
}