summaryrefslogtreecommitdiffstats
path: root/doc/development/tutorials/helloworld.rst
blob: 8940e3dfd8c076f82e2152b5872bcfe2d2dbb7a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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 :doc:`other 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