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 @@

{{ _('Navigation') }}

{%- block relbar2 %}{{ relbar() }}{% endblock %} +{%- macro copyright_block() %} + {%- if hasdoc('copyright') %} + {%- set copyright_prefix = '' + _('Copyright') + '' -%} + {%- else %} + {%- set copyright_prefix = _('Copyright') %} + {%- endif %} + {%- if copyright is iterable and copyright is not string %} + {% for copyright_line in copyright %} + {% trans trimmed copyright_prefix=copyright_prefix, copyright=copyright_line|e %} + © {{ copyright_prefix }} {{ copyright }}. + {% endtrans %} + {%- if not loop.last %}
{%- endif %} + {% endfor %} + {%- else %} + {% trans trimmed copyright_prefix=copyright_prefix, copyright=copyright|e %} + © {{ copyright_prefix }} {{ copyright }}. + {% endtrans %} + {%- endif %} +{%- endmacro %} + {%- block footer %}