说明

此项目为 mdbook 文档中文翻译,

  • mdbook版本:v0.4.14
  • 翻译完成时间: 2021-12-24 23:08:14

相关阅读

指南

mdBook 是一个命令行工具和 Rust crate,用于使用 Markdown 创建书籍。 输出类似于 Gitbook 等工具,非常适合创建产品或 API 文档、教程、课程材料或任何需要干净、易于导航和可自定义的演示文稿的内容。 mdBook 是用 Rust 编写的; 它的性能和简单性使其非常适合用作通过自动化直接发布到托管网站(例如 GitHub Pages)的工具。 事实上,本指南既是 mdBook 文档,也是 mdBook 生成内容的一个很好的例子。

mdBook 包括对 Markdown 预处理和用于生成 HTML 以外格式的替代渲染器的内置支持。 这些工具还支持其他功能,例如验证。 搜索 Rust 的 crates.io 是发现更多扩展的好方法。

API 文档

除了以上特性,mdBook 还有一个 Rust API。 这允许您编写自己的预处理器或渲染器,以及将 mdBook 功能合并到其他应用程序中。 本指南的面向开发人员部分包含更多信息和一些示例。

贡献

mdBook 是免费和开源的。 您可以在 GitHub 上找到源代码,问题和功能请求可以发布在 GitHub 问题跟踪器上。 mdBook 依靠社区来修复错误和添加功能:如果您想做出贡献,请阅读 CONTRIBUTING 指南并考虑打开拉取请求

许可

mdBook 源代码和文档在 Mozilla Public License v2.0 下发布。

命令行工具

mdBook 既可以用作命令行工具,也可以用作 Rust crate。 让我们首先关注命令行工具功能。

二进制文件安装

我们尽最大努力为主要平台提供了预编译的二进制文件。 访问发布页面以下载适合您平台的版本。

源码安装

mdBook 也可以通过在本地机器上编译源代码来安装。

先决条件

mdBook 是用 Rust 编写的,因此需要用 Cargo 编译。 如果您还没有安装 Rust,请立即安装

安装 Crates.io 版本

如果您已经安装了 Rust 和 Cargo,则安装 mdBook 相对容易。 你只需要在你的终端中输入这段代码:

cargo install mdbook

这将从 Crates.io 获取最新版本的源代码并编译它。 您必须将 Cargo 的 bin 目录添加到您的 PATH 环境变量中。

在终端中运行 mdbook help 以验证它是否有效。 恭喜,您已经安装了 mdBook!

安装 Git 版本

**git 版本**包含所有最新的错误修复和功能,如果您不能等到下一个版本,它们将在 Crates.io 的下一个版本中发布。 您可以自己构建 git 版本。 打开您的终端并导航到您选择的目录。 我们需要克隆 git 存储库,然后使用 Cargo 构建它。

git clone --depth=1 https://github.com/rust-lang/mdBook.git
cd mdBook
cargo build --release

可执行文件 mdbook 将在 ./target/release 文件夹中,这应该添加到PATH 环境变量中。

init 命令

每本新书都有一些相同的最小模板。 为此,mdBook 包含了一个 init 命令。

init 命令的用法如下:

mdbook init

第一次使用 init 命令时,会为你设置几个文件:

book-test/
├── book
└── src
    ├── chapter_1.md
    └── SUMMARY.md
  • src 目录是你用 Markdown 写书的地方。 它包含所有源文件、配置文件等。
  • book 目录是你的书被渲染的地方。 所有输出都已准备好上传到服务器以供观众查看。
  • SUMMARY.md 是本书的骨架,将在另一章中进行更详细的讨论。

提示:从 SUMMARY.md 生成章节

当一个SUMMARY.md文件已经存在时,init命令会首先解析它并根据SUMMARY.md中使用的路径生成不存在的文件。 这允许您思考和创建您的书的整个结构,然后让 mdBook 为您生成它。

指定目录

init 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook init path/to/book

--theme

当您使用 --theme 参数时,默认主题将被复制到源目录中名为 theme 的目录中,以便您可以对其进行修改。

主题被选择性覆盖,这意味着如果您不想覆盖特定文件,只需将其删除即可使用默认文件。

--title

指定书名。 如果未提供,交互式提示将要求提供标题。

mdbook init --title="my amazing book"

--ignore

创建一个 .gitignore 文件,配置为忽略构建书籍时创建的 book 目录。 如果未提供,交互式提示将询问是否应创建。

build 命令

build 命令用于渲染您的书:

mdbook build

它将尝试解析您的SUMMARY.md文件以了解您的书的结构并获取相应的文件。 请注意,在 SUMMARY.md 中提到但不存在的文件将会自动创建。

为方便起见,渲染的输出将保持与源相同的目录结构。 大量的books在渲染时将保持结构化。

指定目录

build 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook build path/to/book

--open

当您使用 --open (-o) 标志时,mdbook 在构建后, 将在你默认的 Web 浏览器中打开渲染的books。

--dest-dir

--dest-dir (-d) 选项允许您更改书籍的输出目录。 相对路径是相对于书的根目录解释的。 如果未指定,它将默认为 book.tomlbuild.build-dir 键的值,或为 ./book


注意: build 命令将所有文件(不包括扩展名为 .md 的文件)从源目录复制到 build 目录中。

watch 命令

当您希望在每次文件更改时呈现您的书时, watch 命令很有用。 每次更改文件时,您都可以重复发出 mdbook build。 但是使用 mdbook watch 将监视您的文件,并在您修改文件时自动触发构建; 这包括重新创建已删除的但在SUMMARY.md中但仍然提到的文件!

指定目录

watch 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook watch path/to/book

--open

当您使用 --open (-o) 选项时,mdbook 将在您的默认 Web 浏览器中打开渲染的书籍。

--dest-dir

--dest-dir (-d) 选项允许您更改书籍的输出目录。 相对路径是相对于书的根目录解释的。 如果未指定,它将默认为 book.tomlbuild.build-dir 键的值,或为 ./book

指定排除模式

watch 命令不会自动为 book 根目录中的 .gitignore 文件中列出的文件触发构建。 .gitignore 文件可能包含 gitignore 文档 文档中描述的文件模式。 这对于忽略某些编辑器创建的临时文件很有用。

注意: 仅使用书籍根目录中的 .gitignore文件。 全局的 $HOME/.gitignore 或 父目录中的.gitignore 文件则不会被使用。

serve 命令

默认情况下, serve 命令用于通过 HTTP 在 localhost:3000 提供服务来预览一本书:

mdbook serve

serve 命令监视书的 src 目录的变化,为每次变化重建书和刷新客户端; 这包括重新创建已删除但仍然在SUMMARY.md中提到的文件! websocket 连接用于触发客户端刷新。

注意: serve 命令用于测试一本书的 HTML 输出,并不打算成为网站的完整 HTTP 服务器。

指定目录

serve 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook serve path/to/book

Server 可选项

serve 主机名默认为 localhost,端口默认为 3000。 可以在命令行上指定任一选项:

mdbook serve path/to/book -p 8000 -n 127.0.0.1 

--open

当您使用 --open (-o) 标志时,mdbook 将在启动服务器后在您的默认 Web 浏览器中打开该书。

--dest-dir

--dest-dir (-d) 选项允许您更改书籍的输出目录。 相对路径是相对于书的根目录解释的。 如果未指定,它将默认为 book.tomlbuild.build-dir 键的值,或为 ./book

指定排除模式

watch 命令不会自动为 book 根目录中的 .gitignore 文件中列出的文件触发构建。 .gitignore 文件可能包含 gitignore 文档 文档中描述的文件模式。 这对于忽略某些编辑器创建的临时文件很有用。

注意: 仅使用书籍根目录中的 .gitignore文件。 全局的 $HOME/.gitignore 或 父目录中的.gitignore 文件则不会被使用。

test 命令

写书时,有时需要自动化一些测试。 例如,The Rust Programming Book 使用了许多可能会过时的代码示例。 因此,能够自动测试这些代码示例对他们来说非常重要。

mdBook 支持一个test命令,该命令将运行书中所有可用的测试。 目前,仅支持 rustdoc 测试,但将来可能会扩展。

在一个代码块上禁用测试

rustdoc 不测试包含 ignore 属性的代码块:

fn main() {}

指定非 Rust 语言的代码块 rustdoc 也不测试:

**Foo**: _bar_

rustdoc 会测试没有指定语言的代码块:

This is going to cause an error!

指定目录

test 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook test path/to/book

--library-path

--library-path (-L) 选项允许您将目录添加到 rustdoc 在构建和测试示例时使用的库搜索路径。 可以使用多个选项 (-L foo -L bar) 或逗号分隔列表 (-L foo,bar) 指定多个目录。 该路径应指向包含项目构建输出的 Cargo 构建缓存 deps 目录。 例如,如果您的 Rust 项目的 book 位于名为 my-book 的目录中,则以下命令将在运行test时包含 crate 的依赖项:

mdbook test my-book -L target/debug/deps/

有关更多信息,请参阅 rustdoc 命令行文档

--dest-dir

--dest-dir (-d) 选项允许您更改书籍的输出目录。 相对路径是相对于书的根目录解释的。 如果未指定,它将默认为 book.tomlbuild.build-dir 键的值,或为 ./book

clean 命令

clean 命令用于删除生成的书和任何其他构建工件。

mdbook clean

指定目录

clean 命令可以将目录作为参数用作书的根目录而不是当前工作目录。

mdbook clean path/to/book

--dest-dir

--dest-dir (-d) 选项允许您更改书籍的输出目录。 相对路径是相对于书的根目录解释的。 如果未指定,它将默认为 book.tomlbuild.build-dir 键的值,或为 ./book

mdbook clean --dest-dir=path/to/book

path/to/book 可以是绝对路径或相对路径。

格式

在本节中,您将学习如何:

  • 正确地组织你的书
  • 格式化你的SUMMARY.md文件
  • 使用 book.toml 配置你的书
  • 自定义您的主题

SUMMARY.md

mdBook 使用摘要文件来了解要包含哪些章节、它们应该以什么顺序出现、它们的层次结构是什么以及源文件在哪里。 没有这个文件,就没有书。

这个markdown文件必须命名为SUMMARY.md。 它的格式非常严格,必须遵循下面概述的结构,以便于解析。 下面未指定的任何元素,无论是格式还是文本,最多可能会被忽略,或者在尝试构建书籍时可能会导致错误。

结构

  1. 标题 - 虽然是可选的,但通常以标题开头,通常是# Summary。 然而,这被解析器忽略,并且可以省略。

    # Summary
    
  2. 前缀章节 - 在主要编号章节之前,可以添加不编号的前缀章节。 这对于前言、介绍等很有用。但是,有一些限制。 前缀章节不能嵌套; 它们都应该在根级别。 一旦添加了编号的章节,就无法添加前缀章节。

    [A Prefix Chapter](relative/path/to/markdown.md)
    
    - [First Chapter](relative/path/to/markdown2.md)
    
  3. 部分标题 - 标题可用作以下编号章节的标题。 这可用于在逻辑上分隔本书的不同部分。 标题呈现为不可点击的文本。 标题是可选的,编号的章节可以根据需要分成任意多个部分。

    # My Part Title
    
    - [First Chapter](relative/path/to/markdown.md)
    
  4. 编号章节 - 编号的章节概述了本书的主要内容,并且可以嵌套,从而形成一个很好的层次结构(章节、子章节等)。

    # Title of Part
    
    - [First Chapter](relative/path/to/markdown.md)
    - [Second Chapter](relative/path/to/markdown2.md)
       - [Sub Chapter](relative/path/to/markdown3.md)
    
    # Title of Another Part
    
    - [Another Chapter](relative/path/to/markdown4.md)
    

    编号的章节可以用-* 表示(不要混合分隔符)。

  5. 后缀章节 - 与前缀章节一样,后缀章节是无编号的,但它们位于已编号的章节之后。

    - [Last Chapter](relative/path/to/markdown.md)
    
    [Title of Suffix Chapter](relative/path/to/markdown2.md)
    
  6. 草稿章节 - 草稿章节是没有文件和内容的章节。 一章草稿的目的是表明未来仍有待编写的章节。 或者当您仍在对书的结构进行布局以避免创建文件时,您仍在大量更改书的结构。 草稿章节将在 HTML 呈现器中呈现为目录中的禁用链接,您可以在左侧目录中的下一章节中看到。 草稿章节像普通章节一样编写,但不写入文件路径。

    - [Draft Chapter]()
    
  7. 分割线 - 可以在任何其他元素之前、之间和之后添加分隔符。 它们会在构建的目录中生成 HTML 渲染行。 分隔符是仅包含破折号和至少三个破折号的行: : ---.

    # My Part Title
    
    [A Prefix Chapter](relative/path/to/markdown.md)
    
    ---
    
    - [First Chapter](relative/path/to/markdown2.md)
    

样例

下面是本指南的SUMMARY.md的markdown文件源,结果目录呈现在左侧。

# Summar

<!-- - [zh-cn](index.md) -->

[说明](index.md)

---

# 中文

- [指南](zh-cn/README.md)
- [命令](zh-cn/cli/README.md)
  - [init](zh-cn/cli/init.md)
  - [build](zh-cn/cli/build.md)
  - [watch](zh-cn/cli/watch.md)
  - [serve](zh-cn/cli/serve.md)
  - [test](zh-cn/cli/test.md)
  - [clean](zh-cn/cli/clean.md)
- [结构](zh-cn/format/README.md)
  - [SUMMARY.md](zh-cn/format/summary.md)
    - [草稿章节]()
  - [配置](zh-cn/format/configuration/README.md)
    - [常规](zh-cn/format/configuration/general.md)
    - [预处理](zh-cn/format/configuration/preprocessors.md)
    - [渲染](zh-cn/format/configuration/renderers.md)
    - [环境变量](zh-cn/format/configuration/environment-variables.md)
  - [主题](zh-cn/format/theme/README.md)
    - [index.hbs](zh-cn/format/theme/index-hbs.md)
    - [语法高亮](zh-cn/format/theme/syntax-highlighting.md)
    - [编辑器](zh-cn/format/theme/editor.md)
  - [数学公式支持](zh-cn/format/mathjax.md)
  - [mdBook特定功能](zh-cn/format/mdbook.md)
  - [Markdown](zh-cn/format/markdown.md)
- [持续集成](zh-cn/continuous-integration.md)
- [开发者](zh-cn/for_developers/README.md)
  - [预处理器](zh-cn/for_developers/preprocessors.md)
  - [替换后端](zh-cn/for_developers/backends.md)
- [贡献列表](zh-cn/misc/contributors.md)

----

# 原文

- [en](index.md)
  - [Introduction](en/README.md)
  - [Command Line Tool](en/cli/README.md)
    - [init](en/cli/init.md)
    - [build](en/cli/build.md)
    - [watch](en/cli/watch.md)
    - [serve](en/cli/serve.md)
    - [test](en/cli/test.md)
    - [clean](en/cli/clean.md)
  - [Format](en/format/README.md)
    - [SUMMARY.md](en/format/summary.md)
      - [Draft chapter]()
    - [Configuration](en/format/configuration/README.md)
      - [General](en/format/configuration/general.md)
      - [Preprocessors](en/format/configuration/preprocessors.md)
      - [Renderers](en/format/configuration/renderers.md)
      - [Environment Variables](en/format/configuration/environment-variables.md)
    - [Theme](en/format/theme/README.md)
      - [index.hbs](en/format/theme/index-hbs.md)
      - [Syntax highlighting](en/format/theme/syntax-highlighting.md)
      - [Editor](en/format/theme/editor.md)
    - [MathJax Support](en/format/mathjax.md)
    - [mdBook-specific features](en/format/mdbook.md)
    - [Markdown](en/format/markdown.md)
  - [Continuous Integration](en/continuous-integration.md)
  - [For Developers](en/for_developers/README.md)
    - [Preprocessors](en/for_developers/preprocessors.md)
    - [Alternative Backends](en/for_developers/backends.md)
  - [Contributors](en/misc/contributors.md)

