[docs] Improve tutorials (#12473)

This commit make it clearer, from the filenames and titles,
what the extension tutorials are intended to teach.
This commit is contained in:
Chris Sewell
2024-06-27 22:30:03 +02:00
committed by GitHub
parent 0b5fd6289d
commit d130c2e710
10 changed files with 304 additions and 221 deletions

View File

@@ -373,6 +373,7 @@ select = [
[lint.per-file-ignores]
"doc/*" = [
"ANN", # documentation doesn't need annotations
"TCH001", # documentation doesn't need type-checking blocks
]
"doc/conf.py" = ["INP001", "W605"]
"doc/development/tutorials/examples/*" = ["INP001"]

View File

@@ -306,6 +306,9 @@ def build_redirects(app: Sphinx, exception: Exception | None) -> None:
(('development', 'builders.html'), 'howtos/builders.html'),
(('development', 'theming.html'), 'html_themes/index.html'),
(('development', 'templating.html'), 'html_themes/templating.html'),
(('development', 'tutorials', 'helloworld.html'), 'extending_syntax.html'),
(('development', 'tutorials', 'todo.html'), 'extending_build.html'),
(('development', 'tutorials', 'recipe.html'), 'adding_domain.html'),
):
path = app.outdir.joinpath(*page)
if path.exists():

View File

@@ -1,5 +1,7 @@
Developing a "recipe" extension
===============================
.. _tutorial-adding-domain:
Adding a reference domain
=========================
The objective of this tutorial is to illustrate roles, directives and domains.
Once complete, we will be able to use this extension to describe a recipe and
@@ -41,7 +43,9 @@ For that, we will need to add the following elements to Sphinx:
Prerequisites
-------------
We need the same setup as in :doc:`the previous extensions <todo>`. This time,
We need the same setup as in
:ref:`the previous extensions <tutorial-extend-build>`.
This time,
we will be putting out extension in a file called :file:`recipe.py`.
Here is an example of the folder structure you might obtain:
@@ -77,7 +81,8 @@ The first thing to examine is the ``RecipeDirective`` directive:
:linenos:
:pyobject: RecipeDirective
Unlike :doc:`helloworld` and :doc:`todo`, this directive doesn't derive from
Unlike :ref:`tutorial-extending-syntax` and :ref:`tutorial-extend-build`,
this directive doesn't derive from
:class:`docutils.parsers.rst.Directive` and doesn't define a ``run`` method.
Instead, it derives from :class:`sphinx.directives.ObjectDescription` and
defines ``handle_signature`` and ``add_target_and_index`` methods. This is
@@ -90,9 +95,10 @@ for this node.
We also see that this directive defines ``has_content``, ``required_arguments``
and ``option_spec``. Unlike the ``TodoDirective`` directive added in the
:doc:`previous tutorial <todo>`, this directive takes a single argument, the
recipe name, and an option, ``contains``, in addition to the nested
reStructuredText in the body.
:ref:`previous tutorial <tutorial-extend-build>`,
this directive takes a single argument,
the recipe name, and an option, ``contains``,
in addition to the nested reStructuredText in the body.
.. rubric:: The index classes
@@ -167,7 +173,8 @@ indices and our cross-referencing code use this feature.
.. currentmodule:: sphinx.application
:doc:`As always <todo>`, the ``setup`` function is a requirement and is used to
:ref:`As always <tutorial-extend-build>`,
the ``setup`` function is a requirement and is used to
hook the various parts of our extension into Sphinx. Let's look at the
``setup`` function for this extension.
@@ -224,4 +231,7 @@ Further reading
For more information, refer to the `docutils`_ documentation and
:doc:`/extdev/index`.
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/docs/

View File

@@ -1,7 +1,7 @@
.. _autodoc_ext_tutorial:
Developing autodoc extension for IntEnum
========================================
Developing autodoc extensions
=============================
The objective of this tutorial is to create an extension that adds
support for new type for autodoc. This autodoc extension will format
@@ -27,8 +27,10 @@ We want to add following to autodoc:
Prerequisites
-------------
We need the same setup as in :doc:`the previous extensions <todo>`. This time,
we will be putting out extension in a file called :file:`autodoc_intenum.py`.
We need the same setup as in
:ref:`the previous extensions <tutorial-extend-build>`.
This time, we will be putting out extension
in a file called :file:`autodoc_intenum.py`.
The :file:`my_enums.py` will contain the sample enums we will document.
Here is an example of the folder structure you might obtain:
@@ -139,3 +141,9 @@ This will be the documentation file with auto-documentation directive:
:caption: index.rst
.. autointenum:: my_enums.Colors
Further reading
---------------
If you wish to share your extension across multiple projects or with others,
check out the :ref:`third-party-extensions` section.

View File

@@ -1,18 +1,33 @@
from __future__ import annotations
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective, SphinxRole
from sphinx.util.typing import ExtensionMetadata
class HelloWorld(Directive):
def run(self):
paragraph_node = nodes.paragraph(text='Hello World!')
class HelloRole(SphinxRole):
"""A role to say hello!"""
def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]:
node = nodes.inline(text=f'Hello {self.text}!')
return [node], []
class HelloDirective(SphinxDirective):
"""A directive to say hello!"""
required_arguments = 1
def run(self) -> list[nodes.Node]:
paragraph_node = nodes.paragraph(text=f'hello {self.arguments[0]}!')
return [paragraph_node]
def setup(app: Sphinx) -> ExtensionMetadata:
app.add_directive('helloworld', HelloWorld)
app.add_role('hello', HelloRole())
app.add_directive('hello', HelloDirective)
return {
'version': '0.1',

View File

@@ -1,14 +1,20 @@
Developing a "TODO" extension
=============================
.. _tutorial-extend-build:
Extending the build process
===========================
The objective of this tutorial is to create a more comprehensive extension than
that created in :doc:`helloworld`. Whereas that guide just covered writing a
custom :term:`directive`, this guide adds multiple directives, along with custom
nodes, additional config values and custom event handlers. To this end, we will
cover a ``todo`` extension that adds capabilities to include todo entries in the
documentation, and to collect these in a central place. This is similar the
``sphinxext.todo`` extension distributed with Sphinx.
that created in :ref:`tutorial-extending-syntax`.
Whereas that guide just covered writing
a custom :term:`role` and :term:`directive`,
this guide covers a more complex extension to the Sphinx build process;
adding multiple directives,
along with custom nodes, additional config values and custom event handlers.
To this end, we will cover a ``todo`` extension
that adds capabilities to include todo entries in the documentation,
and to collect these in a central place.
This is similar to the :mod:`sphinx.ext.todo` extension distributed with Sphinx.
Overview
--------
@@ -47,7 +53,8 @@ For that, we will need to add the following elements to Sphinx:
Prerequisites
-------------
As with :doc:`helloworld`, we will not be distributing this plugin via PyPI so
As with :ref:`tutorial-extending-syntax`,
we will not be distributing this plugin via PyPI so
once again we need a Sphinx project to call this from. You can use an existing
project or create a new one using :program:`sphinx-quickstart`.
@@ -83,7 +90,8 @@ explain in detail shortly:
:language: python
:linenos:
This is far more extensive extension than the one detailed in :doc:`helloworld`,
This is far more extensive extension than the one detailed in
:ref:`tutorial-extending-syntax`,
however, we will will look at each piece step-by-step to explain what's
happening.
@@ -250,7 +258,8 @@ ID as the anchor name.
.. currentmodule:: sphinx.application
As noted :doc:`previously <helloworld>`, the ``setup`` function is a requirement
As noted :ref:`previously <tutorial-extending-syntax>`,
the ``setup`` function is a requirement
and is used to plug directives into Sphinx. However, we also use it to hook up
the other parts of our extension. Let's look at our ``setup`` function:
@@ -361,6 +370,9 @@ Further reading
For more information, refer to the `docutils`_ documentation and
:doc:`/extdev/index`.
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/docs/
.. _Python path: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH

View File

@@ -0,0 +1,223 @@
.. _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

View File

@@ -1,189 +0,0 @@
Developing a "Hello world" extension
====================================
The objective of this tutorial is to create a very basic extension that adds a
new directive. This directive will output a paragraph containing "hello world".
Only basic information is provided in this tutorial. For more information, refer
to the :ref:`other tutorials <extension-tutorials-index>` that go into more details.
.. warning::
For this extension, you will need some basic understanding of docutils_
and Python.
Overview
--------
We want the extension to add the following to Sphinx:
* A ``helloworld`` directive, that will simply output the text "hello world".
Prerequisites
-------------
We will not be distributing this plugin via `PyPI`_ and will instead include it
as part of an existing project. This means you will need to use an existing
project or create a new one using :program:`sphinx-quickstart`.
We assume you are using separate source (:file:`source`) and build
(:file:`build`) folders. Your extension file could be in any folder of your
project. In our case, let's do the following:
#. 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
   ├── _static
   ├── conf.py
   ├── somefolder
   ├── index.rst
   ├── somefile.rst
   └── someotherfile.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, and you will see them for
all directives.
.. rubric:: The directive class
Our new directive is declared in the ``HelloWorld`` class.
.. literalinclude:: examples/helloworld.py
:language: python
:linenos:
:lines: 5-9
This class extends the docutils_' ``Directive`` class. All extensions that
create directives should extend this class.
.. seealso::
`The docutils documentation on creating directives <docutils directives_>`_
This class contains a ``run`` method. This method is a requirement and it is
part of every directive. It contains the main logic of the directive and it
returns a list of docutils nodes to be processed by Sphinx. These nodes are
docutils' way of representing the content of a document. There are many types of
nodes available: text, paragraph, reference, table, etc.
.. seealso::
`The docutils documentation on nodes <docutils nodes_>`_
The ``nodes.paragraph`` class creates a new paragraph node. A paragraph
node typically contains some text that we can set during instantiation using
the ``text`` parameter.
.. rubric:: The ``setup`` function
.. currentmodule:: sphinx.application
This function is a requirement. We use it to plug our new directive into
Sphinx.
.. literalinclude:: examples/helloworld.py
:language: python
:linenos:
:lines: 12-
The simplest thing you can do is to call the :meth:`~Sphinx.add_directive` method,
which is what we've done here. For this particular call, the first argument is
the name of the directive itself as used in a reST file. In this case, we would
use ``helloworld``. For example:
.. code-block:: rst
Some intro text here...
.. helloworld::
Some more text here...
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::
We're not distributing this 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...
.. helloworld::
Some more text here...
The sample above would generate:
.. code-block:: text
Some intro text here...
Hello World!
Some more text here...
Further reading
---------------
This is the very basic principle of an extension that creates a new directive.
For a more advanced example, refer to :doc:`todo`.
.. _docutils: https://docutils.sourceforge.io/
.. _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

View File

@@ -6,7 +6,7 @@ Tutorials
.. toctree::
:maxdepth: 2
helloworld
todo
recipe
extending_syntax
extending_build
adding_domain
autodoc_ext

View File

@@ -147,7 +147,7 @@ the individual nodes of each doctree and produces some output in the process.
that checks external links does not need anything more than the parsed
doctrees and therefore does not have phases 2--4.
To see an example of application, refer to :doc:`../development/tutorials/todo`.
To see an example of application, refer to :ref:`tutorial-extend-build`.
.. _ext-metadata: