メインコンテンツまでスキップ

mdBookでFront Matterを処理する

· 約6分
mebiusbox
engineer

mdBookでMarkdownファイルに含まれているFront Matterを処理するスクリプトの作成について解説します.

mdBookではMarkdownファイルをレンダリングする前にプリプロセス(事前処理)する仕組みがあります. これはプリプロセッサが行います.プリプロセッサは標準入力からデータを受け取って加工し、それを標準出力に出力します. ここで、実行ファイルがpythonなどであればスクリプトで処理することもできます. ここでは、Markdownファイルに含まれるFrontMatterを処理するプリプロセッサをPythonプログラムで作成します.

Front Matter

Front Matterはファイルの先頭に付加するメタデータです.通常YAML形式が使われます. たとえば、Docusaurusではブログ記事のソースファイルであるMarkdownファイルにFront Matterを入れてさまざまな設定や追加情報を入れることができます.しかし、mdBookでは標準だと処理されずにそのままレンダリングされます.

Markdownファイルにメタデータは残しつつ、レンダリング時には表示してほしくありません. そこで、プリプロセッサを使います. mdBookのプラグインを探してみるとFront Matterを処理するものがいくつか見つかりますが、実際に使ってみるとFront Matter以外も削除したりと意図した挙動になってませんでした.なので、自分でプリプロセッサを作ってみることにします.ここでは、Pythonを使ってプログラムを作成します.

プリプロセッサ

プリプロセッサをPythonで作成する方法と使用方法はmdBookの公式ドキュメントに書かれています:

前述したとおり、標準入力からデータが入ってきます.データはJSON形式です.データを変更したあとに標準出力に渡します.公式には次の単純なプログラムが紹介されています.

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

これをベースに機能を作成します.今回作成するのはFront Matter部分のカットと、Front Matterに含まれているTitleを見出しとして出力する機能です. 作成したプリプロセッサ mdbook-frontmatter.py は次のとおりです.

mdbook-frontmatter.py
import json
import re
import sys

def process(content):
pattern = re.compile(r"^---(?P<frontmatters>.*?)---\n*", re.DOTALL)
result = pattern.search(content)
if result:
frontmatters = result.group("frontmatters")
title = ""
result = re.search(r"^title:\s*(?P<title>.*?)$", frontmatters, re.MULTILINE)
if result:
title = "# {}\n\n".format(result.group("title").strip('"'))
content = title + pattern.sub("", content)
return content


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)
for section in book["sections"]:
if "Chapter" in section:
section["Chapter"]["content"] = process(section["Chapter"]["content"])
print(json.dumps(book))

すべてのデータがまとめて1つのJSONデータになって渡されます. mdBookではチャプター情報をSUMMARY.mdで記述しますが、そのチャプターごとに book["sections"] に格納されているようです. たとえば、SUMMARY.mdが次のような内容だった場合:

# Summary

- [Chapter 1](./chapter_1.md)
- [Chapter 2](./chapter_2.md)

渡されるJSONデータは次のようなデータが渡されていました:

{
"sections": [
{
"Chapter": {
"name": "Chapter 1",
"content": "---\\ntitle: Chapter 1\\ndescription: Description Here.\\ntags: []\\n---\\n\\n## Chapter 1\\n",
"number": [
1
],
"sub_items": [],
"path": "chapter_1.md",
"source_path": "chapter_1.md",
"parent_names": []
}
},
{
"Chapter": {
"name": "Chapter 2",
"content": "# chapter_2\\n",
"number": [
2
],
"sub_items": [],
"path": "chapter_2.md",
"source_path": "chapter_2.md",
"parent_names": []
}
}
]
}

セクションからChapterキーのデータを参照できます.ChapterデータのcontentがMarkdownファイルの中身のようですので、それをprocess関数に渡して処理し、処理した後のデータを返しています.

process関数の中では正規表現を使ってFront Matterを抽出しています.また、Front Matterの中にTitleがあれば、取り出して見出し(H1)として出力しています. やっていることはとても単純なものです.

あとは、mdBookにプロセッサを設定します.作成した mdbook-frontmatter.py を適当な場所において、mdbook.tomlファイルに次のように記述を追加します.

mdbook.toml
[preprocessor.frontmatter]
command = "py mdbook-frontmatter.py"

Windowsを使っているのでpyとしています.これはpythonなどに書き換えてください.また、Pythonプログラムも必要であれば絶対パスで指定します. これで、buildserveをすれば自動でプリプロセッサが動作します.

以上です.