Erweitern der Syntax mit Rollen und Direktiven

Übersicht

Die Syntax von reStructuredText und MyST kann durch die Erstellung neuer Direktiven – für Block-Level-Elemente – und Rollen – für Inline-Elemente – erweitert werden.

In diesem Tutorial erweitern wir Sphinx um

  • eine hello-Rolle, die einfach den Text Hallo {text}! ausgibt.

  • eine hello-Direktive, die einfach den Text Hallo {text}! als Absatz ausgibt.

Für diese Erweiterung benötigen Sie grundlegende Python-Kenntnisse, und wir werden auch Aspekte der docutils-API einführen.

Einrichten des Projekts

Sie können entweder ein bestehendes Sphinx-Projekt verwenden oder mit sphinx-quickstart ein neues erstellen.

Damit fügen wir die Erweiterung zum Projekt hinzu, innerhalb des source-Ordners.

  1. Erstellen Sie einen _ext-Ordner in source.

  2. Erstellen Sie eine neue Python-Datei im _ext-Ordner mit dem Namen helloworld.py.

Hier ist ein Beispiel für die Ordnerstruktur, die Sie erhalten könnten

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

Schreiben der Erweiterung

Öffnen Sie helloworld.py und fügen Sie den folgenden Code ein.

 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    }

In diesem Beispiel geschehen einige wesentliche Dinge.

Die Rollenklasse

Unsere neue Rolle wird in der HelloRole-Klasse deklariert.

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], []

Diese Klasse erweitert die Klasse SphinxRole. Die Klasse enthält eine run-Methode, die für jede Rolle erforderlich ist. Sie enthält die Hauptlogik der Rolle und gibt ein Tupel zurück, das Folgendes enthält:

  • eine Liste von Inline-Docutils-Knoten, die von Sphinx verarbeitet werden sollen.

  • eine (optionale) Liste von Systemmeldungs-Knoten.

Die Direktivenklasse

Unsere neue Direktive wird in der HelloDirective-Klasse deklariert.

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]

Diese Klasse erweitert die Klasse SphinxDirective. Die Klasse enthält eine run-Methode, die für jede Direktive erforderlich ist. Sie enthält die Hauptlogik der Direktive und gibt eine Liste von Block-Level-Docutils-Knoten zurück, die von Sphinx verarbeitet werden sollen. Sie enthält außerdem ein Attribut required_arguments, das Sphinx mitteilt, wie viele Argumente für die Direktive erforderlich sind.

Was sind docutils-Knoten?

Wenn Sphinx ein Dokument analysiert, erstellt es einen "Abstract Syntax Tree" (AST) von Knoten, die den Inhalt des Dokuments in einer strukturierten Weise darstellen, die im Allgemeinen unabhängig von einem einzelnen Eingabeformat (rST, MyST usw.) oder Ausgabeformat (HTML, LaTeX usw.) ist. Es ist ein Baum, da jeder Knoten Kindknoten haben kann und so weiter.

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

Das Paket docutils bietet viele eingebaute Knoten, um verschiedene Arten von Inhalten wie Text, Absätze, Referenzen, Tabellen usw. darzustellen.

Jeder Knotentyp akzeptiert im Allgemeinen nur eine bestimmte Menge an direkten Kindknoten. Zum Beispiel sollte der document-Knoten nur "Block-Level"-Knoten enthalten, wie paragraph, section, table usw., während der paragraph-Knoten nur "Inline-Level"-Knoten enthalten sollte, wie text, emphasis, strong usw.

Siehe auch

Die docutils-Dokumentation zum Erstellen von Direktiven und Erstellen von Rollen.

Die Funktion setup

Diese Funktion ist erforderlich. Wir verwenden sie, um unsere neue Direktive in Sphinx einzubinden.

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,
    }

Das Einfachste, was Sie tun können, ist, die Methoden Sphinx.add_role() und Sphinx.add_directive() aufzurufen, was wir hier getan haben. Für diesen spezifischen Aufruf ist das erste Argument der Name der Rolle/Direktive selbst, wie er in einer reStructuredText-Datei verwendet wird. In diesem Fall würden wir hello verwenden. Zum Beispiel:

Some intro text here...

.. hello:: world

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

Wir geben auch die Erweiterungsmetadaten zurück, die die Version unserer Erweiterung angeben und dass die Erweiterung sowohl für paralleles Lesen als auch Schreiben sicher verwendet werden kann.

Verwenden der Erweiterung

Die Erweiterung muss in Ihrer conf.py-Datei deklariert werden, damit Sphinx sie erkennt. Hier sind zwei Schritte notwendig:

  1. Fügen Sie das Verzeichnis _ext mit sys.path.append zum Python-Pfad hinzu. Dies sollte am Anfang der Datei platziert werden.

  2. Aktualisieren oder erstellen Sie die Liste extensions und fügen Sie den Namen der Erweiterungsdatei zur Liste hinzu.

Zum Beispiel

import sys
from pathlib import Path

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

extensions = ['helloworld']

Tipp

Da wir unsere Erweiterung nicht als Python-Paket installiert haben, müssen wir den Python-Pfad ändern, damit Sphinx unsere Erweiterung finden kann. Deshalb benötigen wir den Aufruf von sys.path.append.

Sie können die Erweiterung jetzt in einer Datei verwenden. Zum Beispiel:

Some intro text here...

.. hello:: world

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

Das obige Beispiel würde Folgendes generieren:

Some intro text here...

Hello world!

Some text with a hello world! role.

Weitere Lektüre

Dies ist das sehr grundlegende Prinzip einer Erweiterung, die eine neue Rolle und Direktive erstellt.

Für ein fortgeschritteneres Beispiel siehe Erweiterung des Build-Prozesses.

Wenn Sie Ihre Erweiterung über mehrere Projekte oder mit anderen teilen möchten, lesen Sie den Abschnitt Drittanbieter-Erweiterungen.