ドキュメントがメンテナンスされない問題に立ち向かう with Sphinx

ドキュメント管理はサービスの運用に頭を悩ませる課題の一つです。
常に最新の状態を保ち、記載内容には誤りがない...
当然のように聞こえますが、
それを実現するのは非常に難しいという事は敢えて言うまでもないことでしょう。

とはいえ、"ドキュメントは管理されないものだから仕方がない"と割り切ってしまうわけにもいきません。
今回はドキュメントがメンテナンスされるような仕組みを構築する事で
この問題に立ち向かう方法を紹介します。

**ドキュメントを継続的にメンテナンスするために必要な要素**
メンテナンスされやすい環境を構築するにあたり 前提となる幾つかの項目を挙げました。
今回は これらの条件を満たす環境の構築を目指します。

- ソースコードとドキュメントの一元管理
- ドキュメントの更新に対するレビュー
- バージョン管理
- かっこいい見た目 ← 重要


# 具体的にどう実現するか
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116147/59d412e4-eb78-dfd0-f51c-0e1f913f2dee.png)


今回はsphinxと呼ばれるPython製のドキュメント生成ツールを利用します。

**Sphinxの特徴**

- reStructuredText記法で書かれたテキストファイルからHTML, PDF等を生成。
- レイアウトテンプレートで綺麗なドキュメントが作成可能。
- Markdownにも対応

実際のsphinxを使って出来たドキュメントのイメージ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116147/eb95019e-1567-bd06-d5ff-ce58cef5c33b.png)

このSphinxで生成したHTMLを公開する場所については GitHub Pageを利用します。

**GitHub Pageの特徴**
GitHubの提供する静的サイトのホスティングサービス。
master branchのdocsディレクトリ直下にHTMLを置くか
gh-branchs branchの直下にHTMLを置くことで静的サイトを公開することが出来ます。


今回は、master branchに生成されたHTMLファイルを置きたくないので
gh-branchsを使った公開を採用します。

ただ、ドキュメントの更新のたびにgh-branchにpushするのは面倒なので、
master branchに更新がかかったタイミングでCI経由でgh-branchに自動でpushできるようにCI用のシェルを用意します。


# 説明しないこと
sphinxの書き方は非常に長くなるため、ここでは解説しません。
書き方が知りたい方は[Sphinx日本ユーザ会が提供してくれているドキュメント](http://sphinx-users.jp/gettingstarted/make_project.html)を参考にして下さい。

# 実践
※ 今回の内容で作成したプロジェクトはこちらに上げています。
https://github.com/ohs30359-nobuhara/sphinx-gh-page

## サンプルアプリの作成
ドキュメントだけ作っても良いのですが、
実際に運用するケースを想定して簡単なアプリを用意します。

本題ではないので expressを使った簡単なものです。

```
$ npm i express
```

殆どサンプルそのままです。

```javascript
var express = require("express");
var app = express();

var server = app.listen(3000, function(){
    console.log("Node.js is listening to PORT:" + server.address().port);
});

app.get("/", function(req, res, next){
    res.send('Sphinx!!');
});
```

## Sphinxの導入
アプリが出来たところで本題であるsphinxを導入していきます。
sphinxはpipで導入するため pythonをインストールします。

```
$ brew install python
$ which pip
> /usr/local/bin/pip
```

続いてpipでSphinxをインストール。

```
$ pip install -U Sphinx
```

これで準備は完了です。

## ドキュメントの作成
まずはドキュメントを管理するためのdocディレクトリを用意します。

```
$ mkdir doc
```

続いてdocディレクトリに移動して以下のコマンドを実行します。

```
$ sphinx-quickstart
```
色々聞かれますが、基本すべてyesで問題ありません。
```sphinx-quickstart```によりdoc配下にドキュメントのリソースが作成されます。

```
.
├── Makefile
├── make.bat
└── source
    ├── conf.py
    └── index.rst
```

この状態で ```$ make html```を実行すると```doc/build```配下にドキュメントが作成されます。

```
$ make html
```


作成されたドキュメントを表示してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116147/dfd284ec-7266-4d3c-82ea-d1da1e1cc516.png)

## Themeで見栄えを整える
流石に味気が無さすぎるので見栄えを整えます。
sphinxにはThemeと呼ばれるデザインテンプレートが用意されているので、それを利用します。

pipでTeemaを取得してきます。

```
$ pip install sphinx_rtd_theme
```

続いて ```doc/source/conf.py```に以下を追記します。

```doc/source/conf.py
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
```

この状態で もう一度```make html```を実行します。

