reST を書かずに autodoc だけで Sphinx する

# 前置き

Python のパッケージを公開するのにドキュメントがあると良いと思って、[Sphinx](https://www.sphinx-doc.org/ja/master/) で作ってみることにしました。
Sphinx ならソースコード内の docstring から自動的にドキュメントを作ってくれると聞いたので。

自分でやりやすいと思った方法を説明しています。他にもやり方はあるので、興味のある方は調べてみてください (より良い方法があったら教えてください)。

# やりたかったこと

- docstring からドキュメントを生成する。  
※ docstring は [NumPy 形式](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard)または [Google 形式](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings)とします。
- よく見かける安心感のある体裁 ([Read the Docs](https://docs.readthedocs.io/en/latest/)) にする。
- GitHub Pages で公開する。
- [reStructuredText (reST)](https://www.sphinx-doc.org/ja/master/usage/restructuredtext/index.html) を手で書かない。
- バージョンごとのドキュメントを作る。
    <details>
    <summary>イメージ (クリック)</summary>
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/aed690b7-93d3-6042-aa79-50cfb0b4fbf9.png">
    </details>

# 動作確認した環境

- Windows 10 Home 2004
- Python 3.8.1
- Sphinx 3.5.2
- sphinx-rtd-theme 0.5.1
- sphinx-multiversion 0.2.4

# 概要

すでに何らかのパッケージがあって Git で管理されている状況で、そこからなるべく手間をかけずにドキュメントを作る手順をまとめました。
ドキュメントは (中間生成物も含め) パッケージと同じリポジトリ内で管理されます。

今回のやり方では、以下のような構成になります (この記事の内容と直接関係ないファイルやフォルダは省略しています)。

```
my_pkg/
 ├─ .git/
 ├─ .venv/
 ├─ docs/
 │   ├─ build/
 │   │   └─ (生成した HTML)
 │   └─ source/
 │       └─ (reST、設定やテンプレート)
 └─ src/
     └─ my_pkg/
         ├─ __init__.py
         └─ (モジュール、サブパッケージ)
```

Sphinx で作るファイルはすべて "docs" フォルダの下に入ります。
このフォルダ名は何でも良いです (以下、コマンド引数を読み替える等してください)。

| |
|-|
|HTML の出力先を "_build" とする場合もあります。これは "source" フォルダを作らない場合に reST などのファイルと区別する (ドキュメントのソースとして読み込まないようにする) ためです。今回は "source" フォルダを作るので、フォルダ名は "build" となります。|

## おおまかな流れ

最初にいちどだけ実施する「準備」と、パッケージ開発の切りの良いところでドキュメントを作る「生成」に分けて考えます。

### 準備

1. パッケージのインストール
2. リポジトリ内に Sphinx プロジェクトを作成
3. conf.py を編集
4. index.rst を編集
5. リダイレクト用の index.html を作成
6. バージョン切替え用のテンプレートを作成

### 生成

1. ドキュメントの対象となるバージョンを準備する
2. ソースコードから reST を生成する
3. コミットしてタグをつける
4. バージョンごとの HTML を生成する

では、ひとつずつ見ていきましょう。

# パッケージのインストール

Sphinx と、今回使う拡張用のモジュールをインストールします。
私は仮想環境にインストールしました (Read the Docs で [Sphinx のバージョンを固定することが推奨されている](https://docs.readthedocs.io/en/latest/guides/reproducible-builds.html#pinning-dependencies)ので)。

```
(.venv) > pip install sphinx sphinx-rtd-theme sphinx-multiversion
```

["sphinx-rtd-theme"](https://sphinx-rtd-theme.readthedocs.io/en/latest/) は Read the Docs のテーマを使いたい場合に、["sphinx-multiversion"](https://holzhaus.github.io/sphinx-multiversion/) はバージョンごとのドキュメントを作りたい場合にインストールします。
今回は両方ともインストールしている前提で進めます。

# リポジトリ内に Sphinx プロジェクトを作成

プロジェクトというとちょっと構えてしまいますが、上の「概要」のところでも書いたように、今回は Sphinx 関連のファイルはすべて "docs" フォルダの中に作りますので、いつでも簡単にプロジェクトを取り除いたり作り直したりできます。

```
(.venv) > sphinx-quickstart docs
```

ビルドのソースを "source" サブフォルダに入れるか聞かれるので "y" と答えます。

```
> Separate source and build directories (y/n) [n]: y
```

| |
|-|
|"n" またはそのままエンターを押すと、"docs" の直下に reST などのファイルが散らばり、出力先のフォルダ名が "build" ではなく "_build" になります。|

プロジェクト名、著者名、リリース番号、言語を聞かれるので適当に答えます。
これらは後から "conf.py" というファイルを編集して変更できます。

```
> Project name: my_pkg
> Author name(s): satamame
> Project release []: 0.0.1
> Project language [en]: ja
```

"docs" フォルダの中に Sphinx プロジェクトが作成され、以下のような構成になります (関係ないファイルやフォルダは省略しています)。

```
my_pkg/
 ├─ docs/
 │   ├─ build/
 │   │   └─ (HTML の生成先。今は空)
 │   ├─ source/
 │   │   ├─ _static/
 │   │   ├─ _templates/
 │   │   ├─ conf.py
 │   │   └─ index.rst
 │   ├─ make.bat
 │   └─ Makefile
 └─ src/
     └─ my_pkg/
```

おおまかな理解としては、"source" フォルダがドキュメントのソースで、"build" フォルダが生成先と考えてください。
この **"source" フォルダ内のソース (reST ファイル) 等を手で書かずにソースコードから自動生成する**、というところが今回のポイントになります。

# conf.py を編集

ソースを手で書かないと言いましたが、準備段階では編集が必要です。
Sphinx やその拡張用モジュールの設定が "conf.py" の中にまとまっているので、これを編集します。

### パスの指定

"conf.py" の"Path setup" というブロックをコメント解除して以下のように変更します。
これは、autodoc でパッケージのソースコードを読み込むためです。
追加しているパスは "conf.py" からの相対パスになります。

```python:conf.py
# -- Path setup --------------------------------------------------------------
...

import os    # コメント解除
import sys   # コメント解除
sys.path.insert(0, os.path.abspath('../../src'))  # コメント解除＆変更
```

### 拡張用モジュールの指定

"General configuration" というブロックの中で、拡張用モジュールを指定します。
今回は以下の拡張用モジュールを使います。
[autodoc](https://www.sphinx-doc.org/ja/master/usage/extensions/autodoc.html) と [napoleon](https://www.sphinx-doc.org/ja/master/usage/extensions/napoleon.html) は Sphinx に付属していますので、別途インストールの必要はありません。

```python:conf.py
# -- General configuration ---------------------------------------------------
...

extensions = [
    'sphinx.ext.autodoc',   # ソースコード読み込み用
    'sphinx.ext.napoleon',  # docstring パース用
    'sphinx_rtd_theme',     # Read the Docs テーマ (今回は不要*1)
    'sphinx_multiversion',  # マルチバージョン用
]
```

|(*1)|
|-|
|Read the Docs Sphinx Theme の[インストール手順](https://sphinx-rtd-theme.readthedocs.io/en/latest/installing.html)に従って "sphinx_rtd_theme" を拡張用モジュールとして追加していますが、これはローカライズ用で、現状で日本語に対応していないので意味がありません (言語をデフォルトの "en" にした場合も不要)。<br>また、上記インストール手順では "conf.py" の中で `import sphinx_rtd_theme` をするように書いてありますが、これも不要かと思います。|

### テーマの指定

"Options for HTML output" というブロックにある `html_theme` を変えます。

```python:conf.py
# -- Options for HTML output -------------------------------------------------
...

html_theme = 'sphinx_rtd_theme'  # 変更
```

このテーマに固有の設定も、["conf.py" に追加する](https://sphinx-rtd-theme.readthedocs.io/en/latest/configuring.html)ことで可能です。

### マルチバージョンの設定

拡張用モジュールを読み込む設定はしたので、細かな動作を指定します。
個々の設定については[こちら](https://holzhaus.github.io/sphinx-multiversion/master/configuration.html)に仕様があります。

ここでは、「Git の履歴からタグとブランチを見て、条件に合うものをドキュメントのバージョンとして取り出す」という設定をします (デフォルトではすべてのタグとブランチを取り出します)。

たとえば、"conf.py" にこんな行を追加します。

```python:conf.py
# -- Options for sphinx-multiversion -----------------------------------------

smv_tag_whitelist = r'^\d+\.\d+$'   # これにマッチしたタグを抽出
smv_branch_whitelist = r'^master$'  # これにマッチしたブランチを抽出

```

これで、"1.0" や "12.34" のようなタグと "master" というブランチにマッチし、それらをサイドバーから選択できるようになります。
![multi-version-open.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/555c88f8-3abe-d48c-f123-a9f292094d86.png)

# index.rst を編集

"index.rst" を編集して、目次に表示したいパッケージを指定します。

```rst:index.rst
Welcome to my_pkg's documentation!
==================================

.. toctree::
   :maxdepth: 2
   :caption: Contents:

    my_pkg   (これを追加)
```

正確にはパッケージを指定しているというよりは、"my_pkg.rst" を参照する指定をしています。
"my_pkg.rst" はまだありませんが、このあとソースコードから生成します。
my_pkg の下にあるサブパッケージなどは、指定しなくても芋づる式に表示されます。

# リダイレクト用の index.html を作成

これは必須ではありませんが、どういう役に立つのかを知るために、ビルド後のディレクトリを見てみましょう。
sphinx-multiversion を使うので、"build" フォルダの下にはバージョンごとのサブフォルダができます。

```
my_pkg/
 ├─ docs/
 │   ├─ build/
 │   │   ├─ 0.2/
 │   │   │   ├─ index.html
 │   │   │   └─ (index 以外のページ, CSS)
 │   │   └─ master/
 │   │       ├─ index.html
 │   │       └─ (index 以外のページ, CSS)
 │   ├─ source/
 │   ├─ make.bat
 │   └─ Makefile
 └─ src/
```

sphinx-multiversion を使わない場合は "build" の下に "index.html" が出来るので、"build" 以下をそのまま公開できます。
しかし sphinx-multiversion を使う上記の構成では、公開する URL に、たとえば "my_pkg.com/master/" のようにパスを含める必要がでてきます。

このパスを含めない URL でも公開できるように、[リダイレクト用の "index.html"](https://holzhaus.github.io/sphinx-multiversion/master/github_pages.html#redirecting-from-the-document-root) が sphinx-multiversion のサイトに掲載されています。
上記リンク先は GitHub Pages で公開する手順ですが、他のサイトで公開する場合も以下のようにして使えます。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Redirecting to master branch</title>
    <meta charset="utf-8">
    <meta http-equiv="refresh" content="0; url=./master/index.html">
    <link rel="canonical" href="https://my_pkg.com/master/index.html">
  </head>
</html>
```

# バージョン切替え用のテンプレートを作成

バージョン切替えの UI を表示するために、"docs/source/_templates" フォルダの下に "versions.html" というファイルを作ります。
内容は [sphinx-multiversion のサイト](https://holzhaus.github.io/sphinx-multiversion/master/templates.html#readthedocs-theme)からコピーしてきます。

| |
|-|
|このやり方は Read the Docs テーマを使っている場合の特別なやり方です。一般的には、"conf.py" の中で html_sidebars にテンプレートを追加するようですが、Read the Docs テーマを使う場合はその方法では表示できないようです。|

# ドキュメントの対象となるバージョンを準備する

ここからは、バージョンごとのドキュメントを作成する際に毎回実施する作業となります。

まず、「Git 上のタグを見てドキュメントを作るのだ」という事を理解する必要があります。
今回の設定では、"1.0" などのタグをつけたコミットはドキュメント生成の対象になりますから、混乱しないようにパッケージ自体がバージョン 1.0 相当になった時点で作業するようにします。

| |
|-|
|ドキュメント用にブランチを分けるとか、ドキュメントバージョン用のタグの書き方を決めるとか、色々やり方はありますが、今回はパッケージのバージョンとドキュメントのバージョンを合わせるというやり方にします。<br>もちろん、パッケージの内容が 1.0 になった時点でコミットしても構いませんが、"1.0" というタグを付けるのはあくまでもドキュメントの reST を生成したコミットです。|

# ソースコードから reST を生成する

パッケージが準備できて「これでリリースする」と決めたら、**ソースコードからドキュメントのソースを生成**します。

```
(.venv) > sphinx-apidoc -f -o docs/source src
```

このコマンドのリファレンスは[こちら](https://www.sphinx-doc.org/ja/master/man/sphinx-apidoc.html)にあります。

| |
|-|
|コマンドのリファレンスにも書かれていますが、autodoc はパッケージ内のモジュールを `import` しますので、実行されて困るスクリプトは `if __name__ == '__main__':` 等としておく必要があります。|

実行すると "docs/source" フォルダに、パッケージごとの .rst ファイルと "modules.rst" が生成されます。
"index.rst" は勝手に生成されないので、間違って削除しないようにしましょう。

# コミットしてタグをつける

"docs/source" フォルダに reST ファイルが出来ているので、このまま HTML を生成できそうですが、できません。
理由は、sphinx-multiversion が **Git の履歴からドキュメントのソースを取ってくる**からです。

| |
|-|
|sphinx-multiversion を使わずに標準の [sphinx-build](https://www.sphinx-doc.org/ja/master/man/sphinx-build.html) というコマンドを使えば、作業ツリーにあるドキュメントソースから HTML を生成できますが、しないでください。それをすると "docs/build" フォルダの下の (リダイレクト用に作った) "index.html" が上書きされてしまいます。|

という訳で、上で生成したドキュメントソースをコミットして、そのコミットに、上で設定した `smv_tag_whitelist` にマッチするタグ名 (今回のケースでは "1.0" とか "12.34" とか) をつけます。
逆にマッチしないタグ名 ("1.0.5" など) をつければ、ドキュメントのバージョンは更新せずにパッチ等をリリースできます。

# バージョンごとの HTML を生成する

[sphinx-multiversion](https://holzhaus.github.io/sphinx-multiversion/master/quickstart.html) というコマンドを使います。

```
(.venv) > sphinx-multiversion docs/source docs/build
```

実行すると、「リダイレクト用の index.html を作成」のところで見たようなディレクトリになります。
"docs/build/index.html" を開くと (リダイレクトされて) master ブランチから生成したドキュメントが表示されます。
そこからサイドバーを使って他のバージョンに切替えられます。

HTML を生成した時点でもう一度コミットしておいても良いでしょう。
運用としては "docs/build" フォルダの中身をコピーしてホスティングすることになると思います。

# GitHub Pages にプッシュする (おまけ)

「やりたかったこと」に書いたとおり、GitHub Pages でドキュメントを公開しようと頑張ったので、それについて要点だけ書きます。

- GitHub Pages の Source を master にすると CSS が無効になった。
    - ".nojekyll" ファイルを設置しても駄目だった。
- [sphinx-multiversion の手順](https://holzhaus.github.io/sphinx-multiversion/master/github_pages.html)を参考にしたら公開できた (CSS も有効)。
    - ただし最終的にはローカルリポジトリを2個作る方法にした (後述)。
- gh-pages というブランチを新規プッシュすると GitHub Pages になるっぽい。
- gh-pages は --orphan で作っているのでソースコードやテストコードは要らない。
    - と思って消そうとして、Git で管理していないファイルまで消さないよう注意 (これは1個のローカルリポジトリでブランチを切り替えて運用する場合の注意です)。
    - ちなみに[こんな感じ](https://github.com/satamame/playscript/tree/gh-pages) ("docs/build" の中身 + ".gitignore" + ".nojekyll")。
- GitHub Pages の Source には、/ (root) か /docs しか選べないっぽい。
- ローカルリポジトリを2個作る方法はこんな感じ。
    1. 新しく "my_pkg_pages" というフォルダを作って `git init` する。
    1. "my_pkg_pages" に "my_pkg" と同じ GitHub のリモートを設定する。
    1. すでに gh-pages があるならチェックアウトしておく。
    1. "my_pkg" フォルダで HTML を生成する ("docs/build" にできる)。
    1. "docs/build" の中身を "my_pkg_pages" フォルダにコピーしてコミット。
        - ブランチ名は gh-pages とかで良いと思います。 
    1. "my_pkg_pages" をリモートの gh-pages にプッシュする。
    1. 以降、2個のローカルリポジトリでそれぞれ master と gh-pages を管理する。
    - 参考: [不安なくOrphanブランチを作る方法 by GitHub(リモートgit) - Qiita](https://qiita.com/PharaohKJ/items/f90336ce216cf9e57ce2)

# 参考文献

※リンク先の情報を保証するものではありません。

- [Sphinx documentation](https://www.sphinx-doc.org/ja/master/)
- [sphinx.ext.autodoc -- docstringからのドキュメントの取り込み](https://www.sphinx-doc.org/ja/master/usage/extensions/autodoc.html)
- [sphinx-multiversion 0.2.4 documentation](https://holzhaus.github.io/sphinx-multiversion/master/index.html)
- [Getting Started with Sphinx — Read the Docs](https://docs.readthedocs.io/en/latest/intro/getting-started-with-sphinx.html)
- [Read the Docs Sphinx Theme 0.5.1 documentation](https://sphinx-rtd-theme.readthedocs.io/en/latest/index.html)
- [Sphinxの使い方．docstringを読み込んで仕様書を生成 - Qiita](https://qiita.com/futakuchi0117/items/4d3997c1ca1323259844)
- [PythonプロジェクトのドキュメントをSphinxで作成する - Qiita](https://qiita.com/icoxfog417/items/9e2eb932b61aa9b9e427)