aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

Sphinx文書内で展開されるsubstitutionsやextlinksを活用する

aptpod Advent Calendar 2024 の12月9日の記事です。

本日は、テクニカルライターの篠崎が担当します。

当社では今年、ドキュメントサイトをリニューアルし、複数のプロダクトのドキュメントを1つのウェブサイトに統合しました。

これにより、読み手は全体を俯瞰しながら各製品の詳細に読み進めることができるようになりました。 また、私のような制作側にとっては、ページ間のリンクを張りやすくなりました。内容の重複も減らすことができ、更新作業もしやすくなりました。

統合されたドキュメントサイト

製品ドキュメントの作成にはSphinxを使っています。これまでプロダクトごとに別々だったSphinxデータは、これからは1つのプロジェクトとして管理するようにしました。

ただし、いままで別のプロジェクトだったものを1つにまとめるには、Sphinxデータのフォルダーをマージするだけでなく、いろいろな調整が必要でした。

例えば、複数のプロダクトを1つのドキュメントで扱うことになったため、ドキュメント内には複数のプロダクトのバージョン番号が登場するようになりました。

通常、Sphinxでは説明対象プロダクトのバージョンを設定ファイル内に書くことで、それを文中に展開して使用することができますが、複数のプロダクトを扱う場合には一工夫が必要でした。 ここではそれについて説明します。

ドキュメント内で展開できる変数(substitution)

Sphinxでは、設定ファイル conf.py 内で version という変数を定義しておくことにより、本文のreStructuredText原稿で |version| と書いた部分にその値を展開することができます。 後日バージョンが変更になったときは、設定ファイル内の version 定義を変更するだけで文中に反映することができます。

conf.py

version='1.0.0'

reStructuredText原稿

本サイトでは、MyApp v\ |version| について説明します。

(マークアップとしては |version| の前後にスペースが必要です。ただし MyApp v の後にバックスラッシュを入れ、その直後のスペースを無視するようにしています。)

HTMLビルド結果

展開された |version|

この |version| のような |...| を使った一種の変数展開はSphinxではsubstitutionと呼ばれています。 conf.py 内で使用される変数のうち、いくつかの変数はデフォルトでsubstitutionとして使用可能になっています。

最初に書いたとおり、これからは複数のプロダクトを1つのドキュメントで扱うことになったので、「各プロダクトのバージョンを全部substitutionとして扱えるようにしたい」と思いました。そのために、この version とは別のsubstitutionを独自に設定することにしました。

substitutionは、reStructuredText上で以下のような書式で定義することができます。

reStructuredText原稿

.. |my-app1-version| replace:: 1.2.3
.. |my-app2-version| replace:: 4.5.6

これにより |my-app1-version|1.2.3 という文字列に、 |my-app2-version|4.5.6 という文字列に置換されます。

文中で以下のように書くと展開されます。

reStructuredText原稿

本サイトは以下のバージョンについて説明しています。
* MyApp1 v\ |my-app1-version|
* MyApp2 v\ |my-app2-version|

HTMLビルド結果

2つのバージョン番号を展開

ですが、 .. |my-app1-version| replace:: 1.2.3 のような定義はreStructuredTextファイルごとに書かなければなりません。複数のページからなるドキュメントでは大変です。

これを解決するのが、 conf.pyrst_prolog (または rst_epilog )です。 rst_prolog 変数の内容は、ビルド時にすべてのreStructuredTextファイルの先頭に付加されます。

そのため、 conf.py を以下のようにすればよいわけです。

conf.py

rst_prolog='''
.. |my-app1-version| replace:: 1.2.3
.. |my-app2-version| replace:: 4.5.6 
'''

これで、ひとつひとつのreStructuredTextファイルで定義したのと同じ効果を得られます。 どのファイルでもこれらのsubstitutionを使えるようになりました。

