Markdown Code Verification System

A system that verifies every identified code block can be compiled

This system automatically extracts and compiles Rust code blocks from docs, ensuring all example code stays usable in CI. ## Config Specify which Markdown files to verify via `.config/verified-docs.toml`: ```toml [verified] readme = "./README.md" getting_started = "./GETTING-STARTED.md" documents_en_us = "./docs/pages/**" documents_zh_cn = "./docs/_zh_CN/pages/**" ``` Each key is a label used to name the report items; values are single files, directories, or `**` globs. Run all configured files: ```sh cargo ci markdown-check-all ``` You can also test a single file via command-line arg (path is joined onto the current directory): ```sh cargo ci markdown-check docs/pages/1-getting-started.md ``` ## Default Rules Every verified ` ```rust ` code block gets the following injected automatically at compile time — no need to write them explicitly in the block: ### 1. `#![allow(dead_code)]` and `#![allow(unused)]` Added at the top of the generated `main.rs` to suppress dead-code warnings from partial code snippets. ### 2. `use mingling::prelude::*;` If the block already has `use mingling::prelude::*;`, it won't be inserted again. Otherwise it's inserted automatically (with `#[allow(unused_imports)]`). ### 3. `fn main() {}` If the block **does not contain** a `fn main` definition, an empty `fn main() {}` is appended, so the block can compile as a standalone binary project. ### 4. `mingling::macros::gen_program!();` If the block **does not contain** a `gen_program!()` call, `mingling::macros::gen_program!();` is appended automatically. This call is required by the mingling framework. ### 5. Build Cache Dedup — Shared Dep Hash Code blocks with the same `Features` and `Dependencies` are automatically grouped into the same compile group, sharing one `Cargo.toml` and build artifacts, avoiding redundant compilations. > [!NOTE] > > Hash input (all sorted): > > 1. Feature list > 2. External dep name list > 3. External dep version list > 4. `name=version` pairs > > Uses FNV-1a 64-bit hash, stable across runs. ## Verification Steps After the **default rules** are applied, each block goes through: ### 1. Block Extraction - Only ` ```rust ` fenced code blocks are extracted. - Empty blocks (no code lines) are skipped. - Blocks with `// NOT VERIFIED` alone are skipped. ### 2. Temp Project Generation Each dedup-hash group gets its own Cargo project: ``` .temp/doc-test// ├── Cargo.toml └── src/ └── main.rs ``` ### 3. Build Verification Compiled with `cargo check --manifest-path ... --color=always`, stderr inherited to the terminal for real-time progress. Blocks within a group are serial (they share the crate directory); groups run in parallel. - **Build OK** → **PASS** - **Build FAIL** → **FAIL**, last 20 lines of error captured. ### 4. Report Each file's result is exported through the reporter: an `ok` entry when every block passed, otherwise an `err` entry carrying the failed blocks' details. All entries land in `.temp/reports/collect/` (e.g. `Markdown-Check-All.Linux.ok`, `Markdown-Check-All.Linux..err`). `cargo ci report-collect` then assembles everything into `.temp/reports/result.md` (also published to the GitHub Actions job summary). ### 5. Exit Code - Any block fails → non-zero exit code (blocks CI pipeline). - All pass → zero exit code. --- ## Metadata Tag Rules At the start of a ` ```rust ` block (before code content), use these comment headers to declare metadata. Headers are parsed in order; everything after them is treated as code: ### `// NOT VERIFIED` Marks the block **not to be compiled**. Use for illustrative snippets that can't compile on their own. ```rust // NOT VERIFIED // This block is illustrative only, won't be compiled fn placeholder() {} ``` ### `// BUILD TIME` Marks the block as a `build.rs` script instead of `src/main.rs`. The block code is wrapped in `fn main() { }` and written to `build.rs`. A stub `fn main() {}` is generated for `src/main.rs`. The declared features are mirrored into `[build-dependencies]` so `build.rs` sees the same feature set. ```rust // BUILD TIME // Dependencies: // serde = "1" fn main() { // build-time work, e.g. writing generated sources into OUT_DIR } ``` ### `// Features: [...]` Declares the mingling crate features needed by this block, as a JSON string array. These features are written into `Cargo.toml`'s `[dependencies]`. ```rust // Features: ["full", "serde"] ``` ### `// Dependencies:` Declares external crate deps needed by the block. After `// Dependencies:`, each dep goes on one line: `// crate_name = "version"`. ```rust // Dependencies: // serde = "1" // clap = "4" ``` > [!TIP] > > **Special handling**: > > For deps named `serde` or `clap` with a plain string version, > > `features = ["derive"]` is auto-added. > > If the version uses a TOML inline table (e.g. `{ version = "1", features = ["derive"] }`), > > it's kept as-is. --- ## `@@@` Lines (Hidden Compilation) Lines starting with `@@@` are **hidden from the rendered documentation** but still included in compilation. This is useful when you want to show only the core logic while keeping the block fully compilable: ```rust // This line is visible in docs @@@// This line is hidden but still compiled @@@fn setup() { /* hidden boilerplate */ } ``` ### How it works | Stage | Handling | | --------------------- | ------------------------------------------------------------------------------------------------ | | **docsify rendering** | `@@@` lines are stripped before markdown is rendered (via `beforeEach` plugin) | | **CI verification** | `@@@` prefix is stripped during block parsing, remaining content is treated as regular Rust code | ### Convention Use `@@@` for: - `fn main() {}` / `gen_program!()` when the block doesn't need to show them - Common `use` imports that would distract from the example - Type definitions (`pack!`, `#[derive]`) that are necessary for compilation but not the focus - Helper functions that the reader doesn't need to see > [!TIP] > `@@@` is the replacement for `// NOT VERIFIED` — instead of marking a block as uncompilable, > hide the boilerplate and keep everything compiling. --- ## Structure Overview | Module | Responsibility | | --------------------------------------------- | ----------------------------------------------------------------------------------- | | `mingling_ci/src/markdown/project.rs` | Block parsing, Cargo.toml/main.rs generation, FNV-1a dep hash | | `mingling_ci/src/markdown/test.rs` | Grouping by dep hash, parallel `cargo check` execution | | `mingling_ci/src/task/cmd_markdown_check.rs` | `markdown-check` / `markdown-check-all` commands: read config, collect files, report | | `mingling_ci/src/markdown/compare.rs` | Structural signature comparison (for `markdown-compare`) | | `mingling_ci/src/task/cmd_markdown_compare.rs`| `markdown-compare` / `markdown-compare-all` commands | | `.config/verified-docs.toml` | Specifies which doc files to verify | ### Structure Comparison `markdown-compare` (two files or directories) and `markdown-compare-all` (all languages from `.config/docs-lang.txt`, whose first line is the reference directory) check that every translated docs directory **mirrors the structure** of the reference docs exactly: one token per line classifying headings, fenced code blocks (with language tag), `@@@` lines, blank lines, blockquotes, lists and plain text. Translated text may differ; the structure may not. ## Full Example ````markdown ```rust // Features: ["picker"] // Dependencies: // serde = "1" // Example code ... ``` ```` The above block compiles equivalently to: ```rust #![allow(dead_code)] #![allow(unused)] #[allow(unused_imports)] use mingling::prelude::*; // Example code ... fn main() {} mingling::macros::gen_program!(); ``` `Cargo.toml` will contain: ```toml [dependencies] mingling = { path = "../../mingling", features = ["picker"] } serde = { version = "1", features = ["derive"] } ```