Render the manual as one HTML page the application can carry
The manual existed only as docs/manual/README.md, which the forge renders and nothing else does. An installed copy of the application, on a laptop with no network or on a tablet, had no manual it could open. `traces manual` renders the README to docs/manual/index.html with pulldown-cmark (already in the tree as Slint's Markdown parser, so this adds a dependency edge and no crate). The page is one file with an inline stylesheet that follows the system's light or dark preference, a contents list of every section and subsection, and the pictures by their relative media/ paths. Each heading carries the id the forge gives it, so README.md#rating-and-flagging and index.html#rating-and-flagging are the same link. A picture alone in its paragraph becomes a figure whose alt text is shown as the caption, and every picture reserves its 16:11 box before it loads, so a jump into the middle of the page lands where it aimed rather than a screenful above. Links to design documents, which the installed page has no copy of, point at the forge. The page is committed rather than rendered at build time, as the gesture book is: it is user-facing text reviewed in the diff, and the three packagers then only copy it. `traces manual-check` fails in CI when the committed page is not the render of the README, and the pre-commit hook regenerates it when the README is staged.
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
//! traces check # gate: non-zero exit on failure
|
||||
//! traces gestures # write docs/gestures.md and the app's gesture table
|
||||
//! traces gestures-check # gate: non-zero exit if either has drifted
|
||||
//! traces manual # write docs/manual/index.html from its README
|
||||
//! traces manual-check # gate: non-zero exit if the page has drifted
|
||||
//! ```
|
||||
//!
|
||||
//! The gesture half scans a different tag out of the same files — see
|
||||
@@ -59,6 +61,12 @@ const GESTURE_TABLE: &str = "ui/dr-ui/src/gesture_book.rs";
|
||||
/// its own examples is a scanner nobody can document.
|
||||
const GESTURE_ROOTS: &[&str] = &["ui", "apps"];
|
||||
|
||||
/// The manual's source, and the page rendered from it that the application
|
||||
/// carries. Committed and gated like the gesture book — see
|
||||
/// [`traceability::manual`] for why it is not rendered at build time.
|
||||
const MANUAL_SOURCE: &str = "docs/manual/README.md";
|
||||
const MANUAL_PAGE: &str = "docs/manual/index.html";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let base = repo_root()?;
|
||||
let mode = std::env::args().nth(1).unwrap_or_else(|| "report".into());
|
||||
@@ -66,6 +74,9 @@ fn main() -> Result<()> {
|
||||
if mode.starts_with("gestures") {
|
||||
return run_gestures(&base, mode == "gestures-check");
|
||||
}
|
||||
if mode.starts_with("manual") {
|
||||
return run_manual(&base, mode == "manual-check");
|
||||
}
|
||||
|
||||
let req_path = base.join("docs/dev/requirements.md");
|
||||
let markdown = std::fs::read_to_string(&req_path)
|
||||
@@ -191,6 +202,29 @@ fn run_gestures(base: &Path, check: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the manual to its page, or check the committed page is that render.
|
||||
fn run_manual(base: &Path, check: bool) -> Result<()> {
|
||||
let source = std::fs::read_to_string(base.join(MANUAL_SOURCE))
|
||||
.with_context(|| format!("reading {MANUAL_SOURCE}"))?;
|
||||
let page = manual::render_html(&source);
|
||||
let heads = manual::headings(&source);
|
||||
println!("headings {}", heads.len());
|
||||
if heads.is_empty() {
|
||||
bail!("no headings parsed from {MANUAL_SOURCE} — misconfigured, not an empty manual");
|
||||
}
|
||||
if check {
|
||||
let have = std::fs::read_to_string(base.join(MANUAL_PAGE)).unwrap_or_default();
|
||||
if have != page {
|
||||
bail!("{MANUAL_PAGE} is stale — run: cargo run -p traceability -- manual");
|
||||
}
|
||||
println!("\nmanual gate: PASS");
|
||||
return Ok(());
|
||||
}
|
||||
std::fs::write(base.join(MANUAL_PAGE), &page)?;
|
||||
println!("\nwrote {MANUAL_PAGE}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Structural checks that fail regardless of the coverage threshold.
|
||||
fn gate(files: &[PathBuf], defined: &DefinedRequirements, cov: &Coverage) -> Result<()> {
|
||||
// A run that parsed nothing is misconfigured, not passing.
|
||||
|
||||
Reference in New Issue
Block a user