```
$ make html
```

もう一度 ドキュメントを表示すると今度は設定したTeemaが適用されいるはずです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/116147/eb95019e-1567-bd06-d5ff-ce58cef5c33b.png)

## Markdownを使えるようにする

これでドキュメントを書いていく準備は出来ましたが
sphinxはrst形式で書かないといけないため慣れていない人からすれば面倒です。

新しく覚えるの面倒だしMarkdownでも書きたいという要望は発生すると思うので
Markdownでドキュメントを書けるようにします。

pipでcommonmark, recommonmarkを取ってきます。

```
$ pip install commonmark recommonmark
```

先程と同じく ```doc/source/conf.py```を編集します。

```src/doc/source/conf.py
source_suffix = ['.rst', '.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)
```

これで準備は完了です。
早速、Markdownでページを追加してみます。


まずはsampleディレクトリを作成。

```
$ mkdir ./doc/sample
```

とりあえずタイトルだけの簡単なページを作成します。

```doc/sample/index.md
# SAMPLE INDEX
```

続いて```doc/source/index.rst``` に先ほど作成したファイルへのリンクを追記します。

```doc/source/index.rst
   :maxdepth: 2
   :caption: Contents:

   /sample/index
```

これでMarkdown対応は完了です。
```$ make html```を実行するとページが追加されていることが確認できます。


## GitHub Pageに公開する
ドキュメントが作成できるようになったので GitHub Pageに公開します。
基本的に自動生成されるbuild配下のファイル群は管理したくないため
master branchではdoc/sourceのみを管理させるようにし、
gh-pages branchに公開対象のhtmlを管理させるようにします。

最終的なディレクトリ構成は以下になります。
( gh-pages branch は build配下のファイルをそのまま持ってきています )

```
# master branch
.
├── app.js
├── ci
│   └── genDoc.sh
├── doc
│   ├── Makefile
│   ├── make.bat
│   └── source
│       ├── conf.py
│       ├── index.rst
│       └── sample
│           └── index.md
├── package-lock.json
└── package.json

# gh-pages branch
.
├── _sources
├── _static
├── genindex.html
├── index.html
├── objects.inv
├── sample
├── search.html
└── searchindex.js
```

まずci用ディレクトリを作成し、シェルを用意します。

```ci/genDoc.sh
# move to this file
cd `dirname $0`

# move project root & remove files 
cd ..
git checkout gh-pages
find ./ | grep -v .git | xargs rm -r

# get resources from master branch 
git checkout master doc

# build  doc
cd ./doc
make html

# move builded file 
cd ../
mv ./doc/build/html/* ./

# deploy
rm -rf ./doc
touch .nojekyll
git add -A
git commit -m 'updating doc'
git push origin gh-pages

# back to master branch
git checkout master
```

順番に解説していきます。

```cd `dirname $0``` でシェルが置かれているpathに移動します。

```
# move to this file
cd `dirname $0`
```

続いてプロジェクトルートに戻り gh-pages branchにチェックアウト。
```find ./ | grep -v .git | xargs rm -r```で git系以外のファイルをすべて削除。

```
cd ..
git checkout gh-pages
find ./ | grep -v .git | xargs rm -r
```
master branchからdocディレクトリだけをチェックアウトして
```make html```でドキュメントを作成

```
# get resources from master branch 
git checkout master doc

# build  doc
cd ./doc
make html
```

プロジェクトルートに戻り、buildされたhtmlをプロジェクトルートに移動。

```
# move builded file 
cd ../
mv ./doc/build/html/* ./
```

docディレクトリは不要なので削除。

GitHub Pagesでホスティングされたファイルは、Jekyllによって処理されてしまうので
.nojekyllファイルを追加してJekyしてをkill。

htmlファイルと.nojekyllをcommit, pushしてmaster branchに戻る。

```
# deploy
rm -rf ./doc
touch .nojekyll
git add -A
git commit -m 'updating doc'
git push origin gh-pages

git checkout master
```

これでgh-pagesにドキュメントが公開されているはずです。
gh-pagesへのURLはGitHubのsettingに表示されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/116147/b1bd7428-c438-685f-3d31-33b478e2c672.png)

後は 現在利用しているCIで先程のシェルを実行するようにすれば
ドキュメントの更新をCIにより自動化することが出来ます。


# 運用する
これで最低限CIでドキュメントをデプロイする準備が整いました。
最終的には人が更新するものなので正しくメンテナンスされるように
ドキュメントを更新するタイミングや書き方の規約をプロジェクトで設定して下さい。