2024-06-27 15:30:03 -05:00
|
|
|
|
.. _tutorial-adding-domain:
|
|
|
|
|
|
|
|
|
|
Adding a reference domain
|
|
|
|
|
=========================
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
reference that recipe from elsewhere in our documentation.
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
This tutorial is based on a guide first published on `opensource.com`_ and
|
|
|
|
|
is provided here with the original author's permission.
|
|
|
|
|
|
|
|
|
|
.. _opensource.com: https://opensource.com/article/18/11/building-custom-workflows-sphinx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Overview
|
|
|
|
|
--------
|
|
|
|
|
|
|
|
|
|
We want the extension to add the following to Sphinx:
|
|
|
|
|
|
|
|
|
|
* A ``recipe`` :term:`directive`, containing some content describing the recipe
|
2019-02-11 04:44:49 -06:00
|
|
|
|
steps, along with a ``:contains:`` option highlighting the main ingredients
|
2019-02-08 11:47:19 -06:00
|
|
|
|
of the recipe.
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
* A ``ref`` :term:`role`, which provides a cross-reference to the recipe
|
2019-02-08 11:47:19 -06:00
|
|
|
|
itself.
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
* A ``recipe`` :term:`domain`, which allows us to tie together the above role
|
|
|
|
|
and domain, along with things like indices.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
For that, we will need to add the following elements to Sphinx:
|
|
|
|
|
|
|
|
|
|
* A new directive called ``recipe``
|
|
|
|
|
|
|
|
|
|
* New indexes to allow us to reference ingredient and recipes
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
* A new domain called ``recipe``, which will contain the ``recipe`` directive
|
|
|
|
|
and ``ref`` role
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Prerequisites
|
|
|
|
|
-------------
|
|
|
|
|
|
2024-06-27 15:30:03 -05:00
|
|
|
|
We need the same setup as in
|
|
|
|
|
:ref:`the previous extensions <tutorial-extend-build>`.
|
|
|
|
|
This time,
|
2019-02-11 04:44:49 -06:00
|
|
|
|
we will be putting out extension in a file called :file:`recipe.py`.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
Here is an example of the folder structure you might obtain:
|
|
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
|
|
└── source
|
|
|
|
|
├── _ext
|
2019-02-11 04:44:49 -06:00
|
|
|
|
│ └── recipe.py
|
2019-02-08 11:47:19 -06:00
|
|
|
|
├── conf.py
|
|
|
|
|
└── index.rst
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Writing the extension
|
|
|
|
|
---------------------
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
Open :file:`recipe.py` and paste the following code in it, all of which we will
|
|
|
|
|
explain in detail shortly:
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
|
|
|
|
|
|
|
|
|
Let's look at each piece of this extension step-by-step to explain what's going
|
|
|
|
|
on.
|
|
|
|
|
|
|
|
|
|
.. rubric:: The directive class
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
The first thing to examine is the ``RecipeDirective`` directive:
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2020-02-16 10:20:59 -06:00
|
|
|
|
:pyobject: RecipeDirective
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
2024-06-27 15:30:03 -05:00
|
|
|
|
Unlike :ref:`tutorial-extending-syntax` and :ref:`tutorial-extend-build`,
|
|
|
|
|
this directive doesn't derive from
|
2019-02-08 11:47:19 -06:00
|
|
|
|
: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
|
|
|
|
|
because ``ObjectDescription`` is a special-purpose directive that's intended
|
|
|
|
|
for describing things like classes, functions, or, in our case, recipes. More
|
|
|
|
|
specifically, ``handle_signature`` implements parsing the signature of the
|
|
|
|
|
directive and passes on the object's name and type to its superclass, while
|
2022-12-29 07:58:32 -06:00
|
|
|
|
``add_target_and_index`` adds a target (to link to) and an entry to the index
|
2019-02-08 11:47:19 -06:00
|
|
|
|
for this node.
|
|
|
|
|
|
|
|
|
|
We also see that this directive defines ``has_content``, ``required_arguments``
|
|
|
|
|
and ``option_spec``. Unlike the ``TodoDirective`` directive added in the
|
2024-06-27 15:30:03 -05:00
|
|
|
|
: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.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
.. rubric:: The index classes
|
|
|
|
|
|
|
|
|
|
.. currentmodule:: sphinx.domains
|
|
|
|
|
|
|
|
|
|
.. todo:: Add brief overview of indices
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2020-02-16 10:20:59 -06:00
|
|
|
|
:pyobject: IngredientIndex
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
|
|
|
|
:pyobject: RecipeIndex
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
Both ``IngredientIndex`` and ``RecipeIndex`` are derived from :class:`Index`.
|
|
|
|
|
They implement custom logic to generate a tuple of values that define the
|
2019-02-11 04:44:49 -06:00
|
|
|
|
index. Note that ``RecipeIndex`` is a simple index that has only one entry.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
Extending it to cover more object types is not yet part of the code.
|
|
|
|
|
|
|
|
|
|
Both indices use the method :meth:`Index.generate` to do their work. This
|
|
|
|
|
method combines the information from our domain, sorts it, and returns it in a
|
|
|
|
|
list structure that will be accepted by Sphinx. This might look complicated but
|
|
|
|
|
all it really is is a list of tuples like ``('tomato', 'TomatoSoup', 'test',
|
|
|
|
|
'rec-TomatoSoup',...)``. Refer to the :doc:`domain API guide
|
|
|
|
|
</extdev/domainapi>` for more information on this API.
|
|
|
|
|
|
2022-12-29 07:58:32 -06:00
|
|
|
|
These index pages can be referenced with the :rst:role:`ref` role by combining
|
|
|
|
|
the domain name and the index ``name`` value. For example, ``RecipeIndex`` can be
|
|
|
|
|
referenced with ``:ref:`recipe-recipe``` and ``IngredientIndex`` can be referenced
|
|
|
|
|
with ``:ref:`recipe-ingredient```.
|
2020-02-07 21:23:11 -06:00
|
|
|
|
|
2019-02-08 11:47:19 -06:00
|
|
|
|
.. rubric:: The domain
|
|
|
|
|
|
|
|
|
|
A Sphinx domain is a specialized container that ties together roles,
|
|
|
|
|
directives, and indices, among other things. Let's look at the domain we're
|
|
|
|
|
creating here.
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2020-02-16 10:20:59 -06:00
|
|
|
|
:pyobject: RecipeDomain
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
There are some interesting things to note about this ``recipe`` domain and domains
|
2019-02-08 11:47:19 -06:00
|
|
|
|
in general. Firstly, we actually register our directives, roles and indices
|
|
|
|
|
here, via the ``directives``, ``roles`` and ``indices`` attributes, rather than
|
|
|
|
|
via calls later on in ``setup``. We can also note that we aren't actually
|
|
|
|
|
defining a custom role and are instead reusing the
|
|
|
|
|
:class:`sphinx.roles.XRefRole` role and defining the
|
|
|
|
|
:class:`sphinx.domains.Domain.resolve_xref` method. This method takes two
|
|
|
|
|
arguments, ``typ`` and ``target``, which refer to the cross-reference type and
|
|
|
|
|
its target name. We'll use ``target`` to resolve our destination from our
|
2019-02-11 04:44:49 -06:00
|
|
|
|
domain's ``recipes`` because we currently have only one type of node.
|
|
|
|
|
|
|
|
|
|
Moving on, we can see that we've defined ``initial_data``. The values defined in
|
|
|
|
|
``initial_data`` will be copied to ``env.domaindata[domain_name]`` as the
|
|
|
|
|
initial data of the domain, and domain instances can access it via
|
|
|
|
|
``self.data``. We see that we have defined two items in ``initial_data``:
|
2022-12-29 07:58:32 -06:00
|
|
|
|
``recipes`` and ``recipe_ingredients``. Each contains a list of all objects
|
2019-02-11 04:44:49 -06:00
|
|
|
|
defined (i.e. all recipes) and a hash that maps a canonical ingredient name to
|
|
|
|
|
the list of objects. The way we name objects is common across our extension and
|
|
|
|
|
is defined in the ``get_full_qualified_name`` method. For each object created,
|
|
|
|
|
the canonical name is ``recipe.<recipename>``, where ``<recipename>`` is the
|
|
|
|
|
name the documentation writer gives the object (a recipe). This enables the
|
|
|
|
|
extension to use different object types that share the same name. Having a
|
|
|
|
|
canonical name and central place for our objects is a huge advantage. Both our
|
|
|
|
|
indices and our cross-referencing code use this feature.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
.. rubric:: The ``setup`` function
|
|
|
|
|
|
|
|
|
|
.. currentmodule:: sphinx.application
|
|
|
|
|
|
2024-06-27 15:30:03 -05:00
|
|
|
|
:ref:`As always <tutorial-extend-build>`,
|
|
|
|
|
the ``setup`` function is a requirement and is used to
|
2019-02-08 11:47:19 -06:00
|
|
|
|
hook the various parts of our extension into Sphinx. Let's look at the
|
|
|
|
|
``setup`` function for this extension.
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: examples/recipe.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2020-02-16 10:20:59 -06:00
|
|
|
|
:pyobject: setup
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
This looks a little different to what we're used to seeing. There are no calls
|
|
|
|
|
to :meth:`~Sphinx.add_directive` or even :meth:`~Sphinx.add_role`. Instead, we
|
|
|
|
|
have a single call to :meth:`~Sphinx.add_domain` followed by some
|
2023-10-01 13:53:52 -05:00
|
|
|
|
initialization of the :doc:`standard domain </usage/domains/standard>`.
|
|
|
|
|
This is because we had already registered our directives,
|
|
|
|
|
roles and indexes as part of the directive itself.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Using the extension
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
You can now use the extension throughout your project. For example:
|
|
|
|
|
|
|
|
|
|
.. code-block:: rst
|
|
|
|
|
:caption: index.rst
|
|
|
|
|
|
|
|
|
|
Joe's Recipes
|
|
|
|
|
=============
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
Below are a collection of my favourite recipes. I highly recommend the
|
|
|
|
|
:recipe:ref:`TomatoSoup` recipe in particular!
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
.. toctree::
|
|
|
|
|
|
|
|
|
|
tomato-soup
|
|
|
|
|
|
|
|
|
|
.. code-block:: rst
|
|
|
|
|
:caption: tomato-soup.rst
|
|
|
|
|
|
|
|
|
|
The recipe contains `tomato` and `cilantro`.
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
.. recipe:recipe:: TomatoSoup
|
2021-02-12 05:51:53 -06:00
|
|
|
|
:contains: tomato, cilantro, salt, pepper
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
2021-02-12 05:51:53 -06:00
|
|
|
|
This recipe is a tasty tomato soup, combine all ingredients
|
|
|
|
|
and cook.
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
The important things to note are the use of the ``:recipe:ref:`` role to
|
2019-02-08 11:47:19 -06:00
|
|
|
|
cross-reference the recipe actually defined elsewhere (using the
|
2022-12-29 07:58:32 -06:00
|
|
|
|
``:recipe:recipe:`` directive).
|
2019-02-08 11:47:19 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Further reading
|
|
|
|
|
---------------
|
|
|
|
|
|
|
|
|
|
For more information, refer to the `docutils`_ documentation and
|
|
|
|
|
:doc:`/extdev/index`.
|
|
|
|
|
|
2024-06-27 15:30:03 -05:00
|
|
|
|
If you wish to share your extension across multiple projects or with others,
|
|
|
|
|
check out the :ref:`third-party-extensions` section.
|
|
|
|
|
|
2021-05-16 07:42:26 -05:00
|
|
|
|
.. _docutils: https://docutils.sourceforge.io/docs/
|