summaryrefslogtreecommitdiffstats
path: root/doc/development
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development')
-rw-r--r--doc/development/builders.rst18
-rw-r--r--doc/development/templating.rst5
-rw-r--r--doc/development/theming.rst125
-rw-r--r--doc/development/tutorials/examples/autodoc_intenum.py19
-rw-r--r--doc/development/tutorials/examples/helloworld.py8
-rw-r--r--doc/development/tutorials/examples/recipe.py46
-rw-r--r--doc/development/tutorials/examples/todo.py34
-rw-r--r--doc/development/tutorials/recipe.rst6
8 files changed, 178 insertions, 83 deletions
diff --git a/doc/development/builders.rst b/doc/development/builders.rst
index bb67770..7792fbd 100644
--- a/doc/development/builders.rst
+++ b/doc/development/builders.rst
@@ -10,25 +10,19 @@ Discover builders by entry point
that they do not have to be listed in the :confval:`extensions` configuration
value.
-Builder extensions should define an entry point in the ``sphinx.builders``
+Builder extensions should define an entry point in the ``"sphinx.builders"``
group. The name of the entry point needs to match your builder's
:attr:`~.Builder.name` attribute, which is the name passed to the
:option:`sphinx-build -b` option. The entry point value should equal the
dotted name of the extension module. Here is an example of how an entry point
-for 'mybuilder' can be defined in the extension's ``setup.py``
+for 'mybuilder' can be defined in the extension's ``pyproject.toml``
-.. code-block:: python
+.. code-block:: toml
- setup(
- # ...
- entry_points={
- 'sphinx.builders': [
- 'mybuilder = my.extension.module',
- ],
- }
- )
+ [project.entry-points."sphinx.builders"]
+ mybuilder = "my.extension.module"
Note that it is still necessary to register the builder using
:meth:`~.Sphinx.add_builder` in the extension's :func:`setup` function.
-.. _entry points: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
+.. _entry points: https://setuptools.pypa.io/en/latest/userguide/entry_point.html
diff --git a/doc/development/templating.rst b/doc/development/templating.rst
index 73ae6e5..016b8b8 100644
--- a/doc/development/templating.rst
+++ b/doc/development/templating.rst
@@ -30,6 +30,7 @@ No. You have several other options:
produces pickle files with the page contents, and postprocess them using a
custom tool, or use them in your Web application.
+.. _templating-primer:
Jinja/Sphinx Templating Primer
------------------------------
@@ -108,7 +109,7 @@ The following blocks exist in the ``layout.html`` template:
parent documents on the left, and the links to index, modules etc. on the
right). ``relbar1`` appears before the document, ``relbar2`` after the
document. By default, both blocks are filled; to show the relbar only
- before the document, you would override `relbar2` like this::
+ before the document, you would override ``relbar2`` like this::
{% block relbar2 %}{% endblock %}
@@ -422,7 +423,7 @@ are in HTML form), these variables are also available:
.. data:: next
The next document for the navigation. This variable is either false or has
- two attributes `link` and `title`. The title contains HTML markup. For
+ two attributes ``link`` and ``title``. The title contains HTML markup. For
example, to generate a link to the next page, you can use this snippet::
{% if next %}
diff --git a/doc/development/theming.rst b/doc/development/theming.rst
index 538dcaf..13a5802 100644
--- a/doc/development/theming.rst
+++ b/doc/development/theming.rst
@@ -30,11 +30,85 @@ Creating themes
Themes take the form of either a directory or a zipfile (whose name is the
theme name), containing the following:
-* A :file:`theme.conf` file.
+* Either a :file:`theme.toml` file (preferred) or a :file:`theme.conf` file.
* HTML templates, if needed.
* A ``static/`` directory containing any static files that will be copied to the
output static directory on build. These can be images, styles, script files.
+Theme configuration (``theme.toml``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :file:`theme.toml` file is a TOML_ document,
+containing two tables: ``[theme]`` and ``[options]``.
+
+The ``[theme]`` table defines the theme's settings:
+
+* **inherit** (string): The name of the base theme from which to inherit
+ settings, options, templates, and static files.
+ All static files from theme 'ancestors' will be used.
+ The theme will use all options defined in inherited themes.
+ Finally, inherited themes will be used to locate missing templates
+ (for example, if ``"basic"`` is used as the base theme, most templates will
+ already be defined).
+
+ If set to ``"none"``, the theme will not inherit from any other theme.
+ Inheritance is recursive, forming a chain of inherited themes
+ (e.g. ``default`` -> ``classic`` -> ``basic`` -> ``none``).
+
+* **stylesheets** (list of strings): A list of CSS filenames which will be
+ included in generated HTML header.
+ Setting the :confval:`html_style` config value will override this setting.
+
+ Other mechanisms for including multiple stylesheets include ``@import`` in CSS
+ or using a custom HTML template with appropriate ``<link rel="stylesheet">`` tags.
+
+* **sidebars** (list of strings): A list of sidebar templates.
+ This can be overridden by the user via the :confval:`html_sidebars` config value.
+
+* **pygments_style** (table): A TOML table defining the names of Pygments styles
+ to use for highlighting syntax.
+ The table has two recognised keys: ``default`` and ``dark``.
+ The style defined in the ``dark`` key will be used when
+ the CSS media query ``(prefers-color-scheme: dark)`` evaluates to true.
+
+ ``[theme.pygments_style.default]`` can be overridden by the user via the
+ :confval:`pygments_style` config value.
+
+The ``[options]`` table defines the options for the theme.
+It is structured such that each key-value pair corresponds to a variable name
+and the corresponding default value.
+These options can be overridden by the user in :confval:`html_theme_options`
+and are accessible from all templates as ``theme_<name>``.
+
+.. versionadded:: 7.3
+ ``theme.toml`` support.
+
+.. _TOML: https://toml.io/en/
+
+Exemplar :file:`theme.toml` file:
+
+.. code-block:: toml
+
+ [theme]
+ inherit = "basic"
+ stylesheets = [
+ "main-CSS-stylesheet.css",
+ ]
+ sidebars = [
+ "localtoc.html",
+ "relations.html",
+ "sourcelink.html",
+ "searchbox.html",
+ ]
+ # Style names from https://pygments.org/styles/
+ pygments_style = { default = "style_name", dark = "dark_style" }
+
+ [options]
+ variable = "default value"
+
+Theme configuration (``theme.conf``)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
The :file:`theme.conf` file is in INI format [1]_ (readable by the standard
Python :mod:`configparser` module) and has the following structure:
@@ -86,6 +160,24 @@ Python :mod:`configparser` module) and has the following structure:
The stylesheet setting accepts multiple CSS filenames
+Convert ``theme.conf`` to ``theme.toml``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+INI-style theme configuration files (``theme.conf``) can be converted to TOML
+via a helper programme distributed with Sphinx.
+This is intended for one-time use, and may be removed without notice in a future
+version of Sphinx.
+
+.. code-block:: console
+
+ $ python -m sphinx.theming conf_to_toml [THEME DIRECTORY PATH]
+
+The required argument is a path to a directory containing a ``theme.conf`` file.
+The programme will write a ``theme.toml`` file in the same directory,
+and will not modify the original ``theme.conf`` file.
+
+.. versionadded:: 7.3
+
.. _distribute-your-theme:
Distribute your theme as a Python package
@@ -95,21 +187,20 @@ As a way to distribute your theme, you can use a Python package. This makes it
easier for users to set up your theme.
To distribute your theme as a Python package, please define an entry point
-called ``sphinx.html_themes`` in your ``setup.py`` file, and write a ``setup()``
-function to register your themes using ``add_html_theme()`` API in it::
-
- # 'setup.py'
- setup(
- ...
- entry_points = {
- 'sphinx.html_themes': [
- 'name_of_theme = your_package',
- ]
- },
- ...
- )
-
- # 'your_package.py'
+called ``sphinx.html_themes`` in your ``pyproject.toml`` file,
+and write a ``setup()`` function to register your theme
+using the :meth:`~sphinx.application.Sphinx.add_html_theme` API:
+
+.. code-block:: toml
+
+ # pyproject.toml
+
+ [project.entry-points."sphinx.html_themes"]
+ name_of_theme = "your_theme_package"
+
+.. code-block:: python
+
+ # your_theme_package.py
from os import path
def setup(app):
@@ -142,7 +233,7 @@ searches for templates:
When extending a template in the base theme with the same name, use the theme
name as an explicit directory: ``{% extends "basic/layout.html" %}``. From a
user ``templates_path`` template, you can still use the "exclamation mark"
-syntax as described in the templating document.
+syntax as :ref:`described in the templating document <templating-primer>`.
.. _theming-static-templates:
diff --git a/doc/development/tutorials/examples/autodoc_intenum.py b/doc/development/tutorials/examples/autodoc_intenum.py
index 75fa204..c52bb4c 100644
--- a/doc/development/tutorials/examples/autodoc_intenum.py
+++ b/doc/development/tutorials/examples/autodoc_intenum.py
@@ -19,9 +19,9 @@ class IntEnumDocumenter(ClassDocumenter):
option_spec['hex'] = bool_option
@classmethod
- def can_document_member(cls,
- member: Any, membername: str,
- isattr: bool, parent: Any) -> bool:
+ def can_document_member(
+ cls, member: Any, membername: str, isattr: bool, parent: Any
+ ) -> bool:
try:
return issubclass(member, IntEnum)
except TypeError:
@@ -31,11 +31,11 @@ class IntEnumDocumenter(ClassDocumenter):
super().add_directive_header(sig)
self.add_line(' :final:', self.get_sourcename())
- def add_content(self,
- more_content: StringList | None,
- no_docstring: bool = False,
- ) -> None:
-
+ def add_content(
+ self,
+ more_content: StringList | None,
+ no_docstring: bool = False,
+ ) -> None:
super().add_content(more_content, no_docstring)
source_name = self.get_sourcename()
@@ -48,8 +48,7 @@ class IntEnumDocumenter(ClassDocumenter):
if use_hex:
the_member_value = hex(the_member_value)
- self.add_line(
- f"**{the_member_name}**: {the_member_value}", source_name)
+ self.add_line(f'**{the_member_name}**: {the_member_value}', source_name)
self.add_line('', source_name)
diff --git a/doc/development/tutorials/examples/helloworld.py b/doc/development/tutorials/examples/helloworld.py
index d6d81fd..da29562 100644
--- a/doc/development/tutorials/examples/helloworld.py
+++ b/doc/development/tutorials/examples/helloworld.py
@@ -1,16 +1,18 @@
from docutils import nodes
from docutils.parsers.rst import Directive
+from sphinx.application import Sphinx
+from sphinx.util.typing import ExtensionMetadata
-class HelloWorld(Directive):
+class HelloWorld(Directive):
def run(self):
paragraph_node = nodes.paragraph(text='Hello World!')
return [paragraph_node]
-def setup(app):
- app.add_directive("helloworld", HelloWorld)
+def setup(app: Sphinx) -> ExtensionMetadata:
+ app.add_directive('helloworld', HelloWorld)
return {
'version': '0.1',
diff --git a/doc/development/tutorials/examples/recipe.py b/doc/development/tutorials/examples/recipe.py
index c7ebf2a..28d25f2 100644
--- a/doc/development/tutorials/examples/recipe.py
+++ b/doc/development/tutorials/examples/recipe.py
@@ -3,10 +3,12 @@ from collections import defaultdict
from docutils.parsers.rst import directives
from sphinx import addnodes
+from sphinx.application import Sphinx
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index
from sphinx.roles import XRefRole
from sphinx.util.nodes import make_refnode
+from sphinx.util.typing import ExtensionMetadata
class RecipeDirective(ObjectDescription):
@@ -25,8 +27,7 @@ class RecipeDirective(ObjectDescription):
def add_target_and_index(self, name_cls, sig, signode):
signode['ids'].append('recipe' + '-' + sig)
if 'contains' in self.options:
- ingredients = [
- x.strip() for x in self.options.get('contains').split(',')]
+ ingredients = [x.strip() for x in self.options.get('contains').split(',')]
recipes = self.env.get_domain('recipe')
recipes.add_recipe(sig, ingredients)
@@ -42,9 +43,10 @@ class IngredientIndex(Index):
def generate(self, docnames=None):
content = defaultdict(list)
- recipes = {name: (dispname, typ, docname, anchor)
- for name, dispname, typ, docname, anchor, _
- in self.domain.get_objects()}
+ recipes = {
+ name: (dispname, typ, docname, anchor)
+ for name, dispname, typ, docname, anchor, _ in self.domain.get_objects()
+ }
recipe_ingredients = self.domain.data['recipe_ingredients']
ingredient_recipes = defaultdict(list)
@@ -60,8 +62,7 @@ class IngredientIndex(Index):
for ingredient, recipe_names in ingredient_recipes.items():
for recipe_name in recipe_names:
dispname, typ, docname, anchor = recipes[recipe_name]
- content[ingredient].append(
- (dispname, 0, docname, anchor, docname, '', typ))
+ content[ingredient].append((dispname, 0, docname, anchor, docname, '', typ))
# convert the dict to the sorted list of tuples expected
content = sorted(content.items())
@@ -88,8 +89,15 @@ class RecipeIndex(Index):
#
# name, subtype, docname, anchor, extra, qualifier, description
for _name, dispname, typ, docname, anchor, _priority in recipes:
- content[dispname[0].lower()].append(
- (dispname, 0, docname, anchor, docname, '', typ))
+ content[dispname[0].lower()].append((
+ dispname,
+ 0,
+ docname,
+ anchor,
+ docname,
+ '',
+ typ,
+ ))
# convert the dict to the sorted list of tuples expected
content = sorted(content.items())
@@ -98,7 +106,6 @@ class RecipeIndex(Index):
class RecipeDomain(Domain):
-
name = 'recipe'
label = 'Recipe Sample'
roles = {
@@ -122,18 +129,18 @@ class RecipeDomain(Domain):
def get_objects(self):
yield from self.data['recipes']
- def resolve_xref(self, env, fromdocname, builder, typ, target, node,
- contnode):
- match = [(docname, anchor)
- for name, sig, typ, docname, anchor, prio
- in self.get_objects() if sig == target]
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ match = [
+ (docname, anchor)
+ for name, sig, typ, docname, anchor, prio in self.get_objects()
+ if sig == target
+ ]
if len(match) > 0:
todocname = match[0][0]
targ = match[0][1]
- return make_refnode(builder, fromdocname, todocname, targ,
- contnode, targ)
+ return make_refnode(builder, fromdocname, todocname, targ, contnode, targ)
else:
print('Awww, found nothing')
return None
@@ -145,11 +152,10 @@ class RecipeDomain(Domain):
self.data['recipe_ingredients'][name] = ingredients
# name, dispname, type, docname, anchor, priority
- self.data['recipes'].append(
- (name, signature, 'Recipe', self.env.docname, anchor, 0))
+ self.data['recipes'].append((name, signature, 'Recipe', self.env.docname, anchor, 0))
-def setup(app):
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(RecipeDomain)
return {
diff --git a/doc/development/tutorials/examples/todo.py b/doc/development/tutorials/examples/todo.py
index 15368f4..2baac5c 100644
--- a/doc/development/tutorials/examples/todo.py
+++ b/doc/development/tutorials/examples/todo.py
@@ -1,8 +1,10 @@
from docutils import nodes
from docutils.parsers.rst import Directive
+from sphinx.application import Sphinx
from sphinx.locale import _
from sphinx.util.docutils import SphinxDirective
+from sphinx.util.typing import ExtensionMetadata
class todo(nodes.Admonition, nodes.Element):
@@ -22,13 +24,11 @@ def depart_todo_node(self, node):
class TodolistDirective(Directive):
-
def run(self):
return [todolist('')]
class TodoDirective(SphinxDirective):
-
# this enables content in the directive
has_content = True
@@ -57,8 +57,7 @@ def purge_todos(app, env, docname):
if not hasattr(env, 'todo_all_todos'):
return
- env.todo_all_todos = [todo for todo in env.todo_all_todos
- if todo['docname'] != docname]
+ env.todo_all_todos = [todo for todo in env.todo_all_todos if todo['docname'] != docname]
def merge_todos(app, env, docnames, other):
@@ -90,37 +89,40 @@ def process_todo_nodes(app, doctree, fromdocname):
for todo_info in env.todo_all_todos:
para = nodes.paragraph()
filename = env.doc2path(todo_info['docname'], base=None)
- description = (
- _('(The original entry is located in %s, line %d and can be found ') %
- (filename, todo_info['lineno']))
+ description = _(
+ '(The original entry is located in %s, line %d and can be found '
+ ) % (filename, todo_info['lineno'])
para += nodes.Text(description)
# Create a reference
newnode = nodes.reference('', '')
innernode = nodes.emphasis(_('here'), _('here'))
newnode['refdocname'] = todo_info['docname']
- newnode['refuri'] = app.builder.get_relative_uri(
- fromdocname, todo_info['docname'])
+ newnode['refuri'] = app.builder.get_relative_uri(fromdocname, todo_info['docname'])
newnode['refuri'] += '#' + todo_info['target']['refid']
newnode.append(innernode)
para += newnode
para += nodes.Text('.)')
# Insert into the todolist
- content.append(todo_info['todo'])
- content.append(para)
+ content.extend((
+ todo_info['todo'],
+ para,
+ ))
node.replace_self(content)
-def setup(app):
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value('todo_include_todos', False, 'html')
app.add_node(todolist)
- app.add_node(todo,
- html=(visit_todo_node, depart_todo_node),
- latex=(visit_todo_node, depart_todo_node),
- text=(visit_todo_node, depart_todo_node))
+ app.add_node(
+ todo,
+ html=(visit_todo_node, depart_todo_node),
+ latex=(visit_todo_node, depart_todo_node),
+ text=(visit_todo_node, depart_todo_node),
+ )
app.add_directive('todo', TodoDirective)
app.add_directive('todolist', TodolistDirective)
diff --git a/doc/development/tutorials/recipe.rst b/doc/development/tutorials/recipe.rst
index 1ed428a..683cc8c 100644
--- a/doc/development/tutorials/recipe.rst
+++ b/doc/development/tutorials/recipe.rst
@@ -179,9 +179,9 @@ hook the various parts of our extension into Sphinx. Let's look at the
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
-initialization of the :ref:`standard domain <domains-std>`. This is because we
-had already registered our directives, roles and indexes as part of the
-directive itself.
+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.
Using the extension