- 开发工具
- 文档
【免费下载链接】mdBook
Create book from markdown files. Like Gitbook but implemented in Rust
本文以仓库测试套件中的最小夹具rust-playground.md(一段仅含let x = 1;的 Rust 代码块)为切入点,完整梳理 mdBook 将 Rust 代码块自动升级为可运行 Playground 的渲染判定、fn main自动包裹、隐藏行处理、前端资源注入与[output.html.playground]配置体系。读完本文,你将掌握如何在自己的书籍中写出可直接“点击运行”的 Rust 示例,并理解其底层实现链路与可验证的测试依据。
起点:测试夹具里的最小 Rust 代码块
仓库中用于验证 Playground 功能的测试夹具位于tests/testsuite/playground/playground_on_rust_code/,其章节正文 rust-playground.md 全文只有三行:
# Rust Sample ```rust let x = 1;配套的 [SUMMARY.md](https://link.gitcode.com/i/f78eeaa3e6c04c7146105bcab754a085) 将该章节注册进书籍导航: ```markdown # Summary - [Rust Playground](https://link.gitcode.com/i/5b213efc33215afd3c58ba865057cf57)而 book.toml 只声明了书籍标题:
[book] title = "playground_on_rust_code"也就是说,这个夹具刻意保持了“零配置”状态:既没有打开编辑功能,也没有改动任何渲染选项。它的作用只有一个——用最干净的输入验证 mdBook 对 Rust 代码块的默认行为。下面所有原理分析,都以这份三行文档为锚点展开。
渲染链路:update_code_blocks如何识别 playground
mdBook 在 HTML 渲染阶段会遍历解析出的语法树,逐个检查code元素。核心实现在 tree.rs 的update_code_blocks方法中。判定一个代码块是否属于“可运行 Playground”的逻辑位于 tree.rs:
let is_editable = class_set.contains("editable"); let is_playground = class_set.contains("language-rust") && ((!class_set.contains("ignore") && !class_set.contains("noplayground") && !class_set.contains("noplaypen") && self.options.config.playground.runnable) || class_set.contains("mdbook-runnable"));从源码结构可以提炼出三条关键判定规则:
- 语言必须是
rust:只有language-rust类才可能进入 playground 流程,其他语言代码块不受影响; - 必须没有“退出”标记:
ignore、noplayground、noplaypen三者任一存在即排除; - 全局开关
runnable:默认情况下还要求配置项output.html.playground.runnable为true;但mdbook-runnable属性可以强制开启(它专门用于与ignore组合,让“不参与测试但允许读者运行”的示例显示运行按钮)。
通过判定后,代码块会经历两步处理:先是依据edition属性(或全局rust.edition配置)自动补充edition2015/2018/2021/2024类(见 tree.rs),随后在父级pre元素上写入class="playground"(见 tree.rs)。正是这个类,驱动前端book.js为代码块挂上运行按钮,并把代码提交到 Rust 官方在线 Playground 执行。
自动包裹fn main:rustdoc 风格的隐藏行机制
对于let x = 1;这类没有fn main的片段,mdBook 会像 rustdoc 一样自动补全入口函数。实现在 hide_lines.rs 的wrap_rust_main:
pub(crate) fn wrap_rust_main(text: &str) -> Option<String> { if !text.contains("fn main") && !text.contains("quick_main!") { let (attrs, code) = partition_rust_source(text); let newline = if code.is_empty() || code.ends_with('\n') { "" } else { "\n" }; Some(format!( "# #![allow(unused)]\n{attrs}# fn main() {{\n{code}{newline}# }}" )) } else { None } }要点有三:
- 不重复包裹:已有
fn main或quick_main!的代码直接原样输出; - 内层属性被保留:
partition_rust_source(见 hide_lines.rs)用正则把#![...]形式的 inner attributes 从源码头部剥离出来,防止它们被错误地嵌入fn main内部导致编译失败; - 包裹行全部隐藏:补出的
#![allow(unused)]、# fn main() {、# }都带#前缀,最终以boring类的<span>包裹,读者在页面上看不到这些脚手架,但代码被真正送入 Playground 时它们参与编译。
值得注意的一个细节在 tree.rs:当全局editable开启且该代码块带editable属性时,不会自动包裹fn main——因为此时代码进入可编辑的 Ace 编辑器,需要把完整源码原样呈现给读者;反之则自动补全入口函数。而editable默认关闭(见下文配置小节),因此默认情况下所有 Rust 片段都会享受自动包裹。
测试验证:两份期望输出对照
这个三行文档的价值,最终由 playground.rs 中的两个测试锁定。playground_on_rust_code(tests/testsuite/playground.rs#L5-L18)断言渲染后的book/index.html精确等于:
<h1 id="rust-sample"><a class="header" href="#rust-sample">Rust Sample</a></h1> <pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)] </span><span class="boring">fn main() { </span>let x = 1; <span class="boring">}</span></code></pre>这份期望输出完整印证了上文分析的整条链路:标题获得header锚点链接;pre获得playground类;原始代码let x = 1;原样保留;自动补出的三行脚手架被boring隐藏。
对照实验是disabled_playground(tests/testsuite/playground.rs#L20-L30),它使用禁用 playground 的夹具目录,期望输出退化为不带任何类的普通代码块:
<pre><code class="language-rust">let x = 1;</code></pre>两个测试一开一关,恰好覆盖了runnable全局开关的两种状态,是理解该功能行为边界的权威依据。
配置项:[output.html.playground]全参详解
官方指南 renderers.md 给出了完整示例配置,可直接写入书籍根目录的book.toml:
[output.html.playground] editable = false # allows editing the source code copyable = true # include the copy button for copying code snippets copy-js = true # includes the JavaScript for the code editor line-numbers = false # displays line numbers for editable code runnable = true # displays a run button for rust code各参数含义与默认值如下表(默认值与 config.rs 中Playground结构体及其Default实现完全一致):
| 配置键 | 默认值 | 作用 |
|---|---|---|
editable | false | 是否允许读者在线编辑源码(需配合代码块editable属性使用) |
copyable | true | 是否显示复制按钮 |
copy-js | true | 是否把编辑器所需的 JavaScript 拷贝到输出目录 |
line-numbers | false | 可编辑代码是否显示行号;要求editable与copy-js同时为true |
runnable | true | 是否显示运行按钮;设为false将全局关闭 Playground 功能 |
从源码结构看,Playground结构体还带有deny_unknown_fields与 kebab-case 反序列化约束(见 config.rs),这意味着配置键必须严格写成copy-js、line-numbers这样的连字符形式,任何拼写错误都会在解析配置时报错而非静默忽略。
代码块属性:对单个示例的精细控制
除了全局配置,mdBook 还支持在代码块围栏的语言标记后附加属性,用逗号、空格或制表符分隔(官方说明见 mdbook.md):
```rust,noplayground let mut name = String::new(); std::io::stdin().read_line(&mut name).expect("failed to read line"); println!("Hello {}!", name); ```这些属性与mdbook test共用同一套 rustdoc 风格语义,常用集合如下:
| 属性 | 行为 |
|---|---|
editable | 启用该代码块的在线编辑器(需editable全局配置为true,参见 editor.md) |
noplayground | 移除运行按钮,但mdbook test仍会编译测试 |
mdbook-runnable | 强制显示运行按钮,适合与ignore组合使用 |
ignore | 不参与测试、不显示运行按钮,但仍按 Rust 语法高亮 |
should_panic | 测试时预期产生 panic |
no_run | 测试时仅编译不运行,也不显示运行按钮 |
compile_fail | 测试预期编译失败 |
edition2015/edition2018/edition2021/edition2024 | 强制指定 Rust edition,优先级高于全局rust.edition |
前端与静态资源:编辑器 JS 的条件注入
当editable与copy-js同时开启时,HTML 渲染器会做两件事:其一,在 static_files.rs 中把editor.js、ace.js、mode-rust.js、theme-dawn.js、theme-tomorrow_night.js等前端资源一并打包进输出目录;其二,在 hbs_renderer.rs 中向 Handlebars 模板注入playground_js、playground_line_numbers、playground_copyable等布尔标志,控制book.js是否加载编辑器与复制功能。
这条条件注入链解释了配置之间的依赖关系:line-numbers要求editable与copy-js同时为true,正是因为行号渲染依赖编辑器脚本被注入;若copy-js为false,即使editable开启,输出目录中也不会有 Ace 编辑器资源,可编辑能力自然失效。
快速上手:三步让示例可运行
综合上文,在 mdBook 中启用并控制 Rust Playground 的完整路径可以浓缩为三步:
- 默认即可用:无需任何配置,只要在 Markdown 中写出
```rust代码块,runnable默认为true,渲染后即带运行按钮,无main的片段自动补全; - 全局开关:在
book.toml写入[output.html.playground]表,按需调整runnable、copyable、editable、copy-js、line-numbers五个键; - 逐块定制:对特定代码块使用
noplayground、ignore、mdbook-runnable、edition2021等属性覆盖全局行为。
验证方式也很直接:运行测试套件中的 playground.rs,playground_on_rust_code与disabled_playground两个用例会分别锁定“开启”与“关闭”两种状态下的精确 HTML 输出;更多玩法(如{{#playground}}包含语法、可编辑示例)可继续阅读 mdbook.md 与 editor.md。
- 开发工具
- 文档
【免费下载链接】mdBook
Create book from markdown files. Like Gitbook but implemented in Rust
相关推荐
mdBook Rust Playground 实战指南:代码块可运行与可编辑机制的完整解析
mdBook Rust Playground 实战指南:代码块可运行与可编辑机制的完整解析 导读 本文以 mdBook 官方 GUI 测试夹具 tests/gu
开发工具文档mdBook 中 Rust 代码块 Playground 运行按钮的启用、禁用与源码解析
mdBook 中 Rust 代码块 Playground 运行按钮的启用、禁用与源码解析 mdBook 会自动为 Markdown 文档中的 Rust 代码块附
开发工具文档Easy Rust文档测试:doctest确保示例代码可运行
Easy Rust文档测试:doctest确保示例代码可运行 你是否遇到过这样的问题:教程中的代码示例看起来正确,但实际运行时却报错?Rust的文档测试(doc
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考