Skip to main content

soothfast_docs/
reference.rs

1//! API reference fragments: per-crate markdown generated from the surface —
2//! literal source signatures + doc comments, enriched with measured costs
3//! and verified claims from the baseline. Regenerated on demand so the
4//! reference cannot be stale: every number on the page was produced, never
5//! written.
6
7use std::collections::BTreeMap;
8
9use serde_json::Value;
10
11use crate::surface::{ItemGroup, Surface};
12
13/// Rustdoc comments often disambiguate intra-doc links with a reference-style
14/// definition (`` [`Foo::bar`]: crate::Foo::bar ``); the site's markdown
15/// renderer only supports inline links. Drop each definition line and inline
16/// it into the shorthand `[`Foo::bar`]` references it defines. Doesn't handle
17/// the separate `[text][label]` reference form — not seen in practice here.
18pub fn resolve_reference_links(text: &str) -> String {
19    let mut defs = Vec::new();
20    let mut body = String::with_capacity(text.len());
21    for line in text.split_inclusive('\n') {
22        match parse_ref_def(line.trim_end_matches('\n').trim()) {
23            Some(def) => defs.push(def),
24            None => body.push_str(line),
25        }
26    }
27    if defs.is_empty() {
28        return text.to_string();
29    }
30    for (label, target) in &defs {
31        let shorthand = format!("[{label}]");
32        let inline = format!("[{label}]({target})");
33        body = body.replace(&shorthand, &inline);
34    }
35    body
36}
37
38fn parse_ref_def(line: &str) -> Option<(String, String)> {
39    let after_open = line.strip_prefix('[')?;
40    let close = after_open.find(']')?;
41    let label = &after_open[..close];
42    let target = after_open[close + 1..].strip_prefix(':')?.trim();
43    (!label.is_empty() && !target.is_empty()).then(|| (label.to_string(), target.to_string()))
44}
45
46/// Render one crate's surface as a markdown fragment (module-grouped).
47/// `baseline` supplies measurements and assertion verdicts; pass `Value::Null`
48/// to render signatures + docs only.
49pub fn render(
50    crate_name: &str,
51    surface: &Surface,
52    docs_text: &BTreeMap<String, String>,
53    baseline: &Value,
54) -> String {
55    // Grouped so a function with multiple `pub use` aliases (see
56    // `Surface::grouped`) renders once, under its shortest public path.
57    let groups = surface.grouped();
58    let mut by_module: BTreeMap<&str, Vec<&ItemGroup<'_>>> = BTreeMap::new();
59    for g in &groups {
60        let path = g.representative;
61        let module = path.rsplit_once("::").map(|(m, _)| m).unwrap_or(path);
62        by_module.entry(module).or_default().push(g);
63    }
64
65    let mut out = format!(
66        "# `{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"
67    );
68    for (module, groups) in by_module {
69        out.push_str(&format!("\n## `{module}`\n"));
70        for g in groups {
71            let path = g.representative;
72            let info = &surface.items[path];
73            if info.kind == "module" {
74                continue;
75            }
76            let name = path.rsplit("::").next().unwrap_or(path);
77            out.push_str(&format!("\n### `{name}` <sup>{}</sup>\n\n", info.kind));
78            if !info.signature.is_empty() {
79                // `ignore`: bare signatures aren't compilable examples.
80                out.push_str(&format!("```rust ignore\n{}\n```\n", info.signature));
81            }
82            if let Some(docs) = docs_text.get(path)
83                && !docs.trim().is_empty()
84            {
85                out.push_str(&format!("\n{}\n", docs.trim()));
86            }
87            out.push_str(&item_evidence(&g.aliases, baseline));
88        }
89    }
90    out
91}
92
93/// Verified-claims and measured-cost blocks for one item, from the baseline
94/// entries that cover it (`covers = "..."` attribution, or matching any of
95/// the item's aliases directly). Emitted as raw HTML using the same
96/// `.ev-row`/`.ev-ledger` classes the site's evidence-chip CSS already
97/// styles for hand-authored `covers=` ledgers (see soothfast-site's
98/// evidence.rs) — this crate can't depend on soothfast-site to reuse its
99/// chip-building code directly (dependency runs the other way), but nothing
100/// stops it from targeting the same class names, so generated and
101/// hand-authored pages carry one visual identity.
102fn item_evidence(aliases: &[&String], baseline: &Value) -> String {
103    let mut verified = String::new();
104    let mut measured = String::new();
105
106    let Some(items) = baseline["items"].as_object() else {
107        return String::new();
108    };
109    for (id, m) in items {
110        let target = m["covers"].as_str().filter(|c| !c.is_empty()).unwrap_or(id);
111        if !aliases
112            .iter()
113            .any(|p| target == p.as_str() || id == p.as_str())
114        {
115            continue;
116        }
117
118        for a in baseline["assertions"].as_array().into_iter().flatten() {
119            if a["id"].as_str() != Some(id.as_str()) {
120                continue;
121            }
122            let (state, mark) = if a["ok"].as_bool() == Some(true) {
123                ("pass", "✓")
124            } else {
125                ("fail", "✗")
126            };
127            verified.push_str(&format!(
128                "<div class=\"ev-row ev-row-{state}\"><span class=\"ev-mark\">{mark}</span>\
129<span class=\"ev-item\">{}</span><span class=\"ev-detail\">{}</span></div>",
130                escape(a["kind"].as_str().unwrap_or("?")),
131                escape(a["detail"].as_str().unwrap_or("")),
132            ));
133        }
134
135        let mut rows = String::new();
136        for (label, keys) in [
137            ("instructions/iter", ["perfcnt", "instructions"]),
138            ("callgrind Ir/iter", ["callgrind", "ir"]),
139            ("median walltime (ns)", ["walltime", "median_ns"]),
140            ("p99 walltime (ns)", ["walltime", "p99_ns"]),
141            ("allocations/iter", ["alloc", "allocs"]),
142            ("allocated bytes/iter", ["alloc", "bytes"]),
143            ("polls/iter", ["asyncexec", "polls"]),
144            ("wakes/iter", ["asyncexec", "wakes"]),
145        ] {
146            if let Some(v) = m[keys[0]][keys[1]].as_f64() {
147                rows.push_str(&format!(
148                    "<div class=\"ev-row\"><span class=\"ev-metric\">{}</span>\
149<span class=\"ev-actual\">{v:.0}</span></div>",
150                    escape(label),
151                ));
152            }
153        }
154        if !rows.is_empty() {
155            measured.push_str(&format!(
156                "<div class=\"ev-ledger\"><div class=\"ev-ledger-head\">measured as `{}`</div>{rows}</div>",
157                escape(id),
158            ));
159        }
160    }
161
162    if verified.is_empty() && measured.is_empty() {
163        return String::new();
164    }
165    let mut inner = String::new();
166    if !verified.is_empty() {
167        inner.push_str(&format!(
168            "<div class=\"ev-ledger\"><div class=\"ev-ledger-head\">verified claims</div>{verified}</div>"
169        ));
170    }
171    inner.push_str(&measured);
172    format!("\n<div class=\"evidence-panel\">{inner}</div>\n")
173}
174
175/// Minimal HTML-attribute/text escape — this crate has no template engine of
176/// its own (that lives downstream in soothfast-site) so it needs its own
177/// copy for the raw HTML fragments above.
178fn escape(s: &str) -> String {
179    let mut out = String::with_capacity(s.len());
180    for c in s.chars() {
181        match c {
182            '&' => out.push_str("&amp;"),
183            '<' => out.push_str("&lt;"),
184            '>' => out.push_str("&gt;"),
185            '"' => out.push_str("&quot;"),
186            '\'' => out.push_str("&#39;"),
187            c => out.push(c),
188        }
189    }
190    out
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn inlines_shorthand_reference_links() {
199        let text = "See [`Region::utc_offset_secs`] for details.\n\n[`Region::utc_offset_secs`]: crate::constants::Region::utc_offset_secs\n";
200        let resolved = resolve_reference_links(text);
201        assert_eq!(
202            resolved,
203            "See [`Region::utc_offset_secs`](crate::constants::Region::utc_offset_secs) for details.\n\n"
204        );
205    }
206
207    #[test]
208    fn leaves_text_without_definitions_untouched() {
209        let text = "Plain prose with [an inline link](https://example.com).\n";
210        assert_eq!(resolve_reference_links(text), text);
211    }
212}