soothfast-docs 0.1.17

Soothfast docs engine: rustdoc JSON surface, bind blocks, claims, doc-test generation
Documentation
//! API reference fragments: per-crate markdown generated from the surface —
//! literal source signatures + doc comments, enriched with measured costs
//! and verified claims from the baseline. Regenerated on demand so the
//! reference cannot be stale: every number on the page was produced, never
//! written.

use std::collections::BTreeMap;

use serde_json::Value;

use crate::surface::{ItemGroup, Surface};

/// Rustdoc comments often disambiguate intra-doc links with a reference-style
/// definition (`` [`Foo::bar`]: crate::Foo::bar ``); the site's markdown
/// renderer only supports inline links. Drop each definition line and inline
/// it into the shorthand `[`Foo::bar`]` references it defines. Doesn't handle
/// the separate `[text][label]` reference form — not seen in practice here.
pub fn resolve_reference_links(text: &str) -> String {
    let mut defs = Vec::new();
    let mut body = String::with_capacity(text.len());
    for line in text.split_inclusive('\n') {
        match parse_ref_def(line.trim_end_matches('\n').trim()) {
            Some(def) => defs.push(def),
            None => body.push_str(line),
        }
    }
    if defs.is_empty() {
        return text.to_string();
    }
    for (label, target) in &defs {
        let shorthand = format!("[{label}]");
        let inline = format!("[{label}]({target})");
        body = body.replace(&shorthand, &inline);
    }
    body
}

fn parse_ref_def(line: &str) -> Option<(String, String)> {
    let after_open = line.strip_prefix('[')?;
    let close = after_open.find(']')?;
    let label = &after_open[..close];
    let target = after_open[close + 1..].strip_prefix(':')?.trim();
    (!label.is_empty() && !target.is_empty()).then(|| (label.to_string(), target.to_string()))
}

/// Render one crate's surface as a markdown fragment (module-grouped).
/// `baseline` supplies measurements and assertion verdicts; pass `Value::Null`
/// to render signatures + docs only.
pub fn render(
    crate_name: &str,
    surface: &Surface,
    docs_text: &BTreeMap<String, String>,
    baseline: &Value,
) -> String {
    // Grouped so a function with multiple `pub use` aliases (see
    // `Surface::grouped`) renders once, under its shortest public path.
    let groups = surface.grouped();
    let mut by_module: BTreeMap<&str, Vec<&ItemGroup<'_>>> = BTreeMap::new();
    for g in &groups {
        let path = g.representative;
        let module = path.rsplit_once("::").map(|(m, _)| m).unwrap_or(path);
        by_module.entry(module).or_default().push(g);
    }

    let mut out = format!(
        "# `{crate_name}` API reference\n\n> Generated by `cargo soothfast docs reference`\n> Measurements and verified claims come from the latest baseline; CI gates them.\n"
    );
    for (module, groups) in by_module {
        out.push_str(&format!("\n## `{module}`\n"));
        for g in groups {
            let path = g.representative;
            let info = &surface.items[path];
            if info.kind == "module" {
                continue;
            }
            let name = path.rsplit("::").next().unwrap_or(path);
            out.push_str(&format!("\n### `{name}` <sup>{}</sup>\n\n", info.kind));
            if !info.signature.is_empty() {
                // `ignore`: bare signatures aren't compilable examples.
                out.push_str(&format!("```rust ignore\n{}\n```\n", info.signature));
            }
            if let Some(docs) = docs_text.get(path)
                && !docs.trim().is_empty()
            {
                out.push_str(&format!("\n{}\n", docs.trim()));
            }
            out.push_str(&item_evidence(&g.aliases, baseline));
        }
    }
    out
}

