使用角色和指令扩展语法

概述

reStructuredText 和 MyST 的语法都可以通过创建新的 指令(用于块级元素)和 角色(用于内联元素)来扩展。

在本教程中,我们将扩展 Sphinx 以添加

  • 一个 hello 角色,它将简单地输出文本 Hello {text}!

  • 一个 hello 指令,它将简单地输出文本 Hello {text}!,作为一个段落。

对于此扩展,您将需要一些 Python 的基本了解,我们还将介绍 docutils API 的各个方面。

设置项目

您可以使用现有的 Sphinx 项目,也可以使用 sphinx-quickstart 创建一个新项目。

通过这个,我们将扩展添加到项目中的 source 文件夹中

  1. source 中创建一个 _ext 文件夹

  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.

我们还返回 扩展元数据,指示我们扩展的版本,以及可以安全地将扩展用于并行读取和写入的事实。

使用扩展

必须在您的 conf.py 文件中声明扩展,以使 Sphinx 知道它。这里有两个必要的步骤

  1. 使用 sys.path.append_ext 目录添加到 Python 路径。这应该放在文件的顶部。

  2. 更新或创建 extensions 列表,并将扩展文件名添加到列表中

例如

import sys
from pathlib import Path

sys.path.append(str(Path('_ext').resolve()))

extensions = ['helloworld']

提示

因为我们没有将我们的扩展作为 Python 包 安装,所以我们需要修改 Python 路径,以便 Sphinx 可以找到我们的扩展。这就是为什么我们需要调用 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.

延伸阅读

这是一个创建新角色和指令的扩展的基本原理。

有关更高级的示例,请参阅 扩展构建过程

如果您希望在多个项目或与他人共享您的扩展,请查看 第三方扩展 部分。