Sphinx で作成したドキュメントをFlaskで公開する

# はじめに

Flask でアプリケーションを作っているとき、ドキュメントも一緒に公開できれば良いなと考え、Sphinx で作成したHTMLファイルを配置したのですが、当初うまくいかず四苦八苦しました。
そこで、きっちりと動作する設定を確認して備忘録としました。

## Sphinx

Sphinx はドキュメントをHTMLフォーマットで簡単に整形することができます。

```bash

 $ mkdir docs
 $ sphinx-quickstart docs
 $ SPHINX_DOCROOT_DIR=$(cd docs; pwd)
```

ソースと整形後のドキュメントを別ディレクトリにした場合、通常であれば
`${SPHINX_DOCROOT_DIR}/build/html`  以下に作成されます。

## つまずいた理由
Sphinx が生成するファイルには次のようにファイル参照が散在しています。
デフォルトでは、_static、 _template、_images などとなります。

```html
      :
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
      :
```

Flask で管理する静的ファイルは Flask()のインスタンスを初期化するときに、
static_folder に静的ファイルを格納するディレクトリへの相対パスを与えます。

アプリケーションと Sphinx ドキュメントの共存がはじめはうまくいかず、
スタイルシートやスクリプトをうまく読み込むことができませんでした。

## Flask

Flaskで公開する場合は次のようにすると簡単です。

```python
import os
from flask import Flask, send_from_directory

basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__, static_folder='static')

@app.route('/favicon.ico', defaults ={"path": "favicon.ico"})
@app.route('/docs/', defaults = {"path": "index.html"})
@app.route('/docs/<path>')
@app.route('/docs/<dir>/<path>')
def web_docs(dir='', path='index.html'):
    docroot = f"{basedir}/static/html/{dir}"
    return send_from_directory(docroot, path)


if __name__ == "__main__":
    app.run(
        host=os.getenv("FLASK_RUN_HOST", "localhost"),
        port=os.getenv("FLASK_RUN_PORT", 8080),
        threaded=True,
    )
```

Flask の 静的ファイルのディレクトリを作成して、
Sphinx のドキュメントへのリンクを配置します。


```bash
$ mkdir static
$ cd static
$ ln -s "${SPHINX_DOCROOT_DIR}/build/html"  html
```

これでブラウザから http://ホスト/docs ようにアクセスすると、Sphinx で作成されたHTMLファイルをうまく表示してくれます。

## favicon

favicon.ico は [favicon.io](https://favicon.io/) などのサービスを使うと無料で作成することができます。


```bash
 $ cd static
 $ unzip $HOME/favicon.zip 
 $ cp favicon.ico html/
```

Flaskのアプリケーションを追加するのであれば、必要に応じて次のようなテンプレートを用意します。

```html
{% block doc -%}
<!DOCTYPE html>
<html{% block html_attribs %}{% endblock html_attribs %}>
  {%- block html %}
  <head>
    {%- block head %}
    <title>{% block title %}{% endblock title %}</title>

      {%- block metas %}
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta charset="UTF-8">
      {%- endblock metas %}

      {%- block favicon %}
      <link rel="apple-touch-icon" sizes="180x180" 
            href="/static/apple-touch-icon.png">
      <link rel="icon" type="image/png" sizes="32x32"
            href="/static/favicon-32x32.png">
      <link rel="icon" type="image/png" sizes="16x16" 
            href="/static/favicon-16x16.png">
      <link rel="manifest" href="/static/site.webmanifest">
      {%- endblock favicon %}

      {%- block styles %}
      {%- endblock styles %}

    {%- endblock head %}
  </head>
  <body{% block body_attribs %}{% endblock body_attribs %}>
    {%- block body %}

    {%- block contents %} 
    {%- endblock contents %}

    {%- block footer %}
    {%- endblock footer %}

    {%- block scripts %}
    {%- endblock scripts %}

    {%- endblock body %}
  </body>
  {%- endblock html %}
</html>
{% endblock doc -%}
```