Code in documentation, taken from the tests that run it.
A doc example that is not compiled rots, and one that is compiled as a
doctest runs in a crate of its own, where proc macros that name the crate
they expand in get it wrong. shown keeps examples in ordinary tests and
copies them into the pages that show them.
#[test]
fn a_store_over_a_file() -> anyhow::Result<()> {
//@show a store over a file
let store = StoreBuilder::new("./app").build()?;
//@hide
let dir = tempfile::tempdir()?;
//@unhide
store.port().set(9090)?;
//@show-end
Ok(())
}//@show name .. //@show-end is what a page may show; //@hide ..
//@unhide is what it runs but does not show. The common indent comes off.
Markdown, or a doc comment at any indent. In a .rs file only the doc
comments are the page; the code around them is not read.
<!-- shown: a store over a file -->
<!-- /shown -->/// <!-- shown: a store over a file -->
/// <!-- /shown -->
pub struct Store;Everything between the two comments is replaced on each fill. Other kinds of
region - <!-- printed: .. -->, whatever a project needs - go through the
same function, answered by the caller.
The library is the grammar; each project keeps its own cargo xtask that
says which files are tests and which are pages, which fence a block gets, and
whether to write or only check.
let mut marks = shown::Marks::new();
for test in tests {
marks.read(&test, &fs::read_to_string(&test)?)?;
}
for page in pages {
let text = fs::read_to_string(&page)?;
let fence = if page.extension() == Some("rs".as_ref()) { "rust,ignore" } else { "rust" };
let filled = shown::fill(&page, &text, &["shown"], |asked| marks.block(&asked.arg, fence))?;
for asked in &filled.unanswered {
eprintln!("{}:{}: nothing is marked `{}`", page.display(), asked.line, asked.arg);
}
if filled.text == text {
continue;
}
if check {
bail!("{} is out of date; run `cargo xtask docs`", page.display());
}
fs::write(&page, filled.text)?;
}A doc comment wants rust,ignore or text: a plain rust fence there is a
doctest again.
Errors name the file and line: a name marked twice, a //@show never closed,
a //@show-end closing nothing, a region with no end, a fence left open to
the end of its page or its doc comment - which would otherwise turn every
region after it into text.
MIT or Apache-2.0, at your option.