use std::collections::BTreeMap;
use serde_json::Value;
use crate::surface::{ItemGroup, Surface};
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()))
}
pub fn render(
crate_name: &str,
surface: &Surface,
docs_text: &BTreeMap<String, String>,
baseline: &Value,
) -> String {
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() {
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
}
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")
}
fn escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
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);
}
}