5.1. 预处理程序
一个预处理器只是一些代码,运行在加载书之后,和渲染之前,允许您更新和改变本书。可能的用例是:
- 创建自定义帮助程序
{{#include /path/to/file.md}}
- 更新链接
[some chapter](some_chapter.md)
自动更改为[some chapter](some_chapter.html)
,这是 HTML 渲染器功能 - 用 latex 样式(
$$ \frac{1}{3} $$
)的表达式代替为 mathjax 的等价物
MDBook 使用一种相当简单的机制来发现第三方插件。book.toml
添加了一个新表格(例如preprocessor.foo
,给foo
预处理器),然后mdbook
将尝试调用mdbook-foo
程序,作为构建过程的一部分.
虽然预处理器可以进行硬编码,以指定应该运行哪个后端,来处理如preprocessor.foo.renderer
的字段(但奇奇怪怪的是,像 MathJax 用于非 HTML 渲染器没有意义).
[book]
title = "My Book"
authors = ["Michael-F-Bryan"]
[preprocessor.foo]
# 指定命令的使用
command = "python3 /path/to/foo.py"
# `foo` 预处理器 只被用于 HTML 和 EPUB 的渲染器
renderer = ["html", "epub"]
在典型的 unix 样式中,插件的所有输入都被写入stdin
作为 JSON,和mdbook
将从stdout
中读取,如果它是期待的输出.
最简单的入门方法是创建自己的实现Preprocessor
trait(例如在lib.rs
),然后创建一个 shell 二进制文件,将输入转换为正确的Preprocessor
方法。为方便起见,有个无操作预处理器:示例在examples/
目录,可以很容易地适应其他预处理器.
Example 无操作预处理器
// nop-preprocessors.rs extern crate clap; extern crate mdbook; extern crate serde_json; use clap::{App, Arg, ArgMatches, SubCommand}; use mdbook::book::Book; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use std::io; use std::process; use nop_lib::Nop; 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())?; if ctx.mdbook_version != mdbook::MDBOOK_VERSION { // We should probably use the `semver` crate to check compatibility // here... 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") { return Err("Boom!!1!".into()); } } // 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
的每一章可以通过Book::for_each_mut()
成为可变权限,然后随着serde_json
箱写到stdout
.
章节可以直接访问(通过递归迭代章节)或通过便利方法Book::for_each_mut()
.
chapter.content
只是一个恰好是 markdown 的字符串。虽然完全可以使用正则表达式或进行手动查找和替换,但您可能希望将输入处理为更加计算机友好的内容。该pulldown-cmark
crate 实现了一个基于事件,生产质量的 Markdown 解析器,而pulldown-cmark-to-cmark
允许您将事件转换回 markdown 文本.
以下代码块,显示了如何从 markdown 中删除所有强调(粗体),而不会意外地破坏文档.
# #![allow(unused_variables)] #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)) }) } #}
对于其他的一切,看完整的例子.