Qiita内にも他に記事はあるのですが、微妙にバージョンアップによりsphinx-quickstart
で生成されるフォルダ構成ややり方が変わったようなので2024/5月(Sphinx v7.3.7)時点でのメモ
sphinxによるPythonの自動ドキュメント化とは
他の方の記事がたくさんあるのでそちらをご参照いただければと思います。
https://qiita.com/futakuchi0117/items/4d3997c1ca1323259844
https://qiita.com/kinpira/items/505bccacb2fba89c0ff0
色々な用途がありますが、今回の目的は
Pythonの関数コメント、Classコメント等から自動的にドキュメントを作成したいという目的になります。
sphinxインストール & 環境作成
インストール
pip install sphinx
任意のフォルダ下で環境作成(今回はdocsというフォルダを作って配下で実行)
sphinx-quickstart
プロンプトで色々表示されますが今回は以下
> Separate source and build directories (y/n) [n]:y
> Project name: 任意の名前
> Author name(s): 任意の名前
> Project release []: 省略(そのままEnter)
> Project language [en]: 省略(そのままEnter)
フォルダ構成はこんな感じになりました。最初の選択肢でyを指定しない場合は各ファイルがproject直下にベタ置きされる形になります。
docs/
├── build/
├── source/
│ ├── _static/
│ ├── _templates/
│ ├── conf.py
│ └── index.rst
├───Makefile
└───make.bat
自分のファイルを配置します。
どこでも良いのですが、置いた場所は後述のconf.pyでパスを通すことにしてください。
今回は先ほど作ったdocsの外に置くことにしました。(sourceの下に置くのが一般的かもしれないけど、ディレクトリを別にしたかったので)
__init__.py
も含めて通常のモジュールとして動作できる状態にしてください。
my_project/
│
├── docs/
│ ├── source/
│ │ ├── conf.py
│ │ ├── index.rst
│ │ └── ...
│ └── build/
│
├── my_package/
│ ├── __init__.py
│ ├── module1.py
│ ├── module2.py
│ └── ...
│
└── setup.py
設定ファイル
conf.pyを編集します。
# conf.pyから見た位置で自分のモジュールまでのパスを追加
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# extentionsに以下を追加
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
]
今回はGoogleスタイルのコメントで記載したファイル達なのでnapoleonを追加しています。
Googleスタイルとはこんな感じのものです。
https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
def even(param1) :
"""関数の説明タイトル
関数についての細かい説明文
Args:
param1 (int): 引数の説明
Returns:
bool: 戻り値の説明 (例 : True なら成功, False なら失敗.)
"""
return param1 % 2 == 0
rstファイルの作成
docs直下で以下のコマンドを打ちます。
sphinx-apidoc -o source/ ../my_package/
sourceフォルダ内に自分のパッケージ名のrstが作成されていたら成功です。
作成されたドキュメントの目次ページに作ったドキュメントを表示したいので
index.rstに以下の記載を行います。
(この作業は自動にならないのだろうか・・・一応、本記事内の最後に「おまけ2」として自動スクリプトを記載しています。)
.. toctree::
:maxdepth: 2
:caption: Contents:
modules
my_package
htmlドキュメントの生成
正式なコマンドは以下です
sphinx-build -b html rstのあるフォルダ 出力
sphinx-build -b html source/ build
簡易コマンドで以下も用意されています。
make html
ちなみに簡易コマンドは出力先がbuild/htmlに出力されるので「あれ?build配下のファイル更新されないな」と思った方は注意してください。(思いっきりプロンプトに出力フォルダ出てるのですが、めっちゃそれでハマりました。)
おまけ1:見た目の変更
見た目はどれがいいかわからないのですが、とりあえず巷で良いと言われているsphinx_rtd_themeに変更。
pip install sphinx-rtd-theme
conf.pyのhtml_themeのところに
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
他のテーマはこちらから
https://sphinx-themes.org/
おまけ2:自動的にindex.rstに追加する
上記の手順だと手動でindex.rstを更新することになるので自動で追加するものを作りました。
※情報求:本当はsphinx側の機能に欲しいのですがわからなかったので、以下のスクリプトを用意しましたが、rstの考え方が違う、こういうコマンドがある等ありましたらお願いします。
(今回のようなディレクトリ構成の場合)docsフォルダで実行するとindex.rstに他のrstファイルの記述が自動的に記載されるはずです。
import os
def count_indent(line):
"""インデントの数を数える関数。"""
return len(line) - len(line.lstrip())
def find_toctree_entry(lines):
"""与えられた行のリストから toctree のエントリの開始と終了インデックスを見つける。"""
start, end = None, None
directive_indent = None
in_directive = False
for i, line in enumerate(lines):
if '.. toctree::' in line:
# toctree ディレクティブ発見 その次の行にディレクティブがあると仮定してインデント数を取得
directive_indent = count_indent(lines[i + 1])
in_directive = True
elif in_directive:
current_indent = count_indent(line)
if not line.strip().startswith(':'): #ディレクティブが終わったらstart
if start is None:
start = i # 最初のエントリ行を記録
if line.strip() and current_indent < directive_indent:
# エントリ終了条件
if start is not None and not line.strip().startswith(':'):
end = i
break
if start is not None and end is None:
end = len(lines) #
return start, end
def get_existing_entries(lines, start, end):
"""指定された行の範囲から既存のエントリを抽出する。"""
existing_entries = set()
for line in lines[start:end]:
stripped_line = line.strip()
if stripped_line and not stripped_line.startswith(':'):
existing_entries.add(stripped_line)
return existing_entries
def add_new_entries(lines, start, end, new_files, existing_entries):
"""既存のエントリを更新し、新しいエントリが既存のものにない場合、それを index.rst に追加する。"""
# 新しいエントリ一覧を作成
updated_entries = existing_entries.union(new_files)
# 既存のエントリを lines から削除
del lines[start:end]
# 新しいエントリを挿入
new_entries_list = sorted(list(updated_entries))
lines.insert(start, "\n") #空行を挿入
new_start = start +1
for i, entry in enumerate(new_entries_list):
lines.insert(new_start + i, f" {entry}\n")
# エントリの後に空行を挿入(フォーマット整形)
lines.insert(new_start + len(new_entries_list), '\n')
def update_index_rst(source_dir, index_file):
"""指定されたディレクトリ内の .rst ファイルを index.rst に適切に追加する。"""
# .rst ファイル一覧を取得
new_files = set(f.replace('.rst', '') for f in os.listdir(source_dir) if f.endswith('.rst') and f != 'index.rst')
# index.rst の内容を読み込む
with open(index_file, 'r') as file:
lines = file.readlines()
# toctree の開始と終了を見つける
start, end = find_toctree_entry(lines)
# 既存のエントリを取得
existing_entries = get_existing_entries(lines, start, end)
# 新しいエントリが既存のものにない場合のみ追加
add_new_entries(lines, start, end, new_files, existing_entries)
# 変更をファイルに書き戻す
with open(index_file, 'w') as file:
file.writelines(lines)
# パス設定
source_dir = 'source/'
index_file = 'source/index.rst'
update_index_rst(source_dir, index_file)