0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NIJIBOXAdvent Calendar 2023

Day 2

Sphinxの拡張時に、自分用のイベントを追加する

Last updated at Posted at 2023-12-19

Sphinxのコアイベントとイベントハンドラー

Sphinxでのドキュメント作成時に、「特定のタイミングであることをやりたい」というケースがたまに存在します。

例:

  • SASS/SCSSファイルを事前にコンパイルしたい
  • HTML生成時に出力データの調整をしたい
  • 一通りHTMLファイルを生成したあとに、そこからスクリーンショットを生成したい

こういったタイミングで処理を差し込めるように、SphinxにはEvent APIが存在します。

試しに、自身のSphinxドキュメントのconf.pyを編集して以下のようなコードを追加してみましょう。 1

conf.py
def _cowsay(app, err):
    print(r"""
          _________________________________
        / Congratuation!!                  \
        \ Please see generated document.   /
          ---------------------------------
                 \   ^__^
                  \  (oo)\_______
                     (__)\       )\/\
                         ||----w |
                         ||     ||
    """)


def setup(app):
    app.connect("build-finished", _cowsay)

実際にこのコードが記述された状態でビルドを実施すると、以下のような出力が行われます。

Screenshot from 2023-12-17 21-01-42.png

コアイベントたち

上記コードのbuild-finishedは文字通り「ビルド処理が一通り完了した最後のタイミング」を指しています。
このようなSphinxにおいて処理全体におけるチェックポイントと呼べるような箇所をいくつか定めており、「コアイベント」と名付けられています。

いくつかメジャー(?)なコアイベントを紹介します。

イベント名 タイミング
config-inited conf.pyの読み込みを行い、設定オブジェクトが生成された
builder-inited 設定オブジェクトと引数をもとに実行するビルダーを生成した
source-read ドキュメントのソースを読み込んだ(ファイルごとに発生)
html-page-context HTML系ビルダーが、各HTMLファイルを生成する直前(出力ファイルごとに発生)
build-finished ビルド処理が完了した

イベントハンドラー

各種イベント発生時に何をさせたいかはイベントハンドラーとして関数を実装することで実現できます。
前述のサンプルでは_cowsay関数がそれに該当します。

もちろん関数である必要はなくCallableであれば問題なく動作します。
ただし、各イベントごとにイベントハンドラーを呼び出す際の引数の形式は決まっているので、まずはSphinxドキュメントの「コアイベント」を読んで、どのような引数を受け付けるかを確認しましょう。

イベントで想定されている引数を受付さえすれば処理の中身は何でも良いです。
html-page-contextを利用して、生成するHTML向けにページ単位で特殊な値を差し込む」といった実用的な処理だったり、今回のcowsayの発展形として「ドネーションの募集」といったことも可能です。

自分用にイベントを追加する

さて、まれにですがSphinx拡張を開発する際に「やりたい処理を差し込みたいタイミングがコアイベントにない!」というケースがあります。

とあるSphinx拡張での話

自作しているSphinx拡張のsphinx-revealjsには、reStructuredTextの脚注表示をReveal.jsプレゼンテーション向けの配置にするためのサブ拡張が存在します。
その際に、脚注の位置を調整するためのCSSを生成するのですが、ドキュメントの設定で指定したフォントサイズをCSSに反映するという仕様にしました。

「ドキュメントの設定で指定したフォントサイズをCSSに反映する」という動作を実現するためには、CSSのテンプレート 2から、設定値を注入してCSSファイルを生成することになります。

当然ですが、CSSファイルはHTMLファイルに都度生成する必要するものではないため、html-page-contextは不適切です。
とはいえ、Jinja2へ引き渡す情報はcontextオブジェクトに属するため、それよりの前のイベントでは準備ができません。

そのため、「ファイル生成処理より前」で「全体共有のcontextオブジェクトが用意されている」タイミングにイベントが必要となりました。

Sphinxのコアアプリケーションオブジェクトには、「イベントを追加」「イベントの呼び出し」を行うメソッドが用意されています。
これを利用することで、本体以外のプロセスでもイベントの自作をすることができます。

イベントの追加

Sphinx.add_event()メソッドが提供されています。
第1引数にイベント名を指定することで、「Sphinxのビルドプロセスのどこかでこのイベントが発生することがある」ということを本体に登録することができます。

sphinx-extension.py
def setup(ap):
    ...
    # 文字通り、"revealjs:ready-for-writing" というイベントの存在を登録する。
    app.add_event("revealjs:ready-for-writing")
    ...

ここで登録したイベント名は、以降の処理でコアイベントと同様に、Sphinx.connect()の第1引数として指定することが可能になります。

イベントの呼び出し

カスタムイベントの宣言だけしても、実際のビルド時には何も起きません。
ビルド処理の途中にEventManager.emit()を用いることで、あらかじめconnect()で登録していたイベントハンドラーを実行することができます。

builders.py
class RevealjsHTMLBuilder:
    ...
    def prepare_writing(self, docnames: Set[str]):
        super().prepare_writing((docnames))
        self.events.emit("revealjs:ready-for-writing", self.globalcontext)
    ...

重要なポイントとして、コアイベントと同様に「あるイベント用のイベントハンドラーは同じ形式の引数を受け付けるように定義しなければならない」という点です。
上記の例では、emit()実行時にself.defaultcontextというContextオブジェクトを渡しています。
必ず第1引数にはSphinxのコアアプリケーションを渡すため、このイベントは引数を2個受け付ける関数でないといけません。

まとめると、

  1. 初期化のタイミングで、Sphinx.add_event()を実行してカスタムイベントの登録
  2. カスタムイベントのハンドラー関数をSphinx.connect()で追加
  3. ビルド処理の途中で、EventManager.emit()を実行してイベントハンドラーの呼び出し

これらを実施することで、コアイベントとは別に必要なイベントを適宜追加することができます。

気をつけたほうがよい(かもしれない)ポイント

この仕組みは「イベントそのものに関する処理を一つに集約すること」「コアイベントも同様の仕組みを取る」ことで、プラガブルな挙動を実現しています。3

そのため、「他者によって登録したイベント名と重複する」ことやもっと言えば「想定外のところで重複したいイベントに対するemitが発生する」という危険性があります。

自分の場合は、ある程度明示的になるように:を間に入れることで「拡張由来のイベント」であることを伝えるように定義しています。

元々の利用者はそんなに多くはないと思いますが、無理なく協調出来るような実装をしましょう。

  1. すでに、setup()を定義している場合は、_cowsayを定義したうえで、setup()内の適当な場所にapp.connect(~ を差し込んでください。

  2. Sphinxでは、通常の静的ファイル置き場に拡張子が_tで終わるファイルを配置した場合、「静的ファイルのテンプレート」として扱い、Jinja2による生成をすることができます。

  3. ビルダーに一切依存しないコアイベントはここdictとして定義されており、組み込みビルダーであってもadd_eventで登録しています。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?