mdBook の Tips : 入力する Markdown ファイルを1つだけで済ませる

  • mdBook では、基本、章ごとに個別の Markdown ファイルを準備する必要がある
  • そして、SUMMARY.mdファイル内に、各ファイルへのリンクを Markdown 形式で書く
# Part I

- [Section A](SectionA.md)
	- [Subsection A-1](SectionA1.md)
	- [Subsection A-2](SectionA2.md)
- [Section B](SectionB.md)

# Part II

- [Section C](SectionC.md)
	- [Subsection C-1](SectionC1.md)
	- [Subsection C-2](SectionC2.md)
	- [Subsection C-3](SectionC3.md)

# Part III

- [Section D](SectionD.md)
	- [Subsection D-1](SectionD1.md)
  • 上記の場合、全体の構成を変えようとすると、ファイルを追加したり、名前を変えたり、いろいろと面倒そうだ
  • そこで、mdBook のプリプロセッサ機能を使って、1つの Markdown ファイルを読み込むだけで、構造化された本(ウェッブサイト)を構築できるようにした
    • 同様な機能をもつ Rust 製のプリプロセッサ( mdbook-split )が GitHub にあるので、これを使っても良さそう
    • 今回、フォーマットの方法をドキュメントごとに簡単に変えたかったので、適当に自作することにした

プリプロセッサ

  • mdBook のプリプロセッサは、入力した内容を構造化して保持する JSON 形式のデータを標準入力(stdin)で 受け取り、何らかの加工をして、標準出力(stdout)として返せばよい
  • だから、mdBook が実装されている Rust 以外の言語でも、とても簡単にプリプロセッサが作れる
  • 今回、標準入力から受け取った1つの Markdown ファイルを「見出し」ごとに分割して構造化し、結果を格納した JSON 形式のデータを標準出力する Python スクリプト(mdsplit.py)をざっくりと作った
import json
import sys


def add_part(current_part, json_structure):
  if current_part:
    json_structure["sections"].append(current_part)
    current_part = None
  return current_part


def add_subsection(current_subsection, current_section):
  if current_subsection:
    current_section["Chapter"]["sub_items"].append(current_subsection)
    current_section["Chapter"]["content"] += f"- [{current_subsection['Chapter']['name']}]({current_subsection['Chapter']['path']}.md)\n"
    current_subsection = None
  return current_subsection


def add_section(current_section, json_structure):
  if current_section:
    json_structure["sections"].append(current_section)
    current_section = None
  return current_section


def parse_markdown(content):
  lines = content.split("\n")
  json_structure = {"sections": [], "__non_exhaustive": None}
  current_part = None
  current_section = None
  current_subsection = None
  section_count = 0
  subsection_count = 0

  for line in lines:
    if line.startswith("# "):
      # New part
      current_part = {
        "PartTitle": line[2:],
      }
    elif line.startswith("## "):
      # New section
      section_count += 1
      subsection_count = 0
      current_subsection = add_subsection(current_subsection, current_section)
      current_section = add_section(current_section, json_structure)
      current_part = add_part(current_part, json_structure)
      current_section = {
        "Chapter": {
          "name": line[3:],
          "content": f"# {line[3:]}\n",
          "number": [section_count],
          "sub_items": [],
          "path": str(section_count),
          "parent_names": [],
        }
      }
    elif line.startswith("### "):
      # New subsection
      subsection_count += 1
      current_subsection = add_subsection(current_subsection, current_section)
      current_subsection = {
        "Chapter": {
          "name": line[4:],
          "content": f"# {line[4:]}\n",
          "number": [section_count, subsection_count],
          "sub_items": [],
          "parent_names": [current_section["Chapter"]["name"]],
          "path": f"{section_count}-{subsection_count}",
        }
      }
    else:
      # Content line
      if line.startswith("#### "):
        line = f"## {line[5:]}"
      if current_subsection is not None:
        current_subsection["Chapter"]["content"] += f"{line}\n"
      elif current_section is not None:
        current_section["Chapter"]["content"] += f"{line}\n"

  add_subsection(current_subsection, current_section)
  add_section(current_section, json_structure)

  return json_structure


if __name__ == "__main__":
  if len(sys.argv) > 1:
    if sys.argv[1] == "supports":
      sys.exit(0)

  context, book = json.load(sys.stdin)
  content = book["sections"][0]["Chapter"]["content"]
  splited_content = parse_markdown(content)
  print(json.dumps(splited_content))
  • 上記のスクリプトを mdsplit.py として保存する

使い方

  • book.toml にプリプロセッサ mdsplit.py の実行方法を追記する
[preprocessor.mdsplit]
command = "python mdsplit.py"
  • 読み込む Markdown ファイル(たとえば book.md)を SUMMARY.md に追記する
[](./book.md)
  • たとえば、book.mdの内容が、次のようなものだったとする
    • (ダミーで入れていたhogeの文字列は削除した)
# Part I

## Section A

### Subsection A-1

### Subsection A-2

## Section B

# Part II

## Section C

### Subsection C-1

#### Heading 1

#### Heading 2

### Subsection C-2

### Subsection C-3

# Part III

## Section D

### Subsection D-1
  • 上記の Markdown ファイルの内容が mdsplit.py に渡ると、見出し毎に分割した後、いろいろと整えたものを mdBook に返すことで、次のように構造化されたものができる

  • シンプルな Python スクリプトなので、ドキュメントごとに手軽に適宜修正ができそうで良いかなと
  • 1つの Markdown ファイルだけを編集すれば良くなったので、執筆作業に集中できるようになった
mdBook
Posted :