Estendendo a sintaxe com papéis e diretivas¶
Visão geral¶
A sintaxe de reStructuredText e MyST pode ser estendida criando novas diretivas - para elementos em nível de bloco - e papéis - para elementos embutidos.
Neste tutorial vamos estender o Sphinx para adicionar:
Um papel
hello, que simplesmente produzirá o textoHello {text}!.Uma diretiva
hello, que simplesmente produzirá o textoHello {text}!, como um parágrafo.
Para esta extensão, você precisará de algum conhecimento básico de Python e também apresentaremos aspectos da API do docutils.
Configurando o projeto¶
Você pode usar um projeto Sphinx existente ou criar um novo usando sphinx-quickstart.
Com isso vamos adicionar a extensão ao projeto, dentro da pasta source:
Crie uma pasta
_extemsourceCrie um novo arquivo Python na pasta
_extchamadohelloworld.py
Aqui está um exemplo da estrutura de pastas que você pode obter:
└── source
├── _ext
│ └── helloworld.py
├── conf.py
├── index.rst
Escrevendo a extensão¶
Abra helloworld.py e cole o seguinte código nele:
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 }
Algumas coisas essenciais estão acontecendo neste exemplo:
A classe do papel¶
Nosso novo papel é declarado na classe 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], []
Esta classe estende a classe SphinxRole. A classe contém um método run, que é um requisito para cada papel. Contém a lógica principal do papel e retorna uma tupla contendo:
uma lista de nós docutils de nível em linha a serem processados pelo Sphinx.
uma lista (opcional) de nós de mensagens do sistema
A classe da diretiva¶
Nossa nova diretiva é declarada na classe 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]
Esta classe estende a classe SphinxDirective. A classe contém um método run, que é um requisito para toda diretiva. Ele contém a lógica principal da diretiva e retorna uma lista de nós docutils em nível de bloco a serem processados pelo Sphinx. Ele também contém um atributo required_arguments, que informa ao Sphinx quantos argumentos são necessários para a diretiva.
O que são nós de docutils?¶
Quando o Sphinx analisa um documento, ele cria uma “Árvore Sintática Abstrata” (AST) de nós que representam o conteúdo do documento de uma forma estruturada, que geralmente é independente de qualquer entrada (rST, MyST, etc) ou saída (HTML , LaTeX, etc.). É uma árvore porque cada nó pode ter nós filhos e assim por diante:
<document>
<paragraph>
<text>
Hello world!
O pacote docutils fornece muitos nós embutidos, para representar diferentes tipos de conteúdo, como texto, parágrafos, referências, tabelas, etc.
Cada tipo de nó geralmente aceita apenas um conjunto específico de nós filhos diretos, por exemplo, o nó document deve conter apenas nós de “nível de bloco”, como paragraph, section, table, etc, enquanto o nó paragraph deve conter apenas nós de “nível em linha”, como text, emphasis, strong, etc.
Ver também
A documentação do docutils sobre a criação de diretivas e criação de papéis.
A função setup¶
Esta função é um requisito. Nós o usamos para conectar nossa nova diretiva ao 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,
}
A coisa mais simples que você pode fazer é chamar os métodos Sphinx.add_role() e Sphinx.add_directive(), que é o que fizemos aqui. Para esta chamada específica, o primeiro argumento é o nome do próprio papel ou da própria diretiva usados em um arquivo reStructuredText. Neste caso, usaríamos hello. Por exemplo:
Some intro text here...
.. hello:: world
Some text with a :hello:`world` role.
Também retornamos os metadados da extensão que indicam a versão da nossa extensão, juntamente com o fato de que é seguro usar a extensão tanto para leitura quanto para escrita paralela.
Usando a extensão¶
A extensão deve ser declarada em seu arquivo conf.py para que o Sphinx saiba disso. Existem duas etapas necessárias aqui:
Adicione o diretório
_extao caminho do Python usandosys.path.append. Isso deve ser colocado no topo do arquivo.Atualize ou crie a lista
extensionse adicione o nome do arquivo de extensão à lista
Por exemplo:
import sys
from pathlib import Path
sys.path.append(str(Path('_ext').resolve()))
extensions = ['helloworld']
Dica
Por não termos instalado nossa extensão como um pacote Python, precisamos modificar o caminho Python para que o Sphinx possa encontrar nossa extensão. É por isso que precisamos da chamada para sys.path.append.
Agora você pode usar a extensão em um arquivo. Por exemplo:
Some intro text here...
.. hello:: world
Some text with a :hello:`world` role.
A exemplo acima geraria:
Some intro text here...
Hello world!
Some text with a hello world! role.
Leitura adicional¶
Este é o princípio básico de uma extensão que cria um novo papel e uma nova diretiva.
Para um exemplo mais avançado, consulte Estendendo o processo de construção.
Se você deseja compartilhar sua extensão entre vários projetos ou com outras pessoas, confira a seção Extensões de terceiros.