From 5de84c9242643f786eff03726286578726d7d390 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 5 Jun 2024 18:20:59 +0200 Subject: Merging upstream version 7.3.7. Signed-off-by: Daniel Baumann --- doc/development/builders.rst | 18 +-- doc/development/templating.rst | 5 +- doc/development/theming.rst | 125 ++++++++++++++++++--- .../tutorials/examples/autodoc_intenum.py | 19 ++-- doc/development/tutorials/examples/helloworld.py | 8 +- doc/development/tutorials/examples/recipe.py | 46 ++++---- doc/development/tutorials/examples/todo.py | 34 +++--- doc/development/tutorials/recipe.rst | 6 +- 8 files changed, 178 insertions(+), 83 deletions(-) (limited to 'doc/development') 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 ```` 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_``. + +.. 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 `. .. _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 `. This is because we -had already registered our directives, roles and indexes as part of the -directive itself. +initialization of the :doc:`standard domain `. +This is because we had already registered our directives, +roles and indexes as part of the directive itself. Using the extension -- cgit v1.2.3