Merge branch '4.x' into HEAD

This commit is contained in:
Takeshi KOMIYA
2022-01-15 19:46:45 +09:00
516 changed files with 28879 additions and 22758 deletions

18
.github/workflows/create-release.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Create release
on:
push:
tags:
- "v*.*.*"
jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
body: "Changelog: https://www.sphinx-doc.org/en/master/changes.html"

25
.github/workflows/docutils-latest.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Test with the HEAD of docutils
on:
schedule:
- cron: "0 0 * * SUN"
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
- name: Check Python version
run: python --version
- name: Unpin docutils
run: sed -i -e "s/'docutils>=.*'/'docutils'/" setup.py
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Install dependencies
run: pip install -U tox codecov
- name: Run Tox
run: tox -e du-latest -- -vv

View File

@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
name: [py36, py37, py38, py39] name: [py36, py37, py38, py39, py310]
include: include:
- name: py36 - name: py36
python: 3.6 python: 3.6
@@ -23,9 +23,12 @@ jobs:
python: 3.9 python: 3.9
docutils: du17 docutils: du17
coverage: "--cov ./ --cov-append --cov-config setup.cfg" coverage: "--cov ./ --cov-append --cov-config setup.cfg"
- name: py310-dev - name: py310
python: 3.10-dev python: "3.10"
docutils: du17 docutils: du17
- name: py311-dev
python: 3.11-dev
docutils: py311
env: env:
PYTEST_ADDOPTS: ${{ matrix.coverage }} PYTEST_ADDOPTS: ${{ matrix.coverage }}
@@ -47,6 +50,9 @@ jobs:
run: sudo apt-get install graphviz run: sudo apt-get install graphviz
- name: Install dependencies - name: Install dependencies
run: pip install -U tox codecov run: pip install -U tox codecov
- name: Install the latest py package (for py3.11-dev)
run: pip install -U git+https://github.com/pytest-dev/py
if: ${{ matrix.python == '3.11-dev' }}
- name: Run Tox - name: Run Tox
run: tox -e ${{ matrix.docutils }} -- -vv run: tox -e ${{ matrix.docutils }} -- -vv
- name: codecov - name: codecov

View File

@@ -15,6 +15,8 @@ jobs:
ref: 4.x ref: 4.x
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with:
python-version: 3.9 # https://github.com/transifex/transifex-client/pull/330
- name: Install dependencies - name: Install dependencies
run: pip install -U babel jinja2 transifex-client run: pip install -U babel jinja2 transifex-client
- name: Extract translations from source code - name: Extract translations from source code
@@ -33,6 +35,8 @@ jobs:
ref: 4.x ref: 4.x
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with:
python-version: 3.9 # https://github.com/transifex/transifex-client/pull/330
- name: Install dependencies - name: Install dependencies
run: pip install -U babel jinja2 transifex-client run: pip install -U babel jinja2 transifex-client
- name: Extract translations from source code - name: Extract translations from source code
@@ -49,3 +53,4 @@ jobs:
commit-message: 'Update message catalogs' commit-message: 'Update message catalogs'
branch: bot/pull-translations branch: bot/pull-translations
title: Update message catalogs title: Update message catalogs
labels: i18n

261
CHANGES
View File

@@ -1,4 +1,91 @@
Release 4.3.0 (in development) Release 4.4.0 (in development)
==============================
Dependencies
------------
* #10007: Use ``importlib_metadata`` for python-3.9 or older
* #10007: Drop ``setuptools``
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
* #9075: autodoc: Add a config variable :confval:`autodoc_typehints_format`
to suppress the leading module names of typehints of function signatures (ex.
``io.StringIO`` -> ``StringIO``)
* #9831: Autosummary now documents only the members specified in a module's
``__all__`` attribute if :confval:`autosummary_ignore_module_all` is set to
``False``. The default behaviour is unchanged. Autogen also now supports
this behavior with the ``--respect-module-all`` switch.
* #9555: autosummary: Improve error messages on failure to load target object
* #9800: extlinks: Emit warning if a hardcoded link is replaceable
by an extlink, suggesting a replacement.
* #9961: html: Support nested <kbd> HTML elements in other HTML builders
* #10013: html: Allow to change the loading method of JS via ``loading_method``
parameter for :meth:`Sphinx.add_js_file()`
* #9551: html search: "Hide Search Matches" link removes "highlight" parameter
from URL
* #9815: html theme: Wrap sidebar components in div to allow customizing their
layout via CSS
* #9827: i18n: Sort items in glossary by translated terms
* #9899: py domain: Allows to specify cross-reference specifier (``.`` and
``~``) as ``:type:`` option
* #9894: linkcheck: add option ``linkcheck_exclude_documents`` to disable link
checking in matched documents.
* #9793: sphinx-build: Allow to use the parallel build feature in macOS on macOS
and Python3.8+
* #10055: sphinx-build: Create directories when ``-w`` option given
* #9993: std domain: Allow to refer an inline target (ex. ``_`target name```)
via :rst:role:`ref` role
* #9391: texinfo: improve variable in ``samp`` role
* #9578: texinfo: Add :confval:`texinfo_cross_references` to disable cross
references for readability with standalone readers
Bugs fixed
----------
* #9866: autodoc: doccomment for the imported class was ignored
* #9883: autodoc: doccomment for the alias to mocked object was ignored
* #9908: autodoc: debug message is shown on building document using NewTypes
with Python 3.10
* #9968: autodoc: instance variables are not shown if __init__ method has
position-only-arguments
* #9194: autodoc: types under the "typing" module are not hyperlinked
* #10009: autodoc: Crashes if target object raises an error on getting docstring
* #10058: autosummary: Imported members are not shown when
``autodoc_class_signature = 'separated'``
* #9947: i18n: topic directive having a bullet list can't be translatable
* #9878: mathjax: MathJax configuration is placed after loading MathJax itself
* #9857: Generated RFC links use outdated base url
* #9909: HTML, prevent line-wrapping in literal text.
* #10061: html theme: Configuration values added by themes are not be able to
override from conf.py
* #10073: imgconverter: Unnecessary availablity check is called for "data" URIs
* #9925: LaTeX: prohibit also with ``'xelatex'`` line splitting at dashes of
inline and parsed literals
* #9944: LaTeX: extra vertical whitespace for some nested declarations
* #9940: LaTeX: Multi-function declaration in Python domain has cramped
vertical spacing in latexpdf output
* #10015: py domain: types under the "typing" module are not hyperlinked defined
at info-field-list
* #9390: texinfo: Do not emit labels inside footnotes
* #9979: Error level messages were displayed as warning messages
* #10057: Failed to scan documents if the project is placed onto the root
directory
* #9636: code-block: ``:dedent:`` handled numeric argument ``0`` like no argument was given
* #9636: code-block: ``:dedent:`` without argument did strip newlines
Testing
--------
Release 4.3.3 (in development)
============================== ==============================
Dependencies Dependencies
@@ -13,7 +100,103 @@ Deprecated
Features added Features added
-------------- --------------
Bugs fixed
----------
Testing
--------
Release 4.3.2 (released Dec 19, 2021)
=====================================
Bugs fixed
----------
* #9917: C and C++, parse fundamental types no matter the order of simple type
specifiers.
Release 4.3.1 (released Nov 28, 2021)
=====================================
Features added
--------------
* #9864: mathjax: Support chnaging the loading method of MathJax to "defer" via
:confval:`mathjax_options`
Bugs fixed
----------
* #9838: autodoc: AttributeError is raised on building document for functions
decorated by functools.lru_cache
* #9879: autodoc: AttributeError is raised on building document for an object
having invalid __doc__ attribute
* #9844: autodoc: Failed to process a function wrapped with functools.partial if
:confval:`autodoc_preserve_defaults` enabled
* #9872: html: Class namespace collision between autodoc signatures and
docutils-0.17
* #9868: imgmath: Crashed if the dvisvgm command failed to convert equation
* #9864: mathjax: Failed to render equations via MathJax v2. The loading method
of MathJax is back to "async" method again
Release 4.3.0 (released Nov 11, 2021)
=====================================
Dependencies
------------
* Support Python 3.10
Incompatible changes
--------------------
* #9649: ``searchindex.js``: the embedded data has changed format to allow
objects with the same name in different domains.
* #9672: The rendering of Python domain declarations is implemented
with more docutils nodes to allow better CSS styling.
It may break existing styling.
* #9672: the signature of
:py:meth:`domains.py.PyObject.get_signature_prefix` has changed to
return a list of nodes instead of a plain string.
* #9695: ``domains.js.JSObject.display_prefix`` has been changed into a method
``get_display_prefix`` which now returns a list of nodes
instead of a plain string.
* #9695: The rendering of Javascript domain declarations is implemented
with more docutils nodes to allow better CSS styling.
It may break existing styling.
* #9450: mathjax: Load MathJax via "defer" strategy
Deprecated
----------
* ``sphinx.ext.autodoc.AttributeDocumenter._datadescriptor``
* ``sphinx.writers.html.HTMLTranslator._fieldlist_row_index``
* ``sphinx.writers.html.HTMLTranslator._table_row_index``
* ``sphinx.writers.html5.HTML5Translator._fieldlist_row_index``
* ``sphinx.writers.html5.HTML5Translator._table_row_index``
Features added
--------------
* #9639: autodoc: Support asynchronous generator functions * #9639: autodoc: Support asynchronous generator functions
* #9664: autodoc: ``autodoc-process-bases`` supports to inject reST snippet as a
base class
* #9691: C, added new info-field ``retval``
for :rst:dir:`c:function` and :rst:dir:`c:macro`.
* C++, added new info-field ``retval`` for :rst:dir:`cpp:function`.
* #9618: i18n: Add :confval:`gettext_allow_fuzzy_translations` to allow "fuzzy"
messages for translation
* #9672: More CSS classes on Python domain descriptions
* #9695: More CSS classes on Javascript domain descriptions
* #9683: Revert the removal of ``add_stylesheet()`` API. It will be kept until
the Sphinx-6.0 release
* #2068, add :confval:`intersphinx_disabled_reftypes` for disabling
interphinx resolution of cross-references that do not have an explicit
inventory specification. Specific types of cross-references can be disabled,
e.g., ``std:doc`` or all cross-references in a specific domain,
e.g., ``std:*``.
* #9623: Allow to suppress "toctree contains reference to excluded document"
warnings using :confval:`suppress_warnings`
Bugs fixed Bugs fixed
---------- ----------
@@ -21,34 +204,41 @@ Bugs fixed
* #9630: autodoc: Failed to build cross references if :confval:`primary_domain` * #9630: autodoc: Failed to build cross references if :confval:`primary_domain`
is not 'py' is not 'py'
* #9644: autodoc: Crashed on getting source info from problematic object * #9644: autodoc: Crashed on getting source info from problematic object
* #9655: autodoc: mocked object having doc comment is warned unexpectedly
* #9651: autodoc: return type field is not generated even if
:confval:`autodoc_typehints_description_target` is set to "documented" when
its info-field-list contains ``:returns:`` field
* #9657: autodoc: The base class for a subclass of mocked object is incorrect
* #9607: autodoc: Incorrect base class detection for the subclasses of the
generic class
* #9755: autodoc: memory addresses are shown for aliases
* #9752: autodoc: Failed to detect type annotation for slots attribute
* #9756: autodoc: Crashed if classmethod does not have __func__ attribute
* #9757: autodoc: :confval:`autodoc_inherit_docstrings` does not effect to
overridden classmethods
* #9781: autodoc: :confval:`autodoc_preserve_defaults` does not support
hexadecimal numeric
* #9630: autosummary: Failed to build summary table if :confval:`primary_domain` * #9630: autosummary: Failed to build summary table if :confval:`primary_domain`
is not 'py' is not 'py'
* #9636: code-block: ``:dedent:`` handled numeric argument ``0`` like no argument was given * #9670: html: Fix download file with special characters
* #9636: code-block: ``:dedent:`` without argument did strip newlines * #9710: html: Wrong styles for even/odd rows in nested tables
* #9763: html: parameter name and its type annotation are not separated in HTML
Testing * #9649: HTML search: when objects have the same name but in different domains,
-------- return all of them as result instead of just one.
* #7634: intersphinx: references on the file in sub directory are broken
Release 4.2.1 (in development) * #9737: LaTeX: hlist is rendered as a list containing "aggedright" text
============================== * #9678: linkcheck: file extension was shown twice in warnings
* #9697: py domain: An index entry with parens was registered for ``py:method``
Dependencies directive with ``:property:`` option
------------ * #9775: py domain: Literal typehint was converted to a cross reference when
:confval:`autodoc_typehints='description'`
Incompatible changes * #9708: needs_extension failed to check double-digit version correctly
-------------------- * #9688: Fix :rst:dir:`code`` does not recognize ``:class:`` option
* #9733: Fix for logging handler flushing warnings in the middle of the docs
Deprecated build
---------- * #9656: Fix warnings without subtype being incorrectly suppressed
* Intersphinx, for unresolved references with an explicit inventory,
Features added e.g., ``proj:myFunc``, leave the inventory prefix in the unresolved text.
--------------
Bugs fixed
----------
Testing
--------
Release 4.2.0 (released Sep 12, 2021) Release 4.2.0 (released Sep 12, 2021)
===================================== =====================================
@@ -120,6 +310,7 @@ Bugs fixed
with the HEAD of 3.10 with the HEAD of 3.10
* #9436, #9471: autodoc: crashed if ``autodoc_class_signature = "separated"`` * #9436, #9471: autodoc: crashed if ``autodoc_class_signature = "separated"``
* #9456: html search: html_copy_source can't control the search summaries * #9456: html search: html_copy_source can't control the search summaries
* #9500: LaTeX: Failed to build Japanese document on Windows
* #9435: linkcheck: Failed to check anchors in github.com * #9435: linkcheck: Failed to check anchors in github.com
Release 4.1.1 (released Jul 15, 2021) Release 4.1.1 (released Jul 15, 2021)
@@ -1555,6 +1746,14 @@ Bugs fixed
:confval:`intersphinx_mapping` on :event:`config-inited` event :confval:`intersphinx_mapping` on :event:`config-inited` event
* #7343: Sphinx builds has been slower since 2.4.0 on debug mode * #7343: Sphinx builds has been slower since 2.4.0 on debug mode
Release 2.4.5 (released Nov 18, 2021)
=====================================
Dependencies
------------
* #9807: Restrict docutils to 0.17.x or older
Release 2.4.4 (released Mar 05, 2020) Release 2.4.4 (released Mar 05, 2020)
===================================== =====================================
@@ -2391,6 +2590,14 @@ Testing
* Add a helper function: ``sphinx.testing.restructuredtext.parse()`` * Add a helper function: ``sphinx.testing.restructuredtext.parse()``
Release 1.8.6 (released Nov 18, 2021)
=====================================
Dependencies
------------
* #9807: Restrict docutils to 0.17.x or older
Release 1.8.5 (released Mar 10, 2019) Release 1.8.5 (released Mar 10, 2019)
===================================== =====================================

View File

@@ -430,7 +430,7 @@ Books produced using Sphinx
* `"Theoretical Physics Reference" <https://www.theoretical-physics.net/>`__ * `"Theoretical Physics Reference" <https://www.theoretical-physics.net/>`__
* `"The Varnish Book" <https://info.varnish-software.com/the-varnish-book>`__ * `"The Varnish Book" <https://info.varnish-software.com/the-varnish-book>`__
Theses produced using Sphinx These produced using Sphinx
---------------------------- ----------------------------
* `"A Web-Based System for Comparative Analysis of OpenStreetMap Data by the Use * `"A Web-Based System for Comparative Analysis of OpenStreetMap Data by the Use

View File

