ロールとディレクティブで構文を拡張する

概要

reStructuredText と MyST の構文は、新しいディレクティブ(ブロックレベル要素向け)と **ロール**(インライン要素向け)を作成することで拡張できます。

このチュートリアルでは、Sphinx を拡張して次を追加します。

  • hello ロール。これは単にテキスト Hello {text}! を出力します。

  • hello ディレクティブ。これは単にテキスト Hello {text}! を段落として出力します。

この拡張機能では、Python の基礎を少し理解する必要があり、docutils API の側面についても紹介します。

プロジェクトの設定

既存の Sphinx プロジェクトを使用するか、sphinx-quickstart を使用して新しいプロジェクトを作成できます。

これにより、source フォルダー内のプロジェクトに拡張機能を追加します。

  1. _ext フォルダーを source に作成します。

  2. _ext フォルダーに helloworld.py という新しい Python ファイルを作成します。

入手する可能性のあるフォルダー構造の例を次に示します。

└── source
    ├── _ext
    │   └── helloworld.py
    ├── conf.py
    ├── index.rst

拡張機能の記述

helloworld.py を開き、次のコードを貼り付けます。

 1from __future__ import annotations
 2
 3from docutils import nodes
 4
 5from sphinx.application import Sphinx
 6from sphinx.util.docutils import SphinxDirective, SphinxRole
 7from sphinx.util.typing import ExtensionMetadata
 8
 9
10class HelloRole(SphinxRole):
11    """A role to say hello!"""
12
13    def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]:
14        node = nodes.inline(text=f'Hello {self.text}!')
15        return [node], []
16
17
18class HelloDirective(SphinxDirective):
19    """A directive to say hello!"""
20
21    required_arguments = 1
22
23    def run(self) -> list[nodes.Node]:
24        paragraph_node = nodes.paragraph(text=f'hello {self.arguments[0]}!')
25        return [paragraph_node]
26
27
28def setup(app: Sphinx) -> ExtensionMetadata:
29    app.add_role('hello', HelloRole())
30    app.add_directive('hello', HelloDirective)
31
32    return {
33        'version': '0.1',
34        'parallel_read_safe': True,
35        'parallel_write_safe': True,
36    }

この例では、いくつかの重要なことが行われています。

ロール クラス

新しいロールは HelloRole クラスで宣言されます。

1class HelloRole(SphinxRole):
2    """A role to say hello!"""
3
4    def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]:
5        node = nodes.inline(text=f'Hello {self.text}!')
6        return [node], []

このクラスは SphinxRole クラスを継承します。クラスには、ロールごとに必須の run メソッドが含まれています。ロールのメイン ロジックが含まれており、次のものを含むタプルを返します。

  • Sphinx によって処理されるインラインレベルの docutils ノードのリスト。

  • (オプションの)システム メッセージ ノードのリスト

ディレクティブ クラス

新しいディレクティブは HelloDirective クラスに宣言されます。

1class HelloDirective(SphinxDirective):
2    """A directive to say hello!"""
3
4    required_arguments = 1
5
6    def run(self) -> list[nodes.Node]:
7        paragraph_node = nodes.paragraph(text=f'hello {self.arguments[0]}!')
8        return [paragraph_node]

このクラスは SphinxDirective クラスを継承しています。クラスには run メソッドが含まれています。これはすべてのディレクティブで必須です。ディレクティブの主なロジックが含まれており、Sphinxによって処理されるブロックレベルdocutilsノードのリストを返します。また、 required_arguments 属性も含まれており、Sphinxにディレクティブに必要な引数の数を伝えます。

docutilsノードとは?

Sphinxがドキュメントを解析すると、ドキュメントのコンテンツを構造化された方法で表すノードの「抽象構文木」(AST)を作成します。これは通常、入力(rST、MySTなど)または出力(HTML、LaTeXなど)の形式とは無関係です。各ノードに子ノードが存在するため、ツリーになります

<document>
   <paragraph>
      <text>
         Hello world!

docutils パッケージには、テキスト、段落、参照、表など、さまざまな種類のコンテンツを表現するための多くの 組み込みノード が用意されています。

各ノードの種類は通常、特定の直接の子ノードのセットのみを受け入れます。たとえば、 document ノードには paragraphsectiontable など「ブロックレベル」のノードのみを含み、 paragraph ノードには textemphasisstrong など「インラインレベル」のノードのみが含まれます。

以下も参照してください

docutilsのドキュメント ディレクティブの作成およびロールの作成

setup 関数

この関数は必須です。これを利用して新しいディレクティブをSphinxに組み込みます。

def setup(app: Sphinx) -> ExtensionMetadata:
    app.add_role('hello', HelloRole())
    app.add_directive('hello', HelloDirective)

    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }

最も簡単な方法は、Sphinx.add_role() メソッドと Sphinx.add_directive() メソッドを呼び出すことで、実際にこの方法を使用しました。この特定の呼び出しでは、最初の引数は、reStructuredTextファイルで使用されるロール/ディレクティブ自体の名前です。この場合、 hello を使用します。たとえば

Some intro text here...

.. hello:: world

Some text with a :hello:`world` role.

また、拡張機能のバージョンを示す 拡張機能メタデータを返します。また、拡張機能をパラレル読み取りと書き込みの両方に安全に使用できることを示します。

拡張機能の使用

Sphinxに認識させるために、 conf.py ファイルで拡張機能を宣言する必要があります。ここには次の2つの手順が必要です

  1. _ext ディレクトリを sys.path.append を使用して Pythonパス に追加します。これはファイルの先頭に配置する必要があります。

  2. extensionsリストを更新または作成し、拡張ファイル名をリストに追加します

import os
import sys

sys.path.append(os.path.abspath("./_ext"))

extensions = ['helloworld']

ヒント

拡張機能をPython パッケージとしてインストールしていないため、Sphinxが拡張機能を見つけられるようにPython パスを変更する必要があります。これが、sys.path.appendを呼び出す必要がある理由です。

これで、ファイル内で拡張機能を使用できます。例

Some intro text here...

.. hello:: world

Some text with a :hello:`world` role.

上記のサンプルは以下を生成します

Some intro text here...

Hello world!

Some text with a hello world! role.

詳細情報

これは、新しいロールとディレクティブを作成する拡張機能のとても基本的な原理です。

より高度な例については、Extending the build processを参照してください。

拡張機能を複数のプロジェクトや他の人と共有したい場合は、Third-party extensionsセクションを確認してください。