/// Verified-claims and measured-cost blocks for one item, from the baseline
/// entries that cover it (`covers = "..."` attribution, or matching any of
/// the item's aliases directly). Emitted as raw HTML using the same
/// `.ev-row`/`.ev-ledger` classes the site's evidence-chip CSS already
/// styles for hand-authored `covers=` ledgers (see soothfast-site's
/// evidence.rs) — this crate can't depend on soothfast-site to reuse its
/// chip-building code directly (dependency runs the other way), but nothing
/// stops it from targeting the same class names, so generated and
/// hand-authored pages carry one visual identity.
fn item_evidence(aliases: &[&String], baseline: &Value) -> String {
    let mut verified = String::new();
    let mut measured = String::new();

    let Some(items) = baseline["items"].as_object() else {
        return String::new();
    };
    for (id, m) in items {
        let target = m["covers"].as_str().filter(|c| !c.is_empty()).unwrap_or(id);
        if !aliases
            .iter()
            .any(|p| target == p.as_str() || id == p.as_str())
        {
            continue;
        }

        for a in baseline["assertions"].as_array().into_iter().flatten() {
            if a["id"].as_str() != Some(id.as_str()) {
                continue;
            }
            let (state, mark) = if a["ok"].as_bool() == Some(true) {
                ("pass", "✓")
            } else {
                ("fail", "✗")
            };
            verified.push_str(&format!(
                "<div class=\"ev-row ev-row-{state}\"><span class=\"ev-mark\">{mark}</span>\
<span class=\"ev-item\">{}</span><span class=\"ev-detail\">{}</span></div>",
                escape(a["kind"].as_str().unwrap_or("?")),
                escape(a["detail"].as_str().unwrap_or("")),
            ));
        }

        let mut rows = String::new();
        for (label, keys) in [
            ("instructions/iter", ["perfcnt", "instructions"]),
            ("callgrind Ir/iter", ["callgrind", "ir"]),
            ("median walltime (ns)", ["walltime", "median_ns"]),
            ("p99 walltime (ns)", ["walltime", "p99_ns"]),
            ("allocations/iter", ["alloc", "allocs"]),
            ("allocated bytes/iter", ["alloc", "bytes"]),
            ("polls/iter", ["asyncexec", "polls"]),
            ("wakes/iter", ["asyncexec", "wakes"]),
        ] {
            if let Some(v) = m[keys[0]][keys[1]].as_f64() {
                rows.push_str(&format!(
                    "<div class=\"ev-row\"><span class=\"ev-metric\">{}</span>\
<span class=\"ev-actual\">{v:.0}</span></div>",
                    escape(label),
                ));
            }
        }
        if !rows.is_empty() {
            measured.push_str(&format!(
                "<div class=\"ev-ledger\"><div class=\"ev-ledger-head\">measured as `{}`</div>{rows}</div>",
                escape(id),
            ));
        }
    }

    if verified.is_empty() && measured.is_empty() {
        return String::new();
    }
    let mut inner = String::new();
    if !verified.is_empty() {
        inner.push_str(&format!(
            "<div class=\"ev-ledger\"><div class=\"ev-ledger-head\">verified claims</div>{verified}</div>"
        ));
    }
    inner.push_str(&measured);
    format!("\n<div class=\"evidence-panel\">{inner}</div>\n")
}

/// Minimal HTML-attribute/text escape — this crate has no template engine of
/// its own (that lives downstream in soothfast-site) so it needs its own
/// copy for the raw HTML fragments above.
fn escape(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&#39;"),
            c => out.push(c),
        }
    }
    out
}

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

    #[test]
    fn inlines_shorthand_reference_links() {
        let text = "See [`Region::utc_offset_secs`] for details.\n\n[`Region::utc_offset_secs`]: crate::constants::Region::utc_offset_secs\n";
        let resolved = resolve_reference_links(text);
        assert_eq!(
            resolved,
            "See [`Region::utc_offset_secs`](crate::constants::Region::utc_offset_secs) for details.\n\n"
        );
    }

    #[test]
    fn leaves_text_without_definitions_untouched() {
        let text = "Plain prose with [an inline link](https://example.com).\n";
        assert_eq!(resolve_reference_links(text), text);
    }
}