remarkdown を巡る冒険 (原題: remarkdown versus Sphinx)

docutils はある形式の文書を別の形式に変換するフレームワークです。
このフレームワークを利用して rst2html や rst2latex などが提供されています。

しかし、docutils が標準で対応している入力形式は reST 形式に限られているので、
実際には reST 変換用のライブラリとして捉えられていました。

markdown 黄金時代

docutils や Sphinx の周囲では reST を中心としてドキュメントが記述されていますが、
2014年現在、軽量マークアップ言語の主流の座は markdown であると言っても過言ではないでしょう。
flavor が乱立していたり、拡張性に乏しかったり、困ったら HTML で書き下してあったりと、
記述能力が十分とはいえない markdown ですが、github を初めてとして様々な場所で用いられています。
4月1日はすでに過ぎたというのに Internet Draft になっているほどの人気ぶりです。

Sphinx に興味を持った人も、Sphinx と markdown の組み合わせを実現するために様々な試行錯誤を繰り返しています。

remarkdown の到来

そんな流れの中、remarkdown という docutils 拡張が発見されます。
remarkdown は docutils の拡張として動作し、reST のかわりに markdown を docutils の入力に使うことができます。
docutils は html や latex, Open Document (.odt) などの出力に対応しているので、
組み合わせることで markdown から様々なフォーマットへ出力することができるようになります。

remarkdown のインストール

remarkdown は PyPI にパッケージがアップロードされていないため、簡単にインストールする方法がありません。
そのため、次の方法でインストールを行います。

まず、pip を用いて github のアーカイブ経由でパッケージをインストールします。

$ pip install https://github.com/sgenoud/remarkdown/archive/master.zip

なお、setup.py に不備があるので*1、ファイルをひとつ手動でダウンロードする必要があります(パスは各自適当な場所に読み替えてください)。

$ curl -LO https://raw.githubusercontent.com/sgenoud/remarkdown/master/remarkdown/markdown.parsley
$ mv markdown.parslay lib/python2.7/site-packages/remarkdown

remarkdown を使ってみる

remarkdown をインストールすると、一緒に md2html, md2latex, md2xml などのコマンドも一緒にインストールされます。
これらのスクリプトは markdown を入力として docutils を呼び出しています。

$ md2html test.md > test.html

Sphinx meets remarkdown

さて、ここからが本題です。
docutils と remarkdown の組み合わせができることを確認したところで、
今度は Sphinx と remarkdown を組み合わせてみることにします。

Sphinx を書き換える

Sphinx は docutils を利用して文書の変換を行っているのですが、
内部では reST 以外を想定していない作りになっています。

具体的には sphinx/environment.py の BuildEnvironment#read_doc() の中にこんな記述があります。

        pub = Publisher(reader=SphinxStandaloneReader(),
                        writer=SphinxDummyWriter(),
                        source_class=SphinxSourceClass,
                        destination_class=NullOutput)
        pub.set_components(None, 'restructuredtext', None)

Publisher のコンポーネントとして 'restructuredtext' を固定値で指定していますね。
入力フォーマット(パーサ)もここで決定しているので、markdown を入力として利用するには Sphinx を書き換える必要があります。

また、remarkdown は section ノードに必要な属性(ids, names)を正しくセットしていないので、
Sphinx を書き換える際についでに補正します。

Sphinx を書き換える、と言っても本体のソースコードを書き換えてしまうと、他の環境で使えなくなってしまうので、
conf.py の中から次のように書き換えを行います。

from docutils.core import Publisher


class MarkdownPublisher(Publisher):
    def __init__(self, *args, **kwargs):
        Publisher.__init__(self, *args, **kwargs)

        # replace parser FORCELY
        from remarkdown.parser import MarkdownParser
        self.reader.parser = MarkdownParser()

    def publish(self):
        Publisher.publish(self)

        # set names and ids attribute to section node
        from docutils import nodes
        for section in self.document.traverse(nodes.section):
            titlenode = section[0]
            name = nodes.fully_normalize_name(titlenode.astext())
            section['names'].append(name)
            self.document.note_implicit_target(section, section)


# replace Publisher
import sphinx.environment
sphinx.environment.Publisher = MarkdownPublisher

また、conf.py を編集する際、ソースの拡張子の設定もついでに書き換えておくとよいでしょう。

source_suffix = '.md'

Sphinx + remarkdown で文書を書く

さて、これで Sphinx と remarkdown を組み合わせて文章を書くことができるようになりました。

index.md に適当な markdown を入れて make html や make latexpdfja してみてください。
ちゃんと markdown を解釈して、HTML や PDF に変換してくれますね。

問題点

なんとかうまく動くようになったわけですが、まだいくつか問題があります。

  • remarkdown は github flavor を解釈しない
  • HTML を埋め込むとうまく解釈できない / 変換できない
  • toctree ディレクティブがないので、複数ファイルに分割できない

さて、どうしたらいいものやら…

まとめ

remarkdown を使って docutils を拡張したり、Sphinx を拡張したりしてみました。
実験する範囲ではそれっぽく動くものの、ちゃんとドキュメントをつくり上げることを考えると
乗り越え無くてはならない課題はまだいくつか残っているようです。

僕は markdown があまり好きではないのでここで筆を置きますが、
興味がある人は続きにトライしてみてはいかがでしょうか。

*1:記事で紹介している markdown.parsley が入らない問題の他、entry_points がうまく定義出来ていないおかげで setup.py install を実行しても md2* コマンド群がインストールされない問題などがあります。