さらに弊社製品では、ドキュメントにはAPIリファレンスへのリンクが含まれます。 どうしても長いURLを原稿内に書くことになります(例えばこのような形式のURL: https://www.example.com/app1-reference/1.2.3/get-users )。

長いURLを繰り返し記載したいときに便利なのが、sphinx.ext.extlinksです。

sphinx.ext.extlinksは機能拡張ですが、Sphinxに組み込まれているため新たにインストールする必要はありません。使用するには、 conf.pyextensions リストに入れるだけでOKです。

conf.py

# すでにextensionsの定義がある場合は適宜修正してください
extensions = ['sphinx.ext.extlinks']

そのうえで、以下のように、リンクのテンプレートを定義します。

conf.py

extlinks = {
    'app1-api':
        ('https://www.example.com/app1-reference/1.2.3/%s', None),
}

このようにすると、原稿内で以下のような表記ができるようになります。

reStructuredText原稿

:app1-api:`ユーザー一覧取得APIリファレンス <get-users>`

HTMLをビルドすると以下のように展開されます。

HTMLビルド結果(一部省略)

<a href="https://www.example.com/app1-reference/1.2.3/get-users"
  >ユーザー一覧取得APIリファレンス</a>

sphinx.ext.extlinksによるリンク例

上のreStructuredText原稿で <> の中に入っていたURL断片 get-usershttps://www.example.com/app1-reference/1.2.3/ の後ろ(extlinks定義の %s のところ)に展開されています。これにより、長いURLを毎回書く必要がなくなり、原稿がシンプルになります。

当社の場合、APIリファレンスはバージョンごとに用意しているので、リンクには特定のバージョン番号を含める必要があります。

説明対象プロダクトの情報をまとめて定義する

バージョン文字列をsubstitutionsやextlinksなどの複数の形式で使うのであれば、重複のないように1か所で定義したくなります。

ここで「設定ファイル conf.py は実行されるPythonコードでもある」ということが活きてきます。以下のようなことができます。

conf.py

# 2つの説明対象プロダクトの情報をまとめてdictionaryとして定義
app_versions = {
    'my-app1': {
        'name': 'MyApp1',
        'version': '1.2.3',
    },
    'my-app2': {
        'name': 'MyApp2',
        'version': '4.5.6',
    },
}

# substitutionを定義
# app_versionsをループして、
# `.. |my-app1| replace:: MyApp1 - v1.2.3` の形式でrst_prologを作成

rst_prolog=''

for app_id, app_info in app_versions.items():
  rst_prolog += f'.. |{app_id}| replace:: {app_info["name"]} - v{app_info["version"]}\n'

# extlinksを定義
# app_versionsをループして、特定バージョン番号の入った外部リンクの短縮表記を定義する

extensions =['sphinx.ext.extlinks']
extlinks = {}

for app_id, app_info in app_versions.items():
    extlinks[f'{app_id}-api'] = (f'https://www.example.com/{app_id}-reference/{app_info["version"]}/%s', None)

ループの中が少し読みづらいですが、結果は以下のようになるはずです。

rst_prolog = '''
.. |my-app1| replace:: MyApp1 - v1.2.3
.. |my-app2| replace:: MyApp2 - v4.5.6
'''

extlinks = {
    'my-app1-api': ('https://www.example.com/my-app1-reference/1.2.3/%s', None),
    'my-app2-api': ('https://www.example.com/my-app2-reference/4.5.6/%s', None),
}

これにより、以下のような原稿を書くことができるようになります。

reStructuredText原稿

本サイトは以下のバージョンについて説明しています。

* |my-app1|
* |my-app2|

以下のAPIを使用できます:

:my-app1-api:`ユーザー一覧取得APIリファレンス <get-users>`

:my-app2-api:`プロジェクト一覧取得APIリファレンス <get-projects>`

HTMLビルド結果

2つのプロダクトのバージョンと、リファレンスへのリンク

ループを使ってsubstitutionと外部リンクをいっぺんに定義することができました。

これで、説明対象のプロダクトが増えても conf.pyapp_versions に追加していくことで管理できます。

まとめ

Sphinxの設定ファイル conf.py は、これ自体がPythonで書かれたプログラムなので、設定ファイル内でさまざまな操作をプログラム的に行うことができます。この柔軟性は大変便利です。 当社では日ごろ多くのドキュメントをSphinxで作成していますが、この柔軟性にいつも助けられています。

参考文献