diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 2efc1c1844c..2f7b69adcf9 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -31,6 +31,7 @@ jobs:
docutils:
- "0.18"
- "0.19"
+ - "0.20"
steps:
- uses: actions/checkout@v3
@@ -57,13 +58,7 @@ jobs:
env:
PYTHONWARNINGS: ""
- name: Install Docutils ${{ matrix.docutils }}
- run: python -m pip install --upgrade "docutils==${{ matrix.docutils }}.*"
- if: "!endsWith(matrix.python, '-dev')"
- env:
- PYTHONWARNINGS: "error,default:pkg_resources is deprecated:DeprecationWarning::,default:Deprecated call to `pkg_resources.declare_namespace:DeprecationWarning::"
- - name: Install Docutils ${{ matrix.docutils }} (ignore warnings)
- run: python -m pip install --upgrade "docutils==${{ matrix.docutils }}.*"
- if: "endsWith(matrix.python, '-dev')"
+ run: python -m pip install --upgrade "docutils~=${{ matrix.docutils }}.0"
env:
PYTHONWARNINGS: ""
- name: Test with pytest
diff --git a/CHANGES b/CHANGES
index 6cc80e91e9e..e66f11cbf7e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,4 @@
-Release 7.0.1 (in development)
+Release 7.1.0 (in development)
==============================
Dependencies
@@ -10,15 +10,51 @@ Incompatible changes
Deprecated
----------
+* #11412: Emit warnings on using a deprecated Python-specific index entry type
+ (namely, ``module``, ``keyword``, ``operator``, ``object``, ``exception``,
+ ``statement``, and ``builtin``) in the :rst:dir:`index` directive, and
+ set the removal version to Sphinx 9. Patch by Adam Turner.
+
Features added
--------------
+* #11415: Add a checksum to JavaScript and CSS asset URIs included within
+ generated HTML, using the CRC32 algorithm.
+* :meth:`~sphinx.application.Sphinx.require_sphinx` now allows the version
+ requirement to be specified as ``(major, minor)``.
+* #11011: Allow configuring a line-length limit for object signatures, via
+ :confval:`maximum_signature_line_length` and the domain-specific variants.
+ If the length of the signature (in characters) is greater than the configured
+ limit, each parameter in the signature will be split to its own logical line.
+ This behaviour may also be controlled by options on object description
+ directives, for example :rst:dir:`py:function:single-line-parameter-list`.
+ Patch by Thomas Louf, Adam Turner, and Jean-François B.
+* #10983: Support for multiline copyright statements in the footer block.
+ Patch by Stefanie Molin
+
Bugs fixed
----------
Testing
--------
+Release 7.0.1 (released May 12, 2023)
+=====================================
+
+Dependencies
+------------
+
+* #11411: Support `Docutils 0.20`_. Patch by Adam Turner.
+
+.. _Docutils 0.20: https://docutils.sourceforge.io/RELEASE-NOTES.html#release-0-20-2023-05-04
+
+Bugs fixed
+----------
+
+* #11418: Clean up remaining references to ``sphinx.setup_command``
+ following the removal of support for setuptools.
+ Patch by Willem Mulder.
+
Release 7.0.0 (released Apr 29, 2023)
=====================================
diff --git a/Makefile b/Makefile
index 4602246d9cf..5293f6330ca 100644
--- a/Makefile
+++ b/Makefile
@@ -1,56 +1,43 @@
PYTHON ?= python3
.PHONY: all
-all: clean-pyc clean-backupfiles style-check type-check test
+all: style-check type-check test
.PHONY: clean
-clean: clean-pyc clean-pycache clean-patchfiles clean-backupfiles clean-generated clean-testfiles clean-buildfiles clean-mypyfiles
-
-.PHONY: clean-pyc
-clean-pyc:
+clean: clean
+ # clean Python cache files:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
-
-.PHONY: clean-pycache
-clean-pycache:
find . -name __pycache__ -exec rm -rf {} +
-.PHONY: clean-patchfiles
-clean-patchfiles:
- find . -name '*.orig' -exec rm -f {} +
- find . -name '*.rej' -exec rm -f {} +
-
-.PHONY: clean-backupfiles
-clean-backupfiles:
+ # clean backup files:
find . -name '*~' -exec rm -f {} +
find . -name '*.bak' -exec rm -f {} +
find . -name '*.swp' -exec rm -f {} +
find . -name '*.swo' -exec rm -f {} +
-.PHONY: clean-generated
-clean-generated:
+ # clean generated:
find . -name '.DS_Store' -exec rm -f {} +
- rm -rf Sphinx.egg-info/
- rm -rf dist/
+
+ # clean rendered documentation :
+ rm -rf doc/build/
rm -rf doc/_build/
- rm -f sphinx/pycode/*.pickle
- rm -f utils/*3.py*
- rm -f utils/regression_test.js
+ rm -rf build/sphinx/
-.PHONY: clean-testfiles
-clean-testfiles:
+ # clean caches:
+ find . -name '.mypy_cache' -exec rm -rf {} +
+ find . -name '.ruff_cache' -exec rm -rf {} +
+
+ # clean test files:
rm -rf tests/.coverage
rm -rf tests/build
rm -rf .tox/
rm -rf .cache/
+ find . -name '.pytest_cache' -exec rm -rf {} +
-.PHONY: clean-buildfiles
-clean-buildfiles:
- rm -rf build
-
-.PHONY: clean-mypyfiles
-clean-mypyfiles:
- find . -name '.mypy_cache' -exec rm -rf {} +
+ # clean build files:
+ rm -rf dist/
+ rm -rf build/
.PHONY: style-check
style-check:
diff --git a/doc/_themes/sphinx13/layout.html b/doc/_themes/sphinx13/layout.html
index 8010517a69a..86a794306b9 100644
--- a/doc/_themes/sphinx13/layout.html
+++ b/doc/_themes/sphinx13/layout.html
@@ -54,7 +54,7 @@
{{ _('Site navigation') }}
{%- block footer %}
{%- endblock %}
diff --git a/doc/latex.rst b/doc/latex.rst
index 50440342785..a451ae6a43c 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -1479,6 +1479,12 @@ Macros
.. versionadded:: 6.2.0
``\sphinxparam``, ``\sphinxsamedocref``
+ .. versionadded:: 7.1.0
+ ``\sphinxparamcomma`` which defaults to a comma followed by a space and
+ ``\sphinxparamcommaoneperline`` which is used for one-parameter-per-line
+ signatures (see :confval:`maximum_signature_line_length`). It defaults
+ to ``\texttt{,}`` to make these end-of-line separators more distinctive.
+
- More text styling:
.. csv-table::
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 11cd2be31dd..133e2099df9 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -73,6 +73,10 @@ Project information
A copyright statement in the style ``'2008, Author Name'``.
+ .. versionchanged:: 7.1
+ The value may now be a sequence of copyright statements in the above form,
+ which will be displayed each to their own line.
+
.. confval:: project_copyright
An alias of :confval:`copyright`.
@@ -337,6 +341,7 @@ General configuration
* ``epub.unknown_project_files``
* ``epub.duplicated_toc_entry``
* ``i18n.inconsistent_references``
+ * ``index``
* ``image.not_readable``
* ``ref.term``
* ``ref.ref``
@@ -388,6 +393,10 @@ General configuration
Added ``i18n.inconsistent_references``
+ .. versionadded:: 7.1
+
+ Added ``index`` warning type.
+
.. confval:: needs_sphinx
If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will
@@ -670,6 +679,25 @@ General configuration
If the value is a fully-qualified name of a custom Pygments style class,
this is then used as custom style.
+.. confval:: maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter within the signature will be displayed on an individual logical
+ line.
+
+ When ``None`` (the default), there is no maximum length and the entire
+ signature will be displayed on a single logical line.
+
+ A 'logical line' is similar to a hard line break---builders or themes may
+ choose to 'soft wrap' a single logical line, and this setting does not affect
+ that behaviour.
+
+ Domains may provide options to suppress any hard wrapping on an individual
+ object directive, such as seen in the C, C++, and Python domains (e.g.
+ :rst:dir:`py:function:single-line-parameter-list`).
+
+ .. versionadded:: 7.1
+
.. confval:: add_function_parentheses
A boolean that decides whether parentheses are appended to function and
@@ -2907,6 +2935,14 @@ Options for the C domain
.. versionadded:: 4.0.3
+.. confval:: c_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. _cpp-config:
Options for the C++ domain
@@ -2937,6 +2973,14 @@ Options for the C++ domain
.. versionadded:: 1.5
+.. confval:: cpp_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
Options for the Python domain
-----------------------------
@@ -2979,6 +3023,25 @@ Options for the Python domain
.. note:: This configuration is still in experimental
+.. confval:: python_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ argument will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
+Options for the Javascript domain
+---------------------------------
+
+.. confval:: javascript_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
Example of configuration file
-----------------------------
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index c9152d1b18b..7082af04622 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -896,9 +896,10 @@ mainly contained in information units, such as the language reference.
.. index::
single: execution; context
- module: __main__
- module: sys
+ pair: module; __main__
+ pair: module; sys
triple: module; search; path
+ seealso: scope
The execution context
---------------------
@@ -916,25 +917,63 @@ mainly contained in information units, such as the language reference.
The possible entry types are:
single
- Creates a single index entry. Can be made a subentry by separating the
- subentry text with a semicolon (this notation is also used below to
- describe what entries are created).
+ Creates a single index entry.
+ Can be made a sub-entry by separating the sub-entry text with a semicolon
+ (this notation is also used below to describe what entries are created).
+ Examples:
+
+ .. code:: reStructuredText
+
+ .. index:: single: execution
+ single: execution; context
+
+ - ``single: execution`` creates an index entry labelled ``execution``.
+ - ``single: execution; context`` creates an sub-entry of ``execution``
+ labelled ``context``.
pair
- ``pair: loop; statement`` is a shortcut that creates two index entries,
- namely ``loop; statement`` and ``statement; loop``.
+ A shortcut to create two index entries.
+ The pair of values must be separated by a semicolon.
+ Example:
+
+ .. code:: reStructuredText
+
+ .. index:: pair: loop; statement
+
+ This would create two index entries; ``loop; statement`` and ``statement; loop``.
triple
- Likewise, ``triple: module; search; path`` is a shortcut that creates
- three index entries, which are ``module; search path``, ``search; path,
- module`` and ``path; module search``.
+ A shortcut to create three index entries.
+ All three values must be separated by a semicolon.
+ Example:
+
+ .. code:: reStructuredText
+
+ .. index:: triple: module; search; path
+
+ This would create three index entries; ``module; search path``,
+ ``search; path, module``, and ``path; module search``.
see
- ``see: entry; other`` creates an index entry that refers from ``entry`` to
- ``other``.
+ A shortcut to create an index entry that refers to another entry.
+ Example:
+
+ .. code:: reStructuredText
+
+ .. index:: see: entry; other
+
+ This would create an index entry referring from ``entry`` to ``other``
+ (i.e. 'entry': See 'other').
seealso
- Like ``see``, but inserts "see also" instead of "see".
+ Like ``see``, but inserts 'see also' instead of 'see'.
module, keyword, operator, object, exception, statement, builtin
- These all create two index entries. For example, ``module: hashlib``
- creates the entries ``module; hashlib`` and ``hashlib; module``. (These
- are Python-specific and therefore deprecated.)
+ These **deprecated** shortcuts all create two index entries.
+ For example, ``module: hashlib`` creates the entries ``module; hashlib``
+ and ``hashlib; module``.
+
+ .. deprecated:: 1.0
+ These Python-specific entry types are deprecated.
+
+ .. versionchanged:: 7.1
+ Removal version set to Sphinx 9.0.
+ Using these entry types will now emit warnings with the ``index`` category.
You can mark up "main" index entries by prefixing them with an exclamation
mark. The references to "main" entries are emphasized in the generated
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index ac99a28bd11..cbece86e826 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -231,6 +231,16 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
+
.. rst:directive:: .. py:data:: name
Describes global data in a module, including both variables and values used
@@ -329,6 +339,15 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the class constructor's arguments will be emitted on a single
+ logical line, overriding :confval:`python_maximum_signature_line_length`
+ and :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. py:attribute:: name
Describes an object data attribute. The description should include
@@ -441,6 +460,15 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the method's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:option:: staticmethod
:type: no value
@@ -494,6 +522,15 @@ The following directives are provided for module and class contents:
There is no ``py:deco`` role to link to a decorator that is marked up with
this directive; rather, use the :rst:role:`py:func` role.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the decorator's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. py:decoratormethod:: name
.. py:decoratormethod:: name(signature)
@@ -763,6 +800,15 @@ The C domain (name **c**) is suited for documentation of C API.
:retval NULL: under some conditions.
:retval NULL: under some other conditions as well.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`c_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. c:macro:: name
.. c:macro:: name(arg list)
@@ -776,6 +822,15 @@ The C domain (name **c**) is suited for documentation of C API.
.. versionadded:: 3.0
The function style variant.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the macro's parameters will be emitted on a single logical
+ line, overriding :confval:`c_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. c:struct:: name
Describes a C struct.
@@ -1126,6 +1181,15 @@ visibility statement (``public``, ``private`` or ``protected``).
.. cpp:function:: template<> \
void print(int i)
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`cpp_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. cpp:member:: (member) variable declaration
.. cpp:var:: (member) variable declaration
@@ -1908,6 +1972,15 @@ The JavaScript domain (name **js**) provides the following directives:
:throws SomeError: For whatever reason in that case.
:returns: Something.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:method:: name(signature)
This directive is an alias for :rst:dir:`js:function`, however it describes
@@ -1915,6 +1988,15 @@ The JavaScript domain (name **js**) provides the following directives:
.. versionadded:: 1.6
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:class:: name
Describes a constructor that creates an object. This is basically like a
@@ -1933,6 +2015,15 @@ The JavaScript domain (name **js**) provides the following directives:
:param string name: The name of the animal
:param number age: an optional age for the animal
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:data:: name
Describes a global variable or constant.
diff --git a/pyproject.toml b/pyproject.toml
index 424af4938f7..fab20f40947 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -62,7 +62,7 @@ dependencies = [
"sphinxcontrib-qthelp",
"Jinja2>=3.0",
"Pygments>=2.13",
- "docutils>=0.18.1,<0.20",
+ "docutils>=0.18.1,<0.21",
"snowballstemmer>=2.0",
"babel>=2.9",
"alabaster>=0.7,<0.8",
@@ -105,9 +105,6 @@ sphinx-quickstart = "sphinx.cmd.quickstart:main"
sphinx-apidoc = "sphinx.ext.apidoc:main"
sphinx-autogen = "sphinx.ext.autosummary.generate:main"
-[project.entry-points."distutils.commands"]
-build_sphinx = 'sphinx.setup_command:BuildDoc'
-
[tool.flit.module]
name = "sphinx"
@@ -333,7 +330,6 @@ module = [
"sphinx.ext.autosummary",
"sphinx.ext.autosummary.generate",
"sphinx.ext.doctest",
- "sphinx.ext.duration",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.intersphinx",
@@ -344,14 +340,12 @@ module = [
"sphinx.ext.napoleon.docstring",
"sphinx.pycode.parser",
"sphinx.registry",
- "sphinx.setup_command",
"sphinx.testing.util",
"sphinx.transforms.i18n",
"sphinx.transforms.post_transforms.images",
"sphinx.util.cfamily",
"sphinx.util.docfields",
"sphinx.util.docutils",
- "sphinx.util.typing",
"sphinx.writers.latex",
"sphinx.writers.text",
"sphinx.writers.xml",
@@ -378,7 +372,18 @@ module = [
"sphinx.roles",
"sphinx.search.*",
"sphinx.testing.*",
- "sphinx.util.*",
+ "sphinx.util",
+ "sphinx.util.display",
+ "sphinx.util.docfields",
+ "sphinx.util.docutils",
+ "sphinx.util.fileutil",
+ "sphinx.util.i18n",
+ "sphinx.util.inspect",
+ "sphinx.util.inventory",
+ "sphinx.util.logging",
+ "sphinx.util.nodes",
+ "sphinx.util.parallel",
+ "sphinx.util.template",
]
disallow_any_generics = false
@@ -391,7 +396,6 @@ filterwarnings = [
]
markers = [
"apidoc",
- "setup_command",
]
testpaths = ["tests"]
diff --git a/sphinx/__init__.py b/sphinx/__init__.py
index 5af167f10ca..15633197a58 100644
--- a/sphinx/__init__.py
+++ b/sphinx/__init__.py
@@ -19,7 +19,7 @@
warnings.filterwarnings('ignore', 'The frontend.Option class .*',
DeprecationWarning, module='docutils.frontend')
-__version__ = '7.0.1'
+__version__ = '7.1.0'
__display_version__ = __version__ # used for command line version
#: Version info for better programmatic use.
@@ -30,7 +30,7 @@
#:
#: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``.
-version_info = (7, 0, 1, 'beta', 0)
+version_info = (7, 1, 0, 'beta', 0)
package_dir = path.abspath(path.dirname(__file__))
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 44655d9bed1..e92d32a0ef8 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -246,7 +246,12 @@ def astext(self) -> str:
class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
- """Node for a general parameter list."""
+ """Node for a general parameter list.
+
+ As default the parameter list is written in line with the rest of the signature.
+ Set ``multi_line_parameter_list = True`` to describe a multi-line parameter list.
+ In that case each parameter will then be written on its own, indented line.
+ """
child_text_separator = ', '
def astext(self):
diff --git a/sphinx/application.py b/sphinx/application.py
index 67b32269303..495e905704d 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -401,18 +401,26 @@ def setup_extension(self, extname: str) -> None:
logger.debug('[app] setting up extension: %r', extname)
self.registry.load_extension(self, extname)
- def require_sphinx(self, version: str) -> None:
+ @staticmethod
+ def require_sphinx(version: tuple[int, int] | str) -> None:
"""Check the Sphinx version if requested.
Compare *version* with the version of the running Sphinx, and abort the
build when it is too old.
- :param version: The required version in the form of ``major.minor``.
+ :param version: The required version in the form of ``major.minor`` or
+ ``(major, minor)``.
.. versionadded:: 1.0
+ .. versionchanged:: 7.1
+ Type of *version* now allows ``(major, minor)`` form.
"""
- if version > sphinx.__display_version__[:3]:
- raise VersionRequirementError(version)
+ if isinstance(version, tuple):
+ major, minor = version
+ else:
+ major, minor = map(int, version.split('.')[:2])
+ if (major, minor) > sphinx.version_info[:2]:
+ raise VersionRequirementError(f'{major}.{minor}')
# event interface
def connect(self, event: str, callback: Callable, priority: int = 500) -> int:
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index 852f252596e..a5d5a1ef69b 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -560,6 +560,9 @@ def write(
with progress_message(__('preparing documents')):
self.prepare_writing(docnames)
+ with progress_message(__('copying assets')):
+ self.copy_assets()
+
if self.parallel_ok:
# number of subprocesses is parallel-1 because the main process
# is busy loading doctrees and doing write_doc_serialized()
@@ -620,6 +623,10 @@ def prepare_writing(self, docnames: set[str]) -> None:
"""A place where you can add logic before :meth:`write_doc` is run"""
raise NotImplementedError
+ def copy_assets(self) -> None:
+ """Where assets (images, static files, etc) are copied before writing"""
+ pass
+
def write_doc(self, docname: str, doctree: nodes.document) -> None:
"""Where you actually write something to the filesystem."""
raise NotImplementedError
diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py
index 4d460109b30..697b75e4053 100644
--- a/sphinx/builders/gettext.py
+++ b/sphinx/builders/gettext.py
@@ -16,7 +16,6 @@
from sphinx import addnodes, package_dir
from sphinx.application import Sphinx
from sphinx.builders import Builder
-from sphinx.domains.python import pairindextypes
from sphinx.errors import ThemeError
from sphinx.locale import __
from sphinx.util import logging, split_index_msg
@@ -159,10 +158,6 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None:
for node, entries in traverse_translatable_index(doctree):
for typ, msg, _tid, _main, _key in entries:
for m in split_index_msg(typ, msg):
- if typ == 'pair' and m in pairindextypes.values():
- # avoid built-in translated message was incorporated
- # in 'sphinx.util.nodes.process_index_entry'
- continue
catalog.add(m, node)
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 8b8c426a4b0..64a5c1f604d 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -8,6 +8,7 @@
import re
import sys
import warnings
+import zlib
from datetime import datetime
from os import path
from typing import IO, Any, Iterable, Iterator, List, Tuple, Type
@@ -649,6 +650,12 @@ def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, A
'page_source_suffix': source_suffix,
}
+ def copy_assets(self) -> None:
+ self.finish_tasks.add_task(self.copy_download_files)
+ self.finish_tasks.add_task(self.copy_static_files)
+ self.finish_tasks.add_task(self.copy_extra_files)
+ self.finish_tasks.join()
+
def write_doc(self, docname: str, doctree: nodes.document) -> None:
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
@@ -678,9 +685,6 @@ def finish(self) -> None:
self.finish_tasks.add_task(self.gen_pages_from_extensions)
self.finish_tasks.add_task(self.gen_additional_pages)
self.finish_tasks.add_task(self.copy_image_files)
- self.finish_tasks.add_task(self.copy_download_files)
- self.finish_tasks.add_task(self.copy_static_files)
- self.finish_tasks.add_task(self.copy_extra_files)
self.finish_tasks.add_task(self.write_buildinfo)
# dump the search index
@@ -1193,8 +1197,11 @@ def css_tag(css: Stylesheet) -> str:
value = css.attributes[key]
if value is not None:
attrs.append(f'{key}="{html.escape(value, True)}"')
- attrs.append('href="%s"' % pathto(css.filename, resource=True))
- return '' % ' '.join(attrs)
+ uri = pathto(css.filename, resource=True)
+ if checksum := _file_checksum(app.outdir, css.filename):
+ uri += f'?v={checksum}'
+ attrs.append(f'href="{uri}"')
+ return f''
context['css_tag'] = css_tag
@@ -1217,14 +1224,17 @@ def js_tag(js: JavaScript) -> str:
if key == 'body':
body = value
elif key == 'data_url_root':
- attrs.append('data-url_root="%s"' % pathto('', resource=True))
+ attrs.append(f'data-url_root="{pathto("", resource=True)}"')
else:
attrs.append(f'{key}="{html.escape(value, True)}"')
if js.filename:
- attrs.append('src="%s"' % pathto(js.filename, resource=True))
+ uri = pathto(js.filename, resource=True)
+ if checksum := _file_checksum(app.outdir, js.filename):
+ uri += f'?v={checksum}'
+ attrs.append(f'src="{uri}"')
else:
# str value (old styled)
- attrs.append('src="%s"' % pathto(js, resource=True))
+ attrs.append(f'src="{pathto(js, resource=True)}"')
if attrs:
return f''
@@ -1234,6 +1244,21 @@ def js_tag(js: JavaScript) -> str:
context['js_tag'] = js_tag
+def _file_checksum(outdir: str, filename: str) -> str:
+ # Don't generate checksums for HTTP URIs
+ if '://' in filename:
+ return ''
+ try:
+ # Ensure universal newline mode is used to avoid checksum differences
+ with open(path.join(outdir, filename), encoding='utf-8') as f:
+ content = f.read().encode(encoding='utf-8')
+ except FileNotFoundError:
+ return ''
+ if not content:
+ return ''
+ return f'{zlib.crc32(content):08x}'
+
+
def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
context: dict, doctree: Node) -> None:
"""Set up relative resource paths."""
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index 335518f2308..e3e4d5042f2 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -254,6 +254,12 @@ def write_stylesheet(self) -> None:
f.write('% Its contents depend on pygments_style configuration variable.\n\n')
f.write(highlighter.get_stylesheet())
+ def copy_assets(self) -> None:
+ self.copy_support_files()
+
+ if self.config.latex_additional_files:
+ self.copy_latex_additional_files()
+
def write(self, *ignored: Any) -> None:
docwriter = LaTeXWriter(self)
with warnings.catch_warnings():
@@ -267,6 +273,7 @@ def write(self, *ignored: Any) -> None:
self.init_document_data()
self.write_stylesheet()
+ self.copy_assets()
for entry in self.document_data:
docname, targetname, title, author, themename = entry[:5]
@@ -371,10 +378,6 @@ def assemble_doctree(
def finish(self) -> None:
self.copy_image_files()
self.write_message_catalog()
- self.copy_support_files()
-
- if self.config.latex_additional_files:
- self.copy_latex_additional_files()
@progress_message(__('copying TeX support files'))
def copy_support_files(self) -> None:
diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py
index 078991d9dcd..0b642af4cae 100644
--- a/sphinx/builders/texinfo.py
+++ b/sphinx/builders/texinfo.py
@@ -85,6 +85,7 @@ def init_document_data(self) -> None:
def write(self, *ignored: Any) -> None:
self.init_document_data()
+ self.copy_assets()
for entry in self.document_data:
docname, targetname, title, author = entry[:4]
targetname += '.texi'
@@ -168,7 +169,7 @@ def assemble_doctree(
pendingnode.replace_self(newnodes)
return largetree
- def finish(self) -> None:
+ def copy_assets(self) -> None:
self.copy_support_files()
def copy_image_files(self, targetname: str) -> None:
diff --git a/sphinx/config.py b/sphinx/config.py
index ad7c3b56898..b8cf1eda2ca 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -89,8 +89,8 @@ class Config:
# general options
'project': ('Python', 'env', []),
'author': ('unknown', 'env', []),
- 'project_copyright': ('', 'html', [str]),
- 'copyright': (lambda c: c.project_copyright, 'html', [str]),
+ 'project_copyright': ('', 'html', [str, tuple, list]),
+ 'copyright': (lambda c: c.project_copyright, 'html', [str, tuple, list]),
'version': ('', 'env', []),
'release': ('', 'env', []),
'today': ('', 'env', []),
@@ -137,7 +137,7 @@ class Config:
'numfig': (False, 'env', []),
'numfig_secnum_depth': (1, 'env', []),
'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
-
+ 'maximum_signature_line_length': (None, 'env', {int, None}),
'math_number_all': (False, 'env', []),
'math_eqref_format': (None, 'env', [str]),
'math_numfig': (True, 'env', []),
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index c583a770d13..0bb505fba5b 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -727,9 +727,19 @@ def _stringify(self, transform: StringifyTransform) -> str:
def describe_signature(self, signode: TextElement, mode: str,
env: BuildEnvironment, symbol: Symbol) -> None:
verify_description_mode(mode)
+ multi_line_parameter_list = False
+ test_node: Element = signode
+ while test_node.parent:
+ if not isinstance(test_node, addnodes.desc_signature):
+ test_node = test_node.parent
+ continue
+ multi_line_parameter_list = test_node.get('multi_line_parameter_list', False)
+ break
+
# only use the desc_parameterlist for the outer list, not for inner lists
if mode == 'lastIsName':
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
for arg in self.args:
param = addnodes.desc_parameter('', '', noemph=True)
arg.describe_signature(param, 'param', env, symbol=symbol)
@@ -3153,6 +3163,7 @@ class CObject(ObjectDescription[ASTDeclaration]):
option_spec: OptionSpec = {
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
@@ -3258,6 +3269,14 @@ def run(self) -> list[Node]:
def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
parentSymbol: Symbol = self.env.temp_data['c:parent_symbol']
+ max_len = (self.env.config.c_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ signode['multi_line_parameter_list'] = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
parser = DefinitionParser(sig, location=signode, config=self.env.config)
try:
ast = self.parse_definition(parser)
@@ -3866,11 +3885,12 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("c_id_attributes", [], 'env')
app.add_config_value("c_paren_attributes", [], 'env')
app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
+ app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None})
app.add_post_transform(AliasTransform)
return {
'version': 'builtin',
- 'env_version': 2,
+ 'env_version': 3,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index a7d16aa06d4..41f2bd07682 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -2142,9 +2142,19 @@ def _stringify(self, transform: StringifyTransform) -> str:
def describe_signature(self, signode: TextElement, mode: str,
env: BuildEnvironment, symbol: Symbol) -> None:
verify_description_mode(mode)
+ multi_line_parameter_list = False
+ test_node: Element = signode
+ while test_node.parent:
+ if not isinstance(test_node, addnodes.desc_signature):
+ test_node = test_node.parent
+ continue
+ multi_line_parameter_list = test_node.get('multi_line_parameter_list', False)
+ break
+
# only use the desc_parameterlist for the outer list, not for inner lists
if mode == 'lastIsName':
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
for arg in self.args:
param = addnodes.desc_parameter('', '', noemph=True)
arg.describe_signature(param, 'param', env, symbol=symbol)
@@ -7192,6 +7202,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
'tparam-line-spec': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
@@ -7348,6 +7359,14 @@ def run(self) -> list[Node]:
def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration:
parentSymbol: Symbol = self.env.temp_data['cpp:parent_symbol']
+ max_len = (self.env.config.cpp_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ signode['multi_line_parameter_list'] = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
parser = DefinitionParser(sig, location=signode, config=self.env.config)
try:
ast = self.parse_definition(parser)
@@ -8140,6 +8159,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("cpp_index_common_prefix", [], 'env')
app.add_config_value("cpp_id_attributes", [], 'env')
app.add_config_value("cpp_paren_attributes", [], 'env')
+ app.add_config_value("cpp_maximum_signature_line_length", None, 'env', types={int, None})
app.add_post_transform(AliasTransform)
# debug stuff
@@ -8154,7 +8174,7 @@ def initStuff(app):
return {
'version': 'builtin',
- 'env_version': 8,
+ 'env_version': 9,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index 093e291ca57..c6baab8a9ee 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -43,6 +43,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def get_display_prefix(self) -> list[Node]:
@@ -88,6 +89,14 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
signode['object'] = prefix
signode['fullname'] = fullname
+ max_len = (self.env.config.javascript_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ multi_line_parameter_list = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
display_prefix = self.get_display_prefix()
if display_prefix:
signode += addnodes.desc_annotation('', '', *display_prefix)
@@ -108,7 +117,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
if not arglist:
signode += addnodes.desc_parameterlist()
else:
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
return fullname, prefix
def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
@@ -473,10 +482,12 @@ def get_full_qualified_name(self, node: Element) -> str | None:
def setup(app: Sphinx) -> dict[str, Any]:
app.add_domain(JavaScriptDomain)
-
+ app.add_config_value(
+ 'javascript_maximum_signature_line_length', None, 'env', types={int, None},
+ )
return {
'version': 'builtin',
- 'env_version': 2,
+ 'env_version': 3,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index c461cc311fd..3fda5270351 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -50,13 +50,13 @@
pairindextypes = {
- 'module': _('module'),
- 'keyword': _('keyword'),
- 'operator': _('operator'),
- 'object': _('object'),
- 'exception': _('exception'),
- 'statement': _('statement'),
- 'builtin': _('built-in function'),
+ 'module': 'module',
+ 'keyword': 'keyword',
+ 'operator': 'operator',
+ 'object': 'object',
+ 'exception': 'exception',
+ 'statement': 'statement',
+ 'builtin': 'built-in function',
}
@@ -258,10 +258,11 @@ def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
def _parse_arglist(
- arglist: str, env: BuildEnvironment | None = None,
+ arglist: str, env: BuildEnvironment | None = None, multi_line_parameter_list: bool = False,
) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist)
+ params['multi_line_parameter_list'] = multi_line_parameter_list
sig = signature_from_str('(%s)' % arglist)
last_kind = None
for param in sig.parameters.values():
@@ -309,7 +310,9 @@ def _parse_arglist(
return params
-def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
+def _pseudo_parse_arglist(
+ signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False,
+) -> None:
""""Parse" a list of arguments separated by commas.
Arguments can have "optional" annotations given by enclosing them in
@@ -317,6 +320,7 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
string literal (e.g. default argument value).
"""
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
stack: list[Element] = [paramlist]
try:
for argument in arglist.split(','):
@@ -459,6 +463,7 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
'module': directives.unchanged,
'canonical': directives.unchanged,
'annotation': directives.unchanged,
@@ -541,6 +546,14 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
signode['class'] = classname
signode['fullname'] = fullname
+ max_len = (self.env.config.python_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ multi_line_parameter_list = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
sig_prefix = self.get_signature_prefix(sig)
if sig_prefix:
if type(sig_prefix) is str:
@@ -559,15 +572,15 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
signode += addnodes.desc_name(name, name)
if arglist:
try:
- signode += _parse_arglist(arglist, self.env)
+ signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
except SyntaxError:
# fallback to parse arglist original parser.
# it supports to represent optional arguments (ex. "func(foo [, bar])")
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
except NotImplementedError as exc:
logger.warning("could not parse arglist (%r): %s", arglist, exc,
location=signode)
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
else:
if self.needs_arglist():
# for callables, add an empty parameter list
@@ -729,7 +742,7 @@ def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
text = _('%s() (in module %s)') % (name, modname)
self.indexnode['entries'].append(('single', text, node_id, '', None))
else:
- text = f'{pairindextypes["builtin"]}; {name}()'
+ text = f'built-in function; {name}()'
self.indexnode['entries'].append(('pair', text, node_id, '', None))
def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str | None:
@@ -1058,7 +1071,7 @@ def run(self) -> list[Node]:
# the platform and synopsis aren't printed; in fact, they are only
# used in the modindex currently
ret.append(target)
- indextext = f'{pairindextypes["module"]}; {modname}'
+ indextext = f'module; {modname}'
inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
ret.append(inode)
ret.extend(content_node.children)
@@ -1505,13 +1518,15 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_domain(PythonDomain)
app.add_config_value('python_use_unqualified_type_names', False, 'env')
+ app.add_config_value('python_maximum_signature_line_length', None, 'env',
+ types={int, None})
app.add_config_value('python_display_short_literal_types', False, 'env')
app.connect('object-description-transform', filter_meta_fields)
app.connect('missing-reference', builtin_resolver, priority=900)
return {
'version': 'builtin',
- 'env_version': 3,
+ 'env_version': 4,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/ext/duration.py b/sphinx/ext/duration.py
index 749e2910b88..15441244124 100644
--- a/sphinx/ext/duration.py
+++ b/sphinx/ext/duration.py
@@ -57,7 +57,7 @@ def on_source_read(app: Sphinx, docname: str, content: list[str]) -> None:
def on_doctree_read(app: Sphinx, doctree: nodes.document) -> None:
"""Record a reading duration."""
- started_at = app.env.temp_data.get('started_at')
+ started_at = app.env.temp_data['started_at']
duration = datetime.now() - started_at
domain = cast(DurationDomain, app.env.get_domain('duration'))
domain.note_reading_duration(duration)
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index c0a99be08c6..37626e04f4c 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -8,7 +8,7 @@
import subprocess
from os import path
from subprocess import CalledProcessError
-from typing import Any
+from typing import TYPE_CHECKING, Any
from docutils import nodes
from docutils.nodes import Node
@@ -20,7 +20,6 @@
from sphinx.locale import _, __
from sphinx.util import logging, sha1
from sphinx.util.docutils import SphinxDirective, SphinxTranslator
-from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import search_image_for_language
from sphinx.util.nodes import set_source_info
from sphinx.util.osutil import ensuredir
@@ -31,6 +30,9 @@
from sphinx.writers.texinfo import TexinfoTranslator
from sphinx.writers.text import TextTranslator
+if TYPE_CHECKING:
+ from sphinx.config import Config
+
logger = logging.getLogger(__name__)
@@ -391,11 +393,9 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None:
raise nodes.SkipNode
-def on_build_finished(app: Sphinx, exc: Exception) -> None:
- if exc is None and app.builder.format == 'html':
- src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
- dst = path.join(app.outdir, '_static')
- copy_asset(src, dst)
+def on_config_inited(_app: Sphinx, config: Config) -> None:
+ css_path = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
+ config.html_static_path.append(css_path)
def setup(app: Sphinx) -> dict[str, Any]:
@@ -412,5 +412,5 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value('graphviz_dot_args', [], 'html')
app.add_config_value('graphviz_output_format', 'png', 'html')
app.add_css_file('graphviz.css')
- app.connect('build-finished', on_build_finished)
+ app.connect('config-inited', on_config_inited)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py
index c92640576ab..8ab90d19120 100644
--- a/sphinx/locale/__init__.py
+++ b/sphinx/locale/__init__.py
@@ -223,9 +223,3 @@ def gettext(message: str) -> str:
'tip': _('Tip'),
'warning': _('Warning'),
}
-
-# Moved to sphinx.directives.other (will be overridden later)
-versionlabels: dict[str, str] = {}
-
-# Moved to sphinx.domains.python (will be overridden later)
-pairindextypes: dict[str, str] = {}
diff --git a/sphinx/texinputs/sphinxlatexobjects.sty b/sphinx/texinputs/sphinxlatexobjects.sty
index b4ff1f9d02d..a2038a9f160 100644
--- a/sphinx/texinputs/sphinxlatexobjects.sty
+++ b/sphinx/texinputs/sphinxlatexobjects.sty
@@ -146,6 +146,27 @@
\item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}]
\pysigadjustitemsep
}
+
+\def\sphinxoptionalextraspace{0.5mm}
+\newcommand{\pysigwithonelineperarg}[3]{%
+ % render each argument on its own line
+ \item[#1\sphinxcode{(}\strut]
+ \leavevmode\par\nopagebreak
+ % this relies on \pysigstartsignatures having set \parskip to zero
+ \begingroup
+ \let\sphinxparamcomma\sphinxparamcommaoneperline
+ \def\sphinxoptionalhook{\ifvmode\else\kern\sphinxoptionalextraspace\relax\fi}%
+ % The very first \sphinxparam should not emit a \par hence a complication
+ % with a group and global definition here as it may occur in a \sphinxoptional
+ \global\let\spx@sphinxparam\sphinxparam
+ \gdef\sphinxparam{\gdef\sphinxparam{\par\spx@sphinxparam}\spx@sphinxparam}%
+ #2\par
+ \endgroup
+ \global\let\sphinxparam\spx@sphinxparam
+ % fulllineitems sets \labelwidth to be like \leftmargin
+ \nopagebreak\noindent\kern-\labelwidth\sphinxcode{)}{#3}
+ \pysigadjustitemsep
+}
\newcommand{\pysigadjustitemsep}{%
% adjust \itemsep to control the separation with the next signature
% sharing common description
diff --git a/sphinx/texinputs/sphinxlatexstyletext.sty b/sphinx/texinputs/sphinxlatexstyletext.sty
index 913bc8210a6..292facc9132 100644
--- a/sphinx/texinputs/sphinxlatexstyletext.sty
+++ b/sphinx/texinputs/sphinxlatexstyletext.sty
@@ -58,7 +58,8 @@
\protected\def\sphinxparam#1{\emph{#1}}
% \optional is used for ``[, arg]``, i.e. desc_optional nodes.
\long\protected\def\sphinxoptional#1{%
- {\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}
+ {\sphinxoptionalhook\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}
+\let\sphinxoptionalhook\empty
% additional customizable styling
\def\sphinxstyleindexentry #1{\texttt{#1}}
@@ -112,6 +113,11 @@
% Special characters
%
+\def\sphinxparamcomma{, }% by default separate parameters with comma + space
+% If the signature is rendered with one line per param, this wil be used
+% instead (this \texttt makes the comma slightly more distinctive).
+\def\sphinxparamcommaoneperline{\texttt{,}}
+%
% The \kern\z@ is to prevent en-dash and em-dash TeX ligatures.
% A linebreak can occur after the dash in regular text (this is
% normal behaviour of "-" in TeX, it is not related to \kern\z@).
diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html
index f3088f79a95..79eb02de8ec 100644
--- a/sphinx/themes/basic/layout.html
+++ b/sphinx/themes/basic/layout.html
@@ -183,14 +183,30 @@