2018-11-28 10:27:22 -06:00
|
|
|
|
Developing a "TODO" extension
|
2018-11-28 10:27:12 -06:00
|
|
|
|
=============================
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
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.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
Overview
|
|
|
|
|
--------
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
.. note::
|
|
|
|
|
To understand the design of this extension, refer to
|
2018-12-22 04:38:12 -06:00
|
|
|
|
:ref:`important-objects` and :ref:`build-phases`.
|
|
|
|
|
|
2008-12-20 17:10:47 -06:00
|
|
|
|
We want the extension to add the following to Sphinx:
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
* A ``todo`` directive, containing some content that is marked with "TODO" and
|
|
|
|
|
only shown in the output if a new config value is set. Todo entries should not
|
|
|
|
|
be in the output by default.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
* A ``todolist`` directive that creates a list of all todo entries throughout the
|
2008-12-20 17:10:47 -06:00
|
|
|
|
documentation.
|
|
|
|
|
|
|
|
|
|
For that, we will need to add the following elements to Sphinx:
|
|
|
|
|
|
|
|
|
|
* New directives, called ``todo`` and ``todolist``.
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
2008-12-20 17:10:47 -06:00
|
|
|
|
* New document tree nodes to represent these directives, conventionally also
|
|
|
|
|
called ``todo`` and ``todolist``. We wouldn't need new nodes if the new
|
|
|
|
|
directives only produced some content representable by existing nodes.
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
2008-12-20 17:10:47 -06:00
|
|
|
|
* A new config value ``todo_include_todos`` (config value names should start
|
|
|
|
|
with the extension name, in order to stay unique) that controls whether todo
|
|
|
|
|
entries make it into the output.
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
2008-12-20 17:10:47 -06:00
|
|
|
|
* New event handlers: one for the :event:`doctree-resolved` event, to replace
|
|
|
|
|
the todo and todolist nodes, and one for :event:`env-purge-doc` (the reason
|
|
|
|
|
for that will be covered later).
|
|
|
|
|
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
Prerequisites
|
|
|
|
|
-------------
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
As with :doc:`helloworld`, 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`.
|
|
|
|
|
|
|
|
|
|
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:`todo.py`
|
|
|
|
|
|
|
|
|
|
Here is an example of the folder structure you might obtain:
|
|
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
|
|
└── source
|
|
|
|
|
├── _ext
|
|
|
|
|
│ └── todo.py
|
|
|
|
|
├── _static
|
|
|
|
|
├── conf.py
|
|
|
|
|
├── somefolder
|
|
|
|
|
├── index.rst
|
|
|
|
|
├── somefile.rst
|
|
|
|
|
└── someotherfile.rst
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Writing the extension
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
Open :file:`todo.py` and paste the following code in it, all of which we will
|
|
|
|
|
explain in detail shortly:
|
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2014-09-03 09:27:15 -05:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
This is far more extensive extension than the one detailed in :doc:`helloworld`,
|
|
|
|
|
however, we will will look at each piece step-by-step to explain what's
|
|
|
|
|
happening.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
.. rubric:: The node classes
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
Let's start with the node classes:
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2019-02-18 07:38:42 -06:00
|
|
|
|
:lines: 8-21
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
|
|
|
|
Node classes usually don't have to do anything except inherit from the standard
|
|
|
|
|
docutils classes defined in :mod:`docutils.nodes`. ``todo`` inherits from
|
|
|
|
|
``Admonition`` because it should be handled like a note or warning, ``todolist``
|
|
|
|
|
is just a "general" node.
|
|
|
|
|
|
2014-01-20 10:21:44 -06:00
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
Many extensions will not have to create their own node classes and work fine
|
|
|
|
|
with the nodes already provided by `docutils
|
|
|
|
|
<http://docutils.sourceforge.net/docs/ref/doctree.html>`__ and :ref:`Sphinx
|
|
|
|
|
<nodes>`.
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
.. rubric:: The directive classes
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2009-03-07 15:54:36 -06:00
|
|
|
|
A directive class is a class deriving usually from
|
2019-02-08 10:51:58 -06:00
|
|
|
|
:class:`docutils.parsers.rst.Directive`. The directive interface is also
|
2014-01-21 03:32:30 -06:00
|
|
|
|
covered in detail in the `docutils documentation`_; the important thing is that
|
2019-02-08 10:51:58 -06:00
|
|
|
|
the class should have attributes that configure the allowed markup, and a
|
|
|
|
|
``run`` method that returns a list of nodes.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
Looking first at the ``TodolistDirective`` directive:
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2019-02-18 07:38:42 -06:00
|
|
|
|
:lines: 24-27
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
It's very simple, creating and returning an instance of our ``todolist`` node
|
|
|
|
|
class. The ``TodolistDirective`` directive itself has neither content nor
|
|
|
|
|
arguments that need to be handled. That brings us to the ``TodoDirective``
|
|
|
|
|
directive:
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
2019-02-18 07:38:42 -06:00
|
|
|
|
:lines: 30-53
|
|
|
|
|
|
|
|
|
|
Several important things are covered here. First, as you can see, we're now
|
|
|
|
|
subclassing the :class:`~sphinx.util.docutils.SphinxDirective` helper class
|
|
|
|
|
instead of the usual :class:`~docutils.parsers.rst.Directive` class. This
|
|
|
|
|
gives us access to the :ref:`build environment instance <important-objects>`
|
|
|
|
|
using the ``self.env`` property. Without this, we'd have to use the rather
|
|
|
|
|
convoluted ``self.state.document.settings.env``. Then, to act as a link target
|
|
|
|
|
(from ``TodolistDirective``), the ``TodoDirective`` directive needs to return a
|
|
|
|
|
target node in addition to the ``todo`` node. The target ID (in HTML, this will
|
|
|
|
|
be the anchor name) is generated by using ``env.new_serialno`` which returns a
|
|
|
|
|
new unique integer on each call and therefore leads to unique target names. The
|
2019-02-08 10:51:58 -06:00
|
|
|
|
target node is instantiated without any text (the first two arguments).
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2017-01-07 03:35:57 -06:00
|
|
|
|
On creating admonition node, the content body of the directive are parsed using
|
|
|
|
|
``self.state.nested_parse``. The first argument gives the content body, and
|
|
|
|
|
the second one gives content offset. The third argument gives the parent node
|
2019-02-08 10:51:58 -06:00
|
|
|
|
of parsed result, in our case the ``todo`` node. Following this, the ``todo``
|
|
|
|
|
node is added to the environment. This is needed to be able to create a list of
|
|
|
|
|
all todo entries throughout the documentation, in the place where the author
|
|
|
|
|
puts a ``todolist`` directive. For this case, the environment attribute
|
|
|
|
|
``todo_all_todos`` is used (again, the name should be unique, so it is prefixed
|
|
|
|
|
by the extension name). It does not exist when a new environment is created, so
|
|
|
|
|
the directive must check and create it if necessary. Various information about
|
|
|
|
|
the todo entry's location are stored along with a copy of the node.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
|
|
|
|
In the last line, the nodes that should be put into the doctree are returned:
|
|
|
|
|
the target node and the admonition node.
|
|
|
|
|
|
2009-02-17 17:17:28 -06:00
|
|
|
|
The node structure that the directive returns looks like this::
|
|
|
|
|
|
|
|
|
|
+--------------------+
|
|
|
|
|
| target node |
|
|
|
|
|
+--------------------+
|
|
|
|
|
+--------------------+
|
|
|
|
|
| todo node |
|
|
|
|
|
+--------------------+
|
|
|
|
|
\__+--------------------+
|
|
|
|
|
| admonition title |
|
|
|
|
|
+--------------------+
|
|
|
|
|
| paragraph |
|
|
|
|
|
+--------------------+
|
|
|
|
|
| ... |
|
|
|
|
|
+--------------------+
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
.. rubric:: The event handlers
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
Event handlers are one of Sphinx's most powerful features, providing a way to
|
|
|
|
|
do hook into any part of the documentation process. There are many events
|
|
|
|
|
provided by Sphinx itself, as detailed in :ref:`the API guide <events>`, and
|
|
|
|
|
we're going to use a subset of them here.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
Let's look at the event handlers used in the above example. First, the one for
|
|
|
|
|
the :event:`env-purge-doc` event:
|
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
|
|
|
|
:lines: 56-61
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
|
|
|
|
Since we store information from source files in the environment, which is
|
|
|
|
|
persistent, it may become out of date when the source file changes. Therefore,
|
|
|
|
|
before each source file is read, the environment's records of it are cleared,
|
|
|
|
|
and the :event:`env-purge-doc` event gives extensions a chance to do the same.
|
|
|
|
|
Here we clear out all todos whose docname matches the given one from the
|
|
|
|
|
``todo_all_todos`` list. If there are todos left in the document, they will be
|
|
|
|
|
added again during parsing.
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
The other handler belongs to the :event:`doctree-resolved` event:
|
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
|
|
|
|
:lines: 64-103
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
The :event:`doctree-resolved` event is emitted at the end of :ref:`phase 3
|
2019-02-11 04:44:49 -06:00
|
|
|
|
(resolving) <build-phases>` and allows custom resolving to be done. The handler
|
|
|
|
|
we have written for this event is a bit more involved. If the
|
|
|
|
|
``todo_include_todos`` config value (which we'll describe shortly) is false,
|
|
|
|
|
all ``todo`` and ``todolist`` nodes are removed from the documents. If not,
|
|
|
|
|
``todo`` nodes just stay where and how they are. ``todolist`` nodes are
|
|
|
|
|
replaced by a list of todo entries, complete with backlinks to the location
|
|
|
|
|
where they come from. The list items are composed of the nodes from the
|
|
|
|
|
``todo`` entry and docutils nodes created on the fly: a paragraph for each
|
|
|
|
|
entry, containing text that gives the location, and a link (reference node
|
|
|
|
|
containing an italic node) with the backreference. The reference URI is built
|
|
|
|
|
by :meth:`sphinx.builders.Builder.get_relative_uri`` which creates a suitable
|
|
|
|
|
URI depending on the used builder, and appending the todo node's (the target's)
|
|
|
|
|
ID as the anchor name.
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
|
|
|
|
.. rubric:: The ``setup`` function
|
|
|
|
|
|
|
|
|
|
.. currentmodule:: sphinx.application
|
|
|
|
|
|
|
|
|
|
As noted :doc:`previously <helloworld>`, 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:
|
|
|
|
|
|
2019-02-08 11:03:09 -06:00
|
|
|
|
.. literalinclude:: examples/todo.py
|
|
|
|
|
:language: python
|
|
|
|
|
:linenos:
|
|
|
|
|
:lines: 106-
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
|
|
|
|
The calls in this function refer to the classes and functions we added earlier.
|
|
|
|
|
What the individual calls do is the following:
|
|
|
|
|
|
|
|
|
|
* :meth:`~Sphinx.add_config_value` lets Sphinx know that it should recognize the
|
|
|
|
|
new *config value* ``todo_include_todos``, whose default value should be
|
|
|
|
|
``False`` (this also tells Sphinx that it is a boolean value).
|
|
|
|
|
|
|
|
|
|
If the third argument was ``'html'``, HTML documents would be full rebuild if the
|
|
|
|
|
config value changed its value. This is needed for config values that
|
2019-02-11 04:44:49 -06:00
|
|
|
|
influence reading (build :ref:`phase 1 (reading) <build-phases>`).
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
|
|
|
|
* :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also
|
|
|
|
|
can specify visitor functions for each supported output format. These visitor
|
2019-02-11 04:44:49 -06:00
|
|
|
|
functions are needed when the new nodes stay until :ref:`phase 4 (writing)
|
|
|
|
|
<build-phases>`. Since the ``todolist`` node is always replaced in
|
|
|
|
|
:ref:`phase 3 (resolving) <build-phases>`, it doesn't need any.
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
|
|
|
|
* :meth:`~Sphinx.add_directive` adds a new *directive*, given by name and class.
|
|
|
|
|
|
|
|
|
|
* Finally, :meth:`~Sphinx.connect` adds an *event handler* to the event whose
|
|
|
|
|
name is given by the first argument. The event handler function is called
|
|
|
|
|
with several arguments which are documented with the event.
|
|
|
|
|
|
|
|
|
|
With this, our extension is complete.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Using the extension
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
As before, we need to enable the extension by declaring it in our
|
|
|
|
|
:file:`conf.py` file. 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
|
|
|
|
|
|
|
|
|
|
In addition, we may wish to set the ``todo_include_todos`` config value. As
|
|
|
|
|
noted above, this defaults to ``False`` but we can set it explicitly.
|
|
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
sys.path.append(os.path.abspath("./_ext"))
|
|
|
|
|
|
2019-02-11 04:44:49 -06:00
|
|
|
|
extensions = ['todo']
|
2019-02-08 10:51:58 -06:00
|
|
|
|
|
|
|
|
|
todo_include_todos = False
|
|
|
|
|
|
|
|
|
|
You can now use the extension throughout your project. For example:
|
|
|
|
|
|
|
|
|
|
.. code-block:: rst
|
|
|
|
|
:caption: index.rst
|
|
|
|
|
|
|
|
|
|
Hello, world
|
|
|
|
|
============
|
|
|
|
|
|
|
|
|
|
.. toctree::
|
|
|
|
|
somefile.rst
|
|
|
|
|
someotherfile.rst
|
|
|
|
|
|
|
|
|
|
Hello world. Below is the list of TODOs.
|
|
|
|
|
|
|
|
|
|
.. todolist::
|
|
|
|
|
|
|
|
|
|
.. code-block:: rst
|
|
|
|
|
:caption: somefile.rst
|
|
|
|
|
|
|
|
|
|
foo
|
|
|
|
|
===
|
|
|
|
|
|
|
|
|
|
Some intro text here...
|
|
|
|
|
|
|
|
|
|
.. todo:: Fix this
|
|
|
|
|
|
|
|
|
|
.. code-block:: rst
|
|
|
|
|
:caption: someotherfile.rst
|
|
|
|
|
|
|
|
|
|
bar
|
|
|
|
|
===
|
|
|
|
|
|
|
|
|
|
Some more text here...
|
|
|
|
|
|
|
|
|
|
.. todo:: Fix that
|
|
|
|
|
|
|
|
|
|
Because we have configured ``todo_include_todos`` to ``False``, we won't
|
|
|
|
|
actually see anything rendered for the ``todo`` and ``todolist`` directives.
|
|
|
|
|
However, if we toggle this to true, we will see the output described
|
|
|
|
|
previously.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Further reading
|
|
|
|
|
---------------
|
|
|
|
|
|
|
|
|
|
For more information, refer to the `docutils`_ documentation and
|
|
|
|
|
:doc:`/extdev/index`.
|
2008-12-20 17:10:47 -06:00
|
|
|
|
|
|
|
|
|
|
2019-02-08 10:51:58 -06:00
|
|
|
|
.. _docutils: http://docutils.sourceforge.net/docs/
|
|
|
|
|
.. _Python path: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|
2013-07-11 15:30:17 -05:00
|
|
|
|
.. _docutils documentation: http://docutils.sourceforge.net/docs/ref/rst/directives.html
|