Configuration

本节详细介绍了 book.toml 中可用的配置选项:

  • 常规 配置包括 bookrustbuild 部分
  • 预处理器 配置默认和自定义预处理器.
  • 渲染器 配置HTML,Markdown 和 自定义渲染器。
  • 环境变量 配置用于覆盖默认环境变量中的配置选项。

常规配置

您可以在 book.toml 文件中为您的书配置参数。

下面是一个 book.toml 文件的示例:

[book]
title = "Example book"
author = "John Doe"
description = "The example book covers examples."

[rust]
edition = "2018"

[build]
build-dir = "my-example-book"
create-missing = false

[preprocessor.index]

[preprocessor.links]

[output.html]
additional-css = ["custom.css"]

[output.html.search]
limit-results = 15

支持的配置选项

需要注意的是,配置中指定的任何相对路径将始终相对于配置文件所在的书的根目录。

General metadata

这是关于您的书的常规信息。

  • title: 书名
  • authors: 本书的作者
  • description: 这本书的描述,作为元信息添加到每个页面的html<head>
  • src: 默认情况下,源目录位于根文件夹下名src的目录中。 但这可以使用配置文件中的 src 键进行配置。
  • language: 本书的主要语言,例如用作语言属性<html lang="en">

book.toml 

[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src"  # the source files will be found in `root/my-src` instead of `root/src`
language = "en"

Rust 选项

Rust 语言的选项,与运行测试和运行环境集成相关。

  • edition: 默认情况下用于代码片段的 Rust 版本。 默认值为“2015”。 可以使用edition2015edition2018edition2021注释来控制单个代码块,例如:
```rust,edition2015
// This only works in 2015.
let try = true;
```

build 选项

这控制着书的构建过程。

  • build-dir: 放置渲染书籍的目录。默认情况下,这是书籍根目录中的book/

  • create-missing: 默认情况下,在构建本书时将创建在SUMMARY.md中指定的任何缺失文件(即create-missing = true)。 如果值是false,那么如果任何文件不存在,构建过程将改为退出并显示错误。

  • use-default-preprocessors: 通过将此选项设置为false来禁用(linksindex)默认的预处理器。

    如果您通过其配置表声明了相同的和/或其他预处理器,它们将替换默认的预处理运行。

    • 为清楚起见,在没有预处理器配置的情况下,将运行默认的 linksindex
    • 设置 use-default-preprocessors = false 将禁止这些默认预处理器运行。
    • 例如,添加 [preprocessor.links] 将确保无论 use-default-preprocessors 如何, links 都将运行。

配置预处理器

默认情况下,以下预处理器可用并包含在内:

  • links: 处理器助手将展开一章中的 {{ #playground }}{{ #include }}{{ #rustdoc_include }} 并包含文件里面的内容。
  • index: 将所有名为 README.md 的章节文件转换为 index.md。 也就是说,所有的 README.md 都会被渲染到渲染的书中的一个索引文件 index.html 中。

book.toml  

[build]
build-dir = "build"
create-missing = false

[preprocessor.links]

[preprocessor.index]

自定义预处理器配置

像渲染器一样,预处理器需要有自己的表(例如[preprocessor.mathjax])。 在该部分中,您可以通过向表中添加键值对来将额外的配置传递给预处理器。

例如:

[preprocessor.links]
# set the renderers this preprocessor will run for
renderers = ["html"]
some_extra_feature = true

将预处理器依赖项锁定到渲染器

您可以通过将两者绑定在一起来明确指定应该为渲染器运行预处理器。

[preprocessor.mathjax]
renderers = ["html"]  # mathjax only makes sense with the HTML renderer

提供您自己的命令

默认情况下,当您将 [preprocessor.foo] 表添加到 book.toml 文件时,mdbook 将尝试调用 mdbook-foo 可执行文件。 如果要使用不同的程序名称或传递命令行参数,可以通过添加command字段来覆盖此行为。

[preprocessor.random]
command = "python random.py"

需要一定的排序

预处理器的运行顺序可以通过 beforeafter 字段来控制。

例如,假设你希望你的 linenos 预处理器 处理可能已经被 {{#include}} 包含过的行; 然后你希望它在内置的 links 预处理器之后运行,你可以要求使用 beforeafter 字段:

[preprocessor.linenos]
after = [ "links" ]

[preprocessor.links]
before = [ "linenos" ]

也可以在同一个配置文件中指定以上两个,虽然是多余的。

通过 beforeafter 指定的具有相同优先级的预处理器按名称排序。 任何无限循环都将被检测到并产生错误。

渲染配置

HTML 渲染选项

HTML 渲染器也有几个选项。 渲染器的所有选项都需要在 TOML 表 [output.html] 下指定。

以下配置选项可用:

  • theme: mdBook 带有一个默认主题和它所需的所有资源文件。 但是如果设置了这个选项,mdBook 将有选择性地用在指定文件夹中找到主题文件并覆盖默认主题文件。
  • default-theme: 在“更改主题”下拉菜单中默认选择的主题配色方案。 默认为light
  • preferred-dark-theme: 默认的深色主题。 如果浏览器通过'prefers-color-scheme'CSS 媒体查询请求网站的深色版本,则将使用此主题。 默认为navy
  • curly-quotes: 将直引号转换为卷引号,但出现在代码块和代码跨度中的引号除外。 默认为false
  • mathjax-support: 添加对 MathJax 的支持。 默认为false
  • copy-fonts: 将 fonts.css 和相应的字体文件复制到输出目录并在默认主题中使用它们。 默认为true
  • google-analytics: 此字段已被弃用,并将在未来版本中删除。 使用theme/head.hbs 文件添加适当的谷歌分析代码。
  • additional-css: 如果您需要在不覆盖整个样式的情况下稍微更改书籍的外观,您可以指定一组样式表,这些样式表将在默认样式表之后加载,您可以在其中更改样式。
  • additional-js: 如果您需要在不删除当前行为的情况下向书中添加一些行为,您可以指定一组 JavaScript 文件,这些文件将与默认文件一起加载。
  • print: 配置打印选项的子表。 默认情况下,mdBook 添加了对将书籍打印为单页的支持。 这可以使用书右上角的打印图标访问。
  • no-section-label: mdBook 默认在目录列中添加节标签。 例如,“1.”、“2.1”。 将此选项设置为 true 以禁用这些标签。 默认为false
  • fold: 用于配置侧边栏部分折叠行为的子表。
  • playground: 用于配置各种运行环境设置的子表。
  • search: 用于配置浏览器内搜索功能的子表。 mdBook 必须在启用search(搜索)功能的情况下编译(默认情况下启用)。
  • git-repository-url: 该书的 git 仓库的 url。 如果提供了图标链接,将在书的菜单栏中输出。
  • git-repository-icon: 用于 git 仓库链接的 FontAwesome 图标类。 默认为fa-github
  • edit-url-template: 编辑 url 模板,当提供时显示“建议编辑”按钮,用于直接跳转到编辑当前查看的页面。 例如 GitHub 项目将此设置为 https://github.com/<owner>/<repo>/edit/master/{path} 或对于 Bitbucket 项目将其设置为 https://bitbucket.org/<owner>/ <repo>/src/master/{path}?mode=edit 其中 {path} 将替换为仓库中文件的完整路径。
  • redirect: 用于生成重定向到已迁移页面的子表。 该表包含键值对,其中键是需要创建重定向的文件位置,以相对于构建目录的绝对路径表示,(例如 /appendices/bibliography.html)。 该值可以是浏览器应导航到的任何有效 URI(例如 https://rust-lang.org//overview.html../bibliography.html)。
  • input-404: 用于丢失文件的 Markdown 文件的名称。 相应的输出文件将相同,扩展名替换为 html。 默认为 404.md
  • site-url: 将托管图书的网址。 这是确保 404 文件中的导航链接和 脚本/css 导入正常工作所必需的,即使在访问子目录中的 url 时也是如此。 默认为 /。 The url where the book will be hosted. This is required to ensure
  • cname: 将托管您的图书的 DNS 子域或顶级域。 根据 GitHub Pages 的要求,此字符串将写入站点根目录中名为 CNAME 的文件(请参阅管理 GitHub Pages 站点的自定义域)。

[output.html.print] 表的可用配置选项:

  • enable: 启用打印支持。 当为false时,将不会呈现所有打印支持。 默认为true

[output.html.fold] 表的可用配置选项:

  • enable: 启用部分折叠。 关闭时,所有折叠都打开。 默认为false
  • level: 越高折叠区域越开放。 当 level 为 0 时,所有折叠都关闭。 默认为0

[output.html.playground] 表的可用配置选项:

  • editable: 允许编辑源代码。 默认为 false.
  • copyable: 在代码片段上显示复制按钮。 默认为 true.
  • copy-js: 将编辑器的 JavaScript 文件复制到输出目录。默认为 true.
  • line-numbers 在可编辑的代码部分显示行号。 editablecopy-js 都必须设置为 true. 默认为 false.

[output.html.search] 表的可用配置选项:

  • enable: 启用搜索功能。 默认为 true.
  • limit-results: 搜索结果的最大数量。 默认为 30.
  • teaser-word-count: 用于搜索结果预览的字数。 默认为 30.
  • use-boolean-and: 定义多个搜索词之间的逻辑链接。 如果为 true,则所有搜索词都必须出现在每个结果中。 默认为 false.
  • boost-title: 如果搜索词出现在标题中,则搜索结果分数的提升因子。 默认为 2.
  • boost-hierarchy: 如果搜索词出现在层次结构中,则搜索结果分数的提升因子。 层次结构包含父文档的所有标题和所有父标题。 默认为 1.
  • boost-paragraph: 如果搜索词出现在文本中,则搜索结果分数的提升因子。 默认为 1.
  • expand: 如果搜索应该匹配更长的结果,则为真,例如 搜索 micro 应该匹配 microwave。默认为 true.
  • heading-split-level: 搜索结果将链接到包含结果的文档部分。 文档按此级别或更低级别的标题分成多个部分。 默认为 3. (### This is a level 3 heading)
  • copy-js: 将搜索实现的 JavaScript 文件复制到输出目录。 默认为 true.

这显示了 book.toml 中所有可用的 HTML 输出选项:

[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."

[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
copy-fonts = true
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
site-url = "/example-book/"
cname = "myproject.rs"
input-404 = "not-found.md"

[output.html.print]
enable = true

[output.html.fold]
enable = false
level = 0

[output.html.playground]
editable = false
copy-js = true
line-numbers = false

[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
copy-js = true

[output.html.redirect]
"/appendices/bibliography.html" = "https://rustc-dev-guide.rust-lang.org/appendix/bibliography.html"
"/other-installation-methods.html" = "../infra/other-installation-methods.html"

Markdown 渲染

Markdown渲染器 将运行预处理器,然后持续输出Markdown解析结果 。 这对于调试预处理器非常有用,尤其是与 mdbook test 结合使用以查看 mdbook 传递给 rustdoc 的 Markdown文本。

Markdown渲染器 包含在 mdbook 中,但默认情况下禁用。 通过向 book.toml 添加一个空表来启用它,如下所示:

[output.markdown]

Markdown渲染器 目前没有配置选项; 仅是启用还是禁用。

请参阅 预处理 了解如何指定哪些预处理器应在 Markdown 渲染器之前运行。

自定义渲染

可以通过将 [output.foo] 表添加到 book.toml 来启用自定义渲染器。 与预处理类似,这将指示 mdbook 将书的表示传递给 mdbook-foo 以进行渲染。 有关更多详细信息,请参阅替换后端一章。

自定义渲染器可以访问其表中的所有字段(即[output.foo] 下的任何内容)。 mdBook 检查两个常见字段:

  • command: 自定义渲染器执行的命令。 默认为带有 mdbook- 前缀的渲染器名称(例如 mdbook-foo)。
  • optional: 如果为true,则如果未安装该命令将被忽略,否则 mdBook 将失败并显示错误。 默认为false

环境变量

通过设置相应的环境变量,可以从命令行覆盖所有配置值。 由于许多操作系统将环境变量限制为字母数字字符或 _,因此配置键的格式需要与正常的 foo.bar.baz 格式略有不同。

MDBOOK_ 开头的变量用于配置。 密钥是通过删除 MDBOOK_ 前缀并将结果字符串转换为 kebab-case 来创建的。 双下划线 (__) 分隔为嵌套键(.),而单个下划线 (_) 则替换为破折号 (-)。

例子:

  • MDBOOK_foo -> foo
  • MDBOOK_FOO -> foo
  • MDBOOK_FOO__BAR -> foo.bar
  • MDBOOK_FOO_BAR -> foo-bar
  • MDBOOK_FOO_bar__baz -> foo-bar.baz

因此,通过设置 MDBOOK_BOOK__TITLE 环境变量,您可以覆盖书名而无需修改您的 book.toml

备注: 为了方便设置更复杂的配置项,环境变量的值首先被解析为 JSON,如果解析失败则回退到字符串。

这意味着,如果您愿意,您可以在构建书籍时覆盖所有书籍元数据,例如

$ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
$ mdbook build

后一种情况在从脚本或 CI 调用 mdbook 的情况下可能很有用,适用情况为,有时无法在构建之前更新 book.toml

主题

默认渲染器使用一个handlebars模板来渲染你的markdown文件,并带有一个包含在 mdBook 二进制文件中的默认主题。

主题是完全可定制的,您可以通过在项目根目录的 src 文件夹旁边添加一个theme目录,有选择地替换主题中的每个文件。 使用要覆盖的文件的名称创建一个新文件,现在将使用该文件而不是默认文件。

以下是您可以覆盖的文件:

  • index.hbs handlebars 模板.
  • head.hbs 附加到 HTML <head> 部分。
  • header.hbs 内容附加在每个书页的顶部。
  • css/ 包含用于设计样式的 CSS 文件。
    • css/chrome.css 用于 UI 元素。
    • css/general.css 是基本样式。
    • css/print.css 是打印机输出的样式。
    • css/variables.css 包含在其他 CSS 文件中使用的变量。
  • book.js 主要用于添加客户端功能,例如隐藏/取消隐藏侧边栏,更改主题,...
  • highlight.js 是用于突出显示代码片段的 JavaScript,您不需要修改它。
  • highlight.css 是用于代码突出显示的主题。
  • favicon.svgfavicon.png 使用的图标。 SVG 版本由较新的浏览器使用。

通常,当您想要调整主题时,您不需要覆盖所有文件。 如果您只需要更改样式表,则覆盖所有其他文件没有意义。 因为自定义文件优先于内置文件,所以它们不会随着新的修复程序/功能而更新。

注意: 覆盖文件时,可能会破坏某些功能。 因此,我建议使用默认主题中的文件作为模板,并且只添加/修改您需要的内容。 您可以使用 mdbook init --theme 自动将默认主题复制到源目录中,然后删除不想覆盖的文件。

如果完全替换所有内置主题,请务必在配置中设置 output.html.preferred-dark-theme,默认为内置navy主题。

index.hbs

index.hbs 是用于渲染book的handlebars模板。 Markdown 文件被处理为 html,然后注入到该模板中。

如果您想更改书籍的布局或样式,您可能需要稍微修改此模板。 本节是您需要了解的内容。

Data

许多数据通过"context"(上下文)暴露给handlebars模板。 在handlebars模板中,您可以使用这些数据。

{{name_of_property}}

以下是公开的属性列表:

  • language 书的语言采用 en 形式,如 book.toml 中指定(如果未指定,默认为 en)。 例如在 <html lang="{{ language }}"> 中使用。

  • title 用于当前页面的标题。 这与 {{chapter_title }} - {{ book_title }} 相同,除非没有设置 book_title,在这种情况下它只是默认为 chapter_title

  • book_title 书名,在book.toml中指定

  • chapter_title 当前章节的标题,如SUMMARY.md中所列

  • path 从源目录到原始 Markdown 文件的相对路径

  • content 这是渲染过的Markdown。

  • path_to_root 这是一个只包含 ../ 的路径,它指向根目录。 由于保留了原始目录结构,因此使用此 path_to_root 预先添加相关链接很有用。

  • chapters 是一个形式为字典的数组

    {"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
    

    包含本书的所有章节。 例如,它用于构建目录(侧边栏)。

Handlebars Helpers

除了您可以访问的属性外,还有一些handlebars helpers可供您使用。

1. toc

toc helper 是这样使用的

{{#toc}}{{/toc}}

并输出看起来像这样的东西,这取决于你的书的结构

<ul class="chapter">
    <li><a href="link/to/file.html">Some chapter</a></li>
    <li>
        <ul class="section">
            <li><a href="link/to/other_file.html">Some other Chapter</a></li>
        </ul>
    </li>
</ul>

如果你想用另一种结构制作目录,你可以访问包含所有数据的章节属性。 目前唯一的限制是您必须使用 JavaScript 而不是使用handlebars helpers来完成。

<script>
var chapters = {{chapters}};
// Processing here
</script>

2. previous / next

previousnext 的helpers公开了指向前一章和下一章的link(链接)和name(名称)属性。

它们是这样使用的

{{#previous}}
    <a href="{{link}}" class="nav-chapters previous">
        <i class="fa fa-angle-left"></i>
    </a>
{{/previous}}

仅在上一章/下一章存在时才会渲染 inner html。 当然,可以根据自己的喜好更改 inner html。


如果您想公开其他属性或helpers,请创建一个新issue

语法高亮

mdBook 使用具有自定义主题的 Highlight.js 来突出显示语法。

自动语言检测已关闭,因此您可能希望像这样指定您使用的编程语言:

```rust
fn main() {
    // Some code
}
```

支持的语言

默认情况下支持这些语言,但您可以通过提供自己的 highlight.js 文件来添加更多语言:

  • apache
  • armasm
  • bash
  • c
  • coffeescript
  • cpp
  • csharp
  • css
  • d
  • diff
  • go
  • handlebars
  • haskell
  • http
  • ini
  • java
  • javascript
  • json
  • julia
  • kotlin
  • less
  • lua
  • makefile
  • markdown
  • nginx
  • objectivec
  • perl
  • php
  • plaintext
  • properties
  • python
  • r
  • ruby
  • rust
  • scala
  • scss
  • shell
  • sql
  • swift
  • typescript
  • x86asm
  • xml
  • yaml

Custom theme

与主题的其余部分一样,用于语法突出显示的文件可以用您自己的文件覆盖。

  • highlight.js 通常您不必覆盖此文件,除非您想使用更新的版本。
  • highlight.css highlight.js 用于语法高亮显示的主题。

如果你想为 highlight.js 使用另一个主题,请从他们的网站下载它,或者自己制作,将其重命名为 highlight.css 并将其放在你的书的 theme 文件夹中。

现在将使用您的主题而不是默认主题。

隐藏代码行

mdBook 中有一项功能可以让您通过在代码行前加上 # 来隐藏代码行。

# fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
# }

将渲染为

fn main() {
    let x = 5;
    let y = 7;

    println!("{}", x + y);
}

目前,这只适用于使用 rust 注释的代码示例。 因为它会与某些编程语言的语义发生冲突。 将来,我们希望通过 book.toml 使其可配置,以便每个人都可以从中受益。

改进默认主题

如果您认为默认主题对于特定语言来说看起来不太合适,或者可以改进,请随时提交一个新issue来解释您的想法,我会查看它。

您还可以创建pull-request来建议需要改进的地方。

总体而言,主题应该是轻松而清醒的,没有太多华丽的色彩。

编辑器

除了提供可运行的代码环境外,mdBook 还允许它们可编辑。 为了启用可编辑的代码块,需要在 book.toml 中添加以下内容:

[output.html.playground]
editable = true

要使特定块可用于编辑,需要向其添加属性editable

```rust,editable
fn main() {
    let number = 5;
    print!("{}", number);
}
```

以上将导致这个可编辑和运行的代码块:

fn main() {
    let number = 5;
    print!("{}", number);
}

请注意代码块中的 撤销更改 按钮。

自定义编辑器

默认情况下,编辑器是 Ace 编辑器,但如果需要,可以通过提供不同的文件夹来覆盖该功能:

[output.html.playground]
editable = true
editor = "/path/to/editor"

请注意,为了使编辑器更改正常运行,需要覆盖 theme 文件夹内的 book.js,因为它与默认的 Ace 编辑器有一些耦合。

MathJax 支持

mdBook 通过 MathJax 选择性地支持数学方程式。

要启用 MathJax,您需要在 output.html 部分下的 book.toml 中添加 mathjax-support 键。

[output.html]
mathjax-support = true

注意: 尚不支持 MathJax 使用的常用分隔符。 您目前不能使用 $$ ... $$ 作为分隔符,并且 \[ ... \] 分隔符需要额外的反斜杠才能工作。 希望这个限制很快就会解除。

注意: 当您在 MathJax 块中使用双反斜杠(例如 \begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases} 等命令中)时,您需要添加两个额外的反斜杠(例如,\begin{cases} \frac 1 2 \\\\ \frac 3 4 \end{cases})。

内联方程式

内联方程由 \\(\\) 分隔。 例如,要渲染以下内联方程 \( \int x dx = \frac{x^2}{2} + C \),您可以编写以下内容:

\\( \int x dx = \frac{x^2}{2} + C \\)

块方程式

块方程由 \\[\\] 分隔。 渲染以下等式

\[ \mu = \frac{1}{N} \sum_{i=0} x_i \]

你会写:

\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]

mdBook 特定功能

隐藏代码行

mdBook 中有一个功能可以让你通过在代码行前面加上 # 来隐藏代码行,就像你在 Rustdoc 中所做的那样。

# fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
# }

会渲染成

fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
}

包含文件

使用以下语法,您可以将文件包含到您的书中:

{{#include file.rs}}

文件路径必须相对于当前源文件。

mdBook 会将包含的文件解释为 Markdown。 由于 include 命令通常用于插入代码片段和示例,因此您通常会用 ``` 包裹该命令以显示文件内容而不解释它们。

```
{{#include file.rs}}
```

包含文件的一部分

通常您只需要文件的特定部分,例如 相关行举例。 我们支持四种不同的部分模式包括:

{{#include file.rs:2}}
{{#include file.rs::10}}
{{#include file.rs:2:}}
{{#include file.rs:2:10}}

第一个命令只包含文件 file.rs 的第二行。 第二个命令包括到第 10 行的所有行,即从 11 到文件末尾的行被省略。 第三个命令包括第 2 行的所有行,即省略第一行。 最后一个命令包括 file.rs 的摘录,由第 2 行到第 10 行组成。

为了避免在修改包含的文件时破坏您的书籍,您还可以使用锚点而不是行号来包含特定部分。 锚点是一对匹配的线。 锚点开始的行必须与正则表达式 ANCHOR:\s*[\w_-]+ 匹配,类似地,结束行必须与正则表达式 ANCHOR_END:\s*[\w_-]+ 匹配。 这允许您在任何类型的注释行中放置锚点。

考虑包含以下文件:

/* ANCHOR: all */

// ANCHOR: component
struct Paddle {
    hello: f32,
}
// ANCHOR_END: component

////////// ANCHOR: system
impl System for MySystem { ... }
////////// ANCHOR_END: system

/* ANCHOR_END: all */

然后在书中,你所要做的就是:

Here is a component:
```rust,no_run,noplayground
{{#include file.rs:component}}
```

Here is a system:
```rust,no_run,noplayground
{{#include file.rs:system}}
```

This is the full file.
```rust,no_run,noplayground
{{#include file.rs:all}}
```

包含在锚点内的锚点匹配模式的行将被忽略。

包含一个文件,但隐藏最初指定行之外的所有内容

rustdoc_include helper 用于包含来自外部 Rust 文件的代码,这些文件包含完整的示例,但最初仅以与 include 相同的方式显示用行号或锚点指定的特定行。

不在行号范围内或锚点之间的行仍将包含在内,但它们将以 # 开头。 这样,读者可以展开代码片段以查看完整示例,而 Rustdoc 将在您运行 mdbook test 时使用完整示例。

例如,考虑一个名为 file.rs 的文件,其中包含这个 Rust 程序:

fn main() {
    let x = add_one(2);
    assert_eq!(x, 3);
}

fn add_one(num: i32) -> i32 {
    num + 1
}

我们可以使用以下语法包含一个最初仅显示第 2 行的代码段:

调用 `add_one` 函数, 我们传入一个 `i32` 并且将返回值赋值给 `x`:

```rust
{{#rustdoc_include file.rs:2}}
```

这与我们手动插入代码并使用#隐藏除第 2 行之外的所有代码具有相同的效果:

调用 `add_one` 函数, 我们传入一个 `i32` 并且将返回值赋值给 `x`:

```rust
# fn main() {
    let x = add_one(2);
#     assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
#     num + 1
# }
```

也就是说,它看起来像这样(单击“expand”图标以查看文件的其余部分):

fn main() {
    let x = add_one(2);
    assert_eq!(x, 3);
}

fn add_one(num: i32) -> i32 {
    num + 1
}

插入可运行的 Rust 文件

使用以下语法,您可以将可运行的 Rust 文件插入到您的书中:

{{#playground file.rs}}

Rust 文件的路径必须相对于当前源文件

当点击 play 时,代码片段将被发送到 Rust Playground 进行编译和运行。 结果被送回并直接显示在代码下方。

下面是渲染的代码片段的样子:

fn main() {
    println!("Hello World!");

    // You can even hide lines! :D
    println!("I am hidden! Expand the code snippet to see me");
}

控制页面 <title>

A chapter can set a <title> that is different from its entry in the table of contents (sidebar) by including a {{#title ...}} near the top of the page.

通过在页面顶部附近包含一个{{#title ...}},章节可以设置一个<title>,该<title> 与其在目录(侧边栏)中的条目不同。

{{#title My Title}}

Markdown

mdBook 的[解析器][parser]遵循 CommonMark 规范。 您可以学习快速教程,或实时试用 CommonMark。 完整的 Markdown 概述超出了本文档的范围,但以下是一些基础知识的高级概述。 如需更深入的体验,请查看 Markdown 指南

文本和段落

文本渲染相对可预测:

Here is a line of text.

This is a new line.

看起来像您期望的那样:

Here is a line of text.

This is a new line.

标题

标题使用 # 标记并且应该单独在一行上。 更多# 意味着更小的标题:

### A heading 

Some text.

#### A smaller heading 

More text.

一个标题

Some text.

A smaller heading

More text.

列表

列表可以是无序或有序的。 有序列表将自动排序:

* milk
* eggs
* butter

1. carrots
1. celery
1. radishes
  • milk
  • eggs
  • butter
  1. carrots
  2. celery
  3. radishes

链接

链接到 URL 或本地文件很容易:

Use [mdBook](https://github.com/rust-lang/mdBook). 

Read about [mdBook](mdBook.md).

A bare url: <https://www.rust-lang.org>.

Use mdBook.

Read about mdBook.

A bare url: https://www.rust-lang.org.

图片

包含图像只是包含指向它们的链接的问题,就像上面的 链接 部分一样。 以下 markdown 包含在与此文件相同级别的 images 目录中找到的 Rust 徽标的 SVG 图像:

![The Rust Logo](images/rust-logo-blk.svg)

使用 mdBook 构建时生成以下 HTML:

<p><img src="images/rust-logo-blk.svg" alt="The Rust Logo" /></p>

其中,当然显示图像如下:

The Rust Logo

有关更多信息,请参阅 Markdown 指南基本语法文档。

在持续集成中运行 mdbook

虽然以下示例使用 Travis CI,但它们的原则也应该直接转移到其他持续集成提供者。

确保您的图书 build 和 test 通过

这是一个示例 Travis CI .travis.yml 配置,可确保 mdbook buildmdbook test 成功运行。 快速 CI 运转时间的关键是缓存 mdbook 安装,这样您就不会在每次 CI 运行时编译 mdbook

language: rust
sudo: false

cache:
  - cargo

rust:
  - stable

before_script:
  - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
  - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
  - cargo install-update -a

script:
  - mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook

将您的书部署到 GitHub Pages

遵循这些说明将导致在您仓库的master分支上成功运行 CI 后,您的书将被发布到 GitHub pages。

首先,使用“public_repo”权限(或“repo”用于私有仓库)创建一个新的 GitHub “Personal Access Token”(个人访问令牌)。 转到仓库的 Travis CI 设置页面并添加名为 GITHUB_TOKEN 的环境变量,该变量标记为安全且未显示在日志中。

在您仓库的设置页面中,导航到 Options 并将 GitHub Pages 上的 Source 更改为 gh-pages

然后,将此代码段附加到您的 .travis.yml 并更新 book 目录的路径:

deploy:
  provider: pages
  skip-cleanup: true
  github-token: $GITHUB_TOKEN
  local-dir: book # In case of custom book path: path/to/mybook/book
  keep-history: false
  on:
    branch: main

仅此而已!

注意: Travis 有一个新的 dplv2 配置,目前处于测试阶段。 要使用这种新格式,请将 .travis.yml 文件更新为:

language: rust
os: linux
dist: xenial

cache:
  - cargo

rust:
  - stable

before_script:
  - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
  - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
  - cargo install-update -a

script:
  - mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
  
deploy:
  provider: pages
  strategy: git
  edge: true
  cleanup: false
  github-token: $GITHUB_TOKEN
  local-dir: book # In case of custom book path: path/to/mybook/book
  keep-history: false
  on:
    branch: main
  target_branch: gh-pages

手动部署到 GitHub Pages

如果您的 CI 不支持 GitHub Pages,或者您正在使用 Github Pages 等集成部署到其他地方:

注意: 您可能想要使用不同的 tmp 目录:

$> git worktree add /tmp/book gh-pages
$> mdbook build
$> rm -rf /tmp/book/* # this won't delete the .git directory
$> cp -rp book/* /tmp/book/
$> cd /tmp/book
$> git add -A
$> git commit 'new book message'
$> git push origin gh-pages
$> cd -

或者将其放入 Makefile 规则中:

.PHONY: deploy
deploy: book
 @echo "====> deploying to github"
 git worktree add /tmp/book gh-pages
 rm -rf /tmp/book/*
 cp -rp book/* /tmp/book/
 cd /tmp/book && \
  git add -A && \
  git commit -m "deployed on $(shell date) by ${USER}" && \
  git push origin gh-pages

部署你的书到 GitLab Pages

在仓库的项目根目录中,创建一个名为 .gitlab-ci.yml 的文件,其中包含以下内容:

stages:
    - deploy

pages:
  stage: deploy
  image: rust
  variables:
    CARGO_HOME: $CI_PROJECT_DIR/cargo
  before_script:
    - export PATH="$PATH:$CARGO_HOME/bin"
    - mdbook --version || cargo install mdbook
  script:
    - mdbook build -d public
  rules:
    - if: '$CI_COMMIT_REF_NAME == "master"'
  artifacts:
    paths:
      - public
  cache:
    paths:
      - $CARGO_HOME/bin

在您提交并推送这个新文件后,GitLab CI 将自动运行并且您的书将可用!

开发者

虽然 mdbook 主要用作命令行工具,但您也可以直接导入底层库并使用它来管理书籍。 它还具有相当灵活的插件机制,如果您需要对本书进行一些分析或以不同的格式呈现它,则允许您创建自己的自定义工具和使用者(通常称为后端)。

开发者 章节在这里向您展示 mdbook 的更高级用法。

开发人员可以通过两种主要方式与本书的构建过程挂钩,

构建过程

渲染书籍项目的过程需要经过几个步骤。

  1. 加载 book
    • 解析 book.toml, 如果不存在,则回退到默认的 Config
    • 加载 book 章节到内存
    • 发现应该使用哪些预处理器/后端
  2. 运行预处理器
  3. 依次调用各个后端

使用 mdbook 作为库

mdbook 二进制文件只是 mdbook crate 的包装器,将其功能作为命令行程序公开。 因此,很容易创建自己的程序,在内部使用 mdbook ,添加自己的功能(例如自定义预处理器)或调整构建过程。

了解如何使用 mdbook crate 的最简单方法是查看 API 文档。 顶级文档解释了如何使用 MDBook 类型加载和构建一本书,而 config 模块对配置系统给出了很好的解释。

预处理器

预处理器只是一小段代码,它在书籍加载后和渲染之前立即运行,允许您更新和修改书籍。 可能的用例是:

  • 创建一个自定义 helpers 例如 {{#include /path/to/file.md}}
  • 更新链接,以便HTML渲染器渲染时将 [部分章节1](some_chapter1.md) 自动更改为 [部分章节2](some_chapter2.html)
  • 替换 latex 风格的表达式 ($$ \frac{1}{3} $$) 为 等价的 mathjax 表达式

连接到 MDBook

MDBook 使用一种相当简单的机制来发现第三方插件。 一个新表被添加到 book.toml(例如 foo 预处理器的 preprocessor.foo), 然后 mdbook 将尝试调用 mdbook-foo 程序作为构建过程的一部分。

预处理器可以硬编码来指定preprocessor.foo.renderer 键, 指定他应该运行那些后端。 例如,将 MathJax 用于非 HTML 渲染器是没有意义的。

[book]
title = "My Book"
authors = ["Michael-F-Bryan"]

[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]

一旦定义了预处理器并开始构建过程,mdBook 将执行 preprocessor.foo.command 键中定义的命令两次。

它第一次运行为预处理器确定它是否支持给定的渲染器。

mdBook 向进程传递两个参数:第一个参数是字符串supports,第二个参数是渲染器名称。

如果预处理器支持给定的渲染器,则它应该以状态代码 0 退出,如果不支持,则返回非零退出代码。

如果预处理器支持渲染器,则 mdbook 将第二次运行它,且将 JSON 数据传递到 stdin。 JSON 包含一个 [context, book] 数组,其中 context 是序列化的对象 PreprocessorContext,而 book 是包含书籍内容的 Book 对象。

预处理器应该将 Book 对象的 JSON 格式返回到标准输出,并带有它希望执行的任何修改。

最简单的入门方法是创建您自己的 Preprocessor trait 实现(例如在 lib.rs 中), 然后创建一个 shell 二进制文件,将输入转换为正确的 Preprocessor 方法。 为方便起见,examples/ 目录中有一个示例 no-op 预处理器,它可以很容易地适用于其他预处理器。

示例预处理器 no-op
// nop-preprocessors.rs

use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use semver::{Version, VersionReq};
use std::io;
use std::process;

pub fn make_app() -> App<'static, 'static> {
    App::new("nop-preprocessor")
        .about("A mdbook preprocessor which does precisely nothing")
        .subcommand(
            SubCommand::with_name("supports")
                .arg(Arg::with_name("renderer").required(true))
                .about("Check whether a renderer is supported by this preprocessor"),
        )
}

fn main() {
    let matches = make_app().get_matches();

    // Users will want to construct their own preprocessor here
    let preprocessor = Nop::new();

    if let Some(sub_args) = matches.subcommand_matches("supports") {
        handle_supports(&preprocessor, sub_args);
    } else if let Err(e) = handle_preprocessing(&preprocessor) {
        eprintln!("{}", e);
        process::exit(1);
    }
}

fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
    let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;

    let book_version = Version::parse(&ctx.mdbook_version)?;
    let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;

    if !version_req.matches(&book_version) {
        eprintln!(
            "Warning: The {} plugin was built against version {} of mdbook, \
             but we're being called from version {}",
            pre.name(),
            mdbook::MDBOOK_VERSION,
            ctx.mdbook_version
        );
    }

    let processed_book = pre.run(&ctx, book)?;
    serde_json::to_writer(io::stdout(), &processed_book)?;

    Ok(())
}

fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
    let renderer = sub_args.value_of("renderer").expect("Required argument");
    let supported = pre.supports_renderer(renderer);

    // Signal whether the renderer is supported by exiting with 1 or 0.
    if supported {
        process::exit(0);
    } else {
        process::exit(1);
    }
}

/// The actual implementation of the `Nop` preprocessor. This would usually go
/// in your main `lib.rs` file.
mod nop_lib {
    use super::*;

    /// A no-op preprocessor.
    pub struct Nop;

    impl Nop {
        pub fn new() -> Nop {
            Nop
        }
    }

    impl Preprocessor for Nop {
        fn name(&self) -> &str {
            "nop-preprocessor"
        }

        fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
            // In testing we want to tell the preprocessor to blow up by setting a
            // particular config value
            if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
                if nop_cfg.contains_key("blow-up") {
                    anyhow::bail!("Boom!!1!");
                }
            }

            // we *are* a no-op preprocessor after all
            Ok(book)
        }

        fn supports_renderer(&self, renderer: &str) -> bool {
            renderer != "not-supported"
        }
    }
}

实现预处理器的提示

通过将 mdbook 作为库引入,预处理器可以访问现有的基础架构来处理书籍。

例如,自定义预处理器可以使用 CmdPreprocessor::parse_input() 函数反序列化写入stdin的 JSON。 然后可以通过 Book::for_each_mut() 就地修改 Book 的每一章,然后使用 serde_json crate 写入 stdout

章节可以直接访问(通过递归迭代章节)或通过 Book::for_each_mut() 便捷方法访问。

chapter.content 只是一个字符串,恰好是 Markdown。 虽然完全可以使用正则表达式或进行手动查找和替换,但您可能希望将输入处理为对计算机更友好的内容。 pulldown-cmark crate 实现了一个生产质量的基于事件的 Markdown 解析器,使用 pulldown-cmark-to-cmark 允许您将事件转换回 Markdown 文本。

以下代码块显示了如何从 Markdown 中删除所有强调,而不会意外破坏文档。


#![allow(unused)]
fn main() {
fn remove_emphasis(
    num_removed_items: &mut usize,
    chapter: &mut Chapter,
) -> Result<String> {
    let mut buf = String::with_capacity(chapter.content.len());

    let events = Parser::new(&chapter.content).filter(|e| {
        let should_keep = match *e {
            Event::Start(Tag::Emphasis)
            | Event::Start(Tag::Strong)
            | Event::End(Tag::Emphasis)
            | Event::End(Tag::Strong) => false,
            _ => true,
        };
        if !should_keep {
            *num_removed_items += 1;
        }
        should_keep
    });

    cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
        Error::from(format!("Markdown serialization failed: {}", err))
    })
}
}

对于其他所有内容,请查看完整示例

用不同的语言实现预处理器

mdBook 利用 stdinstdout 与预处理器通信的事实使得用 Rust 以外的语言实现它们变得容易。

下面的代码展示了如何在Python中实现一个简单的预处理器,将修改第一章的内容。

下面的示例遵循上面显示的配置,其中 preprocessor.foo.command 实际上指向一个 Python 脚本。

import json
import sys


if __name__ == '__main__':
    if len(sys.argv) > 1: # 我们检查我们是否收到任何参数
        if sys.argv[1] == "supports": 
            # 那么我们最好返回 0 的退出状态代码,因为另一个参数将只是渲染器的名称
            sys.exit(0)

    # 从标准输入加载上下文和书籍表示
    context, book = json.load(sys.stdin)
    # 现在,我们可以修改第一章的内容
    book['sections'][0]['Chapter']['content'] = '# Hello'
    # 我们完成了本书的修改,我们可以将它打印到标准输出,
    print(json.dumps(book))

替换后端

“后端”只是一个 mdbook 在渲染过程中调用的程序。 该程序通过stdin传递书籍和配置信息的 JSON 表示。 一旦后端收到此信息,它就可以自由地做任何想做的事。

GitHub 上已经有几个替代后端,可以用作在实践中如何完成的大概示例。

  • mdbook-linkcheck - 一个用于验证这本书不包含任何损坏的链接的简单程序
  • mdbook-epub - 一个 EPUB 渲染器
  • mdbook-test - 一个通过 rust-skeptic 运行本书内容的程序,以验证所有内容是否正确编译和运行(类似于 rustdoc --test
  • mdbook-man - 从书中生成手册页

此页面将引导您以简单的字数统计程序的形式创建您自己的替代后端。 虽然它将用 Rust 编写,但没有理由不能使用 Python 或 Ruby 之类的其他编程语言来完成。

起步

首先,您需要创建一个新的二进制程序并添加 mdbook 作为依赖项。

>$ cargo new --bin mdbook-wordcount
>$ cd mdbook-wordcount
>$ cargo add mdbook

当我们的 mdbook-wordcount 插件被调用时,mdbook 将通过我们插件的 stdin 向它发送 RenderContext 的 JSON 版本。 为方便起见,有一个 RenderContext::from_json() 构造函数,它将加载一个 RenderContext

这是我们的后端加载本书所需的所有样板。

// src/main.rs
extern crate mdbook;

use std::io;
use mdbook::renderer::RenderContext;

fn main() {
    let mut stdin = io::stdin();
    let ctx = RenderContext::from_json(&mut stdin).unwrap();
}

注意: RenderContext 包含一个version字段。 这让后端可以确定它们是否与它正在调用的 mdbook 版本兼容。 该version直接来自 mdbook 的 Cargo.toml 中的相应字段。

建议后端使用 semver crate 检查此字段,并在可能存在兼容性问题时发出警告。

核查 Book

现在我们的后端有这本书的副本,让我们数一数每章有多少字!

因为 RenderContext 包含一个Book 字段(book),并且Book 具有Book::iter() 方法来迭代Book 中的所有项目,这一步 结果证明和第一个一样容易。


fn main() {
    let mut stdin = io::stdin();
    let ctx = RenderContext::from_json(&mut stdin).unwrap();

    for item in ctx.book.iter() {
        if let BookItem::Chapter(ref ch) = *item {
            let num_words = count_words(ch);
            println!("{}: {}", ch.name, num_words);
        }
    }
}

fn count_words(ch: &Chapter) -> usize {
    ch.content.split_whitespace().count()
}

启用后端

现在我们已经有了基础运行代码,我们想要实际使用它。 首先,安装程序。

cargo install --path .

然后cd到你想要计算单词数的特定书籍目录. 并更新它的book.toml文件。

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

+ [output.html]

+ [output.wordcount]

当它将一本书加载到内存中时,mdbook 将检查您的 book.toml 文件,以通过查找所有 output.* 表来尝试找出要使用的后端。 如果没有提供,它将回退到使用默认的 HTML 渲染器。

值得注意的是,这意味着如果您想添加自己的自定义后端,您还需要确保添加 HTML 后端,即使它的表只是为空。

现在你只需要像往常一样构建你的书,一切都应该正常工作

$ mdbook build
...
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
mdBook: 126
Command Line Tool: 224
init: 283
build: 145
watch: 146
serve: 292
test: 139
Format: 30
SUMMARY.md: 259
Configuration: 784
Theme: 304
index.hbs: 447
Syntax highlighting: 314
MathJax Support: 153
Rust code specific features: 148
For Developers: 788
Alternative Backends: 710
Contributors: 85

我们不需要指定 wordcount 后端的全名/路径的原因是因为 mdbook 将尝试通过约定推断程序的名称。 foo 后端的可执行文件通常称为 mdbook-foo,在 book.toml 中有一个关联的 [output.foo] 条目。 要明确告诉 mdbook 调用什么命令(它可能需要命令行参数或解释脚本),您可以使用command字段。

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

  [output.html]

  [output.wordcount]
+ command = "python /path/to/wordcount.py"

配置

现在假设您不想计算特定章节的字数(它可能是生成的文本/代码等)。 执行此操作的规范方法是通过配置文件book.toml , 将项目添加到配置文件的 [output.foo] 表中。

Config 可以粗略地视为一个嵌套的 hashmap,它允许您调用 get() 之类的方法来访问配置的内容,使用 get_deserialized() 便捷方法检索值并自动反序列化为某个任意类型 T

为了实现这一点,我们将创建我们自己的可序列化 WordcountConfig 结构,它将封装此后端的所有配置。

首先将 serdeserde_derive 添加到 Cargo.toml

cargo add serde serde_derive

然后你可以创建配置结构,


#![allow(unused)]
fn main() {
extern crate serde;
#[macro_use]
extern crate serde_derive;

...

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct WordcountConfig {
  pub ignores: Vec<String>,
}
}

现在我们只需要从我们的 RenderContext 反序列化 WordcountConfig,然后添加一个检查以确保跳过被我们忽略的章节。

  fn main() {
      let mut stdin = io::stdin();
      let ctx = RenderContext::from_json(&mut stdin).unwrap();
+     let cfg: WordcountConfig = ctx.config
+         .get_deserialized("output.wordcount")
+         .unwrap_or_default();

      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
+             if cfg.ignores.contains(&ch.name) {
+                 continue;
+             }
+
              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
          }
      }
  }

输出和信令故障

虽然在构建一本书时将字数打印到终端是很好的,但将它们输出到某个文件中也可能是一个好主意。 mdbook 告诉后端它应该通过 RenderContext 中的destination字段将任何生成的输出放置在哪里。

+ use std::fs::{self, File};
+ use std::io::{self, Write};
- use std::io;
  use mdbook::renderer::RenderContext;
  use mdbook::book::{BookItem, Chapter};

  fn main() {
    ...

+     let _ = fs::create_dir_all(&ctx.destination);
+     let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
+
      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
              ...

              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
+             writeln!(f, "{}: {}", ch.name, num_words).unwrap();
          }
      }
  }

注意: 无法保证目标目录存在或为空(mdbook 可能会保留之前的内容让后端进行缓存),因此使用 fs::create_dir_all() 创建它总是一个好主意。

如果目标目录已经存在,不要假设它是空的。 为了允许后端缓存先前运行的结果,mdbook 可能会在目录中保留旧内容。

处理书籍时总是有可能发生错误(只需查看我们已经编写的所有 unwrap() ),因此 mdbook 会将非零退出代码解释为渲染失败。

例如,如果我们想确保所有章节都有偶数个单词,如果遇到奇数就出错,那么你可以这样做:

+ use std::process;
  ...

  fn main() {
      ...

      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
              ...

              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
              writeln!(f, "{}: {}", ch.name, num_words).unwrap();

+             if cfg.deny_odds && num_words % 2 == 1 {
+               eprintln!("{} has an odd number of words!", ch.name);
+               process::exit(1);
              }
          }
      }
  }

  #[derive(Debug, Default, Serialize, Deserialize)]
  #[serde(default, rename_all = "kebab-case")]
  pub struct WordcountConfig {
      pub ignores: Vec<String>,
+     pub deny_odds: bool,
  }

现在,如果我们重新安装后端并构建一本书,

$ cargo install --path . --force
$ mdbook build /path/to/book
...
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
mdBook: 126
Command Line Tool: 224
init: 283
init has an odd number of words!
2018-01-16 21:21:39 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Error: Rendering failed
2018-01-16 21:21:39 [ERROR] (mdbook::utils):    Caused By: The "mdbook-wordcount" renderer failed

您可能已经注意到,插件子进程的输出会立即传递给用户。 鼓励插件遵循“沉默规则”,仅在必要时生成输出(例如生成错误或警告)。

所有环境变量都传递到后端,允许您使用通常的RUST_LOG来控制日志记录的详细程度。

处理缺失的后端

如果启用未安装的后端,默认行为是抛出错误:

The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
If you want to ignore this error when the "wordcount" backend is not installed,
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.

可以通过将后端标记为可选来更改此行为。

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

  [output.html]

  [output.wordcount]
  command = "python /path/to/wordcount.py"
+ optional = true

这会将错误降级为警告,而是如下所示:

The command was not found, but was marked as optional.
    Command: wordcount

Wrapping Up

虽然人为设计,但希望这个例子足以展示您如何为 mdbook 创建替换后端。 如果您觉得缺少某些内容,请不要犹豫在问题跟踪器中创建问题,以便我们改进用户指南。

本章开头提到的现有后端应该作为它在现实生活中如何完成的一个很好的例子,所以请随意浏览源代码或提出问题。

贡献者

以下是帮助改进 mdBook 的贡献者列表。 向他们欢呼!

如果您觉得自己不在此列表中,请随时将自己添加到仓库中。

说明

此项目为 mdbook 文档中文翻译,

  • mdbook版本:v0.4.14
  • 翻译完成时间: 2021-12-24 23:08:14

相关阅读

Introduction

mdBook is a command line tool and Rust crate to create books with Markdown. The output resembles tools like Gitbook, and is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean, easily navigable and customizable presentation. mdBook is written in Rust; its performance and simplicity made it ideal for use as a tool to publish directly to hosted websites such as GitHub Pages via automation. This guide, in fact, serves as both the mdBook documentation and a fine example of what mdBook produces.

mdBook includes built in support for both preprocessing your Markdown and alternative renderers for producing formats other than HTML. These facilities also enable other functionality such as validation. Searching Rust's crates.io is a great way to discover more extensions.

API Documentation

In addition to the above features, mdBook also has a Rust API. This allows you to write your own preprocessor or renderer, as well as incorporate mdBook features into other applications. The For Developers section of this guide contains more information and some examples.

Contributing

mdBook is free and open source. You can find the source code on GitHub and issues and feature requests can be posted on the GitHub issue tracker. mdBook relies on the community to fix bugs and add features: if you'd like to contribute, please read the CONTRIBUTING guide and consider opening a pull request.

License

The mdBook source and documentation are released under the Mozilla Public License v2.0.

Command Line Tool

mdBook can be used either as a command line tool or a Rust crate. Let's focus on the command line tool capabilities first.

Install From Binaries

Precompiled binaries are provided for major platforms on a best-effort basis. Visit the releases page to download the appropriate version for your platform.

Install From Source

mdBook can also be installed by compiling the source code on your local machine.

Pre-requisite

mdBook is written in Rust and therefore needs to be compiled with Cargo. If you haven't already installed Rust, please go ahead and install it now.

Install Crates.io version

Installing mdBook is relatively easy if you already have Rust and Cargo installed. You just have to type this snippet in your terminal:

cargo install mdbook

This will fetch the source code for the latest release from Crates.io and compile it. You will have to add Cargo's bin directory to your PATH.

Run mdbook help in your terminal to verify if it works. Congratulations, you have installed mdBook!

Install Git version

The git version contains all the latest bug-fixes and features, that will be released in the next version on Crates.io, if you can't wait until the next release. You can build the git version yourself. Open your terminal and navigate to the directory of you choice. We need to clone the git repository and then build it with Cargo.

git clone --depth=1 https://github.com/rust-lang/mdBook.git
cd mdBook
cargo build --release

The executable mdbook will be in the ./target/release folder, this should be added to the path.

The init command

There is some minimal boilerplate that is the same for every new book. It's for this purpose that mdBook includes an init command.

The init command is used like this:

mdbook init

When using the init command for the first time, a couple of files will be set up for you:

book-test/
├── book
└── src
    ├── chapter_1.md
    └── SUMMARY.md
  • The src directory is where you write your book in markdown. It contains all the source files, configuration files, etc.

  • The book directory is where your book is rendered. All the output is ready to be uploaded to a server to be seen by your audience.

  • The SUMMARY.md is the skeleton of your book, and is discussed in more detail in another chapter.

Tip: Generate chapters from SUMMARY.md

When a SUMMARY.md file already exists, the init command will first parse it and generate the missing files according to the paths used in the SUMMARY.md. This allows you to think and create the whole structure of your book and then let mdBook generate it for you.

Specify a directory

The init command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook init path/to/book

--theme

When you use the --theme flag, the default theme will be copied into a directory called theme in your source directory so that you can modify it.

The theme is selectively overwritten, this means that if you don't want to overwrite a specific file, just delete it and the default file will be used.

--title

Specify a title for the book. If not supplied, an interactive prompt will ask for a title.

mdbook init --title="my amazing book"

--ignore

Create a .gitignore file configured to ignore the book directory created when building a book. If not supplied, an interactive prompt will ask whether it should be created.

The build command

The build command is used to render your book:

mdbook build

It will try to parse your SUMMARY.md file to understand the structure of your book and fetch the corresponding files. Note that files mentioned in SUMMARY.md but not present will be created.

The rendered output will maintain the same directory structure as the source for convenience. Large books will therefore remain structured when rendered.

Specify a directory

The build command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook build path/to/book

--open

When you use the --open (-o) flag, mdbook will open the rendered book in your default web browser after building it.

--dest-dir

The --dest-dir (-d) option allows you to change the output directory for the book. Relative paths are interpreted relative to the book's root directory. If not specified it will default to the value of the build.build-dir key in book.toml, or to ./book.


Note: The build command copies all files (excluding files with .md extension) from the source directory into the build directory.

The watch command

The watch command is useful when you want your book to be rendered on every file change. You could repeatedly issue mdbook build every time a file is changed. But using mdbook watch once will watch your files and will trigger a build automatically whenever you modify a file; this includes re-creating deleted files still mentioned in SUMMARY.md!

Specify a directory

The watch command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook watch path/to/book

--open

When you use the --open (-o) option, mdbook will open the rendered book in your default web browser.

--dest-dir

The --dest-dir (-d) option allows you to change the output directory for the book. Relative paths are interpreted relative to the book's root directory. If not specified it will default to the value of the build.build-dir key in book.toml, or to ./book.

Specify exclude patterns

The watch command will not automatically trigger a build for files listed in the .gitignore file in the book root directory. The .gitignore file may contain file patterns described in the gitignore documentation. This can be useful for ignoring temporary files created by some editors.

Note: Only .gitignore from book root directory is used. Global $HOME/.gitignore or .gitignore files in parent directories are not used.

The serve command

The serve command is used to preview a book by serving it via HTTP at localhost:3000 by default:

mdbook serve

The serve command watches the book's src directory for changes, rebuilding the book and refreshing clients for each change; this includes re-creating deleted files still mentioned in SUMMARY.md! A websocket connection is used to trigger the client-side refresh.

Note: The serve command is for testing a book's HTML output, and is not intended to be a complete HTTP server for a website.

Specify a directory

The serve command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook serve path/to/book

Server options

The serve hostname defaults to localhost, and the port defaults to 3000. Either option can be specified on the command line:

mdbook serve path/to/book -p 8000 -n 127.0.0.1 

--open

When you use the --open (-o) flag, mdbook will open the book in your default web browser after starting the server.

--dest-dir

The --dest-dir (-d) option allows you to change the output directory for the book. Relative paths are interpreted relative to the book's root directory. If not specified it will default to the value of the build.build-dir key in book.toml, or to ./book.

Specify exclude patterns

The serve command will not automatically trigger a build for files listed in the .gitignore file in the book root directory. The .gitignore file may contain file patterns described in the gitignore documentation. This can be useful for ignoring temporary files created by some editors.

Note: Only the .gitignore from the book root directory is used. Global $HOME/.gitignore or .gitignore files in parent directories are not used.

The test command

When writing a book, you sometimes need to automate some tests. For example, The Rust Programming Book uses a lot of code examples that could get outdated. Therefore it is very important for them to be able to automatically test these code examples.

mdBook supports a test command that will run all available tests in a book. At the moment, only rustdoc tests are supported, but this may be expanded upon in the future.

Disable tests on a code block

rustdoc doesn't test code blocks which contain the ignore attribute:

```rust,ignore
fn main() {}
```

rustdoc also doesn't test code blocks which specify a language other than Rust:

```markdown
**Foo**: _bar_
```

rustdoc does test code blocks which have no language specified:

```
This is going to cause an error!
```

Specify a directory

The test command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook test path/to/book

--library-path

The --library-path (-L) option allows you to add directories to the library search path used by rustdoc when it builds and tests the examples. Multiple directories can be specified with multiple options (-L foo -L bar) or with a comma-delimited list (-L foo,bar). The path should point to the Cargo build cache deps directory that contains the build output of your project. For example, if your Rust project's book is in a directory named my-book, the following command would include the crate's dependencies when running test:

mdbook test my-book -L target/debug/deps/

See the rustdoc command-line documentation for more information.

--dest-dir

The --dest-dir (-d) option allows you to change the output directory for the book. Relative paths are interpreted relative to the book's root directory. If not specified it will default to the value of the build.build-dir key in book.toml, or to ./book.

The clean command

The clean command is used to delete the generated book and any other build artifacts.

mdbook clean

Specify a directory

The clean command can take a directory as an argument to use as the book's root instead of the current working directory.

mdbook clean path/to/book

--dest-dir

The --dest-dir (-d) option allows you to override the book's output directory, which will be deleted by this command. Relative paths are interpreted relative to the book's root directory. If not specified it will default to the value of the build.build-dir key in book.toml, or to ./book.

mdbook clean --dest-dir=path/to/book

path/to/book could be absolute or relative.

Format

In this section you will learn how to:

  • Structure your book correctly
  • Format your SUMMARY.md file
  • Configure your book using book.toml
  • Customize your theme

SUMMARY.md

The summary file is used by mdBook to know what chapters to include, in what order they should appear, what their hierarchy is and where the source files are. Without this file, there is no book.

This markdown file must be named SUMMARY.md. Its formatting is very strict and must follow the structure outlined below to allow for easy parsing. Any element not specified below, be it formatting or textual, is likely to be ignored at best, or may cause an error when attempting to build the book.

Structure

  1. Title - While optional, it's common practice to begin with a title, generally # Summary. This is ignored by the parser however, and can be omitted.

    # Summary
    
  2. Prefix Chapter - Before the main numbered chapters, prefix chapters can be added that will not be numbered. This is useful for forewords, introductions, etc. There are, however, some constraints. Prefix chapters cannot be nested; they should all be on the root level. And you cannot add prefix chapters once you have added numbered chapters.

    [A Prefix Chapter](relative/path/to/markdown.md)
    
    - [First Chapter](relative/path/to/markdown2.md)
    
  3. Part Title - Headers can be used as a title for the following numbered chapters. This can be used to logically separate different sections of the book. The title is rendered as unclickable text. Titles are optional, and the numbered chapters can be broken into as many parts as desired.

    # My Part Title
    
    - [First Chapter](relative/path/to/markdown.md)
    
  4. Numbered Chapter - Numbered chapters outline the main content of the book and can be nested, resulting in a nice hierarchy (chapters, sub-chapters, etc.).

    # Title of Part
    
    - [First Chapter](relative/path/to/markdown.md)
    - [Second Chapter](relative/path/to/markdown2.md)
       - [Sub Chapter](relative/path/to/markdown3.md)
    
    # Title of Another Part
    
    - [Another Chapter](relative/path/to/markdown4.md)
    

    Numbered chapters can be denoted with either - or * (do not mix delimiters).

  5. Suffix Chapter - Like prefix chapters, suffix chapters are unnumbered, but they come after numbered chapters.

    - [Last Chapter](relative/path/to/markdown.md)
    
    [Title of Suffix Chapter](relative/path/to/markdown2.md)
    
  6. Draft chapters - Draft chapters are chapters without a file and thus content. The purpose of a draft chapter is to signal future chapters still to be written. Or when still laying out the structure of the book to avoid creating the files while you are still changing the structure of the book a lot. Draft chapters will be rendered in the HTML renderer as disabled links in the table of contents, as you can see for the next chapter in the table of contents on the left. Draft chapters are written like normal chapters but without writing the path to the file.

    - [Draft Chapter]()
    
  7. Separators - Separators can be added before, in between, and after any other element. They result in an HTML rendered line in the built table of contents. A separator is a line containing exclusively dashes and at least three of them: ---.

    # My Part Title
    
    [A Prefix Chapter](relative/path/to/markdown.md)
    
    ---
    
    - [First Chapter](relative/path/to/markdown2.md)
    

Example

Below is the markdown source for the SUMMARY.md for this guide, with the resulting table of contents as rendered to the left.

# Summar

<!-- - [zh-cn](index.md) -->

[说明](index.md)

---

# 中文

- [指南](zh-cn/README.md)
- [命令](zh-cn/cli/README.md)
  - [init](zh-cn/cli/init.md)
  - [build](zh-cn/cli/build.md)
  - [watch](zh-cn/cli/watch.md)
  - [serve](zh-cn/cli/serve.md)
  - [test](zh-cn/cli/test.md)
  - [clean](zh-cn/cli/clean.md)
- [结构](zh-cn/format/README.md)
  - [SUMMARY.md](zh-cn/format/summary.md)
    - [草稿章节]()
  - [配置](zh-cn/format/configuration/README.md)
    - [常规](zh-cn/format/configuration/general.md)
    - [预处理](zh-cn/format/configuration/preprocessors.md)
    - [渲染](zh-cn/format/configuration/renderers.md)
    - [环境变量](zh-cn/format/configuration/environment-variables.md)
  - [主题](zh-cn/format/theme/README.md)
    - [index.hbs](zh-cn/format/theme/index-hbs.md)
    - [语法高亮](zh-cn/format/theme/syntax-highlighting.md)
    - [编辑器](zh-cn/format/theme/editor.md)
  - [数学公式支持](zh-cn/format/mathjax.md)
  - [mdBook特定功能](zh-cn/format/mdbook.md)
  - [Markdown](zh-cn/format/markdown.md)
- [持续集成](zh-cn/continuous-integration.md)
- [开发者](zh-cn/for_developers/README.md)
  - [预处理器](zh-cn/for_developers/preprocessors.md)
  - [替换后端](zh-cn/for_developers/backends.md)
- [贡献列表](zh-cn/misc/contributors.md)

----

# 原文

- [en](index.md)
  - [Introduction](en/README.md)
  - [Command Line Tool](en/cli/README.md)
    - [init](en/cli/init.md)
    - [build](en/cli/build.md)
    - [watch](en/cli/watch.md)
    - [serve](en/cli/serve.md)
    - [test](en/cli/test.md)
    - [clean](en/cli/clean.md)
  - [Format](en/format/README.md)
    - [SUMMARY.md](en/format/summary.md)
      - [Draft chapter]()
    - [Configuration](en/format/configuration/README.md)
      - [General](en/format/configuration/general.md)
      - [Preprocessors](en/format/configuration/preprocessors.md)
      - [Renderers](en/format/configuration/renderers.md)
      - [Environment Variables](en/format/configuration/environment-variables.md)
    - [Theme](en/format/theme/README.md)
      - [index.hbs](en/format/theme/index-hbs.md)
      - [Syntax highlighting](en/format/theme/syntax-highlighting.md)
      - [Editor](en/format/theme/editor.md)
    - [MathJax Support](en/format/mathjax.md)
    - [mdBook-specific features](en/format/mdbook.md)
    - [Markdown](en/format/markdown.md)
  - [Continuous Integration](en/continuous-integration.md)
  - [For Developers](en/for_developers/README.md)
    - [Preprocessors](en/for_developers/preprocessors.md)
    - [Alternative Backends](en/for_developers/backends.md)
  - [Contributors](en/misc/contributors.md)

Configuration

This section details the configuration options available in the book.toml:

  • General configuration including the book, rust, build sections
  • Preprocessor configuration for default and custom book preprocessors
  • Renderer configuration for the HTML, Markdown and custom renderers
  • Environment Variable configuration for overriding configuration options in your environment

General Configuration

You can configure the parameters for your book in the book.toml file.

Here is an example of what a book.toml file might look like:

[book]
title = "Example book"
author = "John Doe"
description = "The example book covers examples."

[rust]
edition = "2018"

[build]
build-dir = "my-example-book"
create-missing = false

[preprocessor.index]

[preprocessor.links]

[output.html]
additional-css = ["custom.css"]

[output.html.search]
limit-results = 15

Supported configuration options

It is important to note that any relative path specified in the configuration will always be taken relative from the root of the book where the configuration file is located.

General metadata

This is general information about your book.

  • title: The title of the book
  • authors: The author(s) of the book
  • description: A description for the book, which is added as meta information in the html <head> of each page
  • src: By default, the source directory is found in the directory named src directly under the root folder. But this is configurable with the src key in the configuration file.
  • language: The main language of the book, which is used as a language attribute <html lang="en"> for example.

book.toml

[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."
src = "my-src"  # the source files will be found in `root/my-src` instead of `root/src`
language = "en"

Rust options

Options for the Rust language, relevant to running tests and playground integration.

  • edition: Rust edition to use by default for the code snippets. Default is "2015". Individual code blocks can be controlled with the edition2015, edition2018 or edition2021 annotations, such as:

    ```rust,edition2015
    // This only works in 2015.
    let try = true;
    ```
    

Build options

This controls the build process of your book.

  • build-dir: The directory to put the rendered book in. By default this is book/ in the book's root directory.

  • create-missing: By default, any missing files specified in SUMMARY.md will be created when the book is built (i.e. create-missing = true). If this is false then the build process will instead exit with an error if any files do not exist.

  • use-default-preprocessors: Disable the default preprocessors of (links & index) by setting this option to false.

    If you have the same, and/or other preprocessors declared via their table of configuration, they will run instead.

    • For clarity, with no preprocessor configuration, the default links and index will run.
    • Setting use-default-preprocessors = false will disable these default preprocessors from running.
    • Adding [preprocessor.links], for example, will ensure, regardless of use-default-preprocessors that links it will run.

Configuring Preprocessors

The following preprocessors are available and included by default:

  • links: Expand the {{ #playground }}, {{ #include }}, and {{ #rustdoc_include }} handlebars helpers in a chapter to include the contents of a file.
  • index: Convert all chapter files named README.md into index.md. That is to say, all README.md would be rendered to an index file index.html in the rendered book.

book.toml

[build]
build-dir = "build"
create-missing = false

[preprocessor.links]

[preprocessor.index]

Custom Preprocessor Configuration

Like renderers, preprocessor will need to be given its own table (e.g. [preprocessor.mathjax]). In the section, you may then pass extra configuration to the preprocessor by adding key-value pairs to the table.

For example

[preprocessor.links]
# set the renderers this preprocessor will run for
renderers = ["html"]
some_extra_feature = true

Locking a Preprocessor dependency to a renderer

You can explicitly specify that a preprocessor should run for a renderer by binding the two together.

[preprocessor.mathjax]
renderers = ["html"]  # mathjax only makes sense with the HTML renderer

Provide Your Own Command

By default when you add a [preprocessor.foo] table to your book.toml file, mdbook will try to invoke the mdbook-foo executable. If you want to use a different program name or pass in command-line arguments, this behaviour can be overridden by adding a command field.

[preprocessor.random]
command = "python random.py"

Require A Certain Order

The order in which preprocessors are run can be controlled with the before and after fields. For example, suppose you want your linenos preprocessor to process lines that may have been {{#include}}d; then you want it to run after the built-in links preprocessor, which you can require using either the before or after field:

[preprocessor.linenos]
after = [ "links" ]

or

[preprocessor.links]
before = [ "linenos" ]

It would also be possible, though redundant, to specify both of the above in the same config file.

Preprocessors having the same priority specified through before and after are sorted by name. Any infinite loops will be detected and produce an error.

Configuring Renderers

HTML renderer options

The HTML renderer has a couple of options as well. All the options for the renderer need to be specified under the TOML table [output.html].

The following configuration options are available:

  • theme: mdBook comes with a default theme and all the resource files needed for it. But if this option is set, mdBook will selectively overwrite the theme files with the ones found in the specified folder.
  • default-theme: The theme color scheme to select by default in the 'Change Theme' dropdown. Defaults to light.
  • preferred-dark-theme: The default dark theme. This theme will be used if the browser requests the dark version of the site via the 'prefers-color-scheme' CSS media query. Defaults to navy.
  • curly-quotes: Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans. Defaults to false.
  • mathjax-support: Adds support for MathJax. Defaults to false.
  • copy-fonts: Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to true.
  • google-analytics: This field has been deprecated and will be removed in a future release. Use the theme/head.hbs file to add the appropriate Google Analytics code instead.
  • additional-css: If you need to slightly change the appearance of your book without overwriting the whole style, you can specify a set of stylesheets that will be loaded after the default ones where you can surgically change the style.
  • additional-js: If you need to add some behaviour to your book without removing the current behaviour, you can specify a set of JavaScript files that will be loaded alongside the default one.
  • print: A subtable for configuration print settings. mdBook by default adds support for printing out the book as a single page. This is accessed using the print icon on the top right of the book.
  • no-section-label: mdBook by defaults adds section label in table of contents column. For example, "1.", "2.1". Set this option to true to disable those labels. Defaults to false.
  • fold: A subtable for configuring sidebar section-folding behavior.
  • playground: A subtable for configuring various playground settings.
  • search: A subtable for configuring the in-browser search functionality. mdBook must be compiled with the search feature enabled (on by default).
  • git-repository-url: A url to the git repository for the book. If provided an icon link will be output in the menu bar of the book.
  • git-repository-icon: The FontAwesome icon class to use for the git repository link. Defaults to fa-github.
  • edit-url-template: Edit url template, when provided shows a "Suggest an edit" button for directly jumping to editing the currently viewed page. For e.g. GitHub projects set this to https://github.com/<owner>/<repo>/edit/master/{path} or for Bitbucket projects set it to https://bitbucket.org/<owner>/<repo>/src/master/{path}?mode=edit where {path} will be replaced with the full path of the file in the repository.
  • redirect: A subtable used for generating redirects when a page is moved. The table contains key-value pairs where the key is where the redirect file needs to be created, as an absolute path from the build directory, (e.g. /appendices/bibliography.html). The value can be any valid URI the browser should navigate to (e.g. https://rust-lang.org/, /overview.html, or ../bibliography.html).
  • input-404: The name of the markdown file used for missing files. The corresponding output file will be the same, with the extension replaced with html. Defaults to 404.md.
  • site-url: The url where the book will be hosted. This is required to ensure navigation links and script/css imports in the 404 file work correctly, even when accessing urls in subdirectories. Defaults to /.
  • cname: The DNS subdomain or apex domain at which your book will be hosted. This string will be written to a file named CNAME in the root of your site, as required by GitHub Pages (see Managing a custom domain for your GitHub Pages site).

Available configuration options for the [output.html.print] table:

  • enable: Enable print support. When false, all print support will not be rendered. Defaults to true.

Available configuration options for the [output.html.fold] table:

  • enable: Enable section-folding. When off, all folds are open. Defaults to false.
  • level: The higher the more folded regions are open. When level is 0, all folds are closed. Defaults to 0.

Available configuration options for the [output.html.playground] table:

  • editable: Allow editing the source code. Defaults to false.
  • copyable: Display the copy button on code snippets. Defaults to true.
  • copy-js: Copy JavaScript files for the editor to the output directory. Defaults to true.
  • line-numbers Display line numbers on editable sections of code. Requires both editable and copy-js to be true. Defaults to false.

Available configuration options for the [output.html.search] table:

  • enable: Enables the search feature. Defaults to true.
  • limit-results: The maximum number of search results. Defaults to 30.
  • teaser-word-count: The number of words used for a search result teaser. Defaults to 30.
  • use-boolean-and: Define the logical link between multiple search words. If true, all search words must appear in each result. Defaults to false.
  • boost-title: Boost factor for the search result score if a search word appears in the header. Defaults to 2.
  • boost-hierarchy: Boost factor for the search result score if a search word appears in the hierarchy. The hierarchy contains all titles of the parent documents and all parent headings. Defaults to 1.
  • boost-paragraph: Boost factor for the search result score if a search word appears in the text. Defaults to 1.
  • expand: True if search should match longer results e.g. search micro should match microwave. Defaults to true.
  • heading-split-level: Search results will link to a section of the document which contains the result. Documents are split into sections by headings this level or less. Defaults to 3. (### This is a level 3 heading)
  • copy-js: Copy JavaScript files for the search implementation to the output directory. Defaults to true.

This shows all available HTML output options in the book.toml:

[book]
title = "Example book"
authors = ["John Doe", "Jane Doe"]
description = "The example book covers examples."

[output.html]
theme = "my-theme"
default-theme = "light"
preferred-dark-theme = "navy"
curly-quotes = true
mathjax-support = false
copy-fonts = true
additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
site-url = "/example-book/"
cname = "myproject.rs"
input-404 = "not-found.md"

[output.html.print]
enable = true

[output.html.fold]
enable = false
level = 0

[output.html.playground]
editable = false
copy-js = true
line-numbers = false

[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
copy-js = true

[output.html.redirect]
"/appendices/bibliography.html" = "https://rustc-dev-guide.rust-lang.org/appendix/bibliography.html"
"/other-installation-methods.html" = "../infra/other-installation-methods.html"

Markdown Renderer

The Markdown renderer will run preprocessors and then output the resulting Markdown. This is mostly useful for debugging preprocessors, especially in conjunction with mdbook test to see the Markdown that mdbook is passing to rustdoc.

The Markdown renderer is included with mdbook but disabled by default. Enable it by adding an empty table to your book.toml as follows:

[output.markdown]

There are no configuration options for the Markdown renderer at this time; only whether it is enabled or disabled.

See the preprocessors documentation for how to specify which preprocessors should run before the Markdown renderer.

Custom Renderers

A custom renderer can be enabled by adding a [output.foo] table to your book.toml. Similar to preprocessors this will instruct mdbook to pass a representation of the book to mdbook-foo for rendering. See the alternative backends chapter for more detail.

The custom renderer has access to all the fields within its table (i.e. anything under [output.foo]). mdBook checks for two common fields:

  • command: The command to execute for this custom renderer. Defaults to the name of the renderer with the mdbook- prefix (such as mdbook-foo).
  • optional: If true, then the command will be ignored if it is not installed, otherwise mdBook will fail with an error. Defaults to false.

Environment Variables

All configuration values can be overridden from the command line by setting the corresponding environment variable. Because many operating systems restrict environment variables to be alphanumeric characters or _, the configuration key needs to be formatted slightly differently to the normal foo.bar.baz form.

Variables starting with MDBOOK_ are used for configuration. The key is created by removing the MDBOOK_ prefix and turning the resulting string into kebab-case. Double underscores (__) separate nested keys, while a single underscore (_) is replaced with a dash (-).

For example:

  • MDBOOK_foo -> foo
  • MDBOOK_FOO -> foo
  • MDBOOK_FOO__BAR -> foo.bar
  • MDBOOK_FOO_BAR -> foo-bar
  • MDBOOK_FOO_bar__baz -> foo-bar.baz

So by setting the MDBOOK_BOOK__TITLE environment variable you can override the book's title without needing to touch your book.toml.

Note: To facilitate setting more complex config items, the value of an environment variable is first parsed as JSON, falling back to a string if the parse fails.

This means, if you so desired, you could override all book metadata when building the book with something like

$ export MDBOOK_BOOK="{'title': 'My Awesome Book', authors: ['Michael-F-Bryan']}"
$ mdbook build

The latter case may be useful in situations where mdbook is invoked from a script or CI, where it sometimes isn't possible to update the book.toml before building.

Theme

The default renderer uses a handlebars template to render your markdown files and comes with a default theme included in the mdBook binary.

The theme is totally customizable, you can selectively replace every file from the theme by your own by adding a theme directory next to src folder in your project root. Create a new file with the name of the file you want to override and now that file will be used instead of the default file.

Here are the files you can override:

  • index.hbs is the handlebars template.
  • head.hbs is appended to the HTML <head> section.
  • header.hbs content is appended on top of every book page.
  • css/ contains the CSS files for styling the book.
    • css/chrome.css is for UI elements.
    • css/general.css is the base styles.
    • css/print.css is the style for printer output.
    • css/variables.css contains variables used in other CSS files.
  • book.js is mostly used to add client side functionality, like hiding / un-hiding the sidebar, changing the theme, ...
  • highlight.js is the JavaScript that is used to highlight code snippets, you should not need to modify this.
  • highlight.css is the theme used for the code highlighting.
  • favicon.svg and favicon.png the favicon that will be used. The SVG version is used by newer browsers.

Generally, when you want to tweak the theme, you don't need to override all the files. If you only need changes in the stylesheet, there is no point in overriding all the other files. Because custom files take precedence over built-in ones, they will not get updated with new fixes / features.

Note: When you override a file, it is possible that you break some functionality. Therefore I recommend to use the file from the default theme as template and only add / modify what you need. You can copy the default theme into your source directory automatically by using mdbook init --theme and just remove the files you don't want to override.

If you completely replace all built-in themes, be sure to also set output.html.preferred-dark-theme in the config, which defaults to the built-in navy theme.

index.hbs

index.hbs is the handlebars template that is used to render the book. The markdown files are processed to html and then injected in that template.

If you want to change the layout or style of your book, chances are that you will have to modify this template a little bit. Here is what you need to know.

Data

A lot of data is exposed to the handlebars template with the "context". In the handlebars template you can access this information by using

{{name_of_property}}

Here is a list of the properties that are exposed:

  • language Language of the book in the form en, as specified in book.toml (if not specified, defaults to en). To use in <html lang="{{ language }}"> for example.

  • title Title used for the current page. This is identical to {{ chapter_title }} - {{ book_title }} unless book_title is not set in which case it just defaults to the chapter_title.

  • book_title Title of the book, as specified in book.toml

  • chapter_title Title of the current chapter, as listed in SUMMARY.md

  • path Relative path to the original markdown file from the source directory

  • content This is the rendered markdown.

  • path_to_root This is a path containing exclusively ../'s that points to the root of the book from the current file. Since the original directory structure is maintained, it is useful to prepend relative links with this path_to_root.

  • chapters Is an array of dictionaries of the form

    {"section": "1.2.1", "name": "name of this chapter", "path": "dir/markdown.md"}
    

    containing all the chapters of the book. It is used for example to construct the table of contents (sidebar).

Handlebars Helpers

In addition to the properties you can access, there are some handlebars helpers at your disposal.

1. toc

The toc helper is used like this

{{#toc}}{{/toc}}

and outputs something that looks like this, depending on the structure of your book

<ul class="chapter">
    <li><a href="link/to/file.html">Some chapter</a></li>
    <li>
        <ul class="section">
            <li><a href="link/to/other_file.html">Some other Chapter</a></li>
        </ul>
    </li>
</ul>

If you would like to make a toc with another structure, you have access to the chapters property containing all the data. The only limitation at the moment is that you would have to do it with JavaScript instead of with a handlebars helper.

<script>
var chapters = {{chapters}};
// Processing here
</script>

2. previous / next

The previous and next helpers expose a link and name property to the previous and next chapters.

They are used like this

{{#previous}}
    <a href="{{link}}" class="nav-chapters previous">
        <i class="fa fa-angle-left"></i>
    </a>
{{/previous}}

The inner html will only be rendered if the previous / next chapter exists. Of course the inner html can be changed to your liking.


If you would like other properties or helpers exposed, please create a new issue

Syntax Highlighting

mdBook uses Highlight.js with a custom theme for syntax highlighting.

Automatic language detection has been turned off, so you will probably want to specify the programming language you use like this:

```rust
fn main() {
    // Some code
}
```

Supported languages

These languages are supported by default, but you can add more by supplying your own highlight.js file:

  • apache
  • armasm
  • bash
  • c
  • coffeescript
  • cpp
  • csharp
  • css
  • d
  • diff
  • go
  • handlebars
  • haskell
  • http
  • ini
  • java
  • javascript
  • json
  • julia
  • kotlin
  • less
  • lua
  • makefile
  • markdown
  • nginx
  • objectivec
  • perl
  • php
  • plaintext
  • properties
  • python
  • r
  • ruby
  • rust
  • scala
  • scss
  • shell
  • sql
  • swift
  • typescript
  • x86asm
  • xml
  • yaml

Custom theme

Like the rest of the theme, the files used for syntax highlighting can be overridden with your own.

  • highlight.js normally you shouldn't have to overwrite this file, unless you want to use a more recent version.
  • highlight.css theme used by highlight.js for syntax highlighting.

If you want to use another theme for highlight.js download it from their website, or make it yourself, rename it to highlight.css and put it in the theme folder of your book.

Now your theme will be used instead of the default theme.

Hiding code lines

There is a feature in mdBook that lets you hide code lines by prepending them with a #.

# fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
# }

Will render as

fn main() {
    let x = 5;
    let y = 7;

    println!("{}", x + y);
}

At the moment, this only works for code examples that are annotated with rust. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the book.toml so that everyone can benefit from it.

Improve default theme

If you think the default theme doesn't look quite right for a specific language, or could be improved, feel free to submit a new issue explaining what you have in mind and I will take a look at it.

You could also create a pull-request with the proposed improvements.

Overall the theme should be light and sober, without too many flashy colors.

Editor

In addition to providing runnable code playgrounds, mdBook optionally allows them to be editable. In order to enable editable code blocks, the following needs to be added to the book.toml:

[output.html.playground]
editable = true

To make a specific block available for editing, the attribute editable needs to be added to it:

```rust,editable
fn main() {
    let number = 5;
    print!("{}", number);
}
```

The above will result in this editable playground:

fn main() {
    let number = 5;
    print!("{}", number);
}

Note the new Undo Changes button in the editable playgrounds.

Customizing the Editor

By default, the editor is the Ace editor, but, if desired, the functionality may be overridden by providing a different folder:

[output.html.playground]
editable = true
editor = "/path/to/editor"

Note that for the editor changes to function correctly, the book.js inside of the theme folder will need to be overridden as it has some couplings with the default Ace editor.

MathJax Support

mdBook has optional support for math equations through MathJax.

To enable MathJax, you need to add the mathjax-support key to your book.toml under the output.html section.

[output.html]
mathjax-support = true

Note: The usual delimiters MathJax uses are not yet supported. You can't currently use $$ ... $$ as delimiters and the \[ ... \] delimiters need an extra backslash to work. Hopefully this limitation will be lifted soon.

Note: When you use double backslashes in MathJax blocks (for example in commands such as \begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}) you need to add two extra backslashes (e.g., \begin{cases} \frac 1 2 \\\\ \frac 3 4 \end{cases}).

Inline equations

Inline equations are delimited by \\( and \\). So for example, to render the following inline equation \( \int x dx = \frac{x^2}{2} + C \) you would write the following:

\\( \int x dx = \frac{x^2}{2} + C \\)

Block equations

Block equations are delimited by \\[ and \\]. To render the following equation

\[ \mu = \frac{1}{N} \sum_{i=0} x_i \]

you would write:

\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]

mdBook-specific features

Hiding code lines

There is a feature in mdBook that lets you hide code lines by prepending them with a # like you would with Rustdoc.

# fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
# }

Will render as

fn main() {
    let x = 5;
    let y = 6;

    println!("{}", x + y);
}

Including files

With the following syntax, you can include files into your book:

{{#include file.rs}}

The path to the file has to be relative from the current source file.

mdBook will interpret included files as Markdown. Since the include command is usually used for inserting code snippets and examples, you will often wrap the command with ``` to display the file contents without interpreting them.

```
{{#include file.rs}}
```

Including portions of a file

Often you only need a specific part of the file, e.g. relevant lines for an example. We support four different modes of partial includes:

{{#include file.rs:2}}
{{#include file.rs::10}}
{{#include file.rs:2:}}
{{#include file.rs:2:10}}

The first command only includes the second line from file file.rs. The second command includes all lines up to line 10, i.e. the lines from 11 till the end of the file are omitted. The third command includes all lines from line 2, i.e. the first line is omitted. The last command includes the excerpt of file.rs consisting of lines 2 to 10.

To avoid breaking your book when modifying included files, you can also include a specific section using anchors instead of line numbers. An anchor is a pair of matching lines. The line beginning an anchor must match the regex ANCHOR:\s*[\w_-]+ and similarly the ending line must match the regex ANCHOR_END:\s*[\w_-]+. This allows you to put anchors in any kind of commented line.

Consider the following file to include:

/* ANCHOR: all */

// ANCHOR: component
struct Paddle {
    hello: f32,
}
// ANCHOR_END: component

////////// ANCHOR: system
impl System for MySystem { ... }
////////// ANCHOR_END: system

/* ANCHOR_END: all */

Then in the book, all you have to do is:

Here is a component:
```rust,no_run,noplayground
{{#include file.rs:component}}
```

Here is a system:
```rust,no_run,noplayground
{{#include file.rs:system}}
```

This is the full file.
```rust,no_run,noplayground
{{#include file.rs:all}}
```

Lines containing anchor patterns inside the included anchor are ignored.

Including a file but initially hiding all except specified lines

The rustdoc_include helper is for including code from external Rust files that contain complete examples, but only initially showing particular lines specified with line numbers or anchors in the same way as with include.

The lines not in the line number range or between the anchors will still be included, but they will be prefaced with #. This way, a reader can expand the snippet to see the complete example, and Rustdoc will use the complete example when you run mdbook test.

For example, consider a file named file.rs that contains this Rust program:

fn main() {
    let x = add_one(2);
    assert_eq!(x, 3);
}

fn add_one(num: i32) -> i32 {
    num + 1
}

We can include a snippet that initially shows only line 2 by using this syntax:

To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:

```rust
{{#rustdoc_include file.rs:2}}
```

This would have the same effect as if we had manually inserted the code and hidden all but line 2 using #:

To call the `add_one` function, we pass it an `i32` and bind the returned value to `x`:

```rust
# fn main() {
    let x = add_one(2);
#     assert_eq!(x, 3);
# }
#
# fn add_one(num: i32) -> i32 {
#     num + 1
# }
```

That is, it looks like this (click the "expand" icon to see the rest of the file):

fn main() {
    let x = add_one(2);
    assert_eq!(x, 3);
}

fn add_one(num: i32) -> i32 {
    num + 1
}

Inserting runnable Rust files

With the following syntax, you can insert runnable Rust files into your book:

{{#playground file.rs}}

The path to the Rust file has to be relative from the current source file.

When play is clicked, the code snippet will be sent to the Rust Playground to be compiled and run. The result is sent back and displayed directly underneath the code.

Here is what a rendered code snippet looks like:

fn main() {
    println!("Hello World!");

    // You can even hide lines! :D
    println!("I am hidden! Expand the code snippet to see me");
}

Controlling page <title>

A chapter can set a <title> that is different from its entry in the table of contents (sidebar) by including a {{#title ...}} near the top of the page.

{{#title My Title}}

Markdown

mdBook's parser adheres to the CommonMark specification. You can take a quick tutorial, or try out CommonMark in real time. A complete Markdown overview is out of scope for this documentation, but below is a high level overview of some of the basics. For a more in-depth experience, check out the Markdown Guide.

Text and Paragraphs

Text is rendered relatively predictably:

Here is a line of text.

This is a new line.

Will look like you might expect:

Here is a line of text.

This is a new line.

Headings

Headings use the # marker and should be on a line by themselves. More # mean smaller headings:

### A heading 

Some text.

#### A smaller heading 

More text.

A heading

Some text.

A smaller heading

More text.

Lists

Lists can be unordered or ordered. Ordered lists will order automatically:

* milk
* eggs
* butter

1. carrots
1. celery
1. radishes
  • milk
  • eggs
  • butter
  1. carrots
  2. celery
  3. radishes

Linking to a URL or local file is easy:

Use [mdBook](https://github.com/rust-lang/mdBook). 

Read about [mdBook](mdBook.md).

A bare url: <https://www.rust-lang.org>.

Use mdBook.

Read about mdBook.

A bare url: https://www.rust-lang.org.

Images

Including images is simply a matter of including a link to them, much like in the Links section above. The following markdown includes the Rust logo SVG image found in the images directory at the same level as this file:

![The Rust Logo](images/rust-logo-blk.svg)

Produces the following HTML when built with mdBook:

<p><img src="images/rust-logo-blk.svg" alt="The Rust Logo" /></p>

Which, of course displays the image like so:

The Rust Logo

See the Markdown Guide Basic Syntax document for more.

Running mdbook in Continuous Integration

While the following examples use Travis CI, their principles should straightforwardly transfer to other continuous integration providers as well.

Ensuring Your Book Builds and Tests Pass

Here is a sample Travis CI .travis.yml configuration that ensures mdbook build and mdbook test run successfully. The key to fast CI turnaround times is caching mdbook installs, so that you aren't compiling mdbook on every CI run.

language: rust
sudo: false

cache:
  - cargo

rust:
  - stable

before_script:
  - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
  - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
  - cargo install-update -a

script:
  - mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook

Deploying Your Book to GitHub Pages

Following these instructions will result in your book being published to GitHub pages after a successful CI run on your repository's master branch.

First, create a new GitHub "Personal Access Token" with the "public_repo" permissions (or "repo" for private repositories). Go to your repository's Travis CI settings page and add an environment variable named GITHUB_TOKEN that is marked secure and not shown in the logs.

Whilst still in your repository's settings page, navigate to Options and change the Source on GitHub pages to gh-pages.

Then, append this snippet to your .travis.yml and update the path to the book directory:

deploy:
  provider: pages
  skip-cleanup: true
  github-token: $GITHUB_TOKEN
  local-dir: book # In case of custom book path: path/to/mybook/book
  keep-history: false
  on:
    branch: main

That's it!

Note: Travis has a new dplv2 configuration that is currently in beta. To use this new format, update your .travis.yml file to:

language: rust
os: linux
dist: xenial

cache:
  - cargo

rust:
  - stable

before_script:
  - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
  - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook)
  - cargo install-update -a

script:
  - mdbook build && mdbook test # In case of custom book path: mdbook build path/to/mybook && mdbook test path/to/mybook
  
deploy:
  provider: pages
  strategy: git
  edge: true
  cleanup: false
  github-token: $GITHUB_TOKEN
  local-dir: book # In case of custom book path: path/to/mybook/book
  keep-history: false
  on:
    branch: main
  target_branch: gh-pages

Deploying to GitHub Pages manually

If your CI doesn't support GitHub pages, or you're deploying somewhere else with integrations such as Github Pages: note: you may want to use different tmp dirs:

$> git worktree add /tmp/book gh-pages
$> mdbook build
$> rm -rf /tmp/book/* # this won't delete the .git directory
$> cp -rp book/* /tmp/book/
$> cd /tmp/book
$> git add -A
$> git commit 'new book message'
$> git push origin gh-pages
$> cd -

Or put this into a Makefile rule:

.PHONY: deploy
deploy: book
	@echo "====> deploying to github"
	git worktree add /tmp/book gh-pages
	rm -rf /tmp/book/*
	cp -rp book/* /tmp/book/
	cd /tmp/book && \
		git add -A && \
		git commit -m "deployed on $(shell date) by ${USER}" && \
		git push origin gh-pages

Deploying Your Book to GitLab Pages

Inside your repository's project root, create a file named .gitlab-ci.yml with the following contents:

stages:
    - deploy

pages:
  stage: deploy
  image: rust
  variables:
    CARGO_HOME: $CI_PROJECT_DIR/cargo
  before_script:
    - export PATH="$PATH:$CARGO_HOME/bin"
    - mdbook --version || cargo install mdbook
  script:
    - mdbook build -d public
  rules:
    - if: '$CI_COMMIT_REF_NAME == "master"'
  artifacts:
    paths:
      - public
  cache:
    paths:
      - $CARGO_HOME/bin

After you commit and push this new file, GitLab CI will run and your book will be available!

For Developers

While mdbook is mainly used as a command line tool, you can also import the underlying library directly and use that to manage a book. It also has a fairly flexible plugin mechanism, allowing you to create your own custom tooling and consumers (often referred to as backends) if you need to do some analysis of the book or render it in a different format.

The For Developers chapters are here to show you the more advanced usage of mdbook.

The two main ways a developer can hook into the book's build process is via,

The Build Process

The process of rendering a book project goes through several steps.

  1. Load the book
    • Parse the book.toml, falling back to the default Config if it doesn't exist
    • Load the book chapters into memory
    • Discover which preprocessors/backends should be used
  2. Run the preprocessors
  3. Call each backend in turn

Using mdbook as a Library

The mdbook binary is just a wrapper around the mdbook crate, exposing its functionality as a command-line program. As such it is quite easy to create your own programs which use mdbook internally, adding your own functionality (e.g. a custom preprocessor) or tweaking the build process.

The easiest way to find out how to use the mdbook crate is by looking at the API Docs. The top level documentation explains how one would use the MDBook type to load and build a book, while the config module gives a good explanation on the configuration system.

Preprocessors

A preprocessor is simply a bit of code which gets run immediately after the book is loaded and before it gets rendered, allowing you to update and mutate the book. Possible use cases are:

  • Creating custom helpers like {{#include /path/to/file.md}}
  • Updating links so [some chapter](some_chapter.md) is automatically changed to [some chapter](some_chapter.html) for the HTML renderer
  • Substituting in latex-style expressions ($$ \frac{1}{3} $$) with their mathjax equivalents

Hooking Into MDBook

MDBook uses a fairly simple mechanism for discovering third party plugins. A new table is added to book.toml (e.g. preprocessor.foo for the foo preprocessor) and then mdbook will try to invoke the mdbook-foo program as part of the build process.

A preprocessor can be hard-coded to specify which backend(s) it should be run for with the preprocessor.foo.renderer key. For example, it doesn't make sense for MathJax to be used for non-HTML renderers.

[book]
title = "My Book"
authors = ["Michael-F-Bryan"]

[preprocessor.foo]
# The command can also be specified manually
command = "python3 /path/to/foo.py"
# Only run the `foo` preprocessor for the HTML and EPUB renderer
renderer = ["html", "epub"]

Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the preprocessor.foo.command key twice. The first time it runs the preprocessor to determine if it supports the given renderer. mdBook passes two arguments to the process: the first argument is the string supports and the second argument is the renderer name. The preprocessor should exit with a status code 0 if it supports the given renderer, or return a non-zero exit code if it does not.

If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin. The JSON consists of an array of [context, book] where context is the serialized object PreprocessorContext and book is a Book object containing the content of the book.

The preprocessor should return the JSON format of the Book object to stdout, with any modifications it wishes to perform.

The easiest way to get started is by creating your own implementation of the Preprocessor trait (e.g. in lib.rs) and then creating a shell binary which translates inputs to the correct Preprocessor method. For convenience, there is an example no-op preprocessor in the examples/ directory which can easily be adapted for other preprocessors.

Example no-op preprocessor
// nop-preprocessors.rs

use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::book::Book;
use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
use semver::{Version, VersionReq};
use std::io;
use std::process;

pub fn make_app() -> App<'static, 'static> {
    App::new("nop-preprocessor")
        .about("A mdbook preprocessor which does precisely nothing")
        .subcommand(
            SubCommand::with_name("supports")
                .arg(Arg::with_name("renderer").required(true))
                .about("Check whether a renderer is supported by this preprocessor"),
        )
}

fn main() {
    let matches = make_app().get_matches();

    // Users will want to construct their own preprocessor here
    let preprocessor = Nop::new();

    if let Some(sub_args) = matches.subcommand_matches("supports") {
        handle_supports(&preprocessor, sub_args);
    } else if let Err(e) = handle_preprocessing(&preprocessor) {
        eprintln!("{}", e);
        process::exit(1);
    }
}

fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
    let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;

    let book_version = Version::parse(&ctx.mdbook_version)?;
    let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;

    if !version_req.matches(&book_version) {
        eprintln!(
            "Warning: The {} plugin was built against version {} of mdbook, \
             but we're being called from version {}",
            pre.name(),
            mdbook::MDBOOK_VERSION,
            ctx.mdbook_version
        );
    }

    let processed_book = pre.run(&ctx, book)?;
    serde_json::to_writer(io::stdout(), &processed_book)?;

    Ok(())
}

fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
    let renderer = sub_args.value_of("renderer").expect("Required argument");
    let supported = pre.supports_renderer(renderer);

    // Signal whether the renderer is supported by exiting with 1 or 0.
    if supported {
        process::exit(0);
    } else {
        process::exit(1);
    }
}

/// The actual implementation of the `Nop` preprocessor. This would usually go
/// in your main `lib.rs` file.
mod nop_lib {
    use super::*;

    /// A no-op preprocessor.
    pub struct Nop;

    impl Nop {
        pub fn new() -> Nop {
            Nop
        }
    }

    impl Preprocessor for Nop {
        fn name(&self) -> &str {
            "nop-preprocessor"
        }

        fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
            // In testing we want to tell the preprocessor to blow up by setting a
            // particular config value
            if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
                if nop_cfg.contains_key("blow-up") {
                    anyhow::bail!("Boom!!1!");
                }
            }

            // we *are* a no-op preprocessor after all
            Ok(book)
        }

        fn supports_renderer(&self, renderer: &str) -> bool {
            renderer != "not-supported"
        }
    }
}

Hints For Implementing A Preprocessor

By pulling in mdbook as a library, preprocessors can have access to the existing infrastructure for dealing with books.

For example, a custom preprocessor could use the CmdPreprocessor::parse_input() function to deserialize the JSON written to stdin. Then each chapter of the Book can be mutated in-place via Book::for_each_mut(), and then written to stdout with the serde_json crate.

Chapters can be accessed either directly (by recursively iterating over chapters) or via the Book::for_each_mut() convenience method.

The chapter.content is just a string which happens to be markdown. While it's entirely possible to use regular expressions or do a manual find & replace, you'll probably want to process the input into something more computer-friendly. The pulldown-cmark crate implements a production-quality event-based Markdown parser, with the pulldown-cmark-to-cmark allowing you to translate events back into markdown text.

The following code block shows how to remove all emphasis from markdown, without accidentally breaking the document.


#![allow(unused)]
fn main() {
fn remove_emphasis(
    num_removed_items: &mut usize,
    chapter: &mut Chapter,
) -> Result<String> {
    let mut buf = String::with_capacity(chapter.content.len());

    let events = Parser::new(&chapter.content).filter(|e| {
        let should_keep = match *e {
            Event::Start(Tag::Emphasis)
            | Event::Start(Tag::Strong)
            | Event::End(Tag::Emphasis)
            | Event::End(Tag::Strong) => false,
            _ => true,
        };
        if !should_keep {
            *num_removed_items += 1;
        }
        should_keep
    });

    cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
        Error::from(format!("Markdown serialization failed: {}", err))
    })
}
}

For everything else, have a look at the complete example.

Implementing a preprocessor with a different language

The fact that mdBook utilizes stdin and stdout to communicate with the preprocessors makes it easy to implement them in a language other than Rust. The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter. The example below follows the configuration shown above with preprocessor.foo.command actually pointing to a Python script.

import json
import sys


if __name__ == '__main__':
    if len(sys.argv) > 1: # we check if we received any argument
        if sys.argv[1] == "supports": 
            # then we are good to return an exit status code of 0, since the other argument will just be the renderer's name
            sys.exit(0)

    # load both the context and the book representations from stdin
    context, book = json.load(sys.stdin)
    # and now, we can just modify the content of the first chapter
    book['sections'][0]['Chapter']['content'] = '# Hello'
    # we are done with the book's modification, we can just print it to stdout, 
    print(json.dumps(book))

Alternative Backends

A "backend" is simply a program which mdbook will invoke during the book rendering process. This program is passed a JSON representation of the book and configuration information via stdin. Once the backend receives this information it is free to do whatever it wants.

There are already several alternative backends on GitHub which can be used as a rough example of how this is accomplished in practice.

  • mdbook-linkcheck - a simple program for verifying the book doesn't contain any broken links
  • mdbook-epub - an EPUB renderer
  • mdbook-test - a program to run the book's contents through rust-skeptic to verify everything compiles and runs correctly (similar to rustdoc --test)
  • mdbook-man - generate manual pages from the book

This page will step you through creating your own alternative backend in the form of a simple word counting program. Although it will be written in Rust, there's no reason why it couldn't be accomplished using something like Python or Ruby.

Setting Up

First you'll want to create a new binary program and add mdbook as a dependency.

cargo new --bin mdbook-wordcount
cd mdbook-wordcount
cargo add mdbook

When our mdbook-wordcount plugin is invoked, mdbook will send it a JSON version of RenderContext via our plugin's stdin. For convenience, there's a RenderContext::from_json() constructor which will load a RenderContext.

This is all the boilerplate necessary for our backend to load the book.

// src/main.rs
extern crate mdbook;

use std::io;
use mdbook::renderer::RenderContext;

fn main() {
    let mut stdin = io::stdin();
    let ctx = RenderContext::from_json(&mut stdin).unwrap();
}

Note: The RenderContext contains a version field. This lets backends figure out whether they are compatible with the version of mdbook it's being called by. This version comes directly from the corresponding field in mdbook's Cargo.toml.

It is recommended that backends use the semver crate to inspect this field and emit a warning if there may be a compatibility issue.

Inspecting the Book

Now our backend has a copy of the book, lets count how many words are in each chapter!

Because the RenderContext contains a Book field (book), and a Book has the Book::iter() method for iterating over all items in a Book, this step turns out to be just as easy as the first.


fn main() {
    let mut stdin = io::stdin();
    let ctx = RenderContext::from_json(&mut stdin).unwrap();

    for item in ctx.book.iter() {
        if let BookItem::Chapter(ref ch) = *item {
            let num_words = count_words(ch);
            println!("{}: {}", ch.name, num_words);
        }
    }
}

fn count_words(ch: &Chapter) -> usize {
    ch.content.split_whitespace().count()
}

Enabling the Backend

Now we've got the basics running, we want to actually use it. First, install the program.

cargo install --path .

Then cd to the particular book you'd like to count the words of and update its book.toml file.

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

+ [output.html]

+ [output.wordcount]

When it loads a book into memory, mdbook will inspect your book.toml file to try and figure out which backends to use by looking for all output.* tables. If none are provided it'll fall back to using the default HTML renderer.

Notably, this means if you want to add your own custom backend you'll also need to make sure to add the HTML backend, even if its table just stays empty.

Now you just need to build your book like normal, and everything should Just Work.

$ mdbook build
...
2018-01-16 07:31:15 [INFO] (mdbook::renderer): Invoking the "mdbook-wordcount" renderer
mdBook: 126
Command Line Tool: 224
init: 283
build: 145
watch: 146
serve: 292
test: 139
Format: 30
SUMMARY.md: 259
Configuration: 784
Theme: 304
index.hbs: 447
Syntax highlighting: 314
MathJax Support: 153
Rust code specific features: 148
For Developers: 788
Alternative Backends: 710
Contributors: 85

The reason we didn't need to specify the full name/path of our wordcount backend is because mdbook will try to infer the program's name via convention. The executable for the foo backend is typically called mdbook-foo, with an associated [output.foo] entry in the book.toml. To explicitly tell mdbook what command to invoke (it may require command-line arguments or be an interpreted script), you can use the command field.

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

  [output.html]

  [output.wordcount]
+ command = "python /path/to/wordcount.py"

Configuration

Now imagine you don't want to count the number of words on a particular chapter (it might be generated text/code, etc). The canonical way to do this is via the usual book.toml configuration file by adding items to your [output.foo] table.

The Config can be treated roughly as a nested hashmap which lets you call methods like get() to access the config's contents, with a get_deserialized() convenience method for retrieving a value and automatically deserializing to some arbitrary type T.

To implement this, we'll create our own serializable WordcountConfig struct which will encapsulate all configuration for this backend.

First add serde and serde_derive to your Cargo.toml,

cargo add serde serde_derive

And then you can create the config struct,


#![allow(unused)]
fn main() {
extern crate serde;
#[macro_use]
extern crate serde_derive;

...

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct WordcountConfig {
  pub ignores: Vec<String>,
}
}

Now we just need to deserialize the WordcountConfig from our RenderContext and then add a check to make sure we skip ignored chapters.

  fn main() {
      let mut stdin = io::stdin();
      let ctx = RenderContext::from_json(&mut stdin).unwrap();
+     let cfg: WordcountConfig = ctx.config
+         .get_deserialized("output.wordcount")
+         .unwrap_or_default();

      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
+             if cfg.ignores.contains(&ch.name) {
+                 continue;
+             }
+
              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
          }
      }
  }

Output and Signalling Failure

While it's nice to print word counts to the terminal when a book is built, it might also be a good idea to output them to a file somewhere. mdbook tells a backend where it should place any generated output via the destination field in RenderContext.

+ use std::fs::{self, File};
+ use std::io::{self, Write};
- use std::io;
  use mdbook::renderer::RenderContext;
  use mdbook::book::{BookItem, Chapter};

  fn main() {
    ...

+     let _ = fs::create_dir_all(&ctx.destination);
+     let mut f = File::create(ctx.destination.join("wordcounts.txt")).unwrap();
+
      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
              ...

              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
+             writeln!(f, "{}: {}", ch.name, num_words).unwrap();
          }
      }
  }

Note: There is no guarantee that the destination directory exists or is empty (mdbook may leave the previous contents to let backends do caching), so it's always a good idea to create it with fs::create_dir_all().

If the destination directory already exists, don't assume it will be empty. To allow backends to cache the results from previous runs, mdbook may leave old content in the directory.

There's always the possibility that an error will occur while processing a book (just look at all the unwrap()'s we've written already), so mdbook will interpret a non-zero exit code as a rendering failure.

For example, if we wanted to make sure all chapters have an even number of words, erroring out if an odd number is encountered, then you may do something like this:

+ use std::process;
  ...

  fn main() {
      ...

      for item in ctx.book.iter() {
          if let BookItem::Chapter(ref ch) = *item {
              ...

              let num_words = count_words(ch);
              println!("{}: {}", ch.name, num_words);
              writeln!(f, "{}: {}", ch.name, num_words).unwrap();

+             if cfg.deny_odds && num_words % 2 == 1 {
+               eprintln!("{} has an odd number of words!", ch.name);
+               process::exit(1);
              }
          }
      }
  }

  #[derive(Debug, Default, Serialize, Deserialize)]
  #[serde(default, rename_all = "kebab-case")]
  pub struct WordcountConfig {
      pub ignores: Vec<String>,
+     pub deny_odds: bool,
  }

Now, if we reinstall the backend and build a book,

$ cargo install --path . --force
$ mdbook build /path/to/book
...
2018-01-16 21:21:39 [INFO] (mdbook::renderer): Invoking the "wordcount" renderer
mdBook: 126
Command Line Tool: 224
init: 283
init has an odd number of words!
2018-01-16 21:21:39 [ERROR] (mdbook::renderer): Renderer exited with non-zero return code.
2018-01-16 21:21:39 [ERROR] (mdbook::utils): Error: Rendering failed
2018-01-16 21:21:39 [ERROR] (mdbook::utils):    Caused By: The "mdbook-wordcount" renderer failed

As you've probably already noticed, output from the plugin's subprocess is immediately passed through to the user. It is encouraged for plugins to follow the "rule of silence" and only generate output when necessary (e.g. an error in generation or a warning).

All environment variables are passed through to the backend, allowing you to use the usual RUST_LOG to control logging verbosity.

Handling missing backends

If you enable a backend that isn't installed, the default behavior is to throw an error:

The command `mdbook-wordcount` wasn't found, is the "wordcount" backend installed?
If you want to ignore this error when the "wordcount" backend is not installed,
set `optional = true` in the `[output.wordcount]` section of the book.toml configuration file.

This behavior can be changed by marking the backend as optional.

  [book]
  title = "mdBook Documentation"
  description = "Create book from markdown files. Like Gitbook but implemented in Rust"
  authors = ["Mathieu David", "Michael-F-Bryan"]

  [output.html]

  [output.wordcount]
  command = "python /path/to/wordcount.py"
+ optional = true

This demotes the error to a warning, and it will instead look like this:

The command was not found, but was marked as optional.
    Command: wordcount

Wrapping Up

Although contrived, hopefully this example was enough to show how you'd create an alternative backend for mdbook. If you feel it's missing something, don't hesitate to create an issue in the issue tracker so we can improve the user guide.

The existing backends mentioned towards the start of this chapter should serve as a good example of how it's done in real life, so feel free to skim through the source code or ask questions.

Contributors

Here is a list of the contributors who have helped improving mdBook. Big shout-out to them!

If you feel you're missing from this list, feel free to add yourself in a PR.