各 index.html へのリンクが不親切

MkDocs は以前記事で触れた通りとても便利な Markdown で文書を生成するツールなのだが、MkDocs で複数の Markdown ドキュメントをリンクする形 ([xxx](yyy) 形式での .md ファイル間のリンク) にするとリンクが意図通り作成されない。 具体的には <a href="hoge/index.html"> のようになってほしいところが <a href="hoge/"> のように生成されてしまう。 生成された HTML ドキュメントを Web サーバ上に配置した場合は hoge/ 形式でも正しく表示されるのだが、ローカルに置いた形でブラウザを起動してみる (つまり file:///C:/Users/fuga/hoge/... のような形式の URL) だと例えば hoge/ を開くとその直下の index.html が暗黙的に呼び出されるのではなく hoge/ ディレクトリ以下のファイル一覧が表示されてしまう。

この挙動に関しては MkDocs でマテリアルデザインな Markdown ドキュメントサイトを作ろうにも以下のように書いてある:

Webサーバに載せることが前提
MkDocsで生成した静的サイト用ファイルは、ローカルで index.html を開く使い方は想定されていません。 開くことと表示することはできますが、リンクから他の記事に遷移することができません。 これはMarkdownファイルへのリンクが ./TheTitle/ のように張られており、ファイルプロトコル上では これが ./TheTitle/index.html と解釈されず、正しく表示できません。

理屈はわかるが、HTML ドキュメントを生成したからといって Web サーバを立てて使うとは限らない。 何とかローカルで見たい。

index.html を正しく補ってやる

というわけで対象となるリンクを正しく置き換える Python スクリプトを書いた:

import glob, os, re

# MkDocs がビルドした HTML が置かれているディレクトリ
SITE_PATH = 'C:/Users/fuga/hoge/site'

# ルート index.html と各フォルダに分かれている index.html を対象とする
files = glob.glob(os.path.join(SITE_PATH, 'index.html')) + glob.glob(os.path.join(SITE_PATH, '*', 'index.html'))

for file in files:
    # HTML 読込
    with open(file, 'r', encoding='utf-8') as fp:
        html = fp.read()

    # index.html を付与
    html2 = re.sub(r'href="(.*?)/"', 'href="\\1/index.html"', html)

    # . と .. というリンクもあるのでそれも index.html を付加する...
    html3 = html2.replace('href=".."', 'href="../index.html"').replace('href="."', 'html="./index.html"')

    # HTML 書込
    with open(file, 'w', encoding='utf-8') as fp2:
        fp2.write(html3)

これを適当な名前で保存 (例えば mkdocs_converter.py) し、ソース内の SITE_PATH を MkDocs の site ディレクトリに書き換え python3 mkdocs_converter のように実行すれば変換される。