## 経緯
以下の手法で[[Obsidian]]を使って構築したドキュメントを[[MkDocs]]([[Material for MkDocs]])でデプロイできるようになる。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" />
<span class="link-card-v2-site-name">Minerva</span>
</div>
<div class="link-card-v2-title">
📘MkDocsでObsidianと互換性の高いドキュメントベースを実現する
</div>
<div class="link-card-v2-content">Obsidianやobsidian.nvimと高い互換性を持ち、Markdown・Python・YAML・ターミナル操作の知識がある方向けに、MkDocsとMaterial for MkDocsを使った無料・クローズドなドキュメントサイト構築方法を詳しく解説します。プラグイン設定やカスタマイズ手順も紹介しています。</div>
<img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/%F0%9F%93%98Articles/attachments/2025-02-23.webp" />
<a data-href="📘MkDocsでObsidianと互換性の高いドキュメントベースを実現する" class="internal-link"></a>
</div>
%%[[📘MkDocsでObsidianと互換性の高いドキュメントベースを実現する]]%%
しかし、見出しに関しては問題が発生する。
### 問題の詳細
- [[MkDocs]] ([[Material for MkDocs]])
- [[ASCII]]以外の見出し文字列がリンクに含まれない (無視される)
- たとえば `Obsidianはいいぞ` は `#obsidian` になる
- `Obsidian%E3%81%AF%E3%81%84%E3%81%84%E3%81%9D%E3%82%99` みたいになってほしい
- [[mkdocs-obsidian-bridge]]
- 見出しリンクが不正なリンクとみなされてしまう
- [[📜2025-02-22 mkdocs-obsidian-bridgeを使ってMkdocsでObsidian表記に対応してみた#不要なwarningを削除する]] の方法でもカバーできていない
### 環境
| 対象 | バージョン |
| -------------------------- | -------------- |
| [[macOS]] | 15.7 |
| [[Python]] | 3.13.7 |
| [[Obsidian]] | 1.9.12 |
| [[MkDocs]] | 1.6.1 |
| [[Material for MkDocs]] | 9.6.20 |
| [[mkdocs-obsidian-bridge]] | 1.2.0 (一部変更あり) |
## slugifyを使う
[[Material for MkDocs]]の公式ドキュメントにある `toc.slugify` オプションを利用する。
> `toc.slugify` This option allows for customization of the slug function. For some languages, the default may not produce good and readable identifiers – consider using another slug function like for example those from [Python Markdown Extensions](https://facelessuser.github.io/pymdown-extensions/extras/slugs/):
>
> *[Python Markdown - Material for MkDocs](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown/?h=toc#+toc.slugify)*
`Unicode, case-sensitive` タブの方のサンプルをそのまま使う。
```yaml
markdown_extensions:
- footnotes
- toc:
permalink: true
# 👇 これを追加
slugify: !!python/object/apply:pymdownx.slugs.slugify {}
- obsidian_callouts
- pymdownx.superfences
- pymdownx.magiclink
```
しかし、これではエラーになる。
```error
ERROR - Error reading page '4. Options/4.1. Main/⚙️ Strategy.md': _uslugify() got an unexpected keyword argument 'separator'
中略
File "/Users/tadashi-aikawa/git/github.com/tadashi-aikawa/docs-obsidian-various-complements-plugin/venv/lib/python3.13/site-packages/mkdocs_obsidian_bridge/plugin.py", line 307, in replace_obsidian_link
self.slugify(match['fragment'])
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/Users/tadashi-aikawa/git/github.com/tadashi-aikawa/docs-obsidian-various-complements-plugin/venv/lib/python3.13/site-packages/mkdocs_obsidian_bridge/plugin.py", line 102, in slugify
return f'#{self.toc_slugify(text)}'
~~~~~~~~~~~~~~~~^^^^^^
TypeError: _uslugify() got an unexpected keyword argument 'separator'
```
## エラーの原因調査
エラーは[[mkdocs-obsidian-bridge]]から発生しているので `plugin.py` を確認する。
```python
def on_config(self, config: MkDocsConfig) -> MkDocsConfig:
# mkdocs defaults
toc = {
'slugify': md_slugify,
'separator': '-'
}
# update from the config if changed by a user
toc |= config.mdx_configs.get('toc', dict())
self.toc_slugify = partial(toc['slugify'],
separator=toc['separator']
)
```
`on_config` の最後の部分。
```python
self.toc_slugify = partial(toc['slugify'],
separator=toc['separator']
)
```
[[partial (Python)|partial]]で `separator` が[[キーワード引数 (Python)|キーワード引数]]として[[部分適用]]されているが、それを呼び出した時に『そんなものはない』と怒られていそう。
```python
TypeError: _uslugify() got an unexpected keyword argument 'separator'
```
なお、`toc['slugify']` が参照している `md_slugify` は以下のようにimportしている。
```python
from markdown.extensions.toc import slugify_unicode as md_slugify
```
最終的には `slugify_unicode` であり、ここに `separator` は存在する。
```python
def slugify_unicode(value: str, separator: str) -> str: ...
```
この関数は[[Python Markdown]]のもの。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://python-markdown.github.io/favicon.ico" />
<span class="link-card-v2-site-name">python-markdown.github.io</span>
</div>
<div class="link-card-v2-title">
Table of Contents — Python-Markdown 3.9 documentation
</div>
<a href="https://python-markdown.github.io/extensions/toc/"></a>
</div>
### [[Python Markdown]]のコードを調べてみる
[markdown/markdown/extensions/toc.py at 5354daf618149f92580a1407c036115753c5df73 · Python-Markdown/markdown](https://github.com/Python-Markdown/markdown/blob/5354daf618149f92580a1407c036115753c5df73/markdown/extensions/toc.py#L48)
```python
def slugify_unicode(value: str, separator: str) -> str:
""" Slugify a string, to make it URL friendly while preserving Unicode characters. """
return slugify(value, separator, unicode=True)
```
たしかに定義はされてそう。ただ、そもそも `_uslugify` なんてものはリポジトリに存在しない。少なくとも[[Python Markdown]]はシロ。
### そもそも[[Python Markdown]]じゃないよね
設定の `toc.slugify` について
```yaml
markdown_extensions:
- toc:
slugify: !!python/object/apply:pymdownx.slugs.slugify {}
```
としている時点で `pymdownx.slugs.slugify` と書いてあるので、**[[Python Markdown]]ではなく[[PyMdown Extensions]]を使っている**ことは明らか。
一方、[[mkdocs-obsidian-bridge]]の `md_slugify` は [[Python Markdown]] を決め打ちで見ていることが根本的な問題。
```python
from markdown.extensions.toc import slugify_unicode as md_slugify
# 中略
def on_config(self, config: MkDocsConfig) -> MkDocsConfig:
# mkdocs defaults
toc = {
'slugify': md_slugify, # 🫲 `md_slugify` の参照先がおかしい
'separator': '-'
}
```
## 対策
### slugifyのモジュールを切り替えるべきか
1つの方法は `mkdocs.yml` で `markdown_extensions.toc.slugify` を変更すること。
<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">
Links with accentuated character · Issue #10 · GooRoo/mkdocs-obsidian-bridge
</div>
<div class="link-card-v2-content">
Thanks you for your fantastic work on this mkdocs plugin. I'm using it to make my doc which is bilingual, englis ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/6c77098008c9e6672071a8324c7e39ee0fff7632b72a266fb320edea17dd7901/GooRoo/mkdocs-obsidian-bridge/issues/10" />
<a href="https://github.com/GooRoo/mkdocs-obsidian-bridge/issues/10"></a>
</div>
先程の引用を見直してみると...
> `toc.slugify` This option allows for customization of the slug function. For some languages, the default may not produce good and readable identifiers – consider using another slug function like for example those from [Python Markdown Extensions](https://facelessuser.github.io/pymdown-extensions/extras/slugs/):
>
> *[Python Markdown - Material for MkDocs](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown/?h=toc#+toc.slugify)*
和訳してみる。
> [!left-bubble] ![[chappy.webp]]
> `toc.slugify`
> このオプションはスラッグ関数をカスタマイズするためのものです。
一部の言語ではデフォルトの挙動だと読みやすくない識別子になることがあるため、[Python Markdown Extensions](https://facelessuser.github.io/pymdown-extensions/extras/slugs/) などが提供する別のスラッグ関数の利用を検討してください。
やはり[[Python Markdown]]ではなく[[PyMdown Extensions]] (Python Markdown Extensions) を使ったほうが良さそう。
### [[mkdocs-obsidian-bridge]]を修正する
そもそも [[mkdocs-obsidian-bridge]] はforkしてるので、直接追加してしまったほうが早そう。
`mkdocs_obsidian_bridge/plugin.py`
```diff
- from markdown.extensions.toc import slugify_unicode as md_slugify
+ from pymdownx.slugs import slugify as md_slugify
```
しかし、エラーの内容は変わらない。どちらにしても `separator` が冗長なことに変わりはなさそう。
```error
TypeError: _uslugify() got an unexpected keyword argument 'separator'
```
`separator` の指定もなくす。
`mkdocs_obsidian_bridge/plugin.py`
```diff
self.toc_slugify = partial(
toc["slugify"],
- separator=toc["separator"]
)
```
エラーは変わったが、`sep` という引数は必須らしい。
```error
TypeError: _uslugify() missing 1 required positional argument: 'sep'
```
importしている `pymdownx/slugs.py` のコードを追ってみる。
```python
def slugify(**kwargs):
"""Configurable slugify."""
case = kwargs.get('case', 'none')
percent = kwargs.get('percent_encode', False)
normalize = kwargs.get('normalize', 'NFC')
return functools.partial(_uslugify, case=case, percent_encode=percent, normalize=normalize)
```
`_uslugify` を発見したので更に先へ。
```python
def _uslugify(text, sep, case="none", percent_encode=False, normalize='NFC'):
"""Unicode slugify (`utf-8`)."""
# 中略
```
たしかに `sep` だけは必須そう。それなら `separator` を `sep` にすればよい?
`mkdocs_obsidian_bridge/plugin.py`
```diff
self.toc_slugify = partial(
toc["slugify"],
- separator=toc["separator"]
+ sep=toc["separator"]
)
```
これで期待通りに動いた。
#### そもそもimportの変更は必要だったのか?
importを[[Python Markdown]]から[[PyMdown Extensions]]に変えても以下のエラーは変わらなかった。
```error
TypeError: _uslugify() got an unexpected keyword argument 'separator'
```
ということは、[[Python Markdown]]のimport文のままでもうまいこと動いていたのではないか?
元に戻してみる。
`mkdocs_obsidian_bridge/plugin.py`
```diff
- from pymdownx.slugs import slugify as md_slugify
+ from markdown.extensions.toc import slugify_unicode as md_slugify
```
これでも上手く動いた。よくコードを見てみたら、default値を入れた後に `mkdocs.yml` の `markdown_extensions.toc` の値を代入してそう...。
```python
# mkdocs defaults
toc = {"slugify": md_slugify, "separator": "-"}
# update from the config if changed by a user
toc |= config.mdx_configs.get("toc", dict())
```
つまり、`toc["slugify"]` の値は `mkdocs.yml` で指定した関数になるので最初から[[PyMdown Extensions]]を見ていたということか。。
## 他の回避策
[[mkdocs-obsidian-bridge]]のissueに解決方法が1つ示されていた。
<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">
Links with accentuated character · Issue #10 · GooRoo/mkdocs-obsidian-bridge
</div>
<div class="link-card-v2-content">
Thanks you for your fantastic work on this mkdocs plugin. I'm using it to make my doc which is bilingual, englis ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/6c77098008c9e6672071a8324c7e39ee0fff7632b72a266fb320edea17dd7901/GooRoo/mkdocs-obsidian-bridge/issues/10" />
<a href="https://github.com/GooRoo/mkdocs-obsidian-bridge/issues/10"></a>
</div>
以下のように設定すればいけたとのこと。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://facelessuser.github.io/pymdown-extensions/assets/images/favicon.png" />
<span class="link-card-v2-site-name">facelessuser.github.io</span>
</div>
<div class="link-card-v2-title">
Frequently Asked Questions - PyMdown Extensions Documentation
</div>
<div class="link-card-v2-content">
A Collection of Useful Extensions for Python Markdown
</div>
<a href="https://facelessuser.github.io/pymdown-extensions/faq/#function-references-in-yaml"></a>
</div>
```diff
markdown_extensions:
- - toc:
- permalink: true
- slugify: !!python/object/apply:pymdownx.slugs.slugify {}
+ - markdown.extensions.toc:
+ permalink: true
+ slugify: !!python/object/apply:pymdownx.slugs.slugify {}
```
ただし、この方法は **エイリアスを張った場合に、存在しない見出しへのリンクと判断されてwarningが出力されてしまった** ので厳しそう。[[エイリアス (Obsidian)|エイリアス]]を使わないのであれば、最も簡単な回避策。
## まとめ
以下の設定を追加すればやりたいことはできる。
`mkdocs.yml`
```yaml
markdown_extensions:
- toc:
slugify: !!python/object/apply:pymdownx.slugs.slugify {}
```
`mkdocs_obsidian_bridge/plugin.py`
```diff
self.toc_slugify = partial(
toc["slugify"],
- separator=toc["separator"]
+ sep=toc["separator"]
)
```
ちゃんとやるなら、条件分岐で引数を切り替えたりしたほうがいいけど、PR出す予定は今のところないのでこれで様子見する。日本語の見出しを使うなら、[[PyMdown Extensions]]のほうが良さそうなので、[[Python Markdown]]の方を使わないならこれで事足りるはず。
fork版にもコミット済。
<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">
feat: Add support pymdownx.slugs.slugify for markdown_extensions.toc … · tadashi-aikawa/mkdocs-obsidian-bridge@84cc938
</div>
<div class="link-card-v2-content">
…instead of markdown.extensions.toc.slugify_unicode
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/f64de21946cb2eeeaaf18a57952a387e95e7d98a457cbb1b9762f0360859f7b6/tadashi-aikawa/mkdocs-obsidian-bridge/commit/84cc9381faac94800a291fd9f236300da67d976e" />
<a href="https://github.com/tadashi-aikawa/mkdocs-obsidian-bridge/commit/84cc9381faac94800a291fd9f236300da67d976e"></a>
</div>