Sphinxのドキュメント作成を便利にする「sphinx-quickstart-plus」を作りました

前記事：[Sphinxを便利にして、みんなに使ってもらいたい](http://qiita.com/pashango2/items/d1b379b699af85b529ce)

Sphinxは非常に便利なのですが、ドキュメント作成の手順が非常に煩雑でした。
そこで、Sphinxを拡張するモジュール「sphinx-quickstart-plus」を作りました。

https://github.com/pashango2/sphinx-qsp


# インストール方法

pipで簡単にインストールできます。
Sphinxモジュールに依存していますので、あらかじめSphinxをインストールしておいてください。

```
$ pip install sphinx-quickstart-plus
```

# 使い方
今まで、ドキュメント作成は`sphinx-quickstart`と打っていましたが、`sphinx-quickstart-plus`と打つだけです。

```
$ sphinx-quickstart-plus
```

すると、以下のようなメッセージが流れてきます。

```:出力
Welcome to the Sphinx 1.5.1 quickstart utility.

Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).

Enter the root path for documentation.
> Root path for the documentation [.]: document_path

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]: y

...
```

今までの`sphinx-quickstart`と全く同じ出力が出ます。
「何も変わってねーじゃん」と思われるかもしれませんが、もう少しお付き合いください。

```:出力
...
A Makefile and a Windows command file can be generated for you so that you
only have to run e.g. `make html' instead of invoking sphinx-build
directly.
> Create Makefile? (y/n) [y]: 
> Create Windows command file? (y/n) [y]: 

> ext_commonmark:use CommonMark and AutoStructify (y/n) [n]: y
> ext_nbshpinx:provides a source parser for *.ipynb files (y/n) [n]: y
> ext_blockdiag:Sphinx extension for embedding blockdiag diagrams (y/n) [n]: y
> ext_fontawesome:use font awesome (y/n) [n]: y
> ext_rtd_theme:use Read the Doc theme (y/n) [n]: y
> ext_autobuild:autobuild: Watch a directory and rebuild the documentation (y/n) [n]: y
```

最後に`sphinx-quickstart`になかった設問が出てきます。
今現在の拡張と説明は以下のとおりです。

| 拡張名 | 説明 |
|:-:|:--|
| ext_commonmark | `.rst`だけでなく`.md`(CommonMark)が使えるようになります |
| ext_nbshpinx | Jupyter Notebookが取り込めるようになります  |
| ext_blockdiag | ブロック図が書けるようになります  |
| ext_fontawesome | Font AwesomeがreSTから使えるようになります  |
| ext_rtd_theme |  Read the Docsのテーマを使えるようになります |
| ext_autobuild |  オートビルドが使えるようになります |

これらのSphinx拡張が`conf.py`を編集することなく使えるようになります。

それだけではありません、ふたたび`sphinx-quickstart-plus`を起動すると、冒頭の設問が変わります。

```:出力
> Use latest setting? (y/n) [y]: y
```

'y'とタイプすると、`Path`、`Project name`、`Author`、`Version`、`Release`の5項目を答えるだけで、前回のドキュメントと同じ設定のドキュメントが作成されます。
前回の`sphinx-quickstart-plus`で作成したドキュメント設定を記憶しています。

ちなみに'n'とタイプした場合は、通常の`sphinx-quickstart`の設問が流れますが、デフォルト設定が最後の設定になっていますので、変更箇所以外は`Enter`キーの連打でよいので楽ちんです。

```
$ sphinx-quickstart-plus

> Use latest setting? (y/n) [y]: n

...

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [y]: <-デフォルトが最後の入力になってる

```

## 各拡張選択時に`conf.py`に追加されるコード
### ext_commonmark

```python:conf.py
# ----- CommonMark
source_suffix = [source_suffix, '.md']

from recommonmark.parser import CommonMarkParser
source_parsers = {
    '.md': CommonMarkParser,
}

from recommonmark.transform import AutoStructify

github_doc_root = 'https://github.com/rtfd/recommonmark/tree/master/doc/'
def setup(app):
    app.add_config_value('recommonmark_config', {
            'url_resolver': lambda url: github_doc_root + url,
            'auto_toc_tree_section': 'Contents',
            }, True)
    app.add_transform(AutoStructify)

```

### ext_nbshpinx

```python:conf.py
# ----- Jupyter Notebook nbsphinx
extensions.append('nbsphinx')
exclude_patterns.append('**.ipynb_checkpoints')
```

### ext_blockdiag
```python:conf.py
# ----- blockdiag settings
extensions.extend([
    'sphinxcontrib.blockdiag',
    'sphinxcontrib.seqdiag',
    'sphinxcontrib.actdiag',
    'sphinxcontrib.nwdiag',
    'sphinxcontrib.rackdiag',
    'sphinxcontrib.packetdiag',
])
blockdiag_html_image_format = 'SVG'
seqdiag_html_image_format = 'SVG'
actdiag_html_image_format = 'SVG'
nwdiag_html_image_format = 'SVG'
rackiag_html_image_format = 'SVG'
packetdiag_html_image_format = 'SVG'
```

### ext_fontawesome
```python:conf.py
# ----- sphinx-fontawesome
import sphinx_fontawesome
extensions.append('sphinx_fontawesome')
```

### ext_rtd_theme
```python:conf.py
# ----- Read the Docs Theme
html_theme = "sphinx_rtd_theme"
```
### ext_autobuild

auto_buildは`conf.py`を書き換えませんが、Makefileを書き換えます。

```
$ make livehtml
```

と打つと、オートビルドが始まります。
Windowsの人は`auto_build.bat`が生成されるので、それを実行してください。

```
$ auto_build.bat
```

PyCharmなどの`.md`を編集していると一時ファイルが作成されてしまい、それもビルドしてしまうので、いくつかの一時ファイルをビルド無視する設定も含まれています。


# ライセンスについて

「sphinx-quickstart-plus」はライセンスフリーです、ご自由にお使いください。
なお、このモジュールを使っていかなる不利益が生じても責任は負いませんので、ご理解ください。

https://github.com/pashango2/sphinx-qsp

また、私の環境はUbuntuなのでWindowsとMacの環境ではテストできていません。
バグなどはあるかと思います。
問題がありましたら、GitHubのイシューかコメント欄におねがいします。

# コード解説

このモジュール本体はコメントを合わせて400行もない小さなモジュールです、作成期間も6時間程度で殆どがデバッグ作業です。
ですが、`sphinx-quickstart`の豊富な引数や機能をフルサポートしています。

どうしてこんな事ができるかというと、**モンキーパッチ**という技術を使っているからです。

## モンキーパッチとは？

モンキーパッチとは既存のライブラリの動作を乗っ取って、動的に書き換えてしまうパッチです。
これは動的言語の特性を活かした離れ業であり、コンパイル言語では真似できない手法です。[^1]

[^1]: http://postd.cc/monkey-patching-in-go/ でコンパイル言語のモンキーパッチしてました、アセンブラを使うかなりな黒魔術ですね

<i class="fa fa-info-circle" aria-hidden="true"></i> 補足
モンキーパッチは既存のライブラリのファイルを書き換えるものではありません。
あくまで既存ライブラリのファイルを変更することなく、動的に動作を変える手法です。
`sphinx-quickstart-plus`をインストールしても、元となるSphinxライブラリの動作は一切変わりません。

#### 参考
[モンキーパッチ Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%82%AD%E3%83%BC%E3%83%91%E3%83%83%E3%83%81)

`sphinx-quickstart`は`sphinx.quickstart.py`というモジュールに処理が書かれています。
ですので、普通に`sphinx.quickstart`をインポートしますが、`ask_user`関数はモンキーパッチで書き換えてしまいますので、オリジナル処理の関数ポインタを忘れないよう、別途に`from .. import ask_user`でインポートしています。

```python
from sphinx import quickstart
from sphinx.quickstart import ask_user, generate, do_prompt, nonempty, boolean
```

`quickstart.ask_user`関数を`monkey_patch_ask_user`関数で上書きして乗っ取ります。

```python
    # monkey patch
    quickstart.ask_user = monkey_patch_ask_user
```

`monkey_patch_ask_user`関数で独自の処理を行った後、オリジナルの`quickstart.ask_user`関数を呼び出しています。

```python
def monkey_patch_ask_user(d):
    ... 独自処理

    # オリジナルの ask_user呼び出し
    ask_user(d)

    ... 独自処理
```

他にも色んな関数をモンキーパッチで乗っ取っているのですが、基本的にやっている事は一緒です。

最後に`quickstart.main`を呼びます。

```python
    quickstart.main(argv)
```

## sphinx-quickstart-plusを更に拡張する

Did you want to further expand ‘sphinx-quickstart-plus’? Editing ‘sphinx-quickstart-plus’ source code for extension is not cool.
('sphinx-quickstart-plus'を更に拡張したくなった？拡張のために'sphinx-quickstart-plus'のコードを編集するなんて無粋なことしないでくれよ）

Let’s monkey patch!
（モンキーパッチしようぜ！）

Below is an example of a simple extension.
（下のコードはシンプルな拡張の例）

```python
from sphinx_qsp import quickstart_plus

# Setting your extension.
your_extension = quickstart_plus.Extension(
    "ext_your_ext", "your extensions description.",
    conf_py="""

# ----Your Extension
import your_extension_package
extension.append("your-extension_name")
)
""",
    package=["your_extension_packge"]
)

# Add your extension.
quickstart_plus.qsp_extensions.extend([
    your_extension
])

# Run sphinx-quickstart-plus.
quickstart_plus.main()
```

The base class of extension is the following code.
（拡張に使う基底クラスのコードは以下の通り）

```python
class Extension(object):
    def __init__(self, key, description, conf_py=None, new_makefile=None,
                 makefile=None, package=None):
        self.key = key
        self.description = description
        self.conf_py = conf_py
        self.new_makefile = new_makefile
        self.makefile = makefile
        self.package = package or []

    # noinspection PyUnusedLocal
    def extend_conf_py(self, d):
        return self.conf_py

    def extend_makefile(self, d, make_mode):
        return self.new_makefile if make_mode else self.makefile
```

The extended class of ‘sphinx-autobuild’ is the following code.
（‘sphinx-autobuild’の拡張クラスは以下のようになる）

```python
class AutoBuildExtension(Extension):
    def extend_makefile(self, d, make_mode):
        if d['batchfile']:
            batchfile_path = os.path.join(d['path'], 'auto_build.bat')
            source_dir = d['sep'] and 'source' or '.'
            build_dir = d['sep'] and 'build' or d['dot'] + 'build'

            open(batchfile_path, "w").write(
                AUTO_BUILD_BATCH.format(
                    build_dir=build_dir, source_dir=source_dir,
                    AUTOBUILD_IGNORE=" ".join(AUTOBUILD_IGNORE),
                )
            )

        makefile = self.new_makefile if make_mode else self.makefile
        return makefile.format(" ".join(AUTOBUILD_IGNORE))
```


# あとがき

個人的にいくつかのドキュメントを作成しましたが、めっちゃ便利です。