扩展语法与角色和指令¶
概述¶
reStructuredText 和 MyST 的语法可以通过创建新的 **指令**(用于块级元素)和 **角色**(用于行内元素)来扩展.
在本教程中,我们将扩展 Sphinx 以添加:
一个
hello
角色,简单地输出文本Hello {text}!
.一个
hello
指令,将简单输出文本Hello {text}!
,作为一个段落.
对于这个扩展,您需要一定的Python基础知识,我们还将介绍docutils_ API的相关内容.
设置项目¶
您可以使用现有的 Sphinx 项目,或使用 sphinx-quickstart 创建一个新项目.
这将使我们能够将扩展添加到项目中,位于 source
文件夹内:
在
source
文件夹中创建一个_ext
文件夹在
_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
节点应该只包含 “块级” 节点,如 paragraph
、 section
、 table
等,而 paragraph
节点应该只包含 “行级” 节点,如 text
、 emphasis
、 strong
等.
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 知道它的存在.这里有两个必要的步骤:
将:file:_ext 目录添加到 Python path ,使用
sys.path.append
.这应放置在文件的顶部.更新或创建
extensions
列表,并将扩展文件名添加到列表中
例如:
import os
import sys
sys.path.append(os.path.abspath("./_ext"))
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.
进一步阅读¶
这是创建新角色和指令的扩展的基本原则.
对于一个更高级的例子,请参考 扩展构建过程 .
如果您希望在多个项目之间或与他人共享您的扩展,请查看 第三方扩展 部分.