Preprocessors

January 11, 2022 · View on GitHub

一个 预处理器 只是一些代码,运行在加载书之后,和渲染之前,允许您更新和改变本书。可能的用例是:

  • 创建自定义帮助程序\{{#include /path/to/file.md}}
  • 用 latex 样式($$ \frac{1}{3} $$)的表达式代替为 mathjax 的等价物

配置 预处理器 获取更多信息。

勾住 MDBook

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

一旦预处理器定义了,和构建过程开始,mdBook 会执行preprocessor.foo.command命令两次。 第一次是,预处理器检测出是否支持所给予的渲染器。 mdBook 会传递两个参数: 第一个是 supports 和第二个参数是渲染器的名字。 支持的话,状态就是 0;不然退出代码为非零。

如果支持, 那么 mdbook 开启命令的第二次运行, 将 JSON 数据传递到 stdin。 The JSON 的组成是一个 [context, book] 数组 ( context 是序列化对象 PreprocessorContext) 和 book (是一个Book 包含书的内容)。

预处理器应该返回 Book 对象的 JSON 格式到 stdout,带有它对内容的修改。

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

Example 无操作预处理器
// nop-preprocessors.rs

{{#include ../../examples/nop-preprocessor.rs}}

实现一个预处理器的提示

通过拉取mdbook,作为一个库,预处理器可以访问现有的基础架构来处理书籍.

例如,自定义预处理器可以使用CmdPreprocessor::parse_input()函数, 用于反序列化写入stdin的 JSON。然后是Book的每一章可以通过Book::for_each_mut()成为可变权限,然后随着serde_json箱写到stdout.

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

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

以下代码块,显示了如何从 markdown 中删除所有强调(粗体),而不会意外地破坏文档.

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))
    })
}

对于其他的一切,看完整的例子.

Implementing a preprocessor with a different language

stdin 与 stdout 是预处理器的交流频道,这种方式让其他语言也能参与进来。比如 Python,下面是修改第一章内容的示例: preprocessor.foo.command 指向的实际命令。

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))