diff --git a/.ruff.toml b/.ruff.toml index 7250912000e..2632c4ed041 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -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"] diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css index 0bd52035af0..ecc952d0e8e 100644 --- a/doc/_themes/sphinx13/static/sphinx13.css +++ b/doc/_themes/sphinx13/static/sphinx13.css @@ -381,6 +381,10 @@ aside.topic { background-color: #f8f8f8; } +p.topic-title { + margin-top: 0; +} + table { border-collapse: collapse; margin: 0 -0.5em 0 -0.5em; diff --git a/doc/conf.py b/doc/conf.py index a8c1d32617c..20a0f5b8924 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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(): diff --git a/doc/development/tutorials/recipe.rst b/doc/development/tutorials/adding_domain.rst similarity index 91% rename from doc/development/tutorials/recipe.rst rename to doc/development/tutorials/adding_domain.rst index 683cc8c28b4..8a00211f2fb 100644 --- a/doc/development/tutorials/recipe.rst +++ b/doc/development/tutorials/adding_domain.rst @@ -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 `. This time, +We need the same setup as in +:ref:`the previous extensions `. +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 `, 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 `, +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 `, the ``setup`` function is a requirement and is used to +:ref:`As always `, +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/ diff --git a/doc/development/tutorials/autodoc_ext.rst b/doc/development/tutorials/autodoc_ext.rst index cfd23e7e694..fb2a9176ee2 100644 --- a/doc/development/tutorials/autodoc_ext.rst +++ b/doc/development/tutorials/autodoc_ext.rst @@ -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 `. 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 `. +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. diff --git a/doc/development/tutorials/examples/helloworld.py b/doc/development/tutorials/examples/helloworld.py index da295621e31..3f7e504b6e5 100644 --- a/doc/development/tutorials/examples/helloworld.py +++ b/doc/development/tutorials/examples/helloworld.py @@ -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', diff --git a/doc/development/tutorials/todo.rst b/doc/development/tutorials/extending_build.rst similarity index 92% rename from doc/development/tutorials/todo.rst rename to doc/development/tutorials/extending_build.rst index f23d8adaf32..a81c84b0075 100644 --- a/doc/development/tutorials/todo.rst +++ b/doc/development/tutorials/extending_build.rst @@ -1,14 +1,20 @@ -Developing a "TODO" extension -============================= +.. _tutorial-extend-build: -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. +Extending the build process +=========================== +The objective of this tutorial is to create a more comprehensive extension than +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 `, the ``setup`` function is a requirement +As noted :ref:`previously `, +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 diff --git a/doc/development/tutorials/extending_syntax.rst b/doc/development/tutorials/extending_syntax.rst new file mode 100644 index 00000000000..8d3e3cb1de8 --- /dev/null +++ b/doc/development/tutorials/extending_syntax.rst @@ -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 + + + + + Hello world! + +The docutils_ package provides many `built-in 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 `_, and + `creating 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 ` 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 diff --git a/doc/development/tutorials/helloworld.rst b/doc/development/tutorials/helloworld.rst deleted file mode 100644 index 0b5e1a3702d..00000000000 --- a/doc/development/tutorials/helloworld.rst +++ /dev/null @@ -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 ` 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 `_ - -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 `_ - -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 ` 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 diff --git a/doc/development/tutorials/index.rst b/doc/development/tutorials/index.rst index a9c8368af39..0c5c920332a 100644 --- a/doc/development/tutorials/index.rst +++ b/doc/development/tutorials/index.rst @@ -6,7 +6,7 @@ Tutorials .. toctree:: :maxdepth: 2 - helloworld - todo - recipe + extending_syntax + extending_build + adding_domain autodoc_ext diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 57162e6f3b9..9617dd4312d 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -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: