use std::collections::{BTreeMap, BTreeSet};
const DOCS: &[(&str, &str)] = &[
("README.md", include_str!("../docs/architecture/README.md")),
("REJOIN.md", include_str!("../docs/architecture/REJOIN.md")),
(
"appendices.md",
include_str!("../docs/architecture/appendices.md"),
),
(
"s0-s3-foundations.md",
include_str!("../docs/architecture/s0-s3-foundations.md"),
),
(
"s11-s12-milestones-and-risks.md",
include_str!("../docs/architecture/s11-s12-milestones-and-risks.md"),
),
(
"s13-decision-register.md",
include_str!("../docs/architecture/s13-decision-register.md"),
),
(
"s14-python-bindings.md",
include_str!("../docs/architecture/s14-python-bindings.md"),
),
(
"s4-schema.md",
include_str!("../docs/architecture/s4-schema.md"),
),
(
"s5-modules.md",
include_str!("../docs/architecture/s5-modules.md"),
),
(
"s6-s10-flows-to-dependencies.md",
include_str!("../docs/architecture/s6-s10-flows-to-dependencies.md"),
),
];
fn slug(heading: &str) -> String {
let cleaned: String = heading
.trim()
.to_lowercase()
.chars()
.filter(|c| *c != '`' && *c != '*')
.filter(|c| c.is_alphanumeric() || *c == '_' || *c == '-' || c.is_whitespace())
.map(|c| if c.is_whitespace() { '-' } else { c })
.collect();
cleaned.trim_matches('-').to_string()
}
fn anchors(body: &str) -> BTreeSet<String> {
let mut out = BTreeSet::new();
let mut rest = body;
while let Some(i) = rest.find("<a id=\"") {
rest = &rest[i + 7..];
if let Some(j) = rest.find('"') {
out.insert(rest[..j].to_string());
}
}
for line in body.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with('#') {
let text = trimmed.trim_start_matches('#');
if text.starts_with(' ') {
out.insert(slug(text));
}
}
}
out
}
fn without_code_fences(body: &str) -> String {
let mut out = String::with_capacity(body.len());
let mut inside = false;
for line in body.lines() {
if line.trim_start().starts_with("```") {
inside = !inside;
continue;
}
if !inside {
out.push_str(line);
out.push('\n');
}
}
out
}
fn links(body: &str) -> Vec<(String, Option<String>)> {
let text = without_code_fences(body);
let mut out = Vec::new();
let mut rest = text.as_str();
while let Some(i) = rest.find("](") {
rest = &rest[i + 2..];
let Some(end) = rest.find(')') else { break };
let target = &rest[..end];
rest = &rest[end..];
if target.contains("://") || target.starts_with('.') || target.starts_with('/') {
continue;
}
let (file, frag) = match target.split_once('#') {
Some((f, a)) => (f, Some(a.to_string())),
None => (target, None),
};
if file.ends_with(".md") {
out.push((file.to_string(), frag));
}
}
out
}
#[test]
fn every_cross_reference_resolves() {
let by_name: BTreeMap<&str, &str> = DOCS.iter().copied().collect();
let anchor_sets: BTreeMap<&str, BTreeSet<String>> =
DOCS.iter().map(|(n, b)| (*n, anchors(b))).collect();
let mut broken = Vec::new();
for (name, body) in DOCS {
for (target, frag) in links(body) {
let Some(_) = by_name.get(target.as_str()) else {
broken.push(format!("{name}: no such file -> {target}"));
continue;
};
if let Some(anchor) = frag {
if !anchor_sets[target.as_str()].contains(&anchor) {
broken.push(format!("{name}: dead anchor -> {target}#{anchor}"));
}
}
}
}
assert!(
broken.is_empty(),
"{} cross-reference(s) do not resolve. A renamed heading is a local \
edit whose cost lands on a reader following a link:\n {}",
broken.len(),
broken.join("\n ")
);
}
#[test]
fn every_document_in_the_directory_is_checked() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("docs/architecture");
let on_disk: BTreeSet<String> = std::fs::read_dir(&dir)
.expect("docs/architecture is missing")
.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.ends_with(".md"))
.collect();
let listed: BTreeSet<String> = DOCS.iter().map(|(n, _)| n.to_string()).collect();
assert_eq!(
on_disk,
listed,
"DOCS is out of step with docs/architecture. Unchecked on disk: {:?}; \
listed but absent: {:?}",
on_disk.difference(&listed).collect::<Vec<_>>(),
listed.difference(&on_disk).collect::<Vec<_>>()
);
}
#[test]
fn the_navigation_footers_form_a_single_chain() {
fn next_of(body: &str) -> Option<String> {
let i = body.find("[next](")? + 7;
let rest = &body[i..];
Some(rest[..rest.find(')')?].to_string())
}
fn prev_of(body: &str) -> Option<String> {
let i = body.find("[previous](")? + 11;
let rest = &body[i..];
Some(rest[..rest.find(')')?].to_string())
}
let by_name: BTreeMap<&str, &str> = DOCS.iter().copied().collect();
let mut walked = vec!["README.md".to_string()];
let mut here = "README.md".to_string();
while let Some(next) = next_of(by_name[here.as_str()]) {
assert!(
by_name.contains_key(next.as_str()),
"{here} points next at {next}, which is not in the set"
);
if here != "README.md" {
assert_eq!(
prev_of(by_name[next.as_str()]).as_deref(),
Some(here.as_str()),
"{next}'s `previous` does not point back at {here}"
);
} else {
assert!(
by_name[next.as_str()].contains("[index](README.md)"),
"{next} is first in the chain but does not link back to the index"
);
}
assert!(
!walked.contains(&next),
"the nav chain loops: reached {next} twice"
);
walked.push(next.clone());
here = next;
}
let expected: BTreeSet<String> = DOCS
.iter()
.map(|(n, _)| n.to_string())
.filter(|n| n != "REJOIN.md")
.collect();
let reached: BTreeSet<String> = walked.into_iter().collect();
assert_eq!(
reached,
expected,
"the nav chain does not visit every document. Unreachable: {:?}",
expected.difference(&reached).collect::<Vec<_>>()
);
}