@@ -1,7 +1,7 @@
License for Sphinx License for Sphinx
================== ==================
Copyright (c) 2007-2021 by the Sphinx team (see AUTHORS file). Copyright (c) 2007-2022 by the Sphinx team (see AUTHORS file).
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@@ -1,8 +1,8 @@
# test documentation build configuration file, created by # test documentation build configuration file, created by
# sphinx-quickstart on Sun Jun 26 00:00:43 2016. # sphinx-quickstart on Sun Jun 26 00:00:43 2016.
# #
# This file is execfile()d with the current directory set to its # This file is executed through importlib.import_module with
# containing dir. # the current directory set to its containing dir.
# #
# Note that not all possible configuration values are present in this # Note that not all possible configuration values are present in this
# autogenerated file. # autogenerated file.
@@ -319,6 +319,10 @@ texinfo_documents = [
# #
# texinfo_no_detailmenu = False # texinfo_no_detailmenu = False
# If false, do not generate in manual @ref nodes.
#
# texinfo_cross_references = False
# -- A random example ----------------------------------------------------- # -- A random example -----------------------------------------------------
import sys, os import sys, os

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -118,7 +118,7 @@
this part of the documentation is for you.{%endtrans%}</p> this part of the documentation is for you.{%endtrans%}</p>
<ul> <ul>
<li>{%trans path=pathto("internals/contributing")%}<a href="{{ path }}">Sphinx Contributorss Guide</a></li>{%endtrans%} <li>{%trans path=pathto("internals/contributing")%}<a href="{{ path }}">Sphinx Contributors Guide</a></li>{%endtrans%}
<li>{%trans path=pathto("internals/authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%} <li>{%trans path=pathto("internals/authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
</ul> </ul>

View File

@@ -18,6 +18,6 @@
<input type="submit" name="sub" value="Subscribe" /> <input type="submit" name="sub" value="Subscribe" />
</form> </form>
</div> </div>
<p>{%trans%}or come to the <tt>#sphinx-doc</tt> channel on FreeNode.{%endtrans%}</p> <p>{%trans%}or come to the <tt>#sphinx-doc</tt> channel on <a href="https://web.libera.chat/?channel=#sphinx-doc">libera.chat</a>.{%endtrans%}</p>
<p>{%trans%}You can also open an issue at the <p>{%trans%}You can also open an issue at the
<a href="https://github.com/sphinx-doc/sphinx/issues">tracker</a>.{%endtrans%}</p> <a href="https://github.com/sphinx-doc/sphinx/issues">tracker</a>.{%endtrans%}</p>

View File

@@ -1,4 +1,4 @@
[theme] [theme]
inherit = basic inherit = basic
stylesheet = sphinx13.css stylesheet = sphinx13.css
pygments_style = trac pygments_style = default

View File

@@ -14,7 +14,7 @@ templates_path = ['_templates']
exclude_patterns = ['_build'] exclude_patterns = ['_build']
project = 'Sphinx' project = 'Sphinx'
copyright = '2007-2021, Georg Brandl and the Sphinx team' copyright = '2007-2022, Georg Brandl and the Sphinx team'
version = sphinx.__display_version__ version = sphinx.__display_version__
release = version release = version
show_authors = True show_authors = True
@@ -109,6 +109,7 @@ texinfo_documents = [
intersphinx_mapping = { intersphinx_mapping = {
'python': ('https://docs.python.org/3/', None), 'python': ('https://docs.python.org/3/', None),
'requests': ('https://requests.readthedocs.io/en/master', None), 'requests': ('https://requests.readthedocs.io/en/master', None),
'readthedocs': ('https://docs.readthedocs.io/en/stable', None),
} }
# Sphinx document translation with sphinx gettext feature uses these settings: # Sphinx document translation with sphinx gettext feature uses these settings:

View File

@@ -285,7 +285,7 @@ engine, allowing you to embed variables and control behavior.
For example, the following JavaScript structure: For example, the following JavaScript structure:
.. code-block:: bash .. code-block:: none
mymodule/ mymodule/
├── _static ├── _static
@@ -294,7 +294,7 @@ For example, the following JavaScript structure:
Will result in the following static file placed in your HTML's build output: Will result in the following static file placed in your HTML's build output:
.. code-block:: bash .. code-block:: none
_build/ _build/
└── html └── html

View File

@@ -9,7 +9,7 @@ from sphinx.ext.autodoc import ClassDocumenter, bool_option
class IntEnumDocumenter(ClassDocumenter): class IntEnumDocumenter(ClassDocumenter):
objtype = 'intenum' objtype = 'intenum'
directivetype = 'class' directivetype = ClassDocumenter.objtype
priority = 10 + ClassDocumenter.priority priority = 10 + ClassDocumenter.priority
option_spec = dict(ClassDocumenter.option_spec) option_spec = dict(ClassDocumenter.option_spec)
option_spec['hex'] = bool_option option_spec['hex'] = bool_option
@@ -18,7 +18,10 @@ class IntEnumDocumenter(ClassDocumenter):
def can_document_member(cls, def can_document_member(cls,
member: Any, membername: str, member: Any, membername: str,
isattr: bool, parent: Any) -> bool: isattr: bool, parent: Any) -> bool:
return isinstance(member, IntEnum) try:
return issubclass(member, IntEnum)
except TypeError:
return False
def add_directive_header(self, sig: str) -> None: def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig) super().add_directive_header(sig)
@@ -36,14 +39,13 @@ class IntEnumDocumenter(ClassDocumenter):
use_hex = self.options.hex use_hex = self.options.hex
self.add_line('', source_name) self.add_line('', source_name)
for enum_value in enum_object: for the_member_name, enum_member in enum_object.__members__.items():
the_value_name = enum_value.name the_member_value = enum_member.value
the_value_value = enum_value.value
if use_hex: if use_hex:
the_value_value = hex(the_value_value) the_member_value = hex(the_member_value)
self.add_line( self.add_line(
f"**{the_value_name}**: {the_value_value}", source_name) f"**{the_member_name}**: {the_member_value}", source_name)
self.add_line('', source_name) self.add_line('', source_name)

View File

@@ -22,6 +22,31 @@ The following is a list of deprecated interfaces.
- (will be) Removed - (will be) Removed
- Alternatives - Alternatives
* - ``sphinx.ext.autodoc.AttributeDocumenter._datadescriptor``
- 4.3
- 6.0
- N/A
* - ``sphinx.writers.html.HTMLTranslator._fieldlist_row_index``
- 4.3
- 6.0
- ``sphinx.writers.html.HTMLTranslator._fieldlist_row_indices``
* - ``sphinx.writers.html.HTMLTranslator._table_row_index``
- 4.3
- 6.0
- ``sphinx.writers.html.HTMLTranslator._table_row_indices``
* - ``sphinx.writers.html5.HTML5Translator._fieldlist_row_index``
- 4.3
- 6.0
- ``sphinx.writers.html5.HTML5Translator._fieldlist_row_indices``
* - ``sphinx.writers.html5.HTML5Translator._table_row_index``
- 4.3
- 6.0
- ``sphinx.writers.html5.HTML5Translator._table_row_indices``
* - The optional argument ``app`` for ``sphinx.environment.BuildEnvironment`` * - The optional argument ``app`` for ``sphinx.environment.BuildEnvironment``
- 4.1 - 4.1
- 6.0 - 6.0
@@ -748,6 +773,11 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinx.domains.std.StandardDomain.process_doc()`` - ``sphinx.domains.std.StandardDomain.process_doc()``
* - ``sphinx.domains.js.JSObject.display_prefix``
-
- 4.3
- ``sphinx.domains.js.JSObject.get_display_prefix()``
* - ``sphinx.environment.NoUri`` * - ``sphinx.environment.NoUri``
- 2.1 - 2.1
- 3.0 - 3.0
@@ -1207,7 +1237,7 @@ The following is a list of deprecated interfaces.
* - :meth:`~sphinx.application.Sphinx.add_stylesheet()` * - :meth:`~sphinx.application.Sphinx.add_stylesheet()`
- 1.8 - 1.8
- 4.0 - 6.0
- :meth:`~sphinx.application.Sphinx.add_css_file()` - :meth:`~sphinx.application.Sphinx.add_css_file()`
* - :meth:`~sphinx.application.Sphinx.add_javascript()` * - :meth:`~sphinx.application.Sphinx.add_javascript()`

View File

@@ -13,6 +13,10 @@ Domain API
.. autoclass:: Index .. autoclass:: Index
:members: :members:
.. module:: sphinx.directives
.. autoclass:: ObjectDescription
:members:
Python Domain Python Domain
------------- -------------

View File

@@ -299,6 +299,10 @@ appear in the source. Emacs, on the other-hand, will by default replace
:ref:`texinfo-links` :ref:`texinfo-links`
One can disable generation of the inline references in a document
with :confval:`texinfo_cross_references`. That makes
an info file more readable with stand-alone reader (``info``).
The exact behavior of how Emacs displays references is dependent on the variable The exact behavior of how Emacs displays references is dependent on the variable
``Info-hide-note-references``. If set to the value of ``hide``, Emacs will hide ``Info-hide-note-references``. If set to the value of ``hide``, Emacs will hide
both the ``*note:`` part and the ``target-id``. This is generally the best way both the ``*note:`` part and the ``target-id``. This is generally the best way

View File

@@ -21,7 +21,7 @@ sphinx-users <sphinx-users@googlegroups.com>
sphinx-dev <sphinx-dev@googlegroups.com> sphinx-dev <sphinx-dev@googlegroups.com>
Mailing list for development related discussions. Mailing list for development related discussions.
#sphinx-doc on irc.freenode.net #sphinx-doc on irc.libera.chat
IRC channel for development questions and user support. IRC channel for development questions and user support.
.. _python-sphinx: https://stackoverflow.com/questions/tagged/python-sphinx .. _python-sphinx: https://stackoverflow.com/questions/tagged/python-sphinx

View File

@@ -39,6 +39,10 @@ Options
Document imported members. Document imported members.
.. option:: -a, --respect-module-all
Document exactly the members in a module's ``__all__`` attribute.
Example Example
------- -------
@@ -71,7 +75,7 @@ and assuming ``docs/index.rst`` contained the following:
If you run the following: If you run the following:
.. code-block:: bash .. code-block:: console
$ PYTHONPATH=. sphinx-autogen docs/index.rst $ PYTHONPATH=. sphinx-autogen docs/index.rst

View File

@@ -0,0 +1,166 @@
Automatic documentation generation from code
============================================
In the :ref:`previous section <tutorial-describing-objects>` of the tutorial
you manually documented a Python function in Sphinx. However, the description
was out of sync with the code itself, since the function signature was not
the same. Besides, it would be nice to reuse `Python
docstrings <https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring>`_
in the documentation, rather than having to write the information in two
places.
Fortunately, :doc:`the autodoc extension </usage/extensions/autodoc>` provides this
functionality.
Reusing signatures and docstrings with autodoc
----------------------------------------------
To use autodoc, first add it to the list of enabled extensions:
.. code-block:: python
:caption: docs/source/conf.py
:emphasize-lines: 4
extensions = [
'sphinx.ext.duration',
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
]
Next, move the content of the ``.. py:function`` directive to the function
docstring in the original Python file, as follows:
.. code-block:: python
:caption: lumache.py
:emphasize-lines: 2-11
def get_random_ingredients(kind=None):
"""
Return a list of random ingredients as strings.
:param kind: Optional "kind" of ingredients.
:type kind: list[str] or None
:raise lumache.InvalidKindError: If the kind is invalid.
:return: The ingredients list.
:rtype: list[str]
"""
return ["shells", "gorgonzola", "parsley"]
Finally, replace the ``.. py:function`` directive from the Sphinx documentation
with :rst:dir:`autofunction`:
.. code-block:: rst
:caption: docs/source/usage.rst
:emphasize-lines: 3
you can use the ``lumache.get_random_ingredients()`` function:
.. autofunction:: lumache.get_random_ingredients
If you now build the HTML documentation, the output will be the same!
With the advantage that it is generated from the code itself.
Sphinx took the reStructuredText from the docstring and included it,
also generating proper cross-references.
You can also autogenerate documentation from other objects. For example, add
the code for the ``InvalidKindError`` exception:
.. code-block:: python
:caption: lumache.py
class InvalidKindError(Exception):
"""Raised if the kind is invalid."""
pass
And replace the ``.. py:exception`` directive with :rst:dir:`autoexception`
as follows:
.. code-block:: rst
:caption: docs/source/usage.rst
:emphasize-lines: 4
or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients`
will raise an exception.
.. autoexception:: lumache.InvalidKindError
And again, after running ``make html``, the output will be the same as before.
Generating comprehensive API references
---------------------------------------
While using ``sphinx.ext.autodoc`` makes keeping the code and the documentation
in sync much easier, it still requires you to write an ``auto*`` directive
for every object you want to document. Sphinx provides yet another level of
automation: the :doc:`autosummary </usage/extensions/autosummary>` extension.
The :rst:dir:`autosummary` directive generates documents that contain all the
necessary ``autodoc`` directives. To use it, first enable the autosummary
extension:
.. code-block:: python
:caption: docs/source/conf.py
:emphasize-lines: 5
extensions = [
'sphinx.ext.duration',
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
]
Next, create a new ``api.rst`` file with these contents:
.. code-block:: rst
:caption: docs/source/api.rst
API
===
.. autosummary::
:toctree: generated
lumache
Remember to include the new document in the root toctree:
.. code-block:: rst
:caption: docs/source/index.rst
:emphasize-lines: 7
Contents
--------
.. toctree::
usage
api
Finally, after you build the HTML documentation running ``make html``, it will
contain two new pages:
- ``api.html``, corresponding to ``docs/source/api.rst`` and containing a table
with the objects you included in the ``autosummary`` directive (in this case,
only one).
- ``generated/lumache.html``, corresponding to a newly created reST file
``generated/lumache.rst`` and containing a summary of members of the module,
in this case one function and one exception.
.. figure:: /_static/tutorial/lumache-autosummary.png
:width: 80%
:align: center
:alt: Summary page created by autosummary
Summary page created by autosummary
Each of the links in the summary page will take you to the places where you
originally used the corresponding ``autodoc`` directive, in this case in the
``usage.rst`` document.
.. note::
The generated files are based on `Jinja2
templates <https://jinja2docs.readthedocs.io/>`_ that
:ref:`can be customized <autosummary-customizing-templates>`,
but that is out of scope for this tutorial.

279
doc/tutorial/deploying.rst Normal file
View File

@@ -0,0 +1,279 @@
Appendix: Deploying a Sphinx project online
===========================================
When you are ready to show your documentation project to the world, there are
many options available to do so. Since the HTML generated by Sphinx is static,
you can decouple the process of building your HTML documentation from hosting
such files in the platform of your choice. You will not need a sophisticated
server running Python: virtually every web hosting service will suffice.
Therefore, the challenge is less how or where to serve the static HTML, but
rather how to pick a workflow that automatically updates the deployed
documentation every time there is a change in the source files.
The following sections describe some of the available options to deploy
your online documentation, and give some background information. If you want
to go directly to the practical part, you can skip to :ref:`publishing-sources`.
Sphinx-friendly deployment options
----------------------------------
There are several possible options you have to host your Sphinx documentation.
Some of them are:
**Read the Docs**
`Read the Docs`_ is an online service specialized in hosting technical
documentation written in Sphinx, as well as MkDocs. They have a
number of extra features, such as versioned documentation, traffic and
search analytics, custom domains, user-defined redirects, and more.
**GitHub Pages**
`GitHub Pages`_ is a simple static web hosting tightly integrated with
`GitHub`_: static HTML is served from one of the branches of a project,
and usually sources are stored in another branch so that the output
can be updated every time the sources change (for example using `GitHub
Actions`_). It is free to use and supports custom domains.
**GitLab Pages**
`GitLab Pages`_ is a similar concept to GitHub Pages, integrated with
`GitLab`_ and usually automated with `GitLab CI`_ instead.
**Netlify**
`Netlify`_ is a sophisticated hosting for static sites enhanced by
client-side web technologies like JavaScript (so-called `"Jamstack"`_).
They offer support for headless content management systems and
serverless computing.
**Your own server**
You can always use your own web server to host Sphinx HTML documentation.
It is the option that gives more flexibility, but also more complexity.
All these options have zero cost, with the option of paying for extra features.
.. _Read the Docs: https://readthedocs.org/
.. _GitHub Pages: https://pages.github.com/
.. _GitHub: https://github.com/
.. _GitHub Actions: https://github.com/features/actions
.. _GitLab Pages: https://about.gitlab.com/stages-devops-lifecycle/pages/
.. _GitLab: https://gitlab.com/
.. _GitLab CI: https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/
.. _Netlify: https://www.netlify.com/
.. _"Jamstack": https://jamstack.org/
Embracing the "Docs as Code" philosophy
---------------------------------------
The free offerings of most of the options listed above require your
documentation sources to be publicly available. Moreover, these services
expect you to use a `Version Control System`_, a technology that tracks the
evolution of a collection of files as a series of snapshots ("commits").
The practice of writing documentation in plain text files with the same tools
as the ones used for software development is commonly known as `"Docs as Code"`_.
The most popular Version Control System nowadays is Git_, a free and open
source tool that is the backbone of services like GitHub and GitLab.
Since both Read the Docs and Netlify have integrations with GitHub and GitLab,
and both GitHub and GitLab have an integrated Pages product, the most effective
way of automatically build your documentation online is to upload your sources
to either of these Git hosting services.
.. _Version Control System: https://en.wikipedia.org/wiki/Version_control
.. _"Docs as Code": https://www.writethedocs.org/guide/docs-as-code/
.. _Git: https://git-scm.com/
.. _publishing-sources:
Publishing your documentation sources
-------------------------------------
GitHub
~~~~~~
The quickest way to upload an existing project to GitHub is to:
1. `Sign up for a GitHub account <https://github.com/signup>`_.
2. `Create a new repository <https://github.com/new>`_.
3. Open `the "Upload files" page`_ of your new repository.
4. Select the files on your operating system file browser (in your case
``README.rst``, ``lumache.py``, the makefiles under the ``docs`` directory,
and everything under ``docs/source``) and drag them to the GitHub interface
to upload them all.
5. Click on the :guilabel:`Commit changes` button.
.. _the "Upload files" page: https://docs.github.com/en/repositories/working-with-files/managing-files/adding-a-file-to-a-repository
.. note::
Make sure you don't upload the ``docs/build`` directory, as it contains the
output generated by Sphinx and it will change every time you change the
sources, complicating your workflow.
These steps do not require access to the command line or installing any
additional software. To learn more, you can:
- Follow `this interactive GitHub course`_ to learn more about how the GitHub
interface works.
- Read `this quickstart tutorial`_ to install extra software on your machine
and have more flexibility. You can either use the Git command line, or the
GitHub Desktop application.
.. _this interactive GitHub course: https://lab.github.com/githubtraining/introduction-to-github
.. _this quickstart tutorial: https://docs.github.com/en/get-started/quickstart
GitLab
~~~~~~
Similarly to GitHub, the fastest way to upload your project to GitLab is
using the web interface:
1. `Sign up for a GitLab account <https://gitlab.com/users/sign_up>`_.
2. `Create a new blank project <https://gitlab.com/projects/new>`_.
3. Upload the project files (in your case ``README.rst``, ``lumache.py``, the
makefiles under the ``docs`` directory, and everything under
``docs/source``) one by one using the :guilabel:`Upload File` button [#f1]_.
Again, these steps do not require additional software on your computer. To
learn more, you can:
- Follow `this tutorial`_ to install Git on your machine.
- Browse the `GitLab User documentation`_ to understand the possibilities of
the platform.
.. _this tutorial: https://docs.gitlab.com/ee/gitlab-basics/start-using-git.html
.. _GitLab User documentation: https://docs.gitlab.com/ee/user/index.html
.. note::
Make sure you don't upload the ``docs/build`` directory, as it contains the
output generated by Sphinx and it will change every time you change the
sources, complicating your workflow.
.. [#f1] At the time of writing, `uploading whole directories to GitLab using
only the web
interface <https://gitlab.com/gitlab-org/gitlab/-/issues/228490>`_ is
not yet implemented.
Publishing your HTML documentation
----------------------------------
Read the Docs
~~~~~~~~~~~~~
`Read the Docs`_ offers integration with both GitHub and GitLab. The quickest
way of getting started is to follow :doc:`the RTD
tutorial <readthedocs:tutorial/index>`, which is loosely based on this one.
You can publish your sources on GitHub as explained :ref:`in the previous
section <publishing-sources>`, then skip directly to
:ref:`readthedocs:tutorial/index:Sign up for Read the Docs`.
If you choose GitLab instead, the process is similar.
GitHub Pages
~~~~~~~~~~~~
`GitHub Pages`_ requires you to :ref:`publish your
sources <publishing-sources>` on `GitHub`_. After that, you will need an
automated process that performs the ``make html`` step every time the sources
change. That can be achieved using `GitHub Actions`_.
After you have published your sources on GitHub, create a file named
``.github/workflows/sphinx.yml`` in your repository with the following
contents:
.. code-block:: yaml
:caption: .github/workflows/
name: Sphinx build
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build HTML
uses: ammaraskar/sphinx-action@0.4
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: html-docs
path: docs/build/html/
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/build/html
This contains a GitHub Actions workflow with a single job of four steps:
1. Checkout the code.
2. Build the HTML documentation using Sphinx.
3. Attach the HTML output the artifacts to the GitHub Actions job, for easier
inspection.
4. If the change happens on the default branch, take the contents of
``docs/build/html`` and push it to the ``gh-pages`` branch.
Next, you need to specify the dependencies for the ``make html`` step to be
successful. For that, create a file ``docs/requirements.txt`` and add the
following contents:
.. code-block::
:caption: docs/requirements.txt
furo==2021.11.16
And finally, you are ready to `enable GitHub Pages on your repository`_. For
that, go to :guilabel:`Settings`, then :guilabel:`Pages` on the left sidebar,
select the ``gh-pages`` branch in the "Source" dropdown menu, and click
:guilabel:`Save`. After a few minutes, you should be able to see your HTML at
the designated URL.
.. _enable GitHub Pages on your repository: https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site
GitLab Pages
~~~~~~~~~~~~
`GitLab Pages`_, on the other hand, requires you to :ref:`publish your
sources <publishing-sources>` on `GitLab`_. When you are ready, you can
automate the process of running ``make html`` using `GitLab CI`_.
After you have published your sources on GitLab, create a file named
``.gitlab-ci.yml`` in your repository with these contents:
.. code-block:: yaml
:caption: .gitlab-ci.yml
stages:
- deploy
pages:
stage: deploy
image: python:3.9-slim
before_script:
- apt-get update && apt-get install make --no-install-recommends -y
- python -m pip install sphinx furo
script:
- cd docs && make html
after_script:
- mv docs/build/html/ ./public/
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
This contains a GitLab CI workflow with one job of several steps:
1. Install the necessary dependencies.
2. Build the HTML documentation using Sphinx.
3. Move the output to a known artifacts location.
.. note::
You will need to `validate your account`_ by entering a payment method
(you will be charged a small amount that will then be reimbursed).
.. _validate your account: https://about.gitlab.com/blog/2021/05/17/prevent-crypto-mining-abuse/#validating-an-account
After that, if the pipeline is successful, you should be able to see your HTML
at the designated URL.

View File

@@ -0,0 +1,275 @@
Describing code in Sphinx
=========================
In the :doc:`previous sections of the tutorial </tutorial/index>` you can read
how to write narrative or prose documentation in Sphinx. In this section you
will describe code objects instead.
Sphinx supports documenting code objects in several languages, namely Python,
C, C++, JavaScript, and reStructuredText. Each of them can be documented using
a series of directives and roles grouped by
:doc:`domain </usage/restructuredtext/domains>`. For the remainder of the
tutorial you will use the Python domain, but all the concepts seen in this
section apply for the other domains as well.
.. _tutorial-describing-objects:
Python
------
Documenting Python objects
~~~~~~~~~~~~~~~~~~~~~~~~~~
Sphinx offers several roles and directives to document Python objects,
all grouped together in :ref:`the Python domain <python-domain>`. For example,
you can use the :rst:dir:`py:function` directive to document a Python function,
as follows:
.. code-block:: rst
:caption: docs/source/usage.rst
Creating recipes
----------------
To retrieve a list of random ingredients,
you can use the ``lumache.get_random_ingredients()`` function:
.. py:function:: lumache.get_random_ingredients(kind=None)
Return a list of random ingredients as strings.
:param kind: Optional "kind" of ingredients.
:type kind: list[str] or None
:return: The ingredients list.
:rtype: list[str]
Which will render like this:
.. figure:: /_static/tutorial/lumache-py-function.png
:width: 80%
:align: center
:alt: HTML result of documenting a Python function in Sphinx
The rendered result of documenting a Python function in Sphinx
Notice several things:
- Sphinx parsed the argument of the ``.. py:function`` directive and
highlighted the module, the function name, and the parameters appropriately.
- The directive content includes a one-line description of the function,
as well as a :ref:`info field list <info-field-lists>` containing the function
parameter, its expected type, the return value, and the return type.
.. note::
The ``py:`` prefix specifies the :term:`domain`. You may configure the
default domain so you can omit the prefix, either globally using the
:confval:`primary_domain` configuration, or use the
:rst:dir:`default-domain` directive to change it from the point it is called
until the end of the file.
For example, if you set it to ``py`` (the default), you can write
``.. function::`` directly.
Cross-referencing Python objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, most of these directives generate entities that can be
cross-referenced from any part of the documentation by using
:ref:`a corresponding role <python-roles>`. For the case of functions,
you can use :rst:role:`py:func` for that, as follows:
.. code-block:: rst
:caption: docs/source/usage.rst
The ``kind`` parameter should be either ``"meat"``, ``"fish"``,
or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients`
will raise an exception.
When generating code documentation, Sphinx will generate a cross-reference automatically just
by using the name of the object, without you having to explicitly use a role
for that. For example, you can describe the custom exception raised by the
function using the :rst:dir:`py:exception` directive:
.. code-block:: rst
:caption: docs/source/usage.rst
.. py:exception:: lumache.InvalidKindError
Raised if the kind is invalid.
Then, add this exception to the original description of the function:
.. code-block:: rst
:caption: docs/source/usage.rst
:emphasize-lines: 7
.. py:function:: lumache.get_random_ingredients(kind=None)
Return a list of random ingredients as strings.
:param kind: Optional "kind" of ingredients.
:type kind: list[str] or None
:raise lumache.InvalidKindError: If the kind is invalid.
:return: The ingredients list.
:rtype: list[str]
And finally, this is how the result would look:
.. figure:: /_static/tutorial/lumache-py-function-full.png
:width: 80%
:align: center
:alt: HTML result of documenting a Python function in Sphinx
with cross-references
HTML result of documenting a Python function in Sphinx with cross-references
Beautiful, isn't it?
Including doctests in your documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since you are now describing code from a Python library, it will become useful
to keep both the documentation and the code as synchronized as possible.
One of the ways to do that in Sphinx is to include code snippets in the
documentation, called *doctests*, that are executed when the documentation is
built.
To demonstrate doctests and other Sphinx features covered in this tutorial,
Sphinx will need to be able to import the code. To achieve that, write this
at the beginning of ``conf.py``:
.. code-block:: python
:caption: docs/source/conf.py
:emphasize-lines: 3-5
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here.
import pathlib
import sys
sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix())
.. note::
An alternative to changing the :py:data:`sys.path` variable is to create a
``pyproject.toml`` file and make the code installable,
so it behaves like any other Python library. However, the ``sys.path``
approach is simpler.
Then, before adding doctests to your documentation, enable the
:doc:`doctest </usage/extensions/doctest>` extension in ``conf.py``:
.. code-block:: python
:caption: docs/source/conf.py
:emphasize-lines: 3
extensions = [
'sphinx.ext.duration',
'sphinx.ext.doctest',
]
Next, write a doctest block as follows:
.. code-block:: rst
:caption: docs/source/usage.rst
>>> import lumache
>>> lumache.get_random_ingredients()
['shells', 'gorgonzola', 'parsley']
Doctests include the Python instructions to be run preceded by ``>>>``,
the standard Python interpreter prompt, as well as the expected output
of each instruction. This way, Sphinx can check whether the actual output
matches the expected one.
To observe how a doctest failure looks like (rather than a code error as
above), let's write the return value incorrectly first. Therefore, add a
function ``get_random_ingredients`` like this:
.. code-block:: python
:caption: lumache.py
def get_random_ingredients(kind=None):
return ["eggs", "bacon", "spam"]
You can now run ``make doctest`` to execute the doctests of your documentation.
Initially this will display an error, since the actual code does not behave
as specified:
.. code-block:: console
(.venv) $ make doctest
Running Sphinx v4.2.0
loading pickled environment... done
...
running tests...
Document: usage
---------------
**********************************************************************
File "usage.rst", line 44, in default
Failed example:
lumache.get_random_ingredients()
Expected:
['shells', 'gorgonzola', 'parsley']
Got:
['eggs', 'bacon', 'spam']
**********************************************************************
...
make: *** [Makefile:20: doctest] Error 1
As you can see, doctest reports the expected and the actual results,
for easy examination. It is now time to fix the function:
.. code-block:: python
:caption: lumache.py
:emphasize-lines: 2
def get_random_ingredients(kind=None):
return ["shells", "gorgonzola", "parsley"]
And finally, ``make test`` reports success!
For big projects though, this manual approach can become a bit tedious.
In the next section, you will see :doc:`how to automate the
process </tutorial/automatic-doc-generation>`.
Other languages (C, C++, others)
--------------------------------
Documenting and cross-referencing objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sphinx also supports documenting and cross-referencing objects written in
other programming languages. There are four additional built-in domains:
C, C++, JavaScript, and reStructuredText. Third-party extensions may
define domains for more languages, such as
- `Fortran <https://sphinx-fortran.readthedocs.io>`_,
- `Julia <http://bastikr.github.io/sphinx-julia>`_, or
- `PHP <https://github.com/markstory/sphinxcontrib-phpdomain>`_.
For example, to document a C++ type definition, you would use the built-in
:rst:dir:`cpp:type` directive, as follows:
.. code-block:: rst
.. cpp:type:: std::vector<int> CustomList
A typedef-like declaration of a type.
Which would give the following result:
.. cpp:type:: std::vector<int> CustomList
A typedef-like declaration of a type.
All such directives then generate references that can be
cross-referenced by using the corresponding role. For example, to reference
the previous type definition, you can use the :rst:role:`cpp:type` role
as follows:
.. code-block:: rst
Cross reference to :cpp:type:`CustomList`.
Which would produce a hyperlink to the previous definition: :cpp:type:`CustomList`.

View File

@@ -79,7 +79,8 @@ behavior by adding the following code at the end of your ``conf.py``:
With this configuration value, and after running ``make epub`` again, you will With this configuration value, and after running ``make epub`` again, you will
notice that URLs appear now as footnotes, which avoids cluttering the text. notice that URLs appear now as footnotes, which avoids cluttering the text.
Sweet! Sweet! Read on to explore :doc:`other ways to customize
Sphinx </tutorial/more-sphinx-customization>`.
.. note:: .. note::

View File

@@ -117,3 +117,4 @@ something like this:
Freshly created documentation of Lumache Freshly created documentation of Lumache
There we go! You created your first HTML documentation using Sphinx. There we go! You created your first HTML documentation using Sphinx.
Now you can start :doc:`customizing it </tutorial/first-steps>`.

View File

@@ -33,4 +33,7 @@ project.
first-steps first-steps
more-sphinx-customization more-sphinx-customization
narrative-documentation narrative-documentation
describing-code
automatic-doc-generation
deploying
end end

View File

@@ -73,3 +73,6 @@ appearance:
:alt: HTML documentation of Lumache with the Furo theme :alt: HTML documentation of Lumache with the Furo theme
HTML documentation of Lumache with the Furo theme HTML documentation of Lumache with the Furo theme
It is now time to :doc:`expand the narrative documentation and split it into
several documents </tutorial/narrative-documentation>`.

View File

@@ -85,14 +85,13 @@ introduction paragraph in ``index.rst``:
Check out the :doc:`usage` section for further information. Check out the :doc:`usage` section for further information.
The :rst:role:`doc` role you used automatically references a specific document The :rst:role:`doc` :ref:`role <rst-roles-alt>` you used automatically
in the project, in this case the ``usage.rst`` you created earlier. references a specific document in the project, in this case the ``usage.rst``
you created earlier.
Alternatively, you can also add a cross-reference to an arbitrary part of the Alternatively, you can also add a cross-reference to an arbitrary part of the
project. For that, you need to use the :rst:role:`ref` role, and add an project. For that, you need to use the :rst:role:`ref` role, and add an
explicit *label* that acts as `a target`__. explicit *label* that acts as :duref:`a target <hyperlink-targets>`.
__ https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#hyperlink-targets
For example, to reference the "Installation" subsection, add a label right For example, to reference the "Installation" subsection, add a label right
before the heading, as follows: before the heading, as follows:
@@ -126,3 +125,6 @@ cross-reference to. If you do not include an explicit title, hence using
``:ref:`installation```, the section title will be used (in this case, ``:ref:`installation```, the section title will be used (in this case,
``Installation``). Both the ``:doc:`` and the ``:ref:`` roles will be rendered ``Installation``). Both the ``:doc:`` and the ``:ref:`` roles will be rendered
as hyperlinks in the HTML documentation. as hyperlinks in the HTML documentation.
What about :doc:`documenting code objects in Sphinx </tutorial/describing-code>`?
Read on!

View File

@@ -64,7 +64,7 @@ Options for setuptools integration
This can also be set by passing the `-E` flag to ``setup.py``: This can also be set by passing the `-E` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -E $ python setup.py build_sphinx -E
@@ -75,7 +75,7 @@ Options for setuptools integration
This can also be set by passing the `-a` flag to ``setup.py``: This can also be set by passing the `-a` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -a $ python setup.py build_sphinx -a
@@ -88,7 +88,7 @@ Options for setuptools integration
This can also be set by passing the `-s` flag to ``setup.py``: This can also be set by passing the `-s` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -s $SOURCE_DIR $ python setup.py build_sphinx -s $SOURCE_DIR
@@ -105,7 +105,7 @@ Options for setuptools integration
This can also be set by passing the `-c` flag to ``setup.py``: This can also be set by passing the `-c` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -c $CONFIG_DIR $ python setup.py build_sphinx -c $CONFIG_DIR
@@ -117,7 +117,7 @@ Options for setuptools integration
This can also be set by passing the `-b` flag to ``setup.py``: This can also be set by passing the `-b` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -b $BUILDER $ python setup.py build_sphinx -b $BUILDER
@@ -131,7 +131,7 @@ Options for setuptools integration
This can also be set by passing the `-W` flag to ``setup.py``: This can also be set by passing the `-W` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -W $ python setup.py build_sphinx -W
@@ -169,7 +169,7 @@ Options for setuptools integration
This can also be set by passing the `-i` flag to ``setup.py``: This can also be set by passing the `-i` flag to ``setup.py``:
.. code-block:: bash .. code-block:: console
$ python setup.py build_sphinx -i $ python setup.py build_sphinx -i

View File

@@ -22,9 +22,9 @@ and output behavior.
.. _`docutils.conf`: https://docutils.sourceforge.io/docs/user/config.html .. _`docutils.conf`: https://docutils.sourceforge.io/docs/user/config.html
The configuration file is executed as Python code at build time (using The configuration file is executed as Python code at build time (using
:func:`execfile`, and with the current directory set to its containing :func:`importlib.import_module`, and with the current directory set to its
directory), and therefore can execute arbitrarily complex code. Sphinx then containing directory), and therefore can execute arbitrarily complex code.
reads simple names from the file's namespace as its configuration. Sphinx then reads simple names from the file's namespace as its configuration.
Important points to note: Important points to note:
@@ -329,6 +329,8 @@ General configuration
* ``ref.python`` * ``ref.python``
* ``misc.highlighting_failure`` * ``misc.highlighting_failure``
* ``toc.circular`` * ``toc.circular``
* ``toc.excluded``
* ``toc.not_readable``
* ``toc.secnum`` * ``toc.secnum``
* ``epub.unknown_project_files`` * ``epub.unknown_project_files``
* ``epub.duplicated_toc_entry`` * ``epub.duplicated_toc_entry``
@@ -360,6 +362,10 @@ General configuration
Added ``epub.duplicated_toc_entry`` Added ``epub.duplicated_toc_entry``
.. versionchanged:: 4.3
Added ``toc.excluded`` and ``toc.not_readable``
.. confval:: needs_sphinx .. confval:: needs_sphinx
If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will
@@ -802,6 +808,13 @@ documentation on :ref:`intl` for details.
.. versionchanged:: 1.5 .. versionchanged:: 1.5
Use ``locales`` directory as a default value Use ``locales`` directory as a default value
.. confval:: gettext_allow_fuzzy_translations
If true, "fuzzy" messages in the message catalogs are used for translation.
The default is ``False``.
.. versionadded:: 4.3
.. confval:: gettext_compact .. confval:: gettext_compact
.. versionadded:: 1.1 .. versionadded:: 1.1
@@ -992,7 +1005,7 @@ that use Sphinx's HTMLWriter class.
to indicate the location of document using `The Canonical Link Relation`_. to indicate the location of document using `The Canonical Link Relation`_.
Default: ``''``. Default: ``''``.
.. _The Canonical Link Relation: https://tools.ietf.org/html/rfc6596 .. _The Canonical Link Relation: https://datatracker.ietf.org/doc/html/rfc6596
.. versionadded:: 1.8 .. versionadded:: 1.8
@@ -2486,6 +2499,13 @@ These options influence Texinfo output.
.. versionadded:: 1.1 .. versionadded:: 1.1
.. confval:: texinfo_cross_references
If false, do not generate inline references in a document. That makes
an info file more readable with stand-alone reader (``info``).
Default is ``True``.
.. versionadded:: 4.4
.. _qthelp-options: .. _qthelp-options:
@@ -2637,10 +2657,8 @@ Options for the linkcheck builder
A regular expression that matches a URI. A regular expression that matches a URI.
*auth_info* *auth_info*
Authentication information to use for that URI. The value can be anything Authentication information to use for that URI. The value can be anything
that is understood by the ``requests`` library (see `requests that is understood by the ``requests`` library (see :ref:`requests
Authentication <requests-auth>`_ for details). Authentication <requests:authentication>` for details).
.. _requests-auth: https://requests.readthedocs.io/en/master/user/authentication/
The ``linkcheck`` builder will use the first matching ``auth_info`` value The ``linkcheck`` builder will use the first matching ``auth_info`` value
it can find in the :confval:`linkcheck_auth` list, so values earlier in the it can find in the :confval:`linkcheck_auth` list, so values earlier in the
@@ -2668,10 +2686,23 @@ Options for the linkcheck builder
doubling the wait time between attempts until it succeeds or exceeds the doubling the wait time between attempts until it succeeds or exceeds the
``linkcheck_rate_limit_timeout``. By default, the timeout is 5 minutes. ``linkcheck_rate_limit_timeout``. By default, the timeout is 5 minutes.
.. _Retry-After: https://tools.ietf.org/html/rfc7231#section-7.1.3 .. _Retry-After: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3
.. versionadded:: 3.4 .. versionadded:: 3.4
.. confval:: linkcheck_exclude_documents
A list of regular expressions that match documents in which Sphinx should
not check the validity of links. This can be used for permitting link decay
in legacy or historical sections of the documentation.
Example::
# ignore all links in documents located in a subfolder named 'legacy'
linkcheck_exclude_documents = [r'.*/legacy/.*']
.. versionadded:: 4.4
Options for the XML builder Options for the XML builder
--------------------------- ---------------------------

View File

@@ -662,6 +662,18 @@ There are also config values that you can set:
.. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases .. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases
.. versionadded:: 3.3 .. versionadded:: 3.3
.. confval:: autodoc_typehints_format
This value controls the format of typehints. The setting takes the
following values:
* ``'fully-qualified'`` -- Show the module name and its name of typehints
(default)
* ``'short'`` -- Suppress the leading module names of the typehints
(ex. ``io.StringIO`` -> ``StringIO``)
.. versionadded:: 4.4
.. confval:: autodoc_preserve_defaults .. confval:: autodoc_preserve_defaults
If True, the default argument values of functions will be not evaluated on If True, the default argument values of functions will be not evaluated on
@@ -768,8 +780,6 @@ needed docstring processing in event :event:`autodoc-process-docstring`:
.. event:: autodoc-process-bases (app, name, obj, options, bases) .. event:: autodoc-process-bases (app, name, obj, options, bases)
.. versionadded:: 4.1
Emitted when autodoc has read and processed a class to determine the Emitted when autodoc has read and processed a class to determine the
base-classes. *bases* is a list of classes that the event handler can base-classes. *bases* is a list of classes that the event handler can
modify **in place** to change what Sphinx puts into the output. It's modify **in place** to change what Sphinx puts into the output. It's
@@ -781,6 +791,12 @@ needed docstring processing in event :event:`autodoc-process-docstring`:
:param options: the options given to the class directive :param options: the options given to the class directive
:param bases: the list of base classes signature. see above. :param bases: the list of base classes signature. see above.
.. versionadded:: 4.1
.. versionchanged:: 4.3
``bases`` can contain a string as a base class name. It will be processed
as reST mark-up'ed text.
Skipping members Skipping members
---------------- ----------------

View File

@@ -201,6 +201,25 @@ also use these config values:
.. versionadded:: 2.1 .. versionadded:: 2.1
.. versionchanged:: 4.4
If ``autosummary_ignore_module_all`` is ``False``, this configuration
value is ignored for members listed in ``__all__``.
.. confval:: autosummary_ignore_module_all
If ``False`` and a module has the ``__all__`` attribute set, autosummary
documents every member listed in ``__all__`` and no others. Default is
``True``
Note that if an imported member is listed in ``__all__``, it will be
documented regardless of the value of ``autosummary_imported_members``. To
match the behaviour of ``from module import *``, set
``autosummary_ignore_module_all`` to False and
``autosummary_imported_members`` to True.
.. versionadded:: 4.4
.. confval:: autosummary_filename_map .. confval:: autosummary_filename_map
A dict mapping object names to filenames. This is necessary to avoid A dict mapping object names to filenames. This is necessary to avoid
@@ -210,6 +229,7 @@ also use these config values:
.. versionadded:: 3.2 .. versionadded:: 3.2
.. _autosummary-customizing-templates:
Customizing templates Customizing templates
--------------------- ---------------------

View File

@@ -148,6 +148,35 @@ linking:
exception is raised if the server has not issued a response for timeout exception is raised if the server has not issued a response for timeout
seconds. seconds.
.. confval:: intersphinx_disabled_reftypes
.. versionadded:: 4.3
A list of strings being either:
- the name of a specific reference type in a domain,
e.g., ``std:doc``, ``py:func``, or ``cpp:class``,
- the name of a domain, and a wildcard, e.g.,
``std:*``, ``py:*``, or ``cpp:*``, or
- simply a wildcard ``*``.
The default value is an empty list.
When a cross-reference without an explicit inventory specification is being
resolved by intersphinx, skip resolution if it matches one of the
specifications in this list.
For example, with ``intersphinx_disabled_reftypes = ['std:doc']``
a cross-reference ``:doc:`installation``` will not be attempted to be
resolved by intersphinx, but ``:doc:`otherbook:installation``` will be
attempted to be resolved in the inventory named ``otherbook`` in
:confval:`intersphinx_mapping`.
At the same time, all cross-references generated in, e.g., Python,
declarations will still be attempted to be resolved by intersphinx.
If ``*`` is in the list of domains, then no references without an explicit
inventory will be resolved by intersphinx.
Showing all links of an Intersphinx mapping file Showing all links of an Intersphinx mapping file
------------------------------------------------ ------------------------------------------------
@@ -158,7 +187,7 @@ searching for the root cause of a broken Intersphinx link in a documentation
project. The following example prints the Intersphinx mapping of the Python 3 project. The following example prints the Intersphinx mapping of the Python 3
documentation:: documentation::
$ python -msphinx.ext.intersphinx https://docs.python.org/3/objects.inv $ python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv
Using Intersphinx with inventory file under Basic Authorization Using Intersphinx with inventory file under Basic Authorization
--------------------------------------------------------------- ---------------------------------------------------------------

View File

@@ -200,6 +200,11 @@ Sphinx but is set to automatically include it from a third-party site.
.. versionadded:: 1.8 .. versionadded:: 1.8
.. versionchanged:: 4.4.1
Allow to change the loading method (async or defer) of MathJax if "async"
or "defer" key is set.
.. confval:: mathjax3_config .. confval:: mathjax3_config
The configuration options for MathJax v3 (which is used by default). The configuration options for MathJax v3 (which is used by default).

View File

@@ -120,7 +120,7 @@ Chocolatey
$ choco install sphinx $ choco install sphinx
You would need to `install Chocolatey You would need to `install Chocolatey
<https://chocolatey.org/install/>`_ <https://chocolatey.org/install>`_
before running this. before running this.
For more information, refer to the `chocolatey page`__. For more information, refer to the `chocolatey page`__.
@@ -239,13 +239,13 @@ PDF builds using LaTeX. Please choose one for your purpose.
commands. For example, you can use following command to create a Sphinx commands. For example, you can use following command to create a Sphinx
project: project:
.. code-block:: bash .. code-block:: console
$ docker run -it --rm -v /path/to/document:/docs sphinxdoc/sphinx sphinx-quickstart $ docker run -it --rm -v /path/to/document:/docs sphinxdoc/sphinx sphinx-quickstart
And you can following command this to build HTML document: And you can following command this to build HTML document:
.. code-block:: bash .. code-block:: console
$ docker run --rm -v /path/to/document:/docs sphinxdoc/sphinx make html $ docker run --rm -v /path/to/document:/docs sphinxdoc/sphinx make html

View File

@@ -39,7 +39,7 @@ Sphinx comes with a script called :program:`sphinx-quickstart` that sets up a
source directory and creates a default :file:`conf.py` with the most useful source directory and creates a default :file:`conf.py` with the most useful
configuration values from a few questions it asks you. To use this, run: configuration values from a few questions it asks you. To use this, run:
.. code-block:: shell .. code-block:: console
$ sphinx-quickstart $ sphinx-quickstart
@@ -128,7 +128,7 @@ Running the build
Now that you have added some files and content, let's make a first build of the Now that you have added some files and content, let's make a first build of the
docs. A build is started with the :program:`sphinx-build` program: docs. A build is started with the :program:`sphinx-build` program:
.. code-block:: shell .. code-block:: console
$ sphinx-build -b html sourcedir builddir $ sphinx-build -b html sourcedir builddir
@@ -144,7 +144,7 @@ However, :program:`sphinx-quickstart` script creates a :file:`Makefile` and a
:file:`make.bat` which make life even easier for you. These can be executed by :file:`make.bat` which make life even easier for you. These can be executed by
running :command:`make` with the name of the builder. For example. running :command:`make` with the name of the builder. For example.
.. code-block:: shell .. code-block:: console
$ make html $ make html

View File

@@ -410,7 +410,27 @@ following the arguments and indicated by the colons). Options must be indented
to the same level as the directive content. to the same level as the directive content.
The directive content follows after a blank line and is indented relative to The directive content follows after a blank line and is indented relative to
the directive start. the directive start or if options are present, by the same amount as the
options.
Be careful as the indent is not a fixed number of whitespace, e.g. three, but
any number whitespace. This can be surprising when a fixed indent is used
throughout the document and can make a difference for directives which are
sensitive to whitespace. Compare::
.. code-block::
:caption: A cool example
The output of this line starts with four spaces.
.. code-block::
The output of this line has no spaces at the beginning.
In the first code block, the indent for the content was fixated by the option
line to three spaces, consequently the content starts with four spaces.
In the latter the indent was fixed by the content itself to seven spaces, thus
it does not start with a space.
Images Images

View File

@@ -832,6 +832,9 @@ Glossary
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Index key for glossary term should be considered *experimental*. Index key for glossary term should be considered *experimental*.
.. versionchanged:: 4.4
In internationalized documentation, the ``:sorted:`` flag sorts
according to translated terms.
Meta-information markup Meta-information markup
----------------------- -----------------------

View File

@@ -125,6 +125,7 @@ In short:
component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will
refer to ``Queue.Queue.get`` but only display ``get`` as the link text. refer to ``Queue.Queue.get`` but only display ``get`` as the link text.
.. _python-domain:
The Python Domain The Python Domain
----------------- -----------------
@@ -677,12 +678,55 @@ The C domain (name **c**) is suited for documentation of C API.
Note that you don't have to backslash-escape asterisks in the signature, as Note that you don't have to backslash-escape asterisks in the signature, as
it is not parsed by the reST inliner. it is not parsed by the reST inliner.
In the description of a function you can use the following info fields
(see also :ref:`info-field-lists`).
* ``param``, ``parameter``, ``arg``, ``argument``,
Description of a parameter.
* ``type``: Type of a parameter,
written as if passed to the :rst:role:`c:expr` role.
* ``returns``, ``return``: Description of the return value.
* ``rtype``: Return type,
written as if passed to the :rst:role:`c:expr` role.
* ``retval``, ``retvals``: An alternative to ``returns`` for describing
the result of the function.
.. versionadded:: 4.3
The ``retval`` field type.
For example::
.. c:function:: PyObject *PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
:param type: description of the first parameter.
:param nitems: description of the second parameter.
:returns: a result.
:retval NULL: under some conditions.
:retval NULL: under some other conditions as well.
which renders as
.. c:function:: PyObject *PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
..
** for some editors (e.g., vim) to stop bold-highlighting the source
:param type: description of the first parameter.
:param nitems: description of the second parameter.
:returns: a result.
:retval NULL: under some conditions.
:retval NULL: under some other conditions as well.
.. rst:directive:: .. c:macro:: name .. rst:directive:: .. c:macro:: name
.. c:macro:: name(arg list) .. c:macro:: name(arg list)
Describes a C macro, i.e., a C-language ``#define``, without the replacement Describes a C macro, i.e., a C-language ``#define``, without the replacement
text. text.
In the description of a macro you can use the same info fields as for the
:rst:dir:`c:function` directive.
.. versionadded:: 3.0 .. versionadded:: 3.0
The function style variant. The function style variant.
@@ -1453,14 +1497,23 @@ The ``cpp:namespace-pop`` directive undoes the most recent
Info field lists Info field lists
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
The C++ directives support the following info fields (see also All the C++ directives for declaring entities support the following
:ref:`info-field-lists`): info fields (see also :ref:`info-field-lists`):
* `param`, `parameter`, `arg`, `argument`: Description of a parameter. * ``tparam``: Description of a template parameter.
* `tparam`: Description of a template parameter.
* `returns`, `return`: Description of a return value. The :rst:dir:`cpp:function` directive additionally supports the
following fields:
* ``param``, ``parameter``, ``arg``, ``argument``: Description of a parameter.
* ``returns``, ``return``: Description of a return value.
* ``retval``, ``retvals``: An alternative to ``returns`` for describing
the result of the function.
* `throws`, `throw`, `exception`: Description of a possibly thrown exception. * `throws`, `throw`, `exception`: Description of a possibly thrown exception.
.. versionadded:: 4.3
The ``retval`` field type.
.. _cpp-roles: .. _cpp-roles:
Cross-referencing Cross-referencing

View File

@@ -70,7 +70,7 @@ directory containing :file:`conf.py` and use this configuration::
The third form is a Python package. If a theme you want to use is distributed The third form is a Python package. If a theme you want to use is distributed
as a Python package, you can use it after installing as a Python package, you can use it after installing
.. code-block:: bash .. code-block:: console
# installing theme package # installing theme package
$ pip install sphinxjp.themes.dotted $ pip install sphinxjp.themes.dotted
@@ -127,6 +127,10 @@ Builtin themes
Sphinx comes with a selection of themes to choose from. Sphinx comes with a selection of themes to choose from.
Note that from these themes only the Alabaster and Scrolls themes are
mobile-optimated, the other themes resort to horizontal scrolling
if the screen is too narrow.
.. cssclass:: clear .. cssclass:: clear
These themes are: These themes are:

28
package-lock.json generated
View File

@@ -422,30 +422,10 @@
"dev": true "dev": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.7.0", "version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
"dev": true, "dev": true
"requires": {
"debug": "^3.2.6"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
}, },
"fs-extra": { "fs-extra": {
"version": "7.0.1", "version": "7.0.1",

View File

@@ -1,6 +1,5 @@
import os import os
import sys import sys
from distutils import log
from io import StringIO from io import StringIO
from setuptools import find_packages, setup from setuptools import find_packages, setup
@@ -29,8 +28,8 @@ install_requires = [
'alabaster>=0.7,<0.8', 'alabaster>=0.7,<0.8',
'imagesize', 'imagesize',
'requests>=2.5.0', 'requests>=2.5.0',
'setuptools',
'packaging', 'packaging',
"importlib-metadata>=4.4; python_version < '3.10'",
] ]
extras_require = { extras_require = {
@@ -44,10 +43,9 @@ extras_require = {
'lint': [ 'lint': [
'flake8>=3.5.0', 'flake8>=3.5.0',
'isort', 'isort',
'mypy>=0.900', 'mypy>=0.931',
'docutils-stubs', 'docutils-stubs',
"types-typed-ast", "types-typed-ast",
"types-pkg_resources",
"types-requests", "types-requests",
], ],
'test': [ 'test': [
@@ -149,8 +147,8 @@ else:
if catalog.fuzzy and not self.use_fuzzy: if catalog.fuzzy and not self.use_fuzzy:
continue continue
log.info('writing JavaScript strings in catalog %r to %r', self.log.info('writing JavaScript strings in catalog %r to %r',
po_file, js_file) po_file, js_file)
jscatalog = {} jscatalog = {}
for message in catalog: for message in catalog:
@@ -207,6 +205,7 @@ setup(
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: PyPy',
'Framework :: Setuptools Plugin', 'Framework :: Setuptools Plugin',

View File

@@ -4,7 +4,7 @@
The Sphinx documentation toolchain. The Sphinx documentation toolchain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -27,8 +27,8 @@ if 'PYTHONWARNINGS' not in os.environ:
warnings.filterwarnings('ignore', "'U' mode is deprecated", warnings.filterwarnings('ignore', "'U' mode is deprecated",
DeprecationWarning, module='docutils.io') DeprecationWarning, module='docutils.io')
__version__ = '4.3.0+' __version__ = '4.4.0+'
__released__ = '4.3.0' # used when Sphinx builds its own docs __released__ = '4.4.0' # used when Sphinx builds its own docs
#: Version info for better programmatic use. #: Version info for better programmatic use.
#: #:
@@ -38,7 +38,7 @@ __released__ = '4.3.0' # used when Sphinx builds its own docs
#: #:
#: .. versionadded:: 1.2 #: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``. #: Before version 1.2, check the string ``sphinx.__version__``.
version_info = (4, 3, 0, 'beta', 0) version_info = (4, 4, 0, 'beta', 0)
package_dir = path.abspath(path.dirname(__file__)) package_dir = path.abspath(path.dirname(__file__))

View File

@@ -4,7 +4,7 @@
The Sphinx documentation toolchain. The Sphinx documentation toolchain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Additional docutils nodes. Additional docutils nodes.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -16,6 +16,13 @@ from docutils.nodes import Element
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx
try:
from docutils.nodes import meta as docutils_meta # type: ignore
except ImportError:
# docutils-0.17 or older
from docutils.parsers.rst.directives.html import MetaBody
docutils_meta = MetaBody.meta
class document(nodes.document): class document(nodes.document):
"""The document root element patched by Sphinx. """The document root element patched by Sphinx.
@@ -528,6 +535,8 @@ class manpage(nodes.Inline, nodes.FixedTextElement):
def setup(app: "Sphinx") -> Dict[str, Any]: def setup(app: "Sphinx") -> Dict[str, Any]:
from sphinx.util import docutils # lazy import
app.add_node(toctree) app.add_node(toctree)
app.add_node(desc) app.add_node(desc)
@@ -563,7 +572,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(start_of_file) app.add_node(start_of_file)
app.add_node(highlightlang) app.add_node(highlightlang)
app.add_node(tabular_col_spec) app.add_node(tabular_col_spec)
app.add_node(meta)
app.add_node(pending_xref) app.add_node(pending_xref)
app.add_node(number_reference) app.add_node(number_reference)
app.add_node(download_reference) app.add_node(download_reference)
@@ -571,6 +579,9 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(literal_strong) app.add_node(literal_strong)
app.add_node(manpage) app.add_node(manpage)
if docutils.__version_info__ < (0, 18):
app.add_node(meta)
return { return {
'version': 'builtin', 'version': 'builtin',
'parallel_read_safe': True, 'parallel_read_safe': True,

View File

@@ -6,13 +6,12 @@
Gracefully adapted from the TextPress system by Armin. Gracefully adapted from the TextPress system by Armin.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import os import os
import pickle import pickle
import platform
import sys import sys
import warnings import warnings
from collections import deque from collections import deque
@@ -195,12 +194,6 @@ class Sphinx:
# say hello to the world # say hello to the world
logger.info(bold(__('Running Sphinx v%s') % sphinx.__display_version__)) logger.info(bold(__('Running Sphinx v%s') % sphinx.__display_version__))
# notice for parallel build on macOS and py38+
if sys.version_info > (3, 8) and platform.system() == 'Darwin' and parallel > 1:
logger.info(bold(__("For security reasons, parallel mode is disabled on macOS and "
"python3.8 and above. For more details, please read "
"https://github.com/sphinx-doc/sphinx/issues/6803")))
# status code for command-line application # status code for command-line application
self.statuscode = 0 self.statuscode = 0
@@ -284,7 +277,8 @@ class Sphinx:
self.config.language, self.config.source_encoding) self.config.language, self.config.source_encoding)
for catalog in repo.catalogs: for catalog in repo.catalogs:
if catalog.domain == 'sphinx' and catalog.is_outdated(): if catalog.domain == 'sphinx' and catalog.is_outdated():
catalog.write_mo(self.config.language) catalog.write_mo(self.config.language,
self.config.gettext_allow_fuzzy_translations)
locale_dirs: List[Optional[str]] = list(repo.locale_dirs) locale_dirs: List[Optional[str]] = list(repo.locale_dirs)
locale_dirs += [None] locale_dirs += [None]
@@ -936,24 +930,31 @@ class Sphinx:
""" """
self.registry.add_post_transform(transform) self.registry.add_post_transform(transform)
def add_js_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: def add_js_file(self, filename: str, priority: int = 500,
loading_method: Optional[str] = None, **kwargs: Any) -> None:
"""Register a JavaScript file to include in the HTML output. """Register a JavaScript file to include in the HTML output.
Add *filename* to the list of JavaScript files that the default HTML :param filename: The filename of the JavaScript file. It must be relative to the HTML
template will include in order of *priority* (ascending). The filename static path, a full URI with scheme, or ``None`` value. The ``None``
must be relative to the HTML static path , or a full URI with scheme. value is used to create inline ``<script>`` tag. See the description
If the priority of the JavaScript file is the same as others, the JavaScript of *kwargs* below.
files will be included in order of registration. If the keyword :param priority: The priority to determine the order of ``<script>`` tag for
argument ``body`` is given, its value will be added between the JavaScript files. See list of "prority range for JavaScript
``<script>`` tags. Extra keyword arguments are included as attributes of files" below. If the priority of the JavaScript files it the same
the ``<script>`` tag. as others, the JavaScript files will be loaded in order of
registration.
:param loading_method: The loading method of the JavaScript file. ``'async'`` or
``'defer'`` is allowed.
:param kwargs: Extra keyword arguments are included as attributes of the ``<script>``
tag. A special keyword argument ``body`` is given, its value will be
added between the ``<script>`` tag.
Example:: Example::
app.add_js_file('example.js') app.add_js_file('example.js')
# => <script src="_static/example.js"></script> # => <script src="_static/example.js"></script>
app.add_js_file('example.js', async="async") app.add_js_file('example.js', loading_method="async")
# => <script src="_static/example.js" async="async"></script> # => <script src="_static/example.js" async="async"></script>
app.add_js_file(None, body="var myVariable = 'foo';") app.add_js_file(None, body="var myVariable = 'foo';")
@@ -982,7 +983,15 @@ class Sphinx:
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Take priority argument. Allow to add a JavaScript file to the specific page. Take priority argument. Allow to add a JavaScript file to the specific page.
.. versionchanged:: 4.4
Take loading_method argument. Allow to change the loading method of the
JavaScript file.
""" """
if loading_method == 'async':
kwargs['async'] = 'async'
elif loading_method == 'defer':
kwargs['defer'] = 'defer'
self.registry.add_js_file(filename, priority=priority, **kwargs) self.registry.add_js_file(filename, priority=priority, **kwargs)
if hasattr(self.builder, 'add_js_file'): if hasattr(self.builder, 'add_js_file'):
self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore
@@ -990,12 +999,14 @@ class Sphinx:
def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
"""Register a stylesheet to include in the HTML output. """Register a stylesheet to include in the HTML output.
Add *filename* to the list of CSS files that the default HTML template :param filename: The filename of the CSS file. It must be relative to the HTML
will include in order of *priority* (ascending). The filename must be static path, or a full URI with scheme.
relative to the HTML static path, or a full URI with scheme. If the :param priority: The priority to determine the order of ``<link>`` tag for the
priority of the CSS file is the same as others, the CSS files will be CSS files. See list of "prority range for CSS files" below.
included in order of registration. The keyword arguments are also If the priority of the CSS files it the same as others, the
accepted for attributes of ``<link>`` tag. CSS files will be loaded in order of registration.
:param kwargs: Extra keyword arguments are included as attributes of the ``<link>``
tag.
Example:: Example::
@@ -1046,6 +1057,26 @@ class Sphinx:
if hasattr(self.builder, 'add_css_file'): if hasattr(self.builder, 'add_css_file'):
self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore
def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None
) -> None:
"""An alias of :meth:`add_css_file`.
.. deprecated:: 1.8
"""
logger.warning('The app.add_stylesheet() is deprecated. '
'Please use app.add_css_file() instead.')
attributes = {} # type: Dict[str, Any]
if alternate:
attributes['rel'] = 'alternate stylesheet'
else:
attributes['rel'] = 'stylesheet'
if title:
attributes['title'] = title
self.add_css_file(filename, **attributes)
def add_latex_package(self, packagename: str, options: str = None, def add_latex_package(self, packagename: str, options: str = None,
after_hyperref: bool = False) -> None: after_hyperref: bool = False) -> None:
r"""Register a package to include in the LaTeX source code. r"""Register a package to include in the LaTeX source code.

View File

@@ -4,7 +4,7 @@
Builder superclass for all builders. Builder superclass for all builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -176,7 +176,7 @@ class Builder:
def post_process_images(self, doctree: Node) -> None: def post_process_images(self, doctree: Node) -> None:
"""Pick the best candidate for all image URIs.""" """Pick the best candidate for all image URIs."""
images = ImageAdapter(self.env) images = ImageAdapter(self.env)
for node in doctree.traverse(nodes.image): for node in doctree.findall(nodes.image):
if '?' in node['candidates']: if '?' in node['candidates']:
# don't rewrite nonlocal image URIs # don't rewrite nonlocal image URIs
continue continue
@@ -217,7 +217,8 @@ class Builder:
for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen", for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen",
len(catalogs), self.app.verbosity, len(catalogs), self.app.verbosity,
stringify_func=cat2relpath): stringify_func=cat2relpath):
catalog.write_mo(self.config.language) catalog.write_mo(self.config.language,
self.config.gettext_allow_fuzzy_translations)
def compile_all_catalogs(self) -> None: def compile_all_catalogs(self) -> None:
repo = CatalogRepository(self.srcdir, self.config.locale_dirs, repo = CatalogRepository(self.srcdir, self.config.locale_dirs,

View File

@@ -4,7 +4,7 @@
Base class of epub2/epub3 builders. Base class of epub2/epub3 builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -277,7 +277,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
new_ids.append(new_id) new_ids.append(new_id)
node['ids'] = new_ids node['ids'] = new_ids
for reference in tree.traverse(nodes.reference): for reference in tree.findall(nodes.reference):
if 'refuri' in reference: if 'refuri' in reference:
m = self.refuri_re.match(reference['refuri']) m = self.refuri_re.match(reference['refuri'])
if m: if m:
@@ -285,14 +285,14 @@ class EpubBuilder(StandaloneHTMLBuilder):
if 'refid' in reference: if 'refid' in reference:
reference['refid'] = self.fix_fragment('', reference['refid']) reference['refid'] = self.fix_fragment('', reference['refid'])
for target in tree.traverse(nodes.target): for target in tree.findall(nodes.target):
update_node_id(target) update_node_id(target)
next_node: Node = target.next_node(ascend=True) next_node: Node = target.next_node(ascend=True)
if isinstance(next_node, nodes.Element): if isinstance(next_node, nodes.Element):
update_node_id(next_node) update_node_id(next_node)
for desc_signature in tree.traverse(addnodes.desc_signature): for desc_signature in tree.findall(addnodes.desc_signature):
update_node_id(desc_signature) update_node_id(desc_signature)
def add_visible_links(self, tree: nodes.document, show_urls: str = 'inline') -> None: def add_visible_links(self, tree: nodes.document, show_urls: str = 'inline') -> None:
@@ -323,14 +323,14 @@ class EpubBuilder(StandaloneHTMLBuilder):
# a) place them after the last existing footnote # a) place them after the last existing footnote
# b) place them after an (empty) Footnotes rubric # b) place them after an (empty) Footnotes rubric
# c) create an empty Footnotes rubric at the end of the document # c) create an empty Footnotes rubric at the end of the document
fns = tree.traverse(nodes.footnote) fns = list(tree.findall(nodes.footnote))
if fns: if fns:
fn = fns[-1] fn = fns[-1]
return fn.parent, fn.parent.index(fn) + 1 return fn.parent, fn.parent.index(fn) + 1
for node in tree.traverse(nodes.rubric): for node in tree.findall(nodes.rubric):
if len(node) == 1 and node.astext() == FOOTNOTES_RUBRIC_NAME: if len(node) == 1 and node.astext() == FOOTNOTES_RUBRIC_NAME:
return node.parent, node.parent.index(node) + 1 return node.parent, node.parent.index(node) + 1
doc = tree.traverse(nodes.document)[0] doc = next(tree.findall(nodes.document))
rub = nodes.rubric() rub = nodes.rubric()
rub.append(nodes.Text(FOOTNOTES_RUBRIC_NAME)) rub.append(nodes.Text(FOOTNOTES_RUBRIC_NAME))
doc.append(rub) doc.append(rub)
@@ -339,10 +339,10 @@ class EpubBuilder(StandaloneHTMLBuilder):
if show_urls == 'no': if show_urls == 'no':
return return
if show_urls == 'footnote': if show_urls == 'footnote':
doc = tree.traverse(nodes.document)[0] doc = next(tree.findall(nodes.document))
fn_spot, fn_idx = footnote_spot(tree) fn_spot, fn_idx = footnote_spot(tree)
nr = 1 nr = 1
for node in tree.traverse(nodes.reference): for node in list(tree.findall(nodes.reference)):
uri = node.get('refuri', '') uri = node.get('refuri', '')
if (uri.startswith('http:') or uri.startswith('https:') or if (uri.startswith('http:') or uri.startswith('https:') or
uri.startswith('ftp:')) and uri not in node.astext(): uri.startswith('ftp:')) and uri not in node.astext():
@@ -703,7 +703,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
epub_filename = path.join(self.outdir, outname) epub_filename = path.join(self.outdir, outname)
with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub: with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub:
epub.write(path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED) epub.write(path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED)
for filename in ['META-INF/container.xml', 'content.opf', 'toc.ncx']: for filename in ('META-INF/container.xml', 'content.opf', 'toc.ncx'):
epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED)
for filename in self.files: for filename in self.files:
epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED)

View File

@@ -4,7 +4,7 @@
Changelog builder. Changelog builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -148,7 +148,7 @@ class ChangesBuilder(Builder):
def hl(self, text: str, version: str) -> str: def hl(self, text: str, version: str) -> str:
text = html.escape(text) text = html.escape(text)
for directive in ['versionchanged', 'versionadded', 'deprecated']: for directive in ('versionchanged', 'versionadded', 'deprecated'):
text = text.replace('.. %s:: %s' % (directive, version), text = text.replace('.. %s:: %s' % (directive, version),
'<b>.. %s:: %s</b>' % (directive, version)) '<b>.. %s:: %s</b>' % (directive, version))
return text return text

View File

@@ -4,7 +4,7 @@
Directory HTML builders. Directory HTML builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Do syntax checks, but no writing. Do syntax checks, but no writing.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -5,7 +5,7 @@
Build epub3 files. Build epub3 files.
Originally derived from epub.py. Originally derived from epub.py.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The MessageCatalogBuilder class. The MessageCatalogBuilder class.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -146,7 +146,7 @@ class I18nBuilder(Builder):
def write_doc(self, docname: str, doctree: nodes.document) -> None: def write_doc(self, docname: str, doctree: nodes.document) -> None:
catalog = self.catalogs[docname_to_domain(docname, self.config.gettext_compact)] catalog = self.catalogs[docname_to_domain(docname, self.config.gettext_compact)]
for toctree in self.env.tocs[docname].traverse(addnodes.toctree): for toctree in self.env.tocs[docname].findall(addnodes.toctree):
for node, msg in extract_messages(toctree): for node, msg in extract_messages(toctree):
node.uid = '' # type: ignore # Hack UUID model node.uid = '' # type: ignore # Hack UUID model
catalog.add(msg, node) catalog.add(msg, node)
@@ -180,7 +180,7 @@ if source_date_epoch is not None:
class LocalTimeZone(tzinfo): class LocalTimeZone(tzinfo):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) # type: ignore super().__init__(*args, **kwargs)
self.tzdelta = tzdelta self.tzdelta = tzdelta
def utcoffset(self, dt: datetime) -> timedelta: def utcoffset(self, dt: datetime) -> timedelta:

View File

@@ -4,7 +4,7 @@
Several HTML builders. Several HTML builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -866,7 +866,7 @@ class StandaloneHTMLBuilder(Builder):
Builder.post_process_images(self, doctree) Builder.post_process_images(self, doctree)
if self.config.html_scaled_image_link and self.html_scaled_image_link: if self.config.html_scaled_image_link and self.html_scaled_image_link:
for node in doctree.traverse(nodes.image): for node in doctree.findall(nodes.image):
if not any((key in node) for key in ['scale', 'width', 'height']): if not any((key in node) for key in ['scale', 'width', 'height']):
# resizing options are not given. scaled image link is available # resizing options are not given. scaled image link is available
# only for resized images. # only for resized images.

View File

@@ -4,7 +4,7 @@
Transforms for HTML builder. Transforms for HTML builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -36,7 +36,7 @@ class KeyboardTransform(SphinxPostTransform):
x x
""" """
default_priority = 400 default_priority = 400
builders = ('html',) formats = ('html',)
pattern = re.compile(r'(?<=.)(-|\+|\^|\s+)(?=.)') pattern = re.compile(r'(?<=.)(-|\+|\^|\s+)(?=.)')
multiwords_keys = (('caps', 'lock'), multiwords_keys = (('caps', 'lock'),
('page' 'down'), ('page' 'down'),
@@ -48,7 +48,7 @@ class KeyboardTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
matcher = NodeMatcher(nodes.literal, classes=["kbd"]) matcher = NodeMatcher(nodes.literal, classes=["kbd"])
for node in self.document.traverse(matcher): # type: nodes.literal for node in self.document.findall(matcher): # type: nodes.literal
parts = self.pattern.split(node[-1].astext()) parts = self.pattern.split(node[-1].astext())
if len(parts) == 1 or self.is_multiwords_key(parts): if len(parts) == 1 or self.is_multiwords_key(parts):
continue continue

View File

@@ -4,7 +4,7 @@
LaTeX builder. LaTeX builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -280,7 +280,7 @@ class LaTeXBuilder(Builder):
encoding='utf-8', overwrite_if_changed=True) encoding='utf-8', overwrite_if_changed=True)
with progress_message(__("processing %s") % targetname): with progress_message(__("processing %s") % targetname):
doctree = self.env.get_doctree(docname) doctree = self.env.get_doctree(docname)
toctree = next(iter(doctree.traverse(addnodes.toctree)), None) toctree = next(doctree.findall(addnodes.toctree), None)
if toctree and toctree.get('maxdepth') > 0: if toctree and toctree.get('maxdepth') > 0:
tocdepth = toctree.get('maxdepth') tocdepth = toctree.get('maxdepth')
else: else:
@@ -310,7 +310,7 @@ class LaTeXBuilder(Builder):
def get_contentsname(self, indexfile: str) -> str: def get_contentsname(self, indexfile: str) -> str:
tree = self.env.get_doctree(indexfile) tree = self.env.get_doctree(indexfile)
contentsname = None contentsname = None
for toctree in tree.traverse(addnodes.toctree): for toctree in tree.findall(addnodes.toctree):
if 'caption' in toctree: if 'caption' in toctree:
contentsname = toctree['caption'] contentsname = toctree['caption']
break break
@@ -338,7 +338,7 @@ class LaTeXBuilder(Builder):
new_sect += nodes.title('<Set title in conf.py>', new_sect += nodes.title('<Set title in conf.py>',
'<Set title in conf.py>') '<Set title in conf.py>')
new_tree += new_sect new_tree += new_sect
for node in tree.traverse(addnodes.toctree): for node in tree.findall(addnodes.toctree):
new_sect += node new_sect += node
tree = new_tree tree = new_tree
largetree = inline_all_toctrees(self, self.docnames, indexfile, tree, largetree = inline_all_toctrees(self, self.docnames, indexfile, tree,
@@ -353,7 +353,7 @@ class LaTeXBuilder(Builder):
self.env.resolve_references(largetree, indexfile, self) self.env.resolve_references(largetree, indexfile, self)
# resolve :ref:s to distant tex files -- we can't add a cross-reference, # resolve :ref:s to distant tex files -- we can't add a cross-reference,
# but append the document name # but append the document name
for pendingnode in largetree.traverse(addnodes.pending_xref): for pendingnode in largetree.findall(addnodes.pending_xref):
docname = pendingnode['refdocname'] docname = pendingnode['refdocname']
sectname = pendingnode['refsectname'] sectname = pendingnode['refsectname']
newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] newnodes: List[Node] = [nodes.emphasis(sectname, sectname)]

View File

@@ -4,7 +4,7 @@
consntants for LaTeX builder. consntants for LaTeX builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Additional nodes for LaTeX writer. Additional nodes for LaTeX writer.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Theming support for LaTeX builder. Theming support for LaTeX builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Transforms for LaTeX builder. Transforms for LaTeX builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -33,7 +33,7 @@ class FootnoteDocnameUpdater(SphinxTransform):
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
matcher = NodeMatcher(*self.TARGET_NODES) matcher = NodeMatcher(*self.TARGET_NODES)
for node in self.document.traverse(matcher): # type: Element for node in self.document.findall(matcher): # type: Element
node['docname'] = self.env.docname node['docname'] = self.env.docname
@@ -45,7 +45,7 @@ class SubstitutionDefinitionsRemover(SphinxPostTransform):
formats = ('latex',) formats = ('latex',)
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.substitution_definition): for node in list(self.document.findall(nodes.substitution_definition)):
node.parent.remove(node) node.parent.remove(node)
@@ -81,7 +81,7 @@ class ShowUrlsTransform(SphinxPostTransform):
if show_urls is False or show_urls == 'no': if show_urls is False or show_urls == 'no':
return return
for node in self.document.traverse(nodes.reference): for node in list(self.document.findall(nodes.reference)):
uri = node.get('refuri', '') uri = node.get('refuri', '')
if uri.startswith(URI_SCHEMES): if uri.startswith(URI_SCHEMES):
if uri.startswith('mailto:'): if uri.startswith('mailto:'):
@@ -348,7 +348,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
formats = ('latex',) formats = ('latex',)
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
footnotes = list(self.document.traverse(nodes.footnote)) footnotes = list(self.document.findall(nodes.footnote))
for node in footnotes: for node in footnotes:
node.parent.remove(node) node.parent.remove(node)
@@ -423,7 +423,7 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor):
self.unrestrict(node) self.unrestrict(node)
def depart_table(self, node: nodes.table) -> None: def depart_table(self, node: nodes.table) -> None:
tbody = list(node.traverse(nodes.tbody))[0] tbody = next(node.findall(nodes.tbody))
for footnote in reversed(self.table_footnotes): for footnote in reversed(self.table_footnotes):
fntext = footnotetext('', *footnote.children, ids=footnote['ids']) fntext = footnotetext('', *footnote.children, ids=footnote['ids'])
tbody.insert(0, fntext) tbody.insert(0, fntext)
@@ -501,7 +501,7 @@ class BibliographyTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
citations = thebibliography() citations = thebibliography()
for node in self.document.traverse(nodes.citation): for node in list(self.document.findall(nodes.citation)):
node.parent.remove(node) node.parent.remove(node)
citations += node citations += node
@@ -521,7 +521,7 @@ class CitationReferenceTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
domain = cast(CitationDomain, self.env.get_domain('citation')) domain = cast(CitationDomain, self.env.get_domain('citation'))
matcher = NodeMatcher(addnodes.pending_xref, refdomain='citation', reftype='ref') matcher = NodeMatcher(addnodes.pending_xref, refdomain='citation', reftype='ref')
for node in self.document.traverse(matcher): # type: addnodes.pending_xref for node in self.document.findall(matcher): # type: addnodes.pending_xref
docname, labelid, _ = domain.citations.get(node['reftarget'], ('', '', 0)) docname, labelid, _ = domain.citations.get(node['reftarget'], ('', '', 0))
if docname: if docname:
citation_ref = nodes.citation_reference('', '', *node.children, citation_ref = nodes.citation_reference('', '', *node.children,
@@ -540,7 +540,7 @@ class MathReferenceTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
equations = self.env.get_domain('math').data['objects'] equations = self.env.get_domain('math').data['objects']
for node in self.document.traverse(addnodes.pending_xref): for node in self.document.findall(addnodes.pending_xref):
if node['refdomain'] == 'math' and node['reftype'] in ('eq', 'numref'): if node['refdomain'] == 'math' and node['reftype'] in ('eq', 'numref'):
docname, _ = equations.get(node['reftarget'], (None, None)) docname, _ = equations.get(node['reftarget'], (None, None))
if docname: if docname:
@@ -555,7 +555,7 @@ class LiteralBlockTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
matcher = NodeMatcher(nodes.container, literal_block=True) matcher = NodeMatcher(nodes.container, literal_block=True)
for node in self.document.traverse(matcher): # type: nodes.container for node in self.document.findall(matcher): # type: nodes.container
newnode = captioned_literal_block('', *node.children, **node.attributes) newnode = captioned_literal_block('', *node.children, **node.attributes)
node.replace_self(newnode) node.replace_self(newnode)
@@ -566,7 +566,7 @@ class DocumentTargetTransform(SphinxPostTransform):
formats = ('latex',) formats = ('latex',)
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(addnodes.start_of_file): for node in self.document.findall(addnodes.start_of_file):
section = node.next_node(nodes.section) section = node.next_node(nodes.section)
if section: if section:
section['ids'].append(':doc') # special label for :doc: section['ids'].append(':doc') # special label for :doc:
@@ -602,9 +602,9 @@ class IndexInSectionTitleTransform(SphinxPostTransform):
formats = ('latex',) formats = ('latex',)
def run(self, **kwargs: Any) -> None: def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.title): for node in list(self.document.findall(nodes.title)):
if isinstance(node.parent, nodes.section): if isinstance(node.parent, nodes.section):
for i, index in enumerate(node.traverse(addnodes.index)): for i, index in enumerate(node.findall(addnodes.index)):
# move the index node next to the section title # move the index node next to the section title
node.remove(index) node.remove(index)
node.parent.insert(i + 1, index) node.parent.insert(i + 1, index)

View File

@@ -4,7 +4,7 @@
Utilities for LaTeX builder. Utilities for LaTeX builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The CheckExternalLinksBuilder class. The CheckExternalLinksBuilder class.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -43,18 +43,31 @@ logger = logging.getLogger(__name__)
uri_re = re.compile('([a-z]+:)?//') # matches to foo:// and // (a protocol relative URL) uri_re = re.compile('([a-z]+:)?//') # matches to foo:// and // (a protocol relative URL)
Hyperlink = NamedTuple('Hyperlink', (('uri', str),
('docname', str), class Hyperlink(NamedTuple):
('lineno', Optional[int]))) uri: str
CheckRequest = NamedTuple('CheckRequest', (('next_check', float), docname: str
('hyperlink', Optional[Hyperlink]))) lineno: Optional[int]
CheckResult = NamedTuple('CheckResult', (('uri', str),
('docname', str),
('lineno', int), class CheckRequest(NamedTuple):
('status', str), next_check: float
('message', str), hyperlink: Optional[Hyperlink]
('code', int)))
RateLimit = NamedTuple('RateLimit', (('delay', float), ('next_check', float)))
class CheckResult(NamedTuple):
uri: str
docname: str
lineno: int
status: str
message: str
code: int
class RateLimit(NamedTuple):
delay: float
next_check: float
# Tuple is old styled CheckRequest # Tuple is old styled CheckRequest
CheckRequestType = Union[CheckRequest, Tuple[float, str, str, int]] CheckRequestType = Union[CheckRequest, Tuple[float, str, str, int]]
@@ -255,7 +268,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
elif result.status == 'broken': elif result.status == 'broken':
if self.app.quiet or self.app.warningiserror: if self.app.quiet or self.app.warningiserror:
logger.warning(__('broken link: %s (%s)'), result.uri, result.message, logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
location=(filename, result.lineno)) location=(result.docname, result.lineno))
else: else:
logger.info(red('broken ') + result.uri + red(' - ' + result.message)) logger.info(red('broken ') + result.uri + red(' - ' + result.message))
self.write_entry('broken', result.docname, filename, result.lineno, self.write_entry('broken', result.docname, filename, result.lineno,
@@ -274,7 +287,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
linkstat['text'] = text linkstat['text'] = text
if self.config.linkcheck_allowed_redirects: if self.config.linkcheck_allowed_redirects:
logger.warning('redirect ' + result.uri + ' - ' + text + ' to ' + logger.warning('redirect ' + result.uri + ' - ' + text + ' to ' +
result.message, location=(filename, result.lineno)) result.message, location=(result.docname, result.lineno))
else: else:
logger.info(color('redirect ') + result.uri + logger.info(color('redirect ') + result.uri +
color(' - ' + text + ' to ' + result.message)) color(' - ' + text + ' to ' + result.message))
@@ -378,6 +391,8 @@ class HyperlinkAvailabilityCheckWorker(Thread):
self.anchors_ignore = [re.compile(x) self.anchors_ignore = [re.compile(x)
for x in self.config.linkcheck_anchors_ignore] for x in self.config.linkcheck_anchors_ignore]
self.documents_exclude = [re.compile(doc)
for doc in self.config.linkcheck_exclude_documents]
self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info
in self.config.linkcheck_auth] in self.config.linkcheck_auth]
@@ -519,6 +534,15 @@ class HyperlinkAvailabilityCheckWorker(Thread):
def check(docname: str) -> Tuple[str, str, int]: def check(docname: str) -> Tuple[str, str, int]:
# check for various conditions without bothering the network # check for various conditions without bothering the network
for doc_matcher in self.documents_exclude:
if doc_matcher.match(docname):
info = (
f'{docname} matched {doc_matcher.pattern} from '
'linkcheck_exclude_documents'
)
return 'ignored', info, 0
if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'tel:')): if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'tel:')):
return 'unchecked', '', 0 return 'unchecked', '', 0
elif not uri.startswith(('http:', 'https:')): elif not uri.startswith(('http:', 'https:')):
@@ -639,7 +663,7 @@ class HyperlinkCollector(SphinxPostTransform):
hyperlinks = builder.hyperlinks hyperlinks = builder.hyperlinks
# reference nodes # reference nodes
for refnode in self.document.traverse(nodes.reference): for refnode in self.document.findall(nodes.reference):
if 'refuri' not in refnode: if 'refuri' not in refnode:
continue continue
uri = refnode['refuri'] uri = refnode['refuri']
@@ -653,7 +677,7 @@ class HyperlinkCollector(SphinxPostTransform):
hyperlinks[uri] = uri_info hyperlinks[uri] = uri_info
# image nodes # image nodes
for imgnode in self.document.traverse(nodes.image): for imgnode in self.document.findall(nodes.image):
uri = imgnode['candidates'].get('?') uri = imgnode['candidates'].get('?')
if uri and '://' in uri: if uri and '://' in uri:
newuri = self.app.emit_firstresult('linkcheck-process-uri', uri) newuri = self.app.emit_firstresult('linkcheck-process-uri', uri)
@@ -699,6 +723,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_post_transform(HyperlinkCollector) app.add_post_transform(HyperlinkCollector)
app.add_config_value('linkcheck_ignore', [], None) app.add_config_value('linkcheck_ignore', [], None)
app.add_config_value('linkcheck_exclude_documents', [], None)
app.add_config_value('linkcheck_allowed_redirects', {}, None) app.add_config_value('linkcheck_allowed_redirects', {}, None)
app.add_config_value('linkcheck_auth', [], None) app.add_config_value('linkcheck_auth', [], None)
app.add_config_value('linkcheck_request_headers', {}, None) app.add_config_value('linkcheck_request_headers', {}, None)

View File

@@ -4,7 +4,7 @@
Manual pages builder. Manual pages builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -98,7 +98,7 @@ class ManualPageBuilder(Builder):
logger.info('} ', nonl=True) logger.info('} ', nonl=True)
self.env.resolve_references(largetree, docname, self) self.env.resolve_references(largetree, docname, self)
# remove pending_xref nodes # remove pending_xref nodes
for pendingnode in largetree.traverse(addnodes.pending_xref): for pendingnode in largetree.findall(addnodes.pending_xref):
pendingnode.replace_self(pendingnode.children) pendingnode.replace_self(pendingnode.children)
docwriter.write(largetree, destination) docwriter.write(largetree, destination)

View File

@@ -4,7 +4,7 @@
Single HTML builders. Single HTML builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -54,7 +54,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
def fix_refuris(self, tree: Node) -> None: def fix_refuris(self, tree: Node) -> None:
# fix refuris with double anchor # fix refuris with double anchor
fname = self.config.root_doc + self.out_suffix fname = self.config.root_doc + self.out_suffix
for refnode in tree.traverse(nodes.reference): for refnode in tree.findall(nodes.reference):
if 'refuri' not in refnode: if 'refuri' not in refnode:
continue continue
refuri = refnode['refuri'] refuri = refnode['refuri']

View File

@@ -4,7 +4,7 @@
Texinfo builder. Texinfo builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -138,7 +138,7 @@ class TexinfoBuilder(Builder):
new_sect += nodes.title('<Set title in conf.py>', new_sect += nodes.title('<Set title in conf.py>',
'<Set title in conf.py>') '<Set title in conf.py>')
new_tree += new_sect new_tree += new_sect
for node in tree.traverse(addnodes.toctree): for node in tree.findall(addnodes.toctree):
new_sect += node new_sect += node
tree = new_tree tree = new_tree
largetree = inline_all_toctrees(self, self.docnames, indexfile, tree, largetree = inline_all_toctrees(self, self.docnames, indexfile, tree,
@@ -152,7 +152,7 @@ class TexinfoBuilder(Builder):
logger.info(__("resolving references...")) logger.info(__("resolving references..."))
self.env.resolve_references(largetree, indexfile, self) self.env.resolve_references(largetree, indexfile, self)
# TODO: add support for external :ref:s # TODO: add support for external :ref:s
for pendingnode in largetree.traverse(addnodes.pending_xref): for pendingnode in largetree.findall(addnodes.pending_xref):
docname = pendingnode['refdocname'] docname = pendingnode['refdocname']
sectname = pendingnode['refsectname'] sectname = pendingnode['refsectname']
newnodes: List[Node] = [nodes.emphasis(sectname, sectname)] newnodes: List[Node] = [nodes.emphasis(sectname, sectname)]
@@ -211,6 +211,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('texinfo_domain_indices', True, None, [list]) app.add_config_value('texinfo_domain_indices', True, None, [list])
app.add_config_value('texinfo_show_urls', 'footnote', None) app.add_config_value('texinfo_show_urls', 'footnote', None)
app.add_config_value('texinfo_no_detailmenu', False, None) app.add_config_value('texinfo_no_detailmenu', False, None)
app.add_config_value('texinfo_cross_references', True, None)
return { return {
'version': 'builtin', 'version': 'builtin',

View File

@@ -4,7 +4,7 @@
Plain-text Sphinx builder. Plain-text Sphinx builder.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Docutils-native XML and pseudo-XML builders. Docutils-native XML and pseudo-XML builders.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -71,7 +71,7 @@ class XMLBuilder(Builder):
# work around multiple string % tuple issues in docutils; # work around multiple string % tuple issues in docutils;
# replace tuples in attribute values with lists # replace tuples in attribute values with lists
doctree = doctree.deepcopy() doctree = doctree.deepcopy()
for node in doctree.traverse(nodes.Element): for node in doctree.findall(nodes.Element):
for att, value in node.attributes.items(): for att, value in node.attributes.items():
if isinstance(value, tuple): if isinstance(value, tuple):
node.attributes[att] = list(value) node.attributes[att] = list(value)

View File

@@ -4,6 +4,6 @@
Modules for command line executables. Modules for command line executables.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Build documentation from a provided source. Build documentation from a provided source.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -16,6 +16,7 @@ import os
import pdb import pdb
import sys import sys
import traceback import traceback
from os import path
from typing import IO, Any, List from typing import IO, Any, List
from docutils.utils import SystemMessage from docutils.utils import SystemMessage
@@ -28,6 +29,7 @@ from sphinx.locale import __
from sphinx.util import Tee, format_exception_cut_frames, save_traceback from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import color_terminal, nocolor, red, terminal_safe # type: ignore from sphinx.util.console import color_terminal, nocolor, red, terminal_safe # type: ignore
from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.docutils import docutils_namespace, patch_docutils
from sphinx.util.osutil import abspath, ensuredir
def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA
@@ -240,6 +242,8 @@ def build_main(argv: List[str] = sys.argv[1:]) -> int:
if warning and args.warnfile: if warning and args.warnfile:
try: try:
warnfile = abspath(args.warnfile)
ensuredir(path.dirname(warnfile))
warnfp = open(args.warnfile, 'w') warnfp = open(args.warnfile, 'w')
except Exception as exc: except Exception as exc:
parser.error(__('cannot open warning file %r: %s') % ( parser.error(__('cannot open warning file %r: %s') % (

View File

@@ -10,7 +10,7 @@
This is in its own module so that importing it is fast. It should not This is in its own module so that importing it is fast. It should not
import the main Sphinx modules (like sphinx.applications, sphinx.builders). import the main Sphinx modules (like sphinx.applications, sphinx.builders).
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Quickly setup documentation source to work with Sphinx. Quickly setup documentation source to work with Sphinx.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Build configuration file handling. Build configuration file handling.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -103,6 +103,7 @@ class Config:
'language': (None, 'env', [str]), 'language': (None, 'env', [str]),
'locale_dirs': (['locales'], 'env', []), 'locale_dirs': (['locales'], 'env', []),
'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]),
'gettext_allow_fuzzy_translations': (False, 'gettext', []),
'master_doc': ('index', 'env', []), 'master_doc': ('index', 'env', []),
'root_doc': (lambda config: config.master_doc, 'env', []), 'root_doc': (lambda config: config.master_doc, 'env', []),
@@ -205,7 +206,7 @@ class Config:
except ValueError as exc: except ValueError as exc:
raise ValueError(__('invalid number %r for config value %r, ignoring') % raise ValueError(__('invalid number %r for config value %r, ignoring') %
(value, name)) from exc (value, name)) from exc
elif hasattr(defvalue, '__call__'): elif callable(defvalue):
return value return value
elif defvalue is not None and not isinstance(defvalue, str): elif defvalue is not None and not isinstance(defvalue, str):
raise ValueError(__('cannot override config setting %r with unsupported ' raise ValueError(__('cannot override config setting %r with unsupported '
@@ -250,13 +251,24 @@ class Config:
if name in self.values: if name in self.values:
self.__dict__[name] = config[name] self.__dict__[name] = config[name]
def post_init_values(self) -> None:
"""
Initialize additional config variables that are added after init_values() called.
"""
config = self._raw_config
for name in config:
if name not in self.__dict__ and name in self.values:
self.__dict__[name] = config[name]
check_confval_types(None, self)
def __getattr__(self, name: str) -> Any: def __getattr__(self, name: str) -> Any:
if name.startswith('_'): if name.startswith('_'):
raise AttributeError(name) raise AttributeError(name)
if name not in self.values: if name not in self.values:
raise AttributeError(__('No such config value: %s') % name) raise AttributeError(__('No such config value: %s') % name)
default = self.values[name][0] default = self.values[name][0]
if hasattr(default, '__call__'): if callable(default):
return default(self) return default(self)
return default return default
@@ -412,7 +424,7 @@ def check_confval_types(app: "Sphinx", config: Config) -> None:
for confval in config: for confval in config:
default, rebuild, annotations = config.values[confval.name] default, rebuild, annotations = config.values[confval.name]
if hasattr(default, '__call__'): if callable(default):
default = default(config) # evaluate default value default = default(config) # evaluate default value
if default is None and not annotations: if default is None and not annotations:
continue # neither inferable nor expliclitly annotated types continue # neither inferable nor expliclitly annotated types
@@ -426,7 +438,7 @@ def check_confval_types(app: "Sphinx", config: Config) -> None:
"but `{current}` is given.") "but `{current}` is given.")
logger.warning(msg.format(name=confval.name, logger.warning(msg.format(name=confval.name,
current=confval.value, current=confval.value,
candidates=annotations.candidates)) candidates=annotations.candidates), once=True)
else: else:
if type(confval.value) is type(default): if type(confval.value) is type(default):
continue continue
@@ -451,13 +463,13 @@ def check_confval_types(app: "Sphinx", config: Config) -> None:
permitted = " or ".join(wrapped_annotations) permitted = " or ".join(wrapped_annotations)
logger.warning(msg.format(name=confval.name, logger.warning(msg.format(name=confval.name,
current=type(confval.value), current=type(confval.value),
permitted=permitted)) permitted=permitted), once=True)
else: else:
msg = __("The config value `{name}' has type `{current.__name__}', " msg = __("The config value `{name}' has type `{current.__name__}', "
"defaults to `{default.__name__}'.") "defaults to `{default.__name__}'.")
logger.warning(msg.format(name=confval.name, logger.warning(msg.format(name=confval.name,
current=type(confval.value), current=type(confval.value),
default=type(default))) default=type(default)), once=True)
def check_primary_domain(app: "Sphinx", config: Config) -> None: def check_primary_domain(app: "Sphinx", config: Config) -> None:

View File

@@ -4,7 +4,7 @@
Sphinx deprecation classes and utilities. Sphinx deprecation classes and utilities.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Handlers for additional ReST directives. Handlers for additional ReST directives.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -2,7 +2,7 @@
sphinx.directives.code sphinx.directives.code
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -2,7 +2,7 @@
sphinx.directives.other sphinx.directives.other
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -18,8 +18,8 @@ from docutils.parsers.rst.directives.misc import Include as BaseInclude
from sphinx import addnodes from sphinx import addnodes
from sphinx.domains.changeset import VersionChange # NOQA # for compatibility from sphinx.domains.changeset import VersionChange # NOQA # for compatibility
from sphinx.locale import _ from sphinx.locale import _, __
from sphinx.util import docname_join, url_re from sphinx.util import docname_join, logging, url_re
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.util.matching import Matcher, patfilter from sphinx.util.matching import Matcher, patfilter
from sphinx.util.nodes import explicit_title_re from sphinx.util.nodes import explicit_title_re
@@ -30,6 +30,7 @@ if TYPE_CHECKING:
glob_re = re.compile(r'.*[*?\[].*') glob_re = re.compile(r'.*[*?\[].*')
logger = logging.getLogger(__name__)
def int_or_nothing(argument: str) -> int: def int_or_nothing(argument: str) -> int:
@@ -106,9 +107,8 @@ class TocTree(SphinxDirective):
toctree['entries'].append((None, docname)) toctree['entries'].append((None, docname))
toctree['includefiles'].append(docname) toctree['includefiles'].append(docname)
if not docnames: if not docnames:
ret.append(self.state.document.reporter.warning( logger.warning(__('toctree glob pattern %r didn\'t match any documents'),
'toctree glob pattern %r didn\'t match any documents' entry, location=toctree)
% entry, line=self.lineno))
else: else:
if explicit: if explicit:
ref = explicit.group(2) ref = explicit.group(2)
@@ -128,20 +128,21 @@ class TocTree(SphinxDirective):
toctree['entries'].append((title, ref)) toctree['entries'].append((title, ref))
elif docname not in self.env.found_docs: elif docname not in self.env.found_docs:
if excluded(self.env.doc2path(docname, None)): if excluded(self.env.doc2path(docname, None)):
message = 'toctree contains reference to excluded document %r' message = __('toctree contains reference to excluded document %r')
subtype = 'excluded'
else: else:
message = 'toctree contains reference to nonexisting document %r' message = __('toctree contains reference to nonexisting document %r')
subtype = 'not_readable'
ret.append(self.state.document.reporter.warning(message % docname, logger.warning(message, docname, type='toc', subtype=subtype,
line=self.lineno)) location=toctree)
self.env.note_reread() self.env.note_reread()
else: else:
if docname in all_docnames: if docname in all_docnames:
all_docnames.remove(docname) all_docnames.remove(docname)
else: else:
message = 'duplicated entry found in toctree: %s' logger.warning(__('duplicated entry found in toctree: %s'), docname,
ret.append(self.state.document.reporter.warning(message % docname, location=toctree)
line=self.lineno))
toctree['entries'].append((title, docname)) toctree['entries'].append((title, docname))
toctree['includefiles'].append(docname) toctree['includefiles'].append(docname)
@@ -250,8 +251,9 @@ class Acks(SphinxDirective):
self.state.nested_parse(self.content, self.content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
reporter = self.state.document.reporter logger.warning(__('.. acks content is not a list'),
return [reporter.warning('.. acks content is not a list', line=self.lineno)] location=(self.env.docname, self.lineno))
return []
return [node] return [node]
@@ -274,8 +276,9 @@ class HList(SphinxDirective):
self.state.nested_parse(self.content, self.content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
reporter = self.state.document.reporter logger.warning(__('.. hlist content is not a list'),
return [reporter.warning('.. hlist content is not a list', line=self.lineno)] location=(self.env.docname, self.lineno))
return []
fulllist = node.children[0] fulllist = node.children[0]
# create a hlist node where the items are distributed # create a hlist node where the items are distributed
npercol, nmore = divmod(len(fulllist), ncolumns) npercol, nmore = divmod(len(fulllist), ncolumns)

View File

@@ -2,19 +2,20 @@
sphinx.directives.patches sphinx.directives.patches
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import os import os
import warnings import warnings
from os import path from os import path
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast from typing import TYPE_CHECKING, Any, Dict, List, Sequence, Tuple, cast
from docutils import nodes from docutils import nodes
from docutils.nodes import Node, make_id, system_message from docutils.nodes import Node, make_id, system_message
from docutils.parsers.rst import directives from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import html, images, tables from docutils.parsers.rst.directives import images, tables
from docutils.parsers.rst.roles import set_classes
from sphinx import addnodes from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.deprecation import RemovedInSphinx60Warning
@@ -27,6 +28,12 @@ from sphinx.util.nodes import set_source_info
from sphinx.util.osutil import SEP, os_path, relpath from sphinx.util.osutil import SEP, os_path, relpath
from sphinx.util.typing import OptionSpec from sphinx.util.typing import OptionSpec
try:
from docutils.parsers.rst.directives.misc import Meta as MetaBase # type: ignore
except ImportError:
# docutils-0.17 or older
from docutils.parsers.rst.directives.html import Meta as MetaBase
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx
@@ -60,19 +67,21 @@ class Figure(images.Figure):
return [figure_node] return [figure_node]
class Meta(html.Meta, SphinxDirective): class Meta(MetaBase, SphinxDirective):
def run(self) -> List[Node]: def run(self) -> Sequence[Node]:
result = super().run() result = super().run()
for node in result: for node in result:
# for docutils-0.17 or older. Since docutils-0.18, patching is no longer needed
# because it uses picklable node; ``docutils.nodes.meta``.
if (isinstance(node, nodes.pending) and if (isinstance(node, nodes.pending) and
isinstance(node.details['nodes'][0], html.MetaBody.meta)): isinstance(node.details['nodes'][0], addnodes.docutils_meta)):
meta = node.details['nodes'][0] meta = node.details['nodes'][0]
meta.source = self.env.doc2path(self.env.docname) meta.source = self.env.doc2path(self.env.docname)
meta.line = self.lineno meta.line = self.lineno
meta.rawcontent = meta['content'] # type: ignore meta.rawcontent = meta['content']
# docutils' meta nodes aren't picklable because the class is nested # docutils' meta nodes aren't picklable because the class is nested
meta.__class__ = addnodes.meta # type: ignore meta.__class__ = addnodes.meta
return result return result
@@ -152,6 +161,7 @@ class Code(SphinxDirective):
def run(self) -> List[Node]: def run(self) -> List[Node]:
self.assert_has_content() self.assert_has_content()
set_classes(self.options)
code = '\n'.join(self.content) code = '\n'.join(self.content)
node = nodes.literal_block(code, code, node = nodes.literal_block(code, code,
classes=self.options.get('classes', []), classes=self.options.get('classes', []),

View File

@@ -5,7 +5,7 @@
Support for domains, which are groupings of description directives Support for domains, which are groupings of description directives
and roles describing e.g. constructs of one programming language. and roles describing e.g. constructs of one programming language.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The C language domain. The C language domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -36,7 +36,7 @@ from sphinx.util.cfamily import (ASTAttribute, ASTBaseBase, ASTBaseParenExprList
float_literal_suffix_re, hex_literal_re, identifier_re, float_literal_suffix_re, hex_literal_re, identifier_re,
integer_literal_re, integers_literal_suffix_re, integer_literal_re, integers_literal_suffix_re,
octal_literal_re, verify_description_mode) octal_literal_re, verify_description_mode)
from sphinx.util.docfields import Field, TypedField from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode from sphinx.util.nodes import make_refnode
from sphinx.util.typing import OptionSpec from sphinx.util.typing import OptionSpec
@@ -92,31 +92,22 @@ _id_prefix = [None, 'c.', 'Cv2.']
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_simple_type_sepcifiers_re = re.compile(r"""(?x) # bool, complex, and imaginary are macro "keywords", so they are handled seperately
_simple_type_specifiers_re = re.compile(r"""(?x)
\b( \b(
void|_Bool|bool void|_Bool
# Integer
# -------
|((signed|unsigned)\s+)?(char|(
((long\s+long|long|short)\s+)?int
))
|__uint128|__int128
# extensions
|((signed|unsigned)\s+)?__int(8|16|32|64|128)
# Floating-point
# --------------
|(float|double|long\s+double)(\s+(_Complex|complex|_Imaginary|imaginary))?
|(_Complex|complex|_Imaginary|imaginary)\s+(float|double|long\s+double)
|_Decimal(32|64|128)
# extensions
|__float80|_Float64x|__float128|_Float128|__ibm128
|__fp16
# Fixed-point, extension
|(_Sat\s+)?((signed|unsigned)\s+)?((short|long|long\s+long)\s+)?(_Fract|fract|_Accum|accum)
# Integer types that could be prefixes of the previous ones
# ---------------------------------------------------------
|((signed|unsigned)\s+)?(long\s+long|long|short)
|signed|unsigned |signed|unsigned
|short|long
|char
|int
|__uint128|__int128
|__int(8|16|32|64|128) # extension
|float|double
|_Decimal(32|64|128)
|_Complex|_Imaginary
|__float80|_Float64x|__float128|_Float128|__ibm128 # extension
|__fp16 # extension
|_Sat|_Fract|fract|_Accum|accum # extension
)\b )\b
""") """)
@@ -226,7 +217,7 @@ class ASTNestedName(ASTBase):
assert not self.rooted, str(self) assert not self.rooted, str(self)
assert len(self.names) == 1 assert len(self.names) == 1
self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol) self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol)
elif mode == 'markType' or mode == 'lastIsName' or mode == 'markName': elif mode in ('markType', 'lastIsName', 'markName'):
# Each element should be a pending xref targeting the complete # Each element should be a pending xref targeting the complete
# prefix. # prefix.
prefix = '' prefix = ''
@@ -636,8 +627,9 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec): class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def __init__(self, name: str) -> None: def __init__(self, names: List[str]) -> None:
self.names = name.split() assert len(names) != 0
self.names = names
def _stringify(self, transform: StringifyTransform) -> str: def _stringify(self, transform: StringifyTransform) -> str:
return ' '.join(self.names) return ' '.join(self.names)
@@ -2580,12 +2572,36 @@ class DefinitionParser(BaseParser):
break break
return ASTNestedName(names, rooted) return ASTNestedName(names, rooted)
def _parse_simple_type_specifier(self) -> Optional[str]:
if self.match(_simple_type_specifiers_re):
return self.matched_text
for t in ('bool', 'complex', 'imaginary'):
if t in self.config.c_extra_keywords:
if self.skip_word(t):
return t
return None
def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
names: List[str] = []
self.skip_ws()
while True:
t = self._parse_simple_type_specifier()
if t is None:
break
names.append(t)
self.skip_ws()
if len(names) == 0:
return None
return ASTTrailingTypeSpecFundamental(names)
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec: def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
# fundamental types, https://en.cppreference.com/w/c/language/type # fundamental types, https://en.cppreference.com/w/c/language/type
# and extensions # and extensions
self.skip_ws() self.skip_ws()
if self.match(_simple_type_sepcifiers_re): res = self._parse_simple_type_specifiers()
return ASTTrailingTypeSpecFundamental(self.matched_text) if res is not None:
return res
# prefixed # prefixed
prefix = None prefix = None
@@ -3130,16 +3146,6 @@ class CObject(ObjectDescription[ASTDeclaration]):
Description of a C language object. Description of a C language object.
""" """
doc_field_types = [
TypedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
typerolename='expr', typenames=('type',)),
Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
Field('returntype', label=_('Return type'), has_arg=False,
names=('rtype',)),
]
option_spec: OptionSpec = { option_spec: OptionSpec = {
'noindexentry': directives.flag, 'noindexentry': directives.flag,
} }
@@ -3342,13 +3348,31 @@ class CMemberObject(CObject):
return self.objtype return self.objtype
_function_doc_field_types = [
TypedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
typerolename='expr', typenames=('type',)),
GroupedField('retval', label=_('Return values'),
names=('retvals', 'retval'),
can_collapse=True),
Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
Field('returntype', label=_('Return type'), has_arg=False,
names=('rtype',)),
]
class CFunctionObject(CObject): class CFunctionObject(CObject):
object_type = 'function' object_type = 'function'
doc_field_types = _function_doc_field_types.copy()
class CMacroObject(CObject): class CMacroObject(CObject):
object_type = 'macro' object_type = 'macro'
doc_field_types = _function_doc_field_types.copy()
class CStructObject(CObject): class CStructObject(CObject):
object_type = 'struct' object_type = 'struct'
@@ -3532,7 +3556,7 @@ class AliasTransform(SphinxTransform):
return nodes return nodes
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode): for node in self.document.findall(AliasNode):
node = cast(AliasNode, node) node = cast(AliasNode, node)
sig = node.sig sig = node.sig
parentKey = node.parentKey parentKey = node.parentKey

View File

@@ -4,7 +4,7 @@
The changeset domain. The changeset domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The citation domain. The citation domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -112,7 +112,7 @@ class CitationDefinitionTransform(SphinxTransform):
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
domain = cast(CitationDomain, self.env.get_domain('citation')) domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation): for node in self.document.findall(nodes.citation):
# register citation node to domain # register citation node to domain
node['docname'] = self.env.docname node['docname'] = self.env.docname
domain.note_citation(node) domain.note_citation(node)
@@ -131,7 +131,7 @@ class CitationReferenceTransform(SphinxTransform):
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
domain = cast(CitationDomain, self.env.get_domain('citation')) domain = cast(CitationDomain, self.env.get_domain('citation'))
for node in self.document.traverse(nodes.citation_reference): for node in self.document.findall(nodes.citation_reference):
target = node.astext() target = node.astext()
ref = pending_xref(target, refdomain='citation', reftype='ref', ref = pending_xref(target, refdomain='citation', reftype='ref',
reftarget=target, refwarn=True, reftarget=target, refwarn=True,

View File

@@ -4,7 +4,7 @@
The C++ language domain. The C++ language domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -335,27 +335,17 @@ _keywords = [
] ]
_simple_type_sepcifiers_re = re.compile(r"""(?x) _simple_type_specifiers_re = re.compile(r"""(?x)
\b( \b(
auto|void|bool auto|void|bool
# Integer
# -------
|((signed|unsigned)\s+)?(char|__int128|(
((long\s+long|long|short)\s+)?int
))
|wchar_t|char(8|16|32)_t
# extensions
|((signed|unsigned)\s+)?__int(64|128)
# Floating-point
# --------------
|(float|double|long\s+double)(\s+(_Complex|_Imaginary))?
|(_Complex|_Imaginary)\s+(float|double|long\s+double)
# extensions
|__float80|_Float64x|__float128|_Float128
# Integer types that could be prefixes of the previous ones
# ---------------------------------------------------------
|((signed|unsigned)\s+)?(long\s+long|long|short)
|signed|unsigned |signed|unsigned
|short|long
|char|wchar_t|char(8|16|32)_t
|int
|__int(64|128) # extension
|float|double
|__float80|_Float64x|__float128|_Float128 # extension
|_Complex|_Imaginary # extension
)\b )\b
""") """)
@@ -485,12 +475,12 @@ _id_fundamental_v2 = {
'long double': 'e', 'long double': 'e',
'__float80': 'e', '_Float64x': 'e', '__float80': 'e', '_Float64x': 'e',
'__float128': 'g', '_Float128': 'g', '__float128': 'g', '_Float128': 'g',
'float _Complex': 'Cf', '_Complex float': 'Cf', '_Complex float': 'Cf',
'double _Complex': 'Cd', '_Complex double': 'Cd', '_Complex double': 'Cd',
'long double _Complex': 'Ce', '_Complex long double': 'Ce', '_Complex long double': 'Ce',
'float _Imaginary': 'f', '_Imaginary float': 'f', '_Imaginary float': 'f',
'double _Imaginary': 'd', '_Imaginary double': 'd', '_Imaginary double': 'd',
'long double _Imaginary': 'e', '_Imaginary long double': 'e', '_Imaginary long double': 'e',
'auto': 'Da', 'auto': 'Da',
'decltype(auto)': 'Dc', 'decltype(auto)': 'Dc',
'std::nullptr_t': 'Dn' 'std::nullptr_t': 'Dn'
@@ -786,7 +776,7 @@ class ASTNestedName(ASTBase):
assert len(self.names) == 1 assert len(self.names) == 1
assert not self.templates[0] assert not self.templates[0]
self.names[0].describe_signature(signode, 'param', env, '', symbol) self.names[0].describe_signature(signode, 'param', env, '', symbol)
elif mode == 'markType' or mode == 'lastIsName' or mode == 'markName': elif mode in ('markType', 'lastIsName', 'markName'):
# Each element should be a pending xref targeting the complete # Each element should be a pending xref targeting the complete
# prefix. however, only the identifier part should be a link, such # prefix. however, only the identifier part should be a link, such
# that template args can be a link as well. # that template args can be a link as well.
@@ -1853,8 +1843,12 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec): class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def __init__(self, name: str) -> None: def __init__(self, names: List[str], canonNames: List[str]) -> None:
self.names = name.split() assert len(names) != 0
assert len(names) == len(canonNames), (names, canonNames)
self.names = names
# the canonical name list is for ID lookup
self.canonNames = canonNames
def _stringify(self, transform: StringifyTransform) -> str: def _stringify(self, transform: StringifyTransform) -> str:
return ' '.join(self.names) return ' '.join(self.names)
@@ -1862,14 +1856,14 @@ class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def get_id(self, version: int) -> str: def get_id(self, version: int) -> str:
if version == 1: if version == 1:
res = [] res = []
for a in self.names: for a in self.canonNames:
if a in _id_fundamental_v1: if a in _id_fundamental_v1:
res.append(_id_fundamental_v1[a]) res.append(_id_fundamental_v1[a])
else: else:
res.append(a) res.append(a)
return '-'.join(res) return '-'.join(res)
txt = str(self) txt = ' '.join(self.canonNames)
if txt not in _id_fundamental_v2: if txt not in _id_fundamental_v2:
raise Exception( raise Exception(
'Semi-internal error: Fundamental type "%s" can not be mapped ' 'Semi-internal error: Fundamental type "%s" can not be mapped '
@@ -5391,7 +5385,7 @@ class DefinitionParser(BaseParser):
postFixes: List[ASTPostfixOp] = [] postFixes: List[ASTPostfixOp] = []
while True: while True:
self.skip_ws() self.skip_ws()
if prefixType in ['expr', 'cast', 'typeid']: if prefixType in ('expr', 'cast', 'typeid'):
if self.skip_string_and_ws('['): if self.skip_string_and_ws('['):
expr = self._parse_expression() expr = self._parse_expression()
self.skip_ws() self.skip_ws()
@@ -5855,12 +5849,102 @@ class DefinitionParser(BaseParser):
# ========================================================================== # ==========================================================================
def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
modifier: Optional[str] = None
signedness: Optional[str] = None
width: List[str] = []
typ: Optional[str] = None
names: List[str] = [] # the parsed sequence
self.skip_ws()
while self.match(_simple_type_specifiers_re):
t = self.matched_text
names.append(t)
if t in ('auto', 'void', 'bool',
'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
'int', '__int64', '__int128',
'float', 'double',
'__float80', '_Float64x', '__float128', '_Float128'):
if typ is not None:
self.fail("Can not have both {} and {}.".format(t, typ))
typ = t
elif t in ('signed', 'unsigned'):
if signedness is not None:
self.fail("Can not have both {} and {}.".format(t, signedness))
signedness = t
elif t == 'short':
if len(width) != 0:
self.fail("Can not have both {} and {}.".format(t, width[0]))
width.append(t)
elif t == 'long':
if len(width) != 0 and width[0] != 'long':
self.fail("Can not have both {} and {}.".format(t, width[0]))
width.append(t)
elif t in ('_Imaginary', '_Complex'):
if modifier is not None:
self.fail("Can not have both {} and {}.".format(t, modifier))
modifier = t
self.skip_ws()
if len(names) == 0:
return None
if typ in ('auto', 'void', 'bool',
'wchar_t', 'char8_t', 'char16_t', 'char32_t',
'__float80', '_Float64x', '__float128', '_Float128'):
if modifier is not None:
self.fail("Can not have both {} and {}.".format(typ, modifier))
if signedness is not None:
self.fail("Can not have both {} and {}.".format(typ, signedness))
if len(width) != 0:
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
elif typ == 'char':
if modifier is not None:
self.fail("Can not have both {} and {}.".format(typ, modifier))
if len(width) != 0:
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
elif typ == 'int':
if modifier is not None:
self.fail("Can not have both {} and {}.".format(typ, modifier))
elif typ in ('__int64', '__int128'):
if modifier is not None:
self.fail("Can not have both {} and {}.".format(typ, modifier))
if len(width) != 0:
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
elif typ == 'float':
if signedness is not None:
self.fail("Can not have both {} and {}.".format(typ, signedness))
if len(width) != 0:
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
elif typ == 'double':
if signedness is not None:
self.fail("Can not have both {} and {}.".format(typ, signedness))
if len(width) > 1:
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
if len(width) == 1 and width[0] != 'long':
self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
elif typ is None:
if modifier is not None:
self.fail("Can not have {} without a floating point type.".format(modifier))
else:
assert False, "Unhandled type {}".format(typ)
canonNames: List[str] = []
if modifier is not None:
canonNames.append(modifier)
if signedness is not None:
canonNames.append(signedness)
canonNames.extend(width)
if typ is not None:
canonNames.append(typ)
return ASTTrailingTypeSpecFundamental(names, canonNames)
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec: def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
# fundamental types, https://en.cppreference.com/w/cpp/language/type # fundamental types, https://en.cppreference.com/w/cpp/language/type
# and extensions # and extensions
self.skip_ws() self.skip_ws()
if self.match(_simple_type_sepcifiers_re): res = self._parse_simple_type_specifiers()
return ASTTrailingTypeSpecFundamental(self.matched_text) if res is not None:
return res
# decltype # decltype
self.skip_ws() self.skip_ws()
@@ -6541,7 +6625,7 @@ class DefinitionParser(BaseParser):
# ========================================================================== # ==========================================================================
def _parse_template_paramter(self) -> ASTTemplateParam: def _parse_template_parameter(self) -> ASTTemplateParam:
self.skip_ws() self.skip_ws()
if self.skip_word('template'): if self.skip_word('template'):
# declare a tenplate template parameter # declare a tenplate template parameter
@@ -6613,7 +6697,7 @@ class DefinitionParser(BaseParser):
pos = self.pos pos = self.pos
err = None err = None
try: try:
param = self._parse_template_paramter() param = self._parse_template_parameter()
templateParams.append(param) templateParams.append(param)
except DefinitionError as eParam: except DefinitionError as eParam:
self.pos = pos self.pos = pos
@@ -6934,18 +7018,10 @@ def _make_phony_error_name() -> ASTNestedName:
class CPPObject(ObjectDescription[ASTDeclaration]): class CPPObject(ObjectDescription[ASTDeclaration]):
"""Description of a C++ language object.""" """Description of a C++ language object."""
doc_field_types = [ doc_field_types: List[Field] = [
GroupedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
can_collapse=True),
GroupedField('template parameter', label=_('Template Parameters'), GroupedField('template parameter', label=_('Template Parameters'),
names=('tparam', 'template parameter'), names=('tparam', 'template parameter'),
can_collapse=True), can_collapse=True),
GroupedField('exceptions', label=_('Throws'), rolename='expr',
names=('throws', 'throw', 'exception'),
can_collapse=True),
Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
] ]
option_spec: OptionSpec = { option_spec: OptionSpec = {
@@ -7181,6 +7257,20 @@ class CPPMemberObject(CPPObject):
class CPPFunctionObject(CPPObject): class CPPFunctionObject(CPPObject):
object_type = 'function' object_type = 'function'
doc_field_types = CPPObject.doc_field_types + [
GroupedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
can_collapse=True),
GroupedField('exceptions', label=_('Throws'), rolename='expr',
names=('throws', 'throw', 'exception'),
can_collapse=True),
GroupedField('retval', label=_('Return values'),
names=('retvals', 'retval'),
can_collapse=True),
Field('returnvalue', label=_('Returns'), has_arg=False,
names=('returns', 'return')),
]
class CPPClassObject(CPPObject): class CPPClassObject(CPPObject):
object_type = 'class' object_type = 'class'
@@ -7369,7 +7459,7 @@ class AliasTransform(SphinxTransform):
return nodes return nodes
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode): for node in self.document.findall(AliasNode):
node = cast(AliasNode, node) node = cast(AliasNode, node)
sig = node.sig sig = node.sig
parentKey = node.parentKey parentKey = node.parentKey
@@ -7691,7 +7781,7 @@ class CPPDomain(Domain):
typ: str, target: str, node: pending_xref, typ: str, target: str, node: pending_xref,
contnode: Element) -> Tuple[Optional[Element], Optional[str]]: contnode: Element) -> Tuple[Optional[Element], Optional[str]]:
# add parens again for those that could be functions # add parens again for those that could be functions
if typ == 'any' or typ == 'func': if typ in ('any', 'func'):
target += '()' target += '()'
parser = DefinitionParser(target, location=node, config=env.config) parser = DefinitionParser(target, location=node, config=env.config)
try: try:
@@ -7812,7 +7902,7 @@ class CPPDomain(Domain):
if (env.config.add_function_parentheses and typ == 'func' and if (env.config.add_function_parentheses and typ == 'func' and
title.endswith('operator()')): title.endswith('operator()')):
addParen += 1 addParen += 1
if ((typ == 'any' or typ == 'func') and if (typ in ('any', 'func') and
title.endswith('operator') and title.endswith('operator') and
displayName.endswith('operator()')): displayName.endswith('operator()')):
addParen += 1 addParen += 1

View File

@@ -4,7 +4,7 @@
The index domain. The index domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -48,7 +48,7 @@ class IndexDomain(Domain):
def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None: def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None:
"""Process a document after it is read by the environment.""" """Process a document after it is read by the environment."""
entries = self.entries.setdefault(env.docname, []) entries = self.entries.setdefault(env.docname, [])
for node in document.traverse(addnodes.index): for node in list(document.findall(addnodes.index)):
try: try:
for entry in node['entries']: for entry in node['entries']:
split_index_msg(entry[0], entry[1]) split_index_msg(entry[0], entry[1])

View File

@@ -4,7 +4,7 @@
The JavaScript domain. The JavaScript domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -41,9 +41,6 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
#: added #: added
has_arguments = False has_arguments = False
#: what is displayed right before the documentation entry
display_prefix: str = None
#: If ``allow_nesting`` is ``True``, the object prefixes will be accumulated #: If ``allow_nesting`` is ``True``, the object prefixes will be accumulated
#: based on directive nesting #: based on directive nesting
allow_nesting = False allow_nesting = False
@@ -53,6 +50,10 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
'noindexentry': directives.flag, 'noindexentry': directives.flag,
} }
def get_display_prefix(self) -> List[Node]:
#: what is displayed right before the documentation entry
return []
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
"""Breaks down construct signatures """Breaks down construct signatures
@@ -71,6 +72,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
# If construct is nested, prefix the current prefix # If construct is nested, prefix the current prefix
prefix = self.env.ref_context.get('js:object', None) prefix = self.env.ref_context.get('js:object', None)
mod_name = self.env.ref_context.get('js:module') mod_name = self.env.ref_context.get('js:module')
name = member name = member
try: try:
member_prefix, member_name = member.rsplit('.', 1) member_prefix, member_name = member.rsplit('.', 1)
@@ -91,14 +93,22 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
signode['object'] = prefix signode['object'] = prefix
signode['fullname'] = fullname signode['fullname'] = fullname
if self.display_prefix: display_prefix = self.get_display_prefix()
signode += addnodes.desc_annotation(self.display_prefix, if display_prefix:
self.display_prefix) signode += addnodes.desc_annotation('', '', *display_prefix)
actual_prefix = None
if prefix: if prefix:
signode += addnodes.desc_addname(prefix + '.', prefix + '.') actual_prefix = prefix
elif mod_name: elif mod_name:
signode += addnodes.desc_addname(mod_name + '.', mod_name + '.') actual_prefix = mod_name
signode += addnodes.desc_name(name, name) if actual_prefix:
addName = addnodes.desc_addname('', '')
for p in actual_prefix.split('.'):
addName += addnodes.desc_sig_name(p, p)
addName += addnodes.desc_sig_punctuation('.', '.')
signode += addName
signode += addnodes.desc_name('', '', addnodes.desc_sig_name(name, name))
if self.has_arguments: if self.has_arguments:
if not arglist: if not arglist:
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
@@ -227,9 +237,13 @@ class JSCallable(JSObject):
class JSConstructor(JSCallable): class JSConstructor(JSCallable):
"""Like a callable but with a different prefix.""" """Like a callable but with a different prefix."""
display_prefix = 'class '
allow_nesting = True allow_nesting = True
def get_display_prefix(self) -> List[Node]:
return [addnodes.desc_sig_keyword('class', 'class'),
addnodes.desc_sig_space()]
class JSModule(SphinxDirective): class JSModule(SphinxDirective):
""" """

View File

@@ -4,7 +4,7 @@
The math domain. The math domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -78,7 +78,7 @@ class MathDomain(Domain):
def math_node(node: Node) -> bool: def math_node(node: Node) -> bool:
return isinstance(node, (nodes.math, nodes.math_block)) return isinstance(node, (nodes.math, nodes.math_block))
self.data['has_equations'][docname] = any(document.traverse(math_node)) self.data['has_equations'][docname] = any(document.findall(math_node))
def clear_doc(self, docname: str) -> None: def clear_doc(self, docname: str) -> None:
for equation_id, (doc, eqno) in list(self.equations.items()): for equation_id, (doc, eqno) in list(self.equations.items()):

View File

@@ -4,7 +4,7 @@
The Python domain. The Python domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -26,7 +26,7 @@ from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index, IndexEntry, ObjType from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
@@ -80,31 +80,57 @@ class ModuleEntry(NamedTuple):
deprecated: bool deprecated: bool
def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref: def parse_reftarget(reftarget: str, suppress_prefix: bool = False
"""Convert a type string to a cross reference node.""" ) -> Tuple[str, str, str, bool]:
if text == 'None': """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
refspecific = False
if reftarget.startswith('.'):
reftarget = reftarget[1:]
title = reftarget
refspecific = True
elif reftarget.startswith('~'):
reftarget = reftarget[1:]
title = reftarget.split('.')[-1]
elif suppress_prefix:
title = reftarget.split('.')[-1]
elif reftarget.startswith('typing.'):
title = reftarget[7:]
else:
title = reftarget
if reftarget == 'None' or reftarget.startswith('typing.'):
# typing module provides non-class types. Obj reference is good to refer them.
reftype = 'obj' reftype = 'obj'
else: else:
reftype = 'class' reftype = 'class'
return reftype, reftarget, title, refspecific
def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: bool = False
) -> addnodes.pending_xref:
"""Convert a type string to a cross reference node."""
if env: if env:
kwargs = {'py:module': env.ref_context.get('py:module'), kwargs = {'py:module': env.ref_context.get('py:module'),
'py:class': env.ref_context.get('py:class')} 'py:class': env.ref_context.get('py:class')}
else: else:
kwargs = {} kwargs = {}
reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
if env.config.python_use_unqualified_type_names: if env.config.python_use_unqualified_type_names:
# Note: It would be better to use qualname to describe the object to support support # Note: It would be better to use qualname to describe the object to support support
# nested classes. But python domain can't access the real python object because this # nested classes. But python domain can't access the real python object because this
# module should work not-dynamically. # module should work not-dynamically.
shortname = text.split('.')[-1] shortname = title.split('.')[-1]
contnodes: List[Node] = [pending_xref_condition('', shortname, condition='resolved'), contnodes: List[Node] = [pending_xref_condition('', shortname, condition='resolved'),
pending_xref_condition('', text, condition='*')] pending_xref_condition('', title, condition='*')]
else: else:
contnodes = [nodes.Text(text)] contnodes = [nodes.Text(title)]
return pending_xref('', *contnodes, return pending_xref('', *contnodes,
refdomain='py', reftype=reftype, reftarget=text, **kwargs) refdomain='py', reftype=reftype, reftarget=target,
refspecific=refspecific, **kwargs)
def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]: def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]:
@@ -118,16 +144,28 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
result.extend(unparse(node.right)) result.extend(unparse(node.right))
return result return result
elif isinstance(node, ast.BitOr): elif isinstance(node, ast.BitOr):
return [nodes.Text(' '), addnodes.desc_sig_punctuation('', '|'), nodes.Text(' ')] return [addnodes.desc_sig_space(),
addnodes.desc_sig_punctuation('', '|'),
addnodes.desc_sig_space()]
elif isinstance(node, ast.Constant): # type: ignore elif isinstance(node, ast.Constant): # type: ignore
if node.value is Ellipsis: if node.value is Ellipsis:
return [addnodes.desc_sig_punctuation('', "...")] return [addnodes.desc_sig_punctuation('', "...")]
elif isinstance(node.value, bool):
return [addnodes.desc_sig_keyword('', repr(node.value))]
elif isinstance(node.value, int):
return [addnodes.desc_sig_literal_number('', repr(node.value))]
elif isinstance(node.value, str):
return [addnodes.desc_sig_literal_string('', repr(node.value))]
else: else:
# handles None, which is further handled by type_to_xref later
# and fallback for other types that should be converted
return [nodes.Text(repr(node.value))] return [nodes.Text(repr(node.value))]
elif isinstance(node, ast.Expr): elif isinstance(node, ast.Expr):
return unparse(node.value) return unparse(node.value)
elif isinstance(node, ast.Index): elif isinstance(node, ast.Index):
return unparse(node.value) return unparse(node.value)
elif isinstance(node, ast.Invert):
return [addnodes.desc_sig_punctuation('', '~')]
elif isinstance(node, ast.List): elif isinstance(node, ast.List):
result = [addnodes.desc_sig_punctuation('', '[')] result = [addnodes.desc_sig_punctuation('', '[')]
if node.elts: if node.elts:
@@ -136,7 +174,9 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
# once # once
for elem in node.elts: for elem in node.elts:
result.extend(unparse(elem)) result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ', ')) result.append(addnodes.desc_sig_punctuation('', ','))
result.append(addnodes.desc_sig_space())
result.pop()
result.pop() result.pop()
result.append(addnodes.desc_sig_punctuation('', ']')) result.append(addnodes.desc_sig_punctuation('', ']'))
return result return result
@@ -156,12 +196,16 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
if isinstance(subnode, nodes.Text): if isinstance(subnode, nodes.Text):
result[i] = nodes.literal('', '', subnode) result[i] = nodes.literal('', '', subnode)
return result return result
elif isinstance(node, ast.UnaryOp):
return unparse(node.op) + unparse(node.operand)
elif isinstance(node, ast.Tuple): elif isinstance(node, ast.Tuple):
if node.elts: if node.elts:
result = [] result = []
for elem in node.elts: for elem in node.elts:
result.extend(unparse(elem)) result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ', ')) result.append(addnodes.desc_sig_punctuation('', ','))
result.append(addnodes.desc_sig_space())
result.pop()
result.pop() result.pop()
else: else:
result = [addnodes.desc_sig_punctuation('', '('), result = [addnodes.desc_sig_punctuation('', '('),
@@ -170,10 +214,16 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
return result return result
else: else:
if sys.version_info < (3, 8): if sys.version_info < (3, 8):
if isinstance(node, ast.Ellipsis): if isinstance(node, ast.Bytes):
return [addnodes.desc_sig_literal_string('', repr(node.s))]
elif isinstance(node, ast.Ellipsis):
return [addnodes.desc_sig_punctuation('', "...")] return [addnodes.desc_sig_punctuation('', "...")]
elif isinstance(node, ast.NameConstant): elif isinstance(node, ast.NameConstant):
return [nodes.Text(node.value)] return [nodes.Text(node.value)]
elif isinstance(node, ast.Num):
return [addnodes.desc_sig_literal_string('', repr(node.n))]
elif isinstance(node, ast.Str):
return [addnodes.desc_sig_literal_string('', repr(node.s))]
raise SyntaxError # unsupported syntax raise SyntaxError # unsupported syntax
@@ -183,12 +233,19 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
try: try:
tree = ast_parse(annotation) tree = ast_parse(annotation)
result = unparse(tree) result: List[Node] = []
for i, node in enumerate(result): for node in unparse(tree):
if isinstance(node, nodes.literal): if isinstance(node, nodes.literal):
result[i] = node[0] result.append(node[0])
elif isinstance(node, nodes.Text) and node.strip(): elif isinstance(node, nodes.Text) and node.strip():
result[i] = type_to_xref(str(node), env) if (result and isinstance(result[-1], addnodes.desc_sig_punctuation) and
result[-1].astext() == '~'):
result.pop()
result.append(type_to_xref(str(node), env, suppress_prefix=True))
else:
result.append(type_to_xref(str(node), env))
else:
result.append(node)
return result return result
except SyntaxError: except SyntaxError:
return [type_to_xref(annotation, env)] return [type_to_xref(annotation, env)]
@@ -222,13 +279,13 @@ def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_
if param.annotation is not param.empty: if param.annotation is not param.empty:
children = _parse_annotation(param.annotation, env) children = _parse_annotation(param.annotation, env)
node += addnodes.desc_sig_punctuation('', ':') node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ') node += addnodes.desc_sig_space()
node += addnodes.desc_sig_name('', '', *children) # type: ignore node += addnodes.desc_sig_name('', '', *children) # type: ignore
if param.default is not param.empty: if param.default is not param.empty:
if param.annotation is not param.empty: if param.annotation is not param.empty:
node += nodes.Text(' ') node += addnodes.desc_sig_space()
node += addnodes.desc_sig_operator('', '=') node += addnodes.desc_sig_operator('', '=')
node += nodes.Text(' ') node += addnodes.desc_sig_space()
else: else:
node += addnodes.desc_sig_operator('', '=') node += addnodes.desc_sig_operator('', '=')
node += nodes.inline('', param.default, classes=['default_value'], node += nodes.inline('', param.default, classes=['default_value'],
@@ -271,7 +328,8 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
ends_open += 1 ends_open += 1
argument = argument[:-1].strip() argument = argument[:-1].strip()
if argument: if argument:
stack[-1] += addnodes.desc_parameter(argument, argument) stack[-1] += addnodes.desc_parameter(
'', '', addnodes.desc_sig_name(argument, argument))
while ends_open: while ends_open:
stack.append(addnodes.desc_optional()) stack.append(addnodes.desc_optional())
stack[-2] += stack[-1] stack[-2] += stack[-1]
@@ -304,27 +362,27 @@ class PyXrefMixin:
result = super().make_xref(rolename, domain, target, # type: ignore result = super().make_xref(rolename, domain, target, # type: ignore
innernode, contnode, innernode, contnode,
env, inliner=None, location=None) env, inliner=None, location=None)
result['refspecific'] = True if isinstance(result, pending_xref):
result['py:module'] = env.ref_context.get('py:module') result['refspecific'] = True
result['py:class'] = env.ref_context.get('py:class') result['py:module'] = env.ref_context.get('py:module')
if target.startswith(('.', '~')): result['py:class'] = env.ref_context.get('py:class')
prefix, result['reftarget'] = target[0], target[1:]
if prefix == '.':
text = target[1:]
elif prefix == '~':
text = target.split('.')[-1]
for node in result.traverse(nodes.Text):
node.parent[node.parent.index(node)] = nodes.Text(text)
break
elif isinstance(result, pending_xref) and env.config.python_use_unqualified_type_names:
children = result.children
result.clear()
shortname = target.split('.')[-1] reftype, reftarget, reftitle, _ = parse_reftarget(target)
textnode = innernode('', shortname) if reftarget != reftitle:
contnodes = [pending_xref_condition('', '', textnode, condition='resolved'), result['reftype'] = reftype
pending_xref_condition('', '', *children, condition='*')] result['reftarget'] = reftarget
result.extend(contnodes)
result.clear()
result += innernode(reftitle, reftitle)
elif env.config.python_use_unqualified_type_names:
children = result.children
result.clear()
shortname = target.split('.')[-1]
textnode = innernode('', shortname)
contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
pending_xref_condition('', '', *children, condition='*')]
result.extend(contnodes)
return result return result
@@ -338,31 +396,26 @@ class PyXrefMixin:
split_contnode = bool(contnode and contnode.astext() == target) split_contnode = bool(contnode and contnode.astext() == target)
in_literal = False
results = [] results = []
for sub_target in filter(None, sub_targets): for sub_target in filter(None, sub_targets):
if split_contnode: if split_contnode:
contnode = nodes.Text(sub_target) contnode = nodes.Text(sub_target)
if delims_re.match(sub_target): if in_literal or delims_re.match(sub_target):
results.append(contnode or innernode(sub_target, sub_target)) results.append(contnode or innernode(sub_target, sub_target))
else: else:
results.append(self.make_xref(rolename, domain, sub_target, results.append(self.make_xref(rolename, domain, sub_target,
innernode, contnode, env, inliner, location)) innernode, contnode, env, inliner, location))
if sub_target in ('Literal', 'typing.Literal'):
in_literal = True
return results return results
class PyField(PyXrefMixin, Field): class PyField(PyXrefMixin, Field):
def make_xref(self, rolename: str, domain: str, target: str, pass
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
return super().make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)
class PyGroupedField(PyXrefMixin, GroupedField): class PyGroupedField(PyXrefMixin, GroupedField):
@@ -370,16 +423,7 @@ class PyGroupedField(PyXrefMixin, GroupedField):
class PyTypedField(PyXrefMixin, TypedField): class PyTypedField(PyXrefMixin, TypedField):
def make_xref(self, rolename: str, domain: str, target: str, pass
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
if rolename == 'class' and target == 'None':
# None is not a type, so use obj role instead.
rolename = 'obj'
return super().make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)
class PyObject(ObjectDescription[Tuple[str, str]]): class PyObject(ObjectDescription[Tuple[str, str]]):
@@ -418,11 +462,11 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
allow_nesting = False allow_nesting = False
def get_signature_prefix(self, sig: str) -> str: def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
"""May return a prefix to put before the object name in the """May return a prefix to put before the object name in the
signature. signature.
""" """
return '' return []
def needs_arglist(self) -> bool: def needs_arglist(self) -> bool:
"""May return true if an empty argument list is to be generated even if """May return true if an empty argument list is to be generated even if
@@ -476,7 +520,17 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
sig_prefix = self.get_signature_prefix(sig) sig_prefix = self.get_signature_prefix(sig)
if sig_prefix: if sig_prefix:
signode += addnodes.desc_annotation(sig_prefix, sig_prefix) if type(sig_prefix) is str:
warnings.warn(
"Python directive method get_signature_prefix()"
" returning a string is deprecated."
" It must now return a list of nodes."
" Return value was '{}'.".format(sig_prefix),
RemovedInSphinx60Warning)
signode += addnodes.desc_annotation(sig_prefix, '', # type: ignore
nodes.Text(sig_prefix)) # type: ignore
else:
signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix)
if prefix: if prefix:
signode += addnodes.desc_addname(prefix, prefix) signode += addnodes.desc_addname(prefix, prefix)
@@ -507,7 +561,9 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
anno = self.options.get('annotation') anno = self.options.get('annotation')
if anno: if anno:
signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) signode += addnodes.desc_annotation(' ' + anno, '',
addnodes.desc_sig_space(),
nodes.Text(anno))
return fullname, prefix return fullname, prefix
@@ -609,11 +665,12 @@ class PyFunction(PyObject):
'async': directives.flag, 'async': directives.flag,
}) })
def get_signature_prefix(self, sig: str) -> str: def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
if 'async' in self.options: if 'async' in self.options:
return 'async ' return [addnodes.desc_sig_keyword('', 'async'),
addnodes.desc_sig_space()]
else: else:
return '' return []
def needs_arglist(self) -> bool: def needs_arglist(self) -> bool:
return True return True
@@ -670,11 +727,17 @@ class PyVariable(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ, self.env) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '',
addnodes.desc_sig_punctuation('', ':'),
addnodes.desc_sig_space(), *annotations)
value = self.options.get('value') value = self.options.get('value')
if value: if value:
signode += addnodes.desc_annotation(value, ' = ' + value) signode += addnodes.desc_annotation(value, '',
addnodes.desc_sig_space(),
addnodes.desc_sig_punctuation('', '='),
addnodes.desc_sig_space(),
nodes.Text(value))
return fullname, prefix return fullname, prefix
@@ -698,11 +761,12 @@ class PyClasslike(PyObject):
allow_nesting = True allow_nesting = True
def get_signature_prefix(self, sig: str) -> str: def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
if 'final' in self.options: if 'final' in self.options:
return 'final %s ' % self.objtype return [nodes.Text('final'), addnodes.desc_sig_space(),
nodes.Text(self.objtype), addnodes.desc_sig_space()]
else: else:
return '%s ' % self.objtype return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
if self.objtype == 'class': if self.objtype == 'class':
@@ -734,25 +798,27 @@ class PyMethod(PyObject):
else: else:
return True return True
def get_signature_prefix(self, sig: str) -> str: def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
prefix = [] prefix: List[nodes.Node] = []
if 'final' in self.options: if 'final' in self.options:
prefix.append('final') prefix.append(nodes.Text('final'))
prefix.append(addnodes.desc_sig_space())
if 'abstractmethod' in self.options: if 'abstractmethod' in self.options:
prefix.append('abstract') prefix.append(nodes.Text('abstract'))
prefix.append(addnodes.desc_sig_space())
if 'async' in self.options: if 'async' in self.options:
prefix.append('async') prefix.append(nodes.Text('async'))
prefix.append(addnodes.desc_sig_space())
if 'classmethod' in self.options: if 'classmethod' in self.options:
prefix.append('classmethod') prefix.append(nodes.Text('classmethod'))
prefix.append(addnodes.desc_sig_space())
if 'property' in self.options: if 'property' in self.options:
prefix.append('property') prefix.append(nodes.Text('property'))
prefix.append(addnodes.desc_sig_space())
if 'staticmethod' in self.options: if 'staticmethod' in self.options:
prefix.append('static') prefix.append(nodes.Text('static'))
prefix.append(addnodes.desc_sig_space())
if prefix: return prefix
return ' '.join(prefix) + ' '
else:
return ''
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls name, cls = name_cls
@@ -769,7 +835,7 @@ class PyMethod(PyObject):
if 'classmethod' in self.options: if 'classmethod' in self.options:
return _('%s() (%s class method)') % (methname, clsname) return _('%s() (%s class method)') % (methname, clsname)
elif 'property' in self.options: elif 'property' in self.options:
return _('%s() (%s property)') % (methname, clsname) return _('%s (%s property)') % (methname, clsname)
elif 'staticmethod' in self.options: elif 'staticmethod' in self.options:
return _('%s() (%s static method)') % (methname, clsname) return _('%s() (%s static method)') % (methname, clsname)
else: else:
@@ -831,11 +897,18 @@ class PyAttribute(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ, self.env) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '',
addnodes.desc_sig_punctuation('', ':'),
addnodes.desc_sig_space(),
*annotations)
value = self.options.get('value') value = self.options.get('value')
if value: if value:
signode += addnodes.desc_annotation(value, ' = ' + value) signode += addnodes.desc_annotation(value, '',
addnodes.desc_sig_space(),
addnodes.desc_sig_punctuation('', '='),
addnodes.desc_sig_space(),
nodes.Text(value))
return fullname, prefix return fullname, prefix
@@ -870,19 +943,25 @@ class PyProperty(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ, self.env) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '',
addnodes.desc_sig_punctuation('', ':'),
addnodes.desc_sig_space(),
*annotations)
return fullname, prefix return fullname, prefix
def get_signature_prefix(self, sig: str) -> str: def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
prefix = [] prefix: List[nodes.Node] = []
if 'abstractmethod' in self.options: if 'abstractmethod' in self.options:
prefix.append('abstract') prefix.append(nodes.Text('abstract'))
prefix.append(addnodes.desc_sig_space())
if 'classmethod' in self.options: if 'classmethod' in self.options:
prefix.append('class') prefix.append(nodes.Text('class'))
prefix.append(addnodes.desc_sig_space())
prefix.append('property') prefix.append(nodes.Text('property'))
return ' '.join(prefix) + ' ' prefix.append(addnodes.desc_sig_space())
return prefix
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls name, cls = name_cls
@@ -1401,7 +1480,7 @@ def builtin_resolver(app: Sphinx, env: BuildEnvironment,
return None return None
elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None':
return contnode return contnode
elif node.get('reftype') in ('class', 'exc'): elif node.get('reftype') in ('class', 'obj', 'exc'):
reftarget = node.get('reftarget') reftarget = node.get('reftarget')
if inspect.isclass(getattr(builtins, reftarget, None)): if inspect.isclass(getattr(builtins, reftarget, None)):
# built-in class # built-in class

View File

@@ -4,7 +4,7 @@
The reStructuredText domain. The reStructuredText domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,12 +4,11 @@
The standard domain. The standard domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import re import re
import unicodedata
import warnings import warnings
from copy import copy from copy import copy
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional,
@@ -336,6 +335,7 @@ class Glossary(SphinxDirective):
def run(self) -> List[Node]: def run(self) -> List[Node]:
node = addnodes.glossary() node = addnodes.glossary()
node.document = self.state.document node.document = self.state.document
node['sorted'] = ('sorted' in self.options)
# This directive implements a custom format of the reST definition list # This directive implements a custom format of the reST definition list
# that allows multiple lines of terms before the definition. This is # that allows multiple lines of terms before the definition. This is
@@ -400,9 +400,8 @@ class Glossary(SphinxDirective):
was_empty = False was_empty = False
# now, parse all the entries into a big definition list # now, parse all the entries into a big definition list
items = [] items: List[nodes.definition_list_item] = []
for terms, definition in entries: for terms, definition in entries:
termtexts: List[str] = []
termnodes: List[Node] = [] termnodes: List[Node] = []
system_messages: List[Node] = [] system_messages: List[Node] = []
for line, source, lineno in terms: for line, source, lineno in terms:
@@ -416,7 +415,6 @@ class Glossary(SphinxDirective):
node_id=None, document=self.state.document) node_id=None, document=self.state.document)
term.rawsource = line term.rawsource = line
system_messages.extend(sysmsg) system_messages.extend(sysmsg)
termtexts.append(term.astext())
termnodes.append(term) termnodes.append(term)
termnodes.extend(system_messages) termnodes.extend(system_messages)
@@ -426,16 +424,10 @@ class Glossary(SphinxDirective):
self.state.nested_parse(definition, definition.items[0][1], self.state.nested_parse(definition, definition.items[0][1],
defnode) defnode)
termnodes.append(defnode) termnodes.append(defnode)
items.append((termtexts, items.append(nodes.definition_list_item('', *termnodes))
nodes.definition_list_item('', *termnodes)))
if 'sorted' in self.options: dlist = nodes.definition_list('', *items)
items.sort(key=lambda x:
unicodedata.normalize('NFD', x[0][0].lower()))
dlist = nodes.definition_list()
dlist['classes'].append('glossary') dlist['classes'].append('glossary')
dlist.extend(item[1] for item in items)
node += dlist node += dlist
return messages + [node] return messages + [node]
@@ -770,18 +762,20 @@ class StandardDomain(Domain):
sectname = clean_astext(title) sectname = clean_astext(title)
elif node.tagname == 'rubric': elif node.tagname == 'rubric':
sectname = clean_astext(node) sectname = clean_astext(node)
elif node.tagname == 'target' and len(node) > 0:
# inline target (ex: blah _`blah` blah)
sectname = clean_astext(node)
elif self.is_enumerable_node(node): elif self.is_enumerable_node(node):
sectname = self.get_numfig_title(node) sectname = self.get_numfig_title(node)
if not sectname:
continue
else: else:
toctree = next(iter(node.traverse(addnodes.toctree)), None) toctree = next(node.findall(addnodes.toctree), None)
if toctree and toctree.get('caption'): if toctree and toctree.get('caption'):
sectname = toctree.get('caption') sectname = toctree.get('caption')
else: else:
# anonymous-only labels # anonymous-only labels
continue continue
self.labels[name] = docname, labelid, sectname if sectname:
self.labels[name] = docname, labelid, sectname
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None: def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
self.progoptions[program, name] = (docname, labelid) self.progoptions[program, name] = (docname, labelid)

View File

@@ -4,7 +4,7 @@
Global creation environment. Global creation environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -45,15 +45,18 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
default_settings: Dict[str, Any] = { default_settings: Dict[str, Any] = {
'auto_id_prefix': 'id',
'embed_images': False,
'embed_stylesheet': False, 'embed_stylesheet': False,
'cloak_email_addresses': True, 'cloak_email_addresses': True,
'pep_base_url': 'https://www.python.org/dev/peps/', 'pep_base_url': 'https://www.python.org/dev/peps/',
'pep_references': None, 'pep_references': None,
'rfc_base_url': 'https://tools.ietf.org/html/', 'rfc_base_url': 'https://datatracker.ietf.org/doc/html/',
'rfc_references': None, 'rfc_references': None,
'input_encoding': 'utf-8-sig', 'input_encoding': 'utf-8-sig',
'doctitle_xform': False, 'doctitle_xform': False,
'sectsubtitle_xform': False, 'sectsubtitle_xform': False,
'section_self_link': False,
'halt_level': 5, 'halt_level': 5,
'file_insertion_enabled': True, 'file_insertion_enabled': True,
'smartquotes_locales': [], 'smartquotes_locales': [],
@@ -532,7 +535,7 @@ class BuildEnvironment:
self.apply_post_transforms(doctree, docname) self.apply_post_transforms(doctree, docname)
# now, resolve all toctree nodes # now, resolve all toctree nodes
for toctreenode in doctree.traverse(addnodes.toctree): for toctreenode in doctree.findall(addnodes.toctree):
result = TocTree(self).resolve(docname, builder, toctreenode, result = TocTree(self).resolve(docname, builder, toctreenode,
prune=prune_toctrees, prune=prune_toctrees,
includehidden=includehidden) includehidden=includehidden)
@@ -618,7 +621,7 @@ class BuildEnvironment:
def check_consistency(self) -> None: def check_consistency(self) -> None:
"""Do consistency checks.""" """Do consistency checks."""
included = set().union(*self.included.values()) # type: ignore included = set().union(*self.included.values())
for docname in sorted(self.all_docs): for docname in sorted(self.all_docs):
if docname not in self.files_to_rebuild: if docname not in self.files_to_rebuild:
if docname == self.config.root_doc: if docname == self.config.root_doc:

View File

@@ -4,6 +4,6 @@
Sphinx environment adapters Sphinx environment adapters
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Assets adapter for sphinx.environment. Assets adapter for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Index entries adapters for sphinx.environment. Index entries adapters for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
Toctree adapter for sphinx.environment. Toctree adapter for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -161,7 +161,7 @@ class TocTree:
process_only_nodes(toc, builder.tags) process_only_nodes(toc, builder.tags)
if title and toc.children and len(toc.children) == 1: if title and toc.children and len(toc.children) == 1:
child = toc.children[0] child = toc.children[0]
for refnode in child.traverse(nodes.reference): for refnode in child.findall(nodes.reference):
if refnode['refuri'] == ref and \ if refnode['refuri'] == ref and \
not refnode['anchorname']: not refnode['anchorname']:
refnode.children = [nodes.Text(title)] refnode.children = [nodes.Text(title)]
@@ -193,13 +193,13 @@ class TocTree:
for toplevel in children: for toplevel in children:
# nodes with length 1 don't have any children anyway # nodes with length 1 don't have any children anyway
if len(toplevel) > 1: if len(toplevel) > 1:
subtrees = toplevel.traverse(addnodes.toctree) subtrees = list(toplevel.findall(addnodes.toctree))
if subtrees: if subtrees:
toplevel[1][:] = subtrees # type: ignore toplevel[1][:] = subtrees # type: ignore
else: else:
toplevel.pop(1) toplevel.pop(1)
# resolve all sub-toctrees # resolve all sub-toctrees
for subtocnode in toc.traverse(addnodes.toctree): for subtocnode in list(toc.findall(addnodes.toctree)):
if not (subtocnode.get('hidden', False) and if not (subtocnode.get('hidden', False) and
not includehidden): not includehidden):
i = subtocnode.parent.index(subtocnode) + 1 i = subtocnode.parent.index(subtocnode) + 1
@@ -257,7 +257,7 @@ class TocTree:
# set the target paths in the toctrees (they are not known at TOC # set the target paths in the toctrees (they are not known at TOC
# generation time) # generation time)
for refnode in newnode.traverse(nodes.reference): for refnode in newnode.findall(nodes.reference):
if not url_re.match(refnode['refuri']): if not url_re.match(refnode['refuri']):
refnode['refuri'] = builder.get_relative_uri( refnode['refuri'] = builder.get_relative_uri(
docname, refnode['refuri']) + refnode['anchorname'] docname, refnode['refuri']) + refnode['anchorname']
@@ -308,7 +308,7 @@ class TocTree:
# renders to nothing # renders to nothing
return nodes.paragraph() return nodes.paragraph()
process_only_nodes(toc, builder.tags) process_only_nodes(toc, builder.tags)
for node in toc.traverse(nodes.reference): for node in toc.findall(nodes.reference):
node['refuri'] = node['anchorname'] or '#' node['refuri'] = node['anchorname'] or '#'
return toc return toc
@@ -324,7 +324,7 @@ class TocTree:
else: else:
kwargs['maxdepth'] = int(kwargs['maxdepth']) kwargs['maxdepth'] = int(kwargs['maxdepth'])
kwargs['collapse'] = collapse kwargs['collapse'] = collapse
for toctreenode in doctree.traverse(addnodes.toctree): for toctreenode in doctree.findall(addnodes.toctree):
toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs) toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs)
if toctree: if toctree:
toctrees.append(toctree) toctrees.append(toctree)

View File

@@ -4,7 +4,7 @@
The data collector components for sphinx.environment. The data collector components for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The image collector for sphinx.environment. The image collector for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -43,7 +43,7 @@ class ImageCollector(EnvironmentCollector):
"""Process and rewrite image URIs.""" """Process and rewrite image URIs."""
docname = app.env.docname docname = app.env.docname
for node in doctree.traverse(nodes.image): for node in doctree.findall(nodes.image):
# Map the mimetype to the corresponding image. The writer may # Map the mimetype to the corresponding image. The writer may
# choose the best image from these candidates. The special key * is # choose the best image from these candidates. The special key * is
# set if there is only single candidate to be used by a writer. # set if there is only single candidate to be used by a writer.
@@ -124,7 +124,7 @@ class DownloadFileCollector(EnvironmentCollector):
def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: def process_doc(self, app: Sphinx, doctree: nodes.document) -> None:
"""Process downloadable file paths. """ """Process downloadable file paths. """
for node in doctree.traverse(addnodes.download_reference): for node in doctree.findall(addnodes.download_reference):
targetname = node['reftarget'] targetname = node['reftarget']
if '://' in targetname: if '://' in targetname:
node['refuri'] = targetname node['refuri'] = targetname

View File

@@ -4,7 +4,7 @@
The dependencies collector components for sphinx.environment. The dependencies collector components for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """

View File

@@ -4,7 +4,7 @@
The metadata collector components for sphinx.environment. The metadata collector components for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -33,9 +33,12 @@ class MetadataCollector(EnvironmentCollector):
Keep processing minimal -- just return what docutils says. Keep processing minimal -- just return what docutils says.
""" """
if len(doctree) > 0 and isinstance(doctree[0], nodes.docinfo): index = doctree.first_child_not_matching_class(nodes.PreBibliographic)
if index is None:
return
elif isinstance(doctree[index], nodes.docinfo):
md = app.env.metadata[app.env.docname] md = app.env.metadata[app.env.docname]
for node in doctree[0]: for node in doctree[index]: # type: ignore
# nodes are multiply inherited... # nodes are multiply inherited...
if isinstance(node, nodes.authors): if isinstance(node, nodes.authors):
authors = cast(List[nodes.author], node) authors = cast(List[nodes.author], node)
@@ -58,7 +61,7 @@ class MetadataCollector(EnvironmentCollector):
value = 0 value = 0
md[name] = value md[name] = value
doctree.pop(0) doctree.pop(index)
def setup(app: Sphinx) -> Dict[str, Any]: def setup(app: Sphinx) -> Dict[str, Any]:

View File

@@ -4,7 +4,7 @@
The title collector components for sphinx.environment. The title collector components for sphinx.environment.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
@@ -43,7 +43,7 @@ class TitleCollector(EnvironmentCollector):
longtitlenode = nodes.title() longtitlenode = nodes.title()
longtitlenode += nodes.Text(doctree['title']) longtitlenode += nodes.Text(doctree['title'])
# look for first section title and use that as the title # look for first section title and use that as the title
for node in doctree.traverse(nodes.section): for node in doctree.findall(nodes.section):
visitor = SphinxContentsFilter(doctree) visitor = SphinxContentsFilter(doctree)
node[0].walkabout(visitor) node[0].walkabout(visitor)
titlenode += visitor.get_entry_text() titlenode += visitor.get_entry_text()

Some files were not shown because too many files have changed in this diff Show More