5.5. reStructuredText 自定义扩展
reStructuredText 提供了基于 docutils 模块的扩展支持能力。 扩展以 Python 代码编写,以新的指令或角色的形式提供。 由于 Python 的能力,还可以调用外部程序对内容进行处理。
在编写扩展之前,建议先掌握加载扩展的方法,这里提供一份 HelloWorld 扩展的代码,
作用是提供 helloworld 指令,将该指令转换为形如 Hello World! {{ 时间日期 }} 的段落:
1from docutils import nodes
2from docutils.parsers.rst import Directive
3from time import localtime, strftime
4class HelloWorld(Directive):
5 def run(self):
6 datetime = strftime("%Y-%m-%d %H:%M:%S", localtime())
7 paragraph = f"Hello World! {datetime}"
8 return [nodes.paragraph(text=paragraph)]
本文提供了 Sphinx 的扩展加载方式和 Nikola 加载插件的方式。 请在阅读这两个章节之一并成功运行上面的例子后,阅读后续的 编写扩展 章节。
5.5.1. Sphinx 加载扩展
Sphinx 的扩展可以通过预安装的包的形式来加载,也可以放置在当前项目的 source/_ext 目录下, _ext 目录名前的下划线是告诉模板引擎此文件夹内容不渲染,也可以将此文件夹任意取名,我们将在 source/conf.py 中配置它。
在 source/conf.py 中,通过 Python 的功能将扩展所在的文件夹加载到路径中,然后配置 extensions 变量,
在列表中添加模块名:
# ...
import sys
from pathlib import Path
sys.path.insert(0, Path("_ext").absolute().as_posix())
# ...
extensions = [
"helloworld"
]
为了让 Sphinx 加载此扩展,你需要在上面提供的功能代码 ext_helloworld 的基础上再添加一个 setup 函数:
1from docutils import nodes
2from docutils.parsers.rst import Directive
3from time import localtime, strftime
4# 提供智能提示
5from sphinx.application import Sphinx
6
7
8class HelloWorld(Directive):
9 def run(self):
10 datetime = strftime("%Y-%m-%d %H:%M:%S", localtime())
11 paragraph = f"Hello World! {datetime}"
12 return [nodes.paragraph(text=paragraph)]
13
14
15def setup(app: Sphinx):
16 """Sphinx 使用的加载函数
17
18 :param app: Sphinx 应用
19 :type app: sphinx.application.Sphinx
20 :returns: 该扩展的配置信息
21 """
22 app.add_directive("helloworld", HelloWorld)
23 return {
24 'version': '0.1',
25 # 允许并行读取
26 'parallel_read_safe': True,
27 # 允许并行写入
28 'parallel_write_safe': True,
29 }
.. helloworld::
Hello World! 2022-09-27 03:47:15
5.5.2. Nikola 加载插件
5.5.3. 编写扩展
扩展的两种形式:Directive 和 Role。
前者是 docutils.parsers.rst.Directive 实例,后者可以通过一个函数来加载。
5.5.3.1. 创建一个角色
我们先了解一下角色的创建方式,可以参考官方文档创建 reStructuredText 解释性文本角色 https://docutils.sourceforge.io/docs/howto/rst-roles.html 。
一个角色是一个函数:
- role_function(name, rawtext, text, lineno, inliner, options={}, content=[])
- Parameters
name (str) – 该角色的名称
rawtext (str) – 包含该角色完整标记的纯文本,常用来在发生错误时通过 problematic 结点来抛出错误信息。
text (str) – 该角色的内容
lineno (int) – 该角色在源文件中所处的行数
inliner – 调用该函数的
docutils.parsers.rst.states.Inliner对象, 提供报告错误和访问文档树的一些有用的属性。options (Dict[str, str]) – 通过 role 指令创建的角色会传入此参数,是 role 指令设置的选项和它们的值。
content (List[str]) – 通过 role 指令创建的角色会传入此参数,是 role 指令的内容。 TODO:(截至 2012 年官方文档撰写时,没有角色会使用此参数)
- Returns
一个元组,包含两个元素:
一组结点,将被插入到文档树中角色出现的位置。
一组系统信息,将被插入到文档数中角色出现的块的后一个块中。
它们都可以是空列表。
- Return type
Tuple[List[nodes], List[messages]]
以下面的角色为例:
:github:`zombie110year/learn-rst`
当调用时,各参数的值将是
- name
github- rawtext
:github:`zombie110year/learn-rst`
- text
zombie110year/learn-rst- lineno
看情况,从 0 开始。
对于一个标准的角色,需要用 register_canonical_role 将其注册进 docutils 的解析器中:
docutils.parsers.rst.roles.register_canonical_role("github", github_role)
如果该角色是与所使用的应用程序相关的,则需要使用 register_local_role 函数来注册:
from docutils.parsers.rst import roles
roles.register_local_role(name, role_function)
另外还提供一个 register_generic_role 的注册函数,这个函数是为了便于将那些只是将文本处理为某个结点
的角色注册的,这样的角色没有其他功能:
from docutils.parsers.rst import roles, nodes
roles.register_generic_role("emphasize", nodes.emphasis)
对于我们的 github 角色 Github - zombie110year/learn-rst 来说,我们的目的就是将它转换成
链接到 GitHub 仓库的链接,那么就可以这么编写:
from docutils.parsers.rst import nodes
from docutils.parsers.rst import roles
from docutils.parsers.rst.states import Inliner
def github_role(name: str, rawtext: str, text: str, lineno: int, inliner: Inliner, options={}, content=[]):
"""格式化为链接
"""
# 判断是仓库还是用户
title = f"Github - {text}" if "/" in text else f"{text}@GitHub"
url = f"https://github.com/{text}"
# 返回一个超链接结点
return [nodes.reference(rawsource=rawtext, text=title, refuri=url)], []
这个功能将会判断我们提供的是一个仓库的名称还是一个用户的名称, 并且格式化为不同的显示名。例如:
本仓库为 :github:`zombie110year/learn-rst`,
它的创建者是 :github:`zombie110year`。
本仓库为 Github - zombie110year/learn-rst, 它的创建者是 zombie110year@GitHub。