## 背景
[[Awesome Nav for MkDocs]]は[[MkDocs]]の[[ナビゲーション (MkDocs)|ナビゲーション]]を[[YAML]]で定義することができるプラグイン。
`.nav.yml`
```yaml
nav:
- hoge.md
- dir:
- huga.md
- エイリアス: title.md
```
ただ、[[Obsidian]]で管理する場合は[[Markdown]]の[[内部リンク]]箇条書きで表現できると嬉しいことがある。
`nav.md`
```markdown
nav:
- [[hoge]]
- dir:
- [[huga]]
- [[title|エイリアス]]
```
具体的なメリット。
- デッドリンクにすぐ気付ける
- [[MkDocs]]のビルドよりも早く気付ける
- 参照しているノートのタイトルが変わったときに変更が不要
- 自動で連動更新されるから
- [[Obsidian]]からもナビゲーションメニューを見て関連ノートにジャンプしやすい
- [[🦉Another Quick Switcher]] の [[Outgoing link search]] が便利
- [[Live Preview]]での表示が直感的
## 環境
| 対象 | バージョン |
| -------------------------- | ----- |
| [[MkDocs]] | 1.6.1 |
| [[Awesome Nav for MkDocs]] | 3.2.0 |
| [[mkdocs-gen-files]] | 0.5.0 |
## やってみる (非推奨な方法)
[[Awesome Nav for MkDocs]]では対応していないため、[[hooks (MkDocs)|hooks]]を使って以下の処理を実装する。
1. `nav.md` を読み込む
2. 1を[[Awesome Nav for MkDocs]]が対応している[[YAML]]に変換する
3. 2を `.nav.yml` として吐き出す
4. [[Awesome Nav for MkDocs]] に `.nav.yml` を処理させる
`mkdocs.yml` に以下を追加。
```yaml
hooks:
- markdown_nav.py
plugins:
- awesome-nav
```
`markdown_nav.py`
```python
import re
from pathlib import Path
WIKI_LINK = re.compile(r"\[\[([^\]]+)\]\]")
def transform_nav(text: str) -> str:
def repl(m: re.Match) -> str:
inner = m.group(1)
target, alias = inner.split("|", 1) if "|" in inner else (inner, None)
path = f"{target}.md"
return f"{alias}: {path}" if alias else path
return WIKI_LINK.sub(repl, text)
def on_config(config):
docs_dir = Path(config["docs_dir"])
src = docs_dir / "nav.md"
dst_filename = ".nav.yml"
dst = docs_dir / dst_filename
dst_next_text = transform_nav(src.read_text())
dst_current_text = dst.read_text() if dst.exists() else ""
# WARN: 常に書き込むと無限にon_configが呼ばれる
if dst_next_text != dst_current_text:
dst.write_text(dst_next_text)
return config
```
これで `nav.md` を更新すると、[[Live Reloading (MkDocs)|Live Reloading]]の際に `.nav.yml` が生成される。
ただ、`.nav.yml` が生成されたときに再び[[Live Reloading (MkDocs)|Live Reloading]]が発生し、無限ループに陥るためif文でガードしている。
```python
# WARN: 常に書き込むと無限にon_configが呼ばれる
if dst_next_text != dst_current_text:
dst.write_text(dst_next_text)
```
## ビルドを1回にできないか?
[[mkdocs-macros-plugin]] などを使って少し時間のかかる処理をしているとき、毎回2回のビルドが発生することは望ましくない。対処方法がないかを調べる。
### hotreloadの対象から `.nav.yml` を外せないか?
[[MkDocs]]ではそういうのは認めてなさそう。主張は分からなくもないけど...。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
`server.watch()`: ignore files or directories in watch list ? · Issue #2247 · mkdocs/mkdocs
</div>
<div class="link-card-v2-content">
In the on_serve() method, I am having an unexpected functional issue with the server.watch(): in some cases wher ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/f8f3a51d04c862e543a5f64a4ebab4574b047ae8c316b33d0c34fbb2cac1e0c4/mkdocs/mkdocs/issues/2247" />
<a href="https://github.com/mkdocs/mkdocs/issues/2247"></a>
</div>
### 別アプローチの調査
似たような境遇の人を発見。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
Don't trigger rebuild for given files / directories that are being watched? · Issue #3433 · mkdocs/mkdocs
</div>
<div class="link-card-v2-content">
This is basically the same as #2247. However, that was closed with the argument being that mkdocs is not in cont ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/aea08f1fcdc3140943cb3bb21f078244db8f8b2e15e53ed46c2e74ff48c96b1d/mkdocs/mkdocs/issues/3433" />
<a href="https://github.com/mkdocs/mkdocs/issues/3433"></a>
</div>
[[mkdocs-gen-files]]を使って仮想的な `.nav.yml` を生成すればいけそうな気がする。[[Awesome Nav for MkDocs]]も対応してそう。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
Handle plugin generated content and config by thejcannon · Pull Request #79 · lukasgeiter/mkdocs-awesome-nav
</div>
<div class="link-card-v2-content">
This change allows mkdocs-awesome-pages-plugin to play nicely with mkdocs-gen-files, specifically with generated ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/23aee7d53423d7783dcae9670fece901b156148d1668d9d1146fa6c157bbdda2/lukasgeiter/mkdocs-awesome-nav/pull/79" />
<a href="https://github.com/lukasgeiter/mkdocs-awesome-nav/pull/79"></a>
</div>
## 仮想的な `.nav.yml` を生成するアプローチ
> [[ナビゲーションをMarkdownファイルで設定 (MkDocs)|ナビゲーションをMarkdownファイルで設定]]
```console
pip install mkdocs-gen-files
```
`mkdocs.yml` に追加。
```yaml
plugins:
- gen-files:
scripts:
- gen_nav.py
```
`gen_nav.py` を作成。
```python
import re
from pathlib import Path
import mkdocs_gen_files
WIKI_LINK = re.compile(r"\[\[([^\]]+)\]\]")
def transform_nav(text: str) -> str:
def repl(m: re.Match) -> str:
inner = m.group(1)
target, alias = inner.split("|", 1) if "|" in inner else (inner, None)
path = f"{target}.md"
return f"{alias}: {path}" if alias else path
return WIKI_LINK.sub(repl, text)
def main():
src = Path(mkdocs_gen_files.config["docs_dir"]) / "nav.md"
dst_filename = ".nav.yml"
with mkdocs_gen_files.open(dst_filename, "w") as f:
print(transform_nav(src.read_text()), file=f)
main()
```
これで `docs/nav.md` を変更すれば、変更がナビゲーションメニューに反映される。
## まとめ
[[#仮想的な `.nav.yml` を生成するアプローチ]] だけを実施すればOK。