mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
224 lines
6.2 KiB
ReStructuredText
224 lines
6.2 KiB
ReStructuredText
|
.. _tutorial-extending-syntax:
|
|||
|
|
|||
|
Extending syntax with roles and directives
|
|||
|
==========================================
|
|||
|
|
|||
|
Overview
|
|||
|
--------
|
|||
|
|
|||
|
The syntax of both reStructuredText and MyST can be extended
|
|||
|
by creating new **directives** - for block-level elements -
|
|||
|
and **roles** - for inline elements.
|
|||
|
|
|||
|
In this tutorial we shall extend Sphinx to add:
|
|||
|
|
|||
|
* A ``hello`` role, that will simply output the text ``Hello {text}!``.
|
|||
|
* A ``hello`` directive, that will simply output the text ``Hello {text}!``,
|
|||
|
as a paragraph.
|
|||
|
|
|||
|
For this extension, you will need some basic understanding of Python,
|
|||
|
and we shall also introduce aspects of the docutils_ API.
|
|||
|
|
|||
|
Setting up the project
|
|||
|
----------------------
|
|||
|
|
|||
|
You can either use an existing Sphinx project
|
|||
|
or create a new one using :program:`sphinx-quickstart`.
|
|||
|
|
|||
|
With this we will add the extension to the project,
|
|||
|
within the :file:`source` folder:
|
|||
|
|
|||
|
#. Create an :file:`_ext` folder in :file:`source`
|
|||
|
#. Create a new Python file in the :file:`_ext` folder called
|
|||
|
:file:`helloworld.py`
|
|||
|
|
|||
|
Here is an example of the folder structure you might obtain:
|
|||
|
|
|||
|
.. code-block:: text
|
|||
|
|
|||
|
└── source
|
|||
|
├── _ext
|
|||
|
│ └── helloworld.py
|
|||
|
├── conf.py
|
|||
|
├── index.rst
|
|||
|
|
|||
|
|
|||
|
Writing the extension
|
|||
|
---------------------
|
|||
|
|
|||
|
Open :file:`helloworld.py` and paste the following code in it:
|
|||
|
|
|||
|
.. literalinclude:: examples/helloworld.py
|
|||
|
:language: python
|
|||
|
:linenos:
|
|||
|
|
|||
|
Some essential things are happening in this example:
|
|||
|
|
|||
|
The role class
|
|||
|
...............
|
|||
|
|
|||
|
Our new role is declared in the ``HelloRole`` class.
|
|||
|
|
|||
|
.. literalinclude:: examples/helloworld.py
|
|||
|
:language: python
|
|||
|
:linenos:
|
|||
|
:pyobject: HelloRole
|
|||
|
|
|||
|
This class extends the :class:`.SphinxRole` class.
|
|||
|
The class contains a ``run`` method,
|
|||
|
which is a requirement for every role.
|
|||
|
It contains the main logic of the role and it
|
|||
|
returns a tuple containing:
|
|||
|
|
|||
|
- a list of inline-level docutils nodes to be processed by Sphinx.
|
|||
|
- an (optional) list of system message nodes
|
|||
|
|
|||
|
The directive class
|
|||
|
...................
|
|||
|
|
|||
|
Our new directive is declared in the ``HelloDirective`` class.
|
|||
|
|
|||
|
.. literalinclude:: examples/helloworld.py
|
|||
|
:language: python
|
|||
|
:linenos:
|
|||
|
:pyobject: HelloDirective
|
|||
|
|
|||
|
This class extends the :class:`.SphinxDirective` class.
|
|||
|
The class contains a ``run`` method,
|
|||
|
which is a requirement for every directive.
|
|||
|
It contains the main logic of the directive and it
|
|||
|
returns a list of block-level docutils nodes to be processed by Sphinx.
|
|||
|
It also contains a ``required_arguments`` attribute,
|
|||
|
which tells Sphinx how many arguments are required for the directive.
|
|||
|
|
|||
|
What are docutils nodes?
|
|||
|
........................
|
|||
|
|
|||
|
When Sphinx parses a document,
|
|||
|
it creates an "Abstract Syntax Tree" (AST) of nodes
|
|||
|
that represent the content of the document in a structured way,
|
|||
|
that is generally independent of any one
|
|||
|
input (rST, MyST, etc) or output (HTML, LaTeX, etc) format.
|
|||
|
It is a tree because each node can have children nodes, and so on:
|
|||
|
|
|||
|
.. code-block:: xml
|
|||
|
|
|||
|
<document>
|
|||
|
<paragraph>
|
|||
|
<text>
|
|||
|
Hello world!
|
|||
|
|
|||
|
The docutils_ package provides many `built-in nodes <docutils nodes_>`_,
|
|||
|
to represent different types of content such as
|
|||
|
text, paragraphs, references, tables, etc.
|
|||
|
|
|||
|
Each node type generally only accepts a specific set of direct child nodes,
|
|||
|
for example the ``document`` node should only contain "block-level" nodes,
|
|||
|
such as ``paragraph``, ``section``, ``table``, etc,
|
|||
|
whilst the ``paragraph`` node should only contain "inline-level" nodes,
|
|||
|
such as ``text``, ``emphasis``, ``strong``, etc.
|
|||
|
|
|||
|
.. seealso::
|
|||
|
|
|||
|
The docutils documentation on
|
|||
|
`creating directives <docutils directives_>`_, and
|
|||
|
`creating roles <docutils roles_>`_.
|
|||
|
|
|||
|
The ``setup`` function
|
|||
|
......................
|
|||
|
|
|||
|
This function is a requirement.
|
|||
|
We use it to plug our new directive into Sphinx.
|
|||
|
|
|||
|
.. literalinclude:: examples/helloworld.py
|
|||
|
:language: python
|
|||
|
:pyobject: setup
|
|||
|
|
|||
|
The simplest thing you can do is to call the
|
|||
|
:meth:`.Sphinx.add_role` and :meth:`.Sphinx.add_directive` methods,
|
|||
|
which is what we've done here.
|
|||
|
For this particular call, the first argument is the name of the role/directive itself
|
|||
|
as used in a reST file.
|
|||
|
In this case, we would use ``hello``. For example:
|
|||
|
|
|||
|
.. code-block:: rst
|
|||
|
|
|||
|
Some intro text here...
|
|||
|
|
|||
|
.. hello:: world
|
|||
|
|
|||
|
Some text with a :hello:`world` role.
|
|||
|
|
|||
|
We also return the :ref:`extension metadata <ext-metadata>` that indicates the
|
|||
|
version of our extension, along with the fact that it is safe to use the
|
|||
|
extension for both parallel reading and writing.
|
|||
|
|
|||
|
Using the extension
|
|||
|
-------------------
|
|||
|
|
|||
|
The extension has to be declared in your :file:`conf.py` file to make Sphinx
|
|||
|
aware of it. There are two steps necessary here:
|
|||
|
|
|||
|
#. Add the :file:`_ext` directory to the `Python path`_ using
|
|||
|
``sys.path.append``. This should be placed at the top of the file.
|
|||
|
|
|||
|
#. Update or create the :confval:`extensions` list and add the extension file
|
|||
|
name to the list
|
|||
|
|
|||
|
For example:
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
import os
|
|||
|
import sys
|
|||
|
|
|||
|
sys.path.append(os.path.abspath("./_ext"))
|
|||
|
|
|||
|
extensions = ['helloworld']
|
|||
|
|
|||
|
.. tip::
|
|||
|
|
|||
|
Because we haven't installed our extension as a `Python package`_, we need to
|
|||
|
modify the `Python path`_ so Sphinx can find our extension. This is why we
|
|||
|
need the call to ``sys.path.append``.
|
|||
|
|
|||
|
You can now use the extension in a file. For example:
|
|||
|
|
|||
|
.. code-block:: rst
|
|||
|
|
|||
|
Some intro text here...
|
|||
|
|
|||
|
.. hello:: world
|
|||
|
|
|||
|
Some text with a :hello:`world` role.
|
|||
|
|
|||
|
The sample above would generate:
|
|||
|
|
|||
|
.. code-block:: text
|
|||
|
|
|||
|
Some intro text here...
|
|||
|
|
|||
|
Hello world!
|
|||
|
|
|||
|
Some text with a hello world! role.
|
|||
|
|
|||
|
|
|||
|
Further reading
|
|||
|
---------------
|
|||
|
|
|||
|
This is the very basic principle of an extension
|
|||
|
that creates a new role and directive.
|
|||
|
|
|||
|
For a more advanced example, refer to :ref:`tutorial-extend-build`.
|
|||
|
|
|||
|
If you wish to share your extension across multiple projects or with others,
|
|||
|
check out the :ref:`third-party-extensions` section.
|
|||
|
|
|||
|
.. _docutils: https://docutils.sourceforge.io/
|
|||
|
.. _docutils roles: https://docutils.sourceforge.io/docs/howto/rst-roles.html
|
|||
|
.. _docutils directives: https://docutils.sourceforge.io/docs/howto/rst-directives.html
|
|||
|
.. _docutils nodes: https://docutils.sourceforge.io/docs/ref/doctree.html
|
|||
|
.. _PyPI: https://pypi.org/
|
|||
|
.. _Python package: https://packaging.python.org/
|
|||
|
.. _Python path: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|