Merge branch '4.x' into fix-footnote-in-info

This commit is contained in:
Takeshi KOMIYA 2021-12-11 22:59:56 +09:00
commit 5563f672ec
380 changed files with 57314 additions and 25334 deletions

93
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,93 @@
name: Bug report
description: Something is not working correctly.
labels: "bug"
body:
- type: textarea
attributes:
label: Describe the bug
description: >-
A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: How to Reproduce
description: Please provide steps to reproduce this bug.
value: |
```
$ git clone https://github.com/.../some_project
$ cd some_project
$ pip install -r requirements.txt
$ cd docs
$ make html SPHINXOPTS="-D language=de"
$ # open _build/html/index and see bla bla
```
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: >-
A clear and concise description of what you expected to happen.
- type: input
attributes:
label: Your project
description: >-
Link to your sphinx project, or attach zipped small project sample.
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: >-
If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: markdown
attributes:
value: |
## Environment info
- type: input
attributes:
label: OS
description: >-
[e.g. Unix/Linux/Mac/Win/other with version]
validations:
required: true
- type: input
attributes:
label: Python version
validations:
required: true
- type: input
attributes:
label: Sphinx version
validations:
required: true
- type: input
attributes:
label: Sphinx extensions
description: >-
[e.g. sphinx.ext.autodoc, recommonmark]
validations:
required: false
- type: input
attributes:
label: Extra tools
description: >-
[e.g. Browser, tex or something else]
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: >-
Add any other context about the problem here.
[e.g. URL or Ticket]

View File

@ -1,46 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: '<what happen when you do on which document project>'
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
```
<Paste your command-line here which cause the problem>
$ git clone https://github.com/.../some_project
$ cd some_project
$ pip install -r requirements.txt
$ cd docs
$ make html SPHINXOPTS="-D language=de"
$ # open _build/html/index and see bla bla
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Your project**
Link to your sphinx project, or attach zipped small project sample.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment info**
- OS: [e.g. Unix/Linux/Mac/Win/other with version]
- Python version: [e.g. 3.7.1]
- Sphinx version: [e.g. 1.8.2]
- Sphinx extensions: [e.g. sphinx.ext.autodoc, recommonmark]
- Extra tools: [e.g. Browser, tex or something else]
**Additional context**
Add any other context about the problem here.
- [e.g. URL or Ticket]

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

19
.github/workflows/lock.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: 'Lock old threads'
on:
schedule:
- cron: '0 0 * * *'
permissions:
issues: write
pull-requests: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '30'
pr-lock-inactive-days: '30'

View File

@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
name: [py36, py37, py38, py39]
name: [py36, py37, py38, py39, py310]
include:
- name: py36
python: 3.6
@ -23,9 +23,12 @@ jobs:
python: 3.9
docutils: du17
coverage: "--cov ./ --cov-append --cov-config setup.cfg"
- name: py310-dev
python: 3.10-dev
- name: py310
python: "3.10"
docutils: du17
- name: py311-dev
python: 3.11-dev
docutils: py311
env:
PYTEST_ADDOPTS: ${{ matrix.coverage }}
@ -47,6 +50,9 @@ jobs:
run: sudo apt-get install graphviz
- name: Install dependencies
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
run: tox -e ${{ matrix.docutils }} -- -vv
- name: codecov

View File

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

376
CHANGES
View File

@ -1,10 +1,62 @@
Release 4.1.0 (in development)
Release 4.4.0 (in development)
==============================
Dependencies
------------
* Support jinja2-3.0
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
* #9075: autodoc: Add a config variable :confval:`autodoc_unqualified_typehints`
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.
* #9800: extlinks: Emit warning if a hardcoded link is replaceable
by an extlink, suggesting a replacement.
* #9815: html theme: Wrap sidebar components in div to allow customizing their
layout via CSS
* #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+
* #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
* #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.
* #9925: LaTeX: prohibit also with ``'xelatex'`` line splitting at dashes of
inline and parsed literals
* #9944: LaTeX: extra vertical whitespace for some nested declarations
Testing
--------
Release 4.3.2 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
@ -12,6 +64,238 @@ Incompatible changes
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
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
* #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
----------
* #9630: autodoc: Failed to build cross references if :confval:`primary_domain`
is not 'py'
* #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`
is not 'py'
* #9670: html: Fix download file with special characters
* #9710: html: Wrong styles for even/odd rows in nested tables
* #9763: html: parameter name and its type annotation are not separated in HTML
* #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
* #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``
directive with ``:property:`` option
* #9775: py domain: Literal typehint was converted to a cross reference when
:confval:`autodoc_typehints='description'`
* #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
build
* #9656: Fix warnings without subtype being incorrectly suppressed
* Intersphinx, for unresolved references with an explicit inventory,
e.g., ``proj:myFunc``, leave the inventory prefix in the unresolved text.
Release 4.2.0 (released Sep 12, 2021)
=====================================
Features added
--------------
* #9445: autodoc: Support class properties
* #9479: autodoc: Emit a warning if target is a mocked object
* #9560: autodoc: Allow to refer NewType instances with module name in Python
3.10 or above
* #9447: html theme: Expose the version of Sphinx in the form of tuple as a
template variable ``sphinx_version_tuple``
* #9594: manpage: Suppress the title of man page if description is empty
* #9445: py domain: ``:py:property:`` directive supports ``:classmethod:``
option to describe the class property
* #9524: test: SphinxTestApp can take ``builddir`` as an argument
* #9535: C and C++, support more fundamental types, including GNU extensions.
Bugs fixed
----------
* #9608: apidoc: apidoc does not generate a module definition for implicit
namespace package
* #9504: autodoc: generate incorrect reference to the parent class if the target
class inherites the class having ``_name`` attribute
* #9537, #9589: autodoc: Some objects under ``typing`` module are not displayed
well with the HEAD of 3.10
* #9487: autodoc: typehint for cached_property is not shown
* #9509: autodoc: AttributeError is raised on failed resolving typehints
* #9518: autodoc: autodoc_docstring_signature does not effect to ``__init__()``
and ``__new__()``
* #9522: autodoc: PEP 585 style typehints having arguments (ex. ``list[int]``)
are not displayed well
* #9481: autosummary: some warnings contain non-existing filenames
* #9568: autosummary: summarise overlined sectioned headings correctly
* #9600: autosummary: Type annotations which contain commas in autosummary table
are not removed completely
* #9481: c domain: some warnings contain non-existing filenames
* #9481: cpp domain: some warnings contain non-existing filenames
* #9456: html search: abbreation marks are inserted to the search result if
failed to fetch the content of the page
* #9617: html search: The JS requirement warning is shown if browser is slow
* #9267: html theme: CSS and JS files added by theme were loaded twice
* #9585: py domain: ``:type:`` option for :rst:dir:`py:property` directive does
not create a hyperlink
* #9576: py domain: Literal typehint was converted to a cross reference
* #9535 comment: C++, fix parsing of defaulted function parameters that are
function pointers.
* #9564: smartquotes: don't adjust typography for text with
language-highlighted ``:code:`` role.
* #9512: sphinx-build: crashed with the HEAD of Python 3.10
Release 4.1.2 (released Jul 27, 2021)
=====================================
Incompatible changes
--------------------
* #9435: linkcheck: Disable checking automatically generated anchors on
github.com (ex. anchors in reST/Markdown documents)
Bugs fixed
----------
* #9489: autodoc: Custom types using ``typing.NewType`` are not displayed well
with the HEAD of 3.10
* #9490: autodoc: Some objects under ``typing`` module are not displayed well
with the HEAD of 3.10
* #9436, #9471: autodoc: crashed if ``autodoc_class_signature = "separated"``
* #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
Release 4.1.1 (released Jul 15, 2021)
=====================================
Dependencies
------------
* #9434: sphinxcontrib-htmlhelp-2.0.0 or above
* #9434: sphinxcontrib-serializinghtml-1.1.5 or above
Bugs fixed
----------
* #9438: html: HTML logo or Favicon specified as file not being found on output
Release 4.1.0 (released Jul 12, 2021)
=====================================
Dependencies
------------
* Support jinja2-3.0
Deprecated
----------
* The ``app`` argument of ``sphinx.environment.BuildEnvironment`` becomes
required
* ``sphinx.application.Sphinx.html_theme``
@ -37,22 +321,32 @@ Features added
* #3014: autodoc: Add :event:`autodoc-process-bases` to modify the base classes
of the class definitions
* #9272: autodoc: Render enum values for the default argument value better
* #9384: autodoc: ``autodoc_typehints='none'`` now erases typehints for
variables, attributes and properties
* #3257: autosummary: Support instance attributes for classes
* #9358: html: Add "heading" role to the toctree items
* #9225: html: Add span tag to the return typehint of method/function
* #9129: html search: Show search summaries when html_copy_source = False
* #9307: html search: Prevent corrections and completions in search field
* #9120: html theme: Eliminate prompt characters of code-block from copyable
text
* #9176: i18n: Emit a debug message if message catalog file not found under
:confval:`locale_dirs`
* #9414: LaTeX: Add xeCJKVerbAddon to default fvset config for Chinese documents
* #9016: linkcheck: Support checking anchors on github.com
* #9016: linkcheck: Add a new event :event:`linkcheck-process-uri` to modify
URIs before checking hyperlinks
* #6525: linkcheck: Add :confval:`linkcheck_allowed_redirects` to mark
hyperlinks that are redirected to expected URLs as "working"
* #1874: py domain: Support union types using ``|`` in info-field-list
* #9268: py domain: :confval:`python_use_unqualified_type_names` supports type
field in info-field-list
* #9097: Optimize the paralell build
* #9097: Optimize the parallel build
* #9131: Add :confval:`nitpick_ignore_regex` to ignore nitpicky warnings using
regular expressions
* #9174: Add ``Sphinx.set_html_assets_policy`` to tell extensions to include
HTML assets in all the pages. Extensions can check this via
``Sphinx.registry.html_assets_policy``
* C++, add support for
- ``inline`` variables,
@ -65,6 +359,7 @@ Features added
(e.g., ``Sortable auto &v``).
* C, add support for digit separators in literals.
* #9166: LaTeX: support containers in LaTeX output
Bugs fixed
@ -77,7 +372,13 @@ Bugs fixed
* #9250: autodoc: The inherited method not having docstring is wrongly parsed
* #9283: autodoc: autoattribute directive failed to generate document for an
attribute not having any comment
* #9364: autodoc: single element tuple on the default argument value is wrongly
rendered
* #9362: autodoc: AttributeError is raised on processing a subclass of Tuple[()]
* #9404: autodoc: TypeError is raised on processing dict-like object (not a
class) via autoclass directive
* #9317: html: Pushing left key causes visiting the next page at the first page
* #9381: html: URL for html_favicon and html_log does not work
* #9270: html theme : pyramid theme generates incorrect logo links
* #9217: manpage: The name of manpage directory that is generated by
:confval:`man_make_section_directory` is not correct
@ -85,29 +386,28 @@ Bugs fixed
* #9306: Linkcheck reports broken link when remote server closes the connection
on HEAD request
* #9280: py domain: "exceptions" module is not displayed
* #9418: py domain: a Callable annotation with no parameters
(e.g. ``Callable[[], None])`` will be rendered with a bracket missing
(``Callable[], None]``)
* #9319: quickstart: Make sphinx-quickstart exit when conf.py already exists
* #9387: xml: XML Builder ignores custom visitors
* #9224: ``:param:`` and ``:type:`` fields does not support a type containing
whitespace (ex. ``Dict[str, str]``)
* #8945: when transforming typed fields, call the specified role instead of
making an single xref. For C and C++, use the ``expr`` role for typed fields.
Testing
--------
Release 4.0.3 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Release 4.0.3 (released Jul 05, 2021)
=====================================
Features added
--------------
* C, add C23 keywords ``_Decimal32``, ``_Decimal64``, and ``_Decimal128``.
* #9354: C, add :confval:`c_extra_keywords` to allow user-defined keywords
during parsing.
* Revert the removal of ``sphinx.util:force_decode()`` to become some 3rd party
extensions available again during 5.0
Bugs fixed
----------
@ -116,9 +416,9 @@ Bugs fixed
* #9313: LaTeX: complex table with merged cells broken since 4.0
* #9305: LaTeX: backslash may cause Improper discretionary list pdf build error
with Japanese engines
Testing
--------
* #9354: C, remove special macro names from the keyword list.
See also :confval:`c_extra_keywords`.
* #9322: KeyError is raised on PropagateDescDomain transform
Release 4.0.2 (released May 20, 2021)
=====================================
@ -185,7 +485,7 @@ Incompatible changes
``autodoc_typehints='description'`` and ``autoclass_content='class'`` set
* #8898: extlinks: "%s" becomes required keyword in the link caption string
* domain: The ``Index`` class becomes subclasses of ``abc.ABC`` to indicate
methods that must be overrided in the concrete classes
methods that must be overridden in the concrete classes
* #4826: py domain: The structure of python objects is changed. A boolean value
is added to indicate that the python object is canonical one
* #7425: MathJax: The MathJax was changed from 2 to 3. Users using a custom
@ -593,7 +893,7 @@ Bugs fixed
* #8567: autodoc: Instance attributes are incorrectly added to Parent class
* #8566: autodoc: The ``autodoc-process-docstring`` event is emitted to the
alias classes unexpectedly
* #8583: autodoc: Unnecessary object comparision via ``__eq__`` method
* #8583: autodoc: Unnecessary object comparison via ``__eq__`` method
* #8565: linkcheck: Fix PriorityQueue crash when link tuples are not
comparable
@ -659,7 +959,7 @@ Bugs fixed
* #8443: autodoc: autodata directive can't create document for PEP-526 based
type annotated variables
* #8443: autodoc: autoattribute directive can't create document for PEP-526
based uninitalized variables
based uninitialized variables
* #8480: autodoc: autoattribute could not create document for __slots__
attributes
* #8503: autodoc: autoattribute could not create document for a GenericAlias as
@ -816,7 +1116,7 @@ Bugs fixed
* #8091: autodoc: AttributeError is raised on documenting an attribute on Python
3.5.2
* #8099: autodoc: NameError is raised when target code uses ``TYPE_CHECKING``
* C++, fix parsing of template template paramters, broken by the fix of #7944
* C++, fix parsing of template template parameters, broken by the fix of #7944
Release 3.2.0 (released Aug 08, 2020)
=====================================
@ -897,7 +1197,7 @@ Bugs fixed
module has submodules
* #4258: napoleon: decorated special methods are not shown
* #7799: napoleon: parameters are not escaped for combined params in numpydoc
* #7780: napoleon: multiple paramaters declaration in numpydoc was wrongly
* #7780: napoleon: multiple parameters declaration in numpydoc was wrongly
recognized when napoleon_use_params=True
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
* #7846: html theme: XML-invalid files were generated
@ -1105,7 +1405,7 @@ Bugs fixed
* #3673: autodoc: member-order="bysource" does not work for a module having
__all__
* #7668: autodoc: wrong retann value is passed to a handler of
autodoc-proccess-signature
autodoc-process-signature
* #7711: autodoc: fails with ValueError when processing numpy objects
* #7791: autodoc: TypeError is raised on documenting singledispatch function
* #7551: autosummary: a nested class is indexed as non-nested class
@ -1263,7 +1563,7 @@ Incompatible changes
has been renamed to ``sphinx_line_type``.
* #6462: double backslashes in domain directives are no longer replaced by
single backslashes as default. A new configuration value
:confval:`strip_signature_backslash` can be used by users to reenable it.
:confval:`strip_signature_backslash` can be used by users to re-enable it.
3.0.0 final
@ -1311,7 +1611,7 @@ Features added
* #6417: py domain: Allow to make a style for arguments of functions and methods
* #7238, #7239: py domain: Emit a warning on describing a python object if the
entry is already added as the same name
* #7341: py domain: type annotations in singature are converted to cross refs
* #7341: py domain: type annotations in signature are converted to cross refs
* Support priority of event handlers. For more detail, see
:py:meth:`.Sphinx.connect()`
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
@ -1336,7 +1636,7 @@ Features added
- Cross-referencing respecting the current scope.
- Possible to document anonymous entities.
- More specific directives and roles for each type of entitiy,
- More specific directives and roles for each type of entity,
e.g., handling scoping of enumerators.
- New role :rst:role:`c:expr` for rendering expressions and types
in text.
@ -1404,6 +1704,14 @@ Bugs fixed
:confval:`intersphinx_mapping` on :event:`config-inited` event
* #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)
=====================================
@ -1723,7 +2031,7 @@ Features added
* #6533: LaTeX: refactor visit_enumerated_list() to use ``\sphinxsetlistlabels``
* #6628: quickstart: Use ``https://docs.python.org/3/`` for default setting of
:confval:`intersphinx_mapping`
* #6419: sphinx-build: give reasons why rebuilded
* #6419: sphinx-build: give reasons why rebuilt
Bugs fixed
----------
@ -2240,6 +2548,14 @@ Testing
* 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)
=====================================

View File

@ -430,7 +430,7 @@ Books produced using Sphinx
* `"Theoretical Physics Reference" <https://www.theoretical-physics.net/>`__
* `"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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
doc/_static/tutorial/lumache-furo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -33,7 +33,7 @@
<li>{%trans path=pathto('ext/builtins')%}<b>Extensions:</b> automatic testing of code snippets, inclusion of
docstrings from Python modules (API docs), and
<a href="{{ path }}#builtin-sphinx-extensions">more</a>{%endtrans%}</li>
<li>{%trans path=pathto("usage/extensions")%}<b>Contributed extensions:</b> dozens of
<li>{%trans path=pathto("usage/extensions/index")%}<b>Contributed extensions:</b> dozens of
extensions <a href="{{ path }}#third-party-extensions">contributed by users</a>;
most of them installable from PyPI{%endtrans%}</li>
</ul>
@ -118,12 +118,12 @@
this part of the documentation is for you.{%endtrans%}</p>
<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%}
</ul>
<h2>{%trans%}Code of Conduct{%endtrans%}</h2>
<p>{%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}</p>
<p>{%trans path=pathto("internals/code-of-conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}</p>
{% endblock %}

View File

@ -35,7 +35,7 @@ htmlhelp_basename = 'Sphinxdoc'
epub_theme = 'epub'
epub_basename = 'sphinx'
epub_author = 'Georg Brandl'
epub_publisher = 'https://sphinx-doc.org/'
epub_publisher = 'https://www.sphinx-doc.org/'
epub_uid = 'web-site'
epub_scheme = 'url'
epub_identifier = epub_publisher
@ -109,6 +109,7 @@ texinfo_documents = [
intersphinx_mapping = {
'python': ('https://docs.python.org/3/', 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:

View File

@ -256,6 +256,9 @@ Here is some sample code to accomplish this:
.. code-block:: python
from os import path
from sphinx.util.fileutil import copy_asset_file
def copy_custom_files(app, exc):
if app.builder.format == 'html' and not exc:
staticdir = path.join(app.builder.outdir, '_static')

View File

@ -9,7 +9,7 @@ from sphinx.ext.autodoc import ClassDocumenter, bool_option
class IntEnumDocumenter(ClassDocumenter):
objtype = 'intenum'
directivetype = 'class'
directivetype = ClassDocumenter.objtype
priority = 10 + ClassDocumenter.priority
option_spec = dict(ClassDocumenter.option_spec)
option_spec['hex'] = bool_option
@ -18,7 +18,10 @@ class IntEnumDocumenter(ClassDocumenter):
def can_document_member(cls,
member: Any, membername: str,
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:
super().add_directive_header(sig)
@ -36,14 +39,13 @@ class IntEnumDocumenter(ClassDocumenter):
use_hex = self.options.hex
self.add_line('', source_name)
for enum_value in enum_object:
the_value_name = enum_value.name
the_value_value = enum_value.value
for the_member_name, enum_member in enum_object.__members__.items():
the_member_value = enum_member.value
if use_hex:
the_value_value = hex(the_value_value)
the_member_value = hex(the_member_value)
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)

View File

@ -24,7 +24,7 @@ class RecipeDirective(ObjectDescription):
def add_target_and_index(self, name_cls, sig, signode):
signode['ids'].append('recipe' + '-' + sig)
if 'contains' not in self.options:
if 'contains' in self.options:
ingredients = [
x.strip() for x in self.options.get('contains').split(',')]

View File

@ -16,6 +16,7 @@ Builder API
.. autoattribute:: name
.. autoattribute:: format
.. autoattribute:: epilog
.. autoattribute:: allow_parallel
.. autoattribute:: supported_image_types
.. autoattribute:: supported_remote_images
.. autoattribute:: supported_data_uri_images

View File

@ -22,6 +22,31 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- 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``
- 4.1
- 6.0
@ -748,6 +773,11 @@ The following is a list of deprecated interfaces.
- 4.0
- ``sphinx.domains.std.StandardDomain.process_doc()``
* - ``sphinx.domains.js.JSObject.display_prefix``
-
- 4.3
- ``sphinx.domains.js.JSObject.get_display_prefix()``
* - ``sphinx.environment.NoUri``
- 2.1
- 3.0
@ -1036,7 +1066,7 @@ The following is a list of deprecated interfaces.
* - ``sphinx.util.force_decode()``
- 2.0
- 4.0
- 5.0
- N/A
* - ``sphinx.util.get_matching_docs()``
@ -1207,7 +1237,7 @@ The following is a list of deprecated interfaces.
* - :meth:`~sphinx.application.Sphinx.add_stylesheet()`
- 1.8
- 4.0
- 6.0
- :meth:`~sphinx.application.Sphinx.add_css_file()`
* - :meth:`~sphinx.application.Sphinx.add_javascript()`

View File

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

View File

@ -175,7 +175,7 @@ as metadata of the extension. Metadata keys currently recognized are:
.. note:: The *parallel-read-safe* extension must satisfy the following
conditions:
* The core logic of the extension is parallely executable during
* The core logic of the extension is parallelly executable during
the reading phase.
* It has event handlers for :event:`env-merge-info` and
:event:`env-purge-doc` events if it stores dataa to the build
@ -188,7 +188,7 @@ as metadata of the extension. Metadata keys currently recognized are:
.. note:: The *parallel-write-safe* extension must satisfy the following
conditions:
* The core logic of the extension is parallely executable during
* The core logic of the extension is parallelly executable during
the writing phase.

View File

@ -299,6 +299,10 @@ appear in the source. Emacs, on the other-hand, will by default replace
: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
``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

View File

@ -76,6 +76,9 @@ Glossary
master document
The document that contains the root :rst:dir:`toctree` directive.
root document
Same as :term:`master document`.
object
The basic building block of Sphinx documentation. Every "object
directive" (e.g. :rst:dir:`function` or :rst:dir:`object`) creates such a

View File

@ -531,6 +531,10 @@ Keys that don't need to be overridden unless in special cases are:
Changed default for ``'pdflatex'``. Previously it was using
``'\\fvset{fontsize=\\small}'``.
.. versionchanged:: 4.1.0
Changed default for Chinese documents to
``'\\fvset{fontsize=\\small,formatcom=\\xeCJKVerbAddon}'``
Keys that are set by other options and therefore should not be overridden are:
``'docclass'``
@ -570,7 +574,7 @@ The colors used in the above are provided by the ``svgnames`` option of the
It is possible to insert further uses of the ``\sphinxsetup`` LaTeX macro
directly into the body of the document, via the help of the :rst:dir:`raw`
directive. This chapter is styled in the PDF output using the following at the
start of the chaper::
start of the chapter::
.. raw:: latex
@ -603,7 +607,7 @@ macros may be significant.
Do not use quotes to enclose values, whether numerical or strings.
``bookmarksdepth``
Controls the depth of the collapsable bookmarks panel in the PDF.
Controls the depth of the collapsible bookmarks panel in the PDF.
May be either a number (e.g. ``3``) or a LaTeX sectioning name (e.g.
``subsubsection``, i.e. without backslash).
For details, refer to the ``hyperref`` LaTeX docs.
@ -708,7 +712,7 @@ Do not use quotes to enclose values, whether numerical or strings.
As the default is set to a high value, the forceful algorithm is triggered
only in overfull case, i.e. in presence of a string longer than full
linewidth. Set this to ``0`` to force all input lines to be hard wrapped
at the current avaiable linewidth::
at the current available linewidth::
latex_elements = {
'sphinxsetup': "verbatimforcewraps, verbatimmaxunderfull=0",
@ -778,7 +782,7 @@ Do not use quotes to enclose values, whether numerical or strings.
.. versionchanged:: 1.5
The breaking of long code lines was added at 1.4.2. The default
definition of the continuation symbol was changed at 1.5 to accomodate
definition of the continuation symbol was changed at 1.5 to accommodate
various font sizes (e.g. code-blocks can be in footnotes).
``TitleColor``
@ -1147,6 +1151,20 @@ Miscellany
Formerly, use of *fncychap* with other styles than ``Bjarne`` was
dysfunctional.
- Docutils :dudir:`container` directives are supported in LaTeX output: to
let a container class with name ``foo`` influence the final PDF via LaTeX,
it is only needed to define in the preamble an environment
``sphinxclassfoo``. A simple example would be:
.. code-block:: latex
\newenvironment{sphinxclassred}{\color{red}}{}
Currently the class names must contain only ascii characters and avoid
characters special to LaTeX such as ``\``.
.. versionadded:: 4.1.0
.. hint::
As an experimental feature, Sphinx can use user-defined template file for

View File

@ -19,7 +19,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://sphinx-doc.org/
echo.https://www.sphinx-doc.org/
exit /b 1
)

View File

@ -39,6 +39,10 @@ Options
Document imported members.
.. option:: -a, --respect-module-all
Document exactly the members in a module's ``__all__`` attribute.
Example
-------

View File

@ -35,7 +35,7 @@ Options
.. option:: --no-sep
If specified, create build directroy under source directroy.
If specified, create build directory under source directory.
.. option:: --dot=DOT

View File

@ -115,7 +115,7 @@ The following blocks exist in the ``layout.html`` template:
``rootrellink``, ``relbaritems``
Inside the relbar there are three sections: The ``rootrellink``, the links
from the documentation and the custom ``relbaritems``. The ``rootrellink``
is a block that by default contains a list item pointing to the master
is a block that by default contains a list item pointing to the root
document by default, the ``relbaritems`` is an empty block. If you
override them to add extra links into the bar make sure that they are list
items and end with the :data:`reldelim1`.
@ -372,7 +372,16 @@ in the future.
.. data:: sphinx_version
The version of Sphinx used to build.
The version of Sphinx used to build represented as a string for example "3.5.1".
.. data:: sphinx_version_tuple
The version of Sphinx used to build represented as a tuple of five elements.
For Sphinx version 3.5.1 beta 3 this would be `(3, 5, 1, 'beta', 3)``.
The fourth element can be one of: ``alpha``, ``beta``, ``rc``, ``final``.
``final`` always has 0 as the last element.
.. versionadded:: 4.2
.. data:: style

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`.

6
doc/tutorial/end.rst Normal file
View File

@ -0,0 +1,6 @@
Where to go from here
=====================
This tutorial covered the very first steps to create a documentation project
with Sphinx. To continue learning more about Sphinx, check out the :ref:`rest
of the documentation <contents>`.

View File

@ -0,0 +1,92 @@
First steps to document your project using Sphinx
=================================================
Building your HTML documentation
--------------------------------
The ``index.rst`` file that ``sphinx-quickstart`` created has some content
already, and it gets rendered as the front page of your HTML documentation. It
is written in reStructuredText, a powerful markup language.
Modify the file as follows:
.. code-block:: rst
:caption: docs/source/index.rst
Welcome to Lumache's documentation!
===================================
**Lumache** (/lu'make/) is a Python library for cooks and food lovers that
creates recipes mixing random ingredients. It pulls data from the `Open Food
Facts database <https://world.openfoodfacts.org/>`_ and offers a *simple* and
*intuitive* API.
.. note::
This project is under active development.
This showcases several features of the reStructuredText syntax, including:
- a **section header** using ``===`` for the underline,
- two examples of :ref:`rst-inline-markup`: ``**strong emphasis**`` (typically
bold) and ``*emphasis*`` (typically italics),
- an **inline external link**,
- and a ``note`` **admonition** (one of the available :ref:`directives
<rst-directives>`)
Now to render it with the new content, you can use the ``sphinx-build`` command
as before, or leverage the convenience script as follows:
.. code-block:: console
(.venv) $ cd docs
(.venv) $ make html
After running this command, you will see that ``index.html`` reflects the new
changes!
Building your documentation in other formats
--------------------------------------------
Sphinx supports a variety of formats apart from HTML, including PDF, EPUB,
:ref:`and more <builders>`. For example, to build your documentation
in EPUB format, run this command from the ``docs`` directory:
.. code-block:: console
(.venv) $ make epub
After that, you will see the files corresponding to the e-book under
``docs/build/epub/``. You can either open ``Lumache.epub`` with an
EPUB-compatible e-book viewer, like `Calibre <https://calibre-ebook.com/>`_,
or preview ``index.xhtml`` on a web browser.
.. note::
To quickly display a complete list of possible output formats, plus some
extra useful commands, you can run :code:`make help`.
Each output format has some specific configuration options that you can tune,
:ref:`including EPUB <epub-options>`. For instance, the default value of
:confval:`epub_show_urls` is ``inline``, which means that, by default, URLs are
shown right after the corresponding link, in parentheses. You can change that
behavior by adding the following code at the end of your ``conf.py``:
.. code-block:: python
# EPUB options
epub_show_urls = 'footnote'
With this configuration value, and after running ``make epub`` again, you will
notice that URLs appear now as footnotes, which avoids cluttering the text.
Sweet! Read on to explore :doc:`other ways to customize
Sphinx </tutorial/more-sphinx-customization>`.
.. note::
Generating a PDF using Sphinx can be done running ``make latexpdf``,
provided that the system has a working LaTeX installation,
as explained in the documentation of :class:`sphinx.builders.latex.LaTeXBuilder`.
Although this is perfectly feasible, such installations are often big,
and in general LaTeX requires careful configuration in some cases,
so PDF generation is out of scope for this tutorial.

View File

@ -0,0 +1,120 @@
Getting started
===============
Setting up your project and development environment
---------------------------------------------------
In a new directory, create a file called ``README.rst`` with the following
content.
.. code-block:: rst
:caption: README.rst
Lumache
=======
**Lumache** (/lu'make/) is a Python library for cooks and food lovers that
creates recipes mixing random ingredients.
It is a good moment to create a Python virtual environment and install the
required tools. For that, open a command line terminal, ``cd`` into the
directory you just created, and run the following commands:
.. code-block:: console
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install sphinx
.. note::
The installation method used above is described in more detail in
:ref:`install-pypi`. For the rest of this tutorial, the instructions will
assume a Python virtual environment.
If you executed these instructions correctly, you should have the Sphinx command
line tools available. You can do a basic verification running this command:
.. code-block:: console
(.venv) $ sphinx-build --version
sphinx-build 4.0.2
If you see a similar output, you are on the right path!
Creating the documentation layout
---------------------------------
Then from the command line, run the following command:
.. code-block:: console
(.venv) $ sphinx-quickstart docs
This will present to you a series of questions required to create the basic
directory and configuration layout for your project inside the ``docs`` folder.
To proceed, answer each question as follows:
- ``> Separate source and build directories (y/n) [n]``: Write "``y``" (without
quotes) and press :kbd:`Enter`.
- ``> Project name``: Write "``Lumache``" (without quotes) and press
:kbd:`Enter`.
- ``> Author name(s)``: Write "``Graziella``" (without quotes) and press
:kbd:`Enter`.
- ``> Project release []``: Write "``0.1``" (without quotes) and press
:kbd:`Enter`.
- ``> Project language [en]``: Leave it empty (the default, English) and press
:kbd:`Enter`.
After the last question, you will see the new ``docs`` directory with the
following content.
.. code-block:: text
docs
├── build
├── make.bat
├── Makefile
└── source
├── conf.py
├── index.rst
├── _static
└── _templates
The purpose of each of these files is:
``build/``
An empty directory (for now) that will hold the rendered documentation.
``make.bat`` and ``Makefile``
Convenience scripts to simplify some common Sphinx operations, such as
rendering the content.
``source/conf.py``
A Python script holding the configuration of the Sphinx project. It contains
the project name and release you specified to ``sphinx-quickstart``, as well
as some extra configuration keys.
``source/index.rst``
The :term:`root document` of the project, which serves as welcome page and
contains the root of the "table of contents tree" (or *toctree*).
Thanks to this bootstrapping step, you already have everything needed to render
the documentation as HTML for the first time. To do that, run this command:
.. code-block:: console
(.venv) $ sphinx-build -b html docs/source/ docs/build/html
And finally, open ``docs/build/html/index.html`` in your browser. You should see
something like this:
.. figure:: /_static/tutorial/lumache-first-light.png
:width: 80%
:align: center
:alt: Freshly created documentation of Lumache
Freshly created documentation of Lumache
There we go! You created your first HTML documentation using Sphinx.
Now you can start :doc:`customizing it </tutorial/first-steps>`.

View File

@ -27,166 +27,13 @@ a basic understanding of how it works, as well as a working Python installation
for development, since you will use *Python virtual environments* to create the
project.
Getting started
---------------
.. toctree::
Setting up your project and development environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In a new directory, create a file called ``README.rst`` with the following
content.
.. code-block:: rest
Lumache
=======
**Lumache** (/lu'make/) is a Python library for cooks and food lovers that
creates recipes mixing random ingredients.
It is a good moment to create a Python virtual environment and install the
required tools. For that, open a command line terminal, ``cd`` into the
directory you just created, and run the following commands:
.. code-block:: console
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip install sphinx
.. note::
The installation method used above is described in more detail in
:ref:`install-pypi`. For the rest of this tutorial, the instructions will
assume a Python virtual environment.
If you executed these instructions correctly, you should have the Sphinx command
line tools available. You can do a basic verification running this command:
.. code-block:: console
(.venv) $ sphinx-build --version
sphinx-build 4.0.2
If you see a similar output, you are on the right path!
Creating the documentation layout
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then from the command line, run the following command:
.. code-block:: console
(.venv) $ sphinx-quickstart docs
This will present to you a series of questions required to create the basic
directory and configuration layout for your project inside the ``docs`` folder.
To proceed, answer each question as follows:
- ``> Separate source and build directories (y/n) [n]``: Write "``y``" (without
quotes) and press :kbd:`Enter`.
- ``> Project name``: Write "``Lumache``" (without quotes) and press
:kbd:`Enter`.
- ``> Author name(s)``: Write "``Graziella``" (without quotes) and press
:kbd:`Enter`.
- ``> Project release []``: Write "``0.1``" (without quotes) and press
:kbd:`Enter`.
- ``> Project language [en]``: Leave it empty (the default, English) and press
:kbd:`Enter`.
After the last question, you will see the new ``docs`` directory with the
following content.
.. code-block:: text
docs
├── build
├── make.bat
├── Makefile
└── source
├── conf.py
├── index.rst
├── _static
└── _templates
The purpose of each of these files is:
``build/``
An empty directory (for now) that will hold the rendered documentation.
``make.bat`` and ``Makefile``
Convenience scripts to simplify some common Sphinx operations, such as
rendering the content.
``source/conf.py``
A Python script holding the configuration of the Sphinx project. It contains
the project name and release you specified to ``sphinx-quickstart``, as well
as some extra configuration keys.
``source/index.rst``
The :term:`master document` of the project, which serves as welcome page and
contains the root of the "table of contents tree" (or *toctree*).
Thanks to this bootstrapping step, you already have everything needed to render
the documentation as HTML for the first time. To do that, run this command:
.. code-block:: console
(.venv) $ sphinx-build -b html docs/source/ docs/build/html
And finally, open `docs/build/html/index.html` in your browser. You should see
something like this:
.. image:: /_static/tutorial/lumache-first-light.png
There we go! You created your first HTML documentation using Sphinx.
Making some tweaks to the index
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``index.rst`` file that ``sphinx-quickstart`` created has some content
already, and it gets rendered as the front page of our HTML documentation. It
is written in reStructuredText, a powerful markup language.
Modify the file as follows:
.. code-block:: rest
Welcome to Lumache's documentation!
===================================
**Lumache** (/lu'make/) is a Python library for cooks and food lovers that
creates recipes mixing random ingredients. It pulls data from the `Open Food
Facts database <https://world.openfoodfacts.org/>`_ and offers a *simple* and
*intuitive* API.
.. note::
This project is under active development.
This showcases several features of the reStructuredText syntax, including:
- a **section header** using ``===`` for the underline,
- two examples of :ref:`rst-inline-markup`: ``**strong emphasis**`` (typically
bold) and ``*emphasis*`` (typically italics),
- an **inline external link**,
- and a ``note`` **admonition** (one of the available :ref:`directives
<rst-directives>`)
Now to render it with the new content, you can use the ``sphinx-build`` command
as before, or leverage the convenience script as follows:
.. code-block:: console
(.venv) $ cd docs
(.venv) $ make html
After running this command, you will see that ``index.html`` reflects the new
changes!
Where to go from here
---------------------
This tutorial covered the very first steps to create a documentation project
with Sphinx. To continue learning more about Sphinx, check out the :ref:`rest
of the documentation <contents>`.
getting-started
first-steps
more-sphinx-customization
narrative-documentation
describing-code
automatic-doc-generation
deploying
end

View File

@ -0,0 +1,78 @@
More Sphinx customization
=========================
There are two main ways to customize your documentation beyond what is possible
with core Sphinx: extensions and themes.
Enabling a built-in extension
-----------------------------
In addition to these configuration values, you can customize Sphinx even more
by using :doc:`extensions </usage/extensions/index>`. Sphinx ships several
:ref:`builtin ones <builtin-extensions>`, and there are many more
:ref:`maintained by the community <third-party-extensions>`.
For example, to enable the :mod:`sphinx.ext.duration` extension,
locate the ``extensions`` list in your ``conf.py`` and add one element as
follows:
.. code-block:: python
:caption: docs/source/conf.py
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.duration',
]
After that, every time you generate your documentation, you will see a short
durations report at the end of the console output, like this one:
.. code-block:: console
(.venv) $ make html
...
The HTML pages are in build/html.
====================== slowest reading durations =======================
0.042 temp/source/index
Using a third-party HTML theme
------------------------------
Themes, on the other hand, are a way to customize the appearance of your
documentation. Sphinx has several :ref:`builtin themes <builtin-themes>`, and
there are also `third-party ones <https://sphinx-themes.org/>`_.
For example, to use the `Furo <https://pradyunsg.me/furo/>`_ third-party theme
in your HTML documentation, first you will need to install it with ``pip`` in
your Python virtual environment, like this:
.. code-block:: console
(.venv) $ pip install furo
And then, locate the ``html_theme`` variable on your ``conf.py`` and replace
its value as follows:
.. code-block:: python
:caption: docs/source/conf.py
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'furo'
With this change, you will notice that your HTML documentation has now a new
appearance:
.. figure:: /_static/tutorial/lumache-furo.png
:width: 80%
:align: center
:alt: 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

@ -0,0 +1,130 @@
Narrative documentation in Sphinx
=================================
Structuring your documentation across multiple pages
----------------------------------------------------
The file ``index.rst`` created by ``sphinx-quickstart`` is the :term:`root
document`, whose main function is to serve as a welcome page and to contain the
root of the "table of contents tree" (or *toctree*). Sphinx allows you to
assemble a project from different files, which is helpful when the project
grows.
As an example, create a new file ``docs/source/usage.rst`` (next to
``index.rst``) with these contents:
.. code-block:: rst
:caption: docs/source/usage.rst
Usage
=====
Installation
------------
To use Lumache, first install it using pip:
.. code-block:: console
(.venv) $ pip install lumache
This new file contains two :ref:`section <rst-sections>` headers, normal
paragraph text, and a :rst:dir:`code-block` directive that renders
a block of content as source code, with appropriate syntax highlighting
(in this case, generic ``console`` text).
The structure of the document is determined by the succession of heading
styles, which means that, by using ``---`` for the "Installation" section
after ``===`` for the "Usage" section, you have declared "Installation" to
be a *subsection* of "Usage".
To complete the process, add a ``toctree`` :ref:`directive <rst-directives>` at
the end of ``index.rst`` including the document you just created, as follows:
.. code-block:: rst
:caption: docs/source/index.rst
Contents
--------
.. toctree::
usage
This step inserts that document in the root of the *toctree*, so now it belongs
to the structure of your project, which so far looks like this:
.. code-block:: text
index
└── usage
If you build the HTML documentation running ``make html``, you will see
that the ``toctree`` gets rendered as a list of hyperlinks, and this allows you
to navigate to the new page you just created. Neat!
.. warning::
Documents outside a *toctree* will result in ``WARNING: document isn't
included in any toctree`` messages during the build process, and will be
unreachable for users.
Adding cross-references
-----------------------
One powerful feature of Sphinx is the ability to seamlessly add
:ref:`cross-references <xref-syntax>` to specific parts of the documentation:
a document, a section, a figure, a code object, etc. This tutorial is full of
them!
To add a cross-reference, write this sentence right after the
introduction paragraph in ``index.rst``:
.. code-block:: rst
:caption: docs/source/index.rst
Check out the :doc:`usage` section for further information.
The :rst:role:`doc` :ref:`role <rst-roles-alt>` you used automatically
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
project. For that, you need to use the :rst:role:`ref` role, and add an
explicit *label* that acts as :duref:`a target <hyperlink-targets>`.
For example, to reference the "Installation" subsection, add a label right
before the heading, as follows:
.. code-block:: rst
:caption: docs/source/usage.rst
:emphasize-lines: 4
Usage
=====
.. _installation:
Installation
------------
...
And make the sentence you added in ``index.rst`` look like this:
.. code-block:: rst
:caption: docs/source/index.rst
Check out the :doc:`usage` section for further information, including how to
:ref:`install <installation>` the project.
Notice a trick here: the ``install`` part specifies how the link will look like
(we want it to be a specific word, so the sentence makes sense), whereas the
``<installation>`` part refers to the actual label we want to add a
cross-reference to. If you do not include an explicit title, hence using
``:ref:`installation```, the section title will be used (in this case,
``Installation``). Both the ``:doc:`` and the ``:ref:`` roles will be rendered
as hyperlinks in the HTML documentation.
What about :doc:`documenting code objects in Sphinx </tutorial/describing-code>`?
Read on!

View File

@ -164,7 +164,7 @@ Options for setuptools integration
.. setuptools-confval:: link-index
A boolean that ensures index.html will be linked to the master doc. Default
A boolean that ensures index.html will be linked to the root doc. Default
is false.
This can also be set by passing the `-i` flag to ``setup.py``:

View File

@ -51,7 +51,7 @@ The builder's "name" must be given to the **-b** command-line option of
This is an HTML builder that combines the whole project in one output file.
(Obviously this only works with smaller projects.) The file is named like
the master document. No indices will be generated.
the root document. No indices will be generated.
.. autoattribute:: name

View File

@ -22,9 +22,9 @@ and output behavior.
.. _`docutils.conf`: https://docutils.sourceforge.io/docs/user/config.html
The configuration file is executed as Python code at build time (using
:func:`execfile`, and with the current directory set to its containing
directory), and therefore can execute arbitrarily complex code. Sphinx then
reads simple names from the file's namespace as its configuration.
:func:`importlib.import_module`, and with the current directory set to its
containing directory), and therefore can execute arbitrarily complex code.
Sphinx then reads simple names from the file's namespace as its configuration.
Important points to note:
@ -329,6 +329,8 @@ General configuration
* ``ref.python``
* ``misc.highlighting_failure``
* ``toc.circular``
* ``toc.excluded``
* ``toc.not_readable``
* ``toc.secnum``
* ``epub.unknown_project_files``
* ``epub.duplicated_toc_entry``
@ -360,6 +362,10 @@ General configuration
Added ``epub.duplicated_toc_entry``
.. versionchanged:: 4.3
Added ``toc.excluded`` and ``toc.not_readable``
.. confval:: needs_sphinx
If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will
@ -453,7 +459,7 @@ General configuration
As a special character, ``%s`` will be replaced to figure number.
Default is to use ``'Fig. %s'`` for ``'figure'``, ``'Table %s'`` for
``'table'``, ``'Listing %s'`` for ``'code-block'`` and ``'Section'`` for
``'table'``, ``'Listing %s'`` for ``'code-block'`` and ``'Section %s'`` for
``'section'``.
.. versionadded:: 1.3
@ -802,6 +808,13 @@ documentation on :ref:`intl` for details.
.. versionchanged:: 1.5
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
.. versionadded:: 1.1
@ -992,7 +1005,7 @@ that use Sphinx's HTMLWriter class.
to indicate the location of document using `The Canonical Link Relation`_.
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
@ -1309,11 +1322,11 @@ that use Sphinx's HTMLWriter class.
.. confval:: html_use_opensearch
If nonempty, an `OpenSearch <https://www.opensearch.org/>`_ description
file will be output, and all pages will contain a ``<link>`` tag referring
to it. Since OpenSearch doesn't support relative URLs for its search page
location, the value of this option must be the base URL from which these
documents are served (without trailing slash), e.g.
If nonempty, an `OpenSearch <https://github.com/dewitt/opensearch>`_
description file will be output, and all pages will contain a ``<link>``
tag referring to it. Since OpenSearch doesn't support relative URLs for
its search page location, the value of this option must be the base URL
from which these documents are served (without trailing slash), e.g.
``"https://docs.python.org"``. The default is ``''``.
.. confval:: html_file_suffix
@ -2331,6 +2344,8 @@ These options influence manual page output.
*description*
Description of the manual page. This is used in the NAME section.
Can be an empty string if you do not want to automatically generate
the NAME section.
*authors*
A list of strings with authors, or a single string. Can be an empty
@ -2484,6 +2499,13 @@ These options influence Texinfo output.
.. 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:
@ -2527,11 +2549,34 @@ Options for the linkcheck builder
.. versionadded:: 1.1
.. confval:: linkcheck_allowed_redirects
A dictionary that maps a pattern of the source URI to a pattern of the canonical
URI. The linkcheck builder treats the redirected link as "working" when:
- the link in the document matches the source URI pattern, and
- the redirect location matches the canonical URI pattern.
Example:
.. code-block:: python
linkcheck_allowed_redirects = {
# All HTTP redirections from the source URI to the canonical URI will be treated as "working".
r'https://sphinx-doc\.org/.*': r'https://sphinx-doc\.org/en/master/.*'
}
If set, linkcheck builder will emit a warning when disallowed redirection
found. It's useful to detect unexpected redirects under :option:`the
warn-is-error mode <sphinx-build -W>`.
.. versionadded:: 4.1
.. confval:: linkcheck_request_headers
A dictionary that maps baseurls to HTTP request headers.
The key is a URL base string like ``"https://sphinx-doc.org/"``. To specify
The key is a URL base string like ``"https://www.sphinx-doc.org/"``. To specify
headers for other hosts, ``"*"`` can be used. It matches all hosts only when
the URL does not match other settings.
@ -2542,7 +2587,7 @@ Options for the linkcheck builder
.. code-block:: python
linkcheck_request_headers = {
"https://sphinx-doc.org/": {
"https://www.sphinx-doc.org/": {
"Accept": "text/html",
"Accept-Encoding": "utf-8",
},
@ -2612,10 +2657,8 @@ Options for the linkcheck builder
A regular expression that matches a URI.
*auth_info*
Authentication information to use for that URI. The value can be anything
that is understood by the ``requests`` library (see `requests
Authentication <requests-auth>`_ for details).
.. _requests-auth: https://requests.readthedocs.io/en/master/user/authentication/
that is understood by the ``requests`` library (see :ref:`requests
Authentication <requests:authentication>` for details).
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
@ -2643,10 +2686,23 @@ Options for the linkcheck builder
doubling the wait time between attempts until it succeeds or exceeds the
``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
.. 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
---------------------------
@ -2689,6 +2745,14 @@ Options for the C domain
.. versionadded:: 3.0
.. confval:: c_extra_keywords
A list of identifiers to be recognized as keywords by the C parser.
It defaults to ``['alignas', 'alignof', 'bool', 'complex', 'imaginary',
'noreturn', 'static_assert', 'thread_local']``.
.. versionadded:: 4.0.3
.. confval:: c_allow_pre_v3
A boolean (default ``False``) controlling whether to parse and try to

View File

@ -353,6 +353,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
autodata
automethod
autoattribute
autoproperty
These work exactly like :rst:dir:`autoclass` etc.,
but do not offer the options used for automatic member documentation.
@ -422,6 +423,8 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
option.
.. versionchanged:: 2.0
:rst:dir:`autodecorator` added.
.. versionchanged:: 2.1
:rst:dir:`autoproperty` added.
.. versionchanged:: 3.4
:rst:dir:`autodata` and :rst:dir:`autoattribute` now have a ``no-value``
option.
@ -659,6 +662,13 @@ There are also config values that you can set:
.. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases
.. versionadded:: 3.3
.. confval:: autodoc_unqualified_typehints
If True, the leading module names of typehints of function signatures (ex.
``io.StringIO`` -> ``StringIO``). Defaults to False.
.. versionadded:: 4.4
.. confval:: autodoc_preserve_defaults
If True, the default argument values of functions will be not evaluated on
@ -765,8 +775,6 @@ needed docstring processing in event :event:`autodoc-process-docstring`:
.. event:: autodoc-process-bases (app, name, obj, options, bases)
.. versionadded:: 4.1
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
modify **in place** to change what Sphinx puts into the output. It's
@ -778,6 +786,12 @@ needed docstring processing in event :event:`autodoc-process-docstring`:
:param options: the options given to the class directive
: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
----------------

View File

@ -201,6 +201,25 @@ also use these config values:
.. 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
A dict mapping object names to filenames. This is necessary to avoid
@ -210,6 +229,7 @@ also use these config values:
.. versionadded:: 3.2
.. _autosummary-customizing-templates:
Customizing templates
---------------------

View File

@ -83,7 +83,7 @@ It adds these directives:
.. versionadded:: 1.6
.. rst:directive:option:: class: class names
:type: a list of class names separeted by spaces
:type: a list of class names separated by spaces
The class name of the graph.
@ -139,7 +139,7 @@ It adds these directives:
.. versionadded:: 1.6
.. rst:directive:option:: class: class names
:type: a list of class names separeted by spaces
:type: a list of class names separated by spaces
The class name of the graph.
@ -191,7 +191,7 @@ It adds these directives:
.. versionadded:: 1.6
.. rst:directive:option:: class: class names
:type: a list of class names separeted by spaces
:type: a list of class names separated by spaces
The class name of the graph.
@ -227,13 +227,13 @@ There are also these config values:
attribute must be set, such as ``"_top"`` and ``"_blank"``. For example, the
link in the following graph should work in the svg output: ::
.. graphviz::
.. graphviz::
digraph example {
a [label="sphinx", href="https://sphinx-doc.org", target="_top"];
b [label="other"];
a -> b;
}
digraph example {
a [label="sphinx", href="https://www.sphinx-doc.org/", target="_top"];
b [label="other"];
a -> b;
}
.. versionadded:: 1.0
Previously, output always was PNG.

View File

@ -10,6 +10,8 @@ This chapter describes the extensions bundled with Sphinx. For the API
documentation on writing your own extension, refer to :ref:`dev-extensions`.
.. _builtin-extensions:
Built-in extensions
-------------------
@ -38,6 +40,8 @@ These extensions are built in and can be activated by respective entries in the
viewcode
.. _third-party-extensions:
Third-party extensions
----------------------

View File

@ -148,6 +148,35 @@ linking:
exception is raised if the server has not issued a response for timeout
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
------------------------------------------------
@ -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
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
---------------------------------------------------------------

View File

@ -200,6 +200,11 @@ Sphinx but is set to automatically include it from a third-party site.
.. 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
The configuration options for MathJax v3 (which is used by default).

View File

@ -325,9 +325,9 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
**If True**::
def __init__(self):
\"\"\"
"""
This will be included in the docs because it has a docstring
\"\"\"
"""
def __init__(self):
# This will NOT be included in the docs
@ -509,7 +509,7 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
.. confval:: napoleon_preprocess_types
True to convert the type definitions in the docstrings as references.
Defaults to *True*.
Defaults to *False*.
.. versionadded:: 3.2.1
.. versionchanged:: 3.5

View File

@ -120,7 +120,7 @@ Chocolatey
$ choco install sphinx
You would need to `install Chocolatey
<https://chocolatey.org/install/>`_
<https://chocolatey.org/install>`_
before running this.
For more information, refer to the `chocolatey page`__.
@ -223,7 +223,7 @@ of images:
.. _Docker Hub: https://hub.docker.com/
.. _sphinxdoc/sphinx: https://hub.docker.com/repository/docker/sphinxdoc/sphinx
.. _sphinxdoc/sphinx-latexpdf: https://hub.docker.com/repository/docker/sphinxdoc/sphinx-latexpdf>
.. _sphinxdoc/sphinx-latexpdf: https://hub.docker.com/repository/docker/sphinxdoc/sphinx-latexpdf
Former one is used for standard usage of Sphinx, and latter one is mainly used for
PDF builds using LaTeX. Please choose one for your purpose.

View File

@ -48,8 +48,8 @@ Defining document structure
---------------------------
Let's assume you've run :program:`sphinx-quickstart`. It created a source
directory with :file:`conf.py` and a master document, :file:`index.rst`. The
main function of the :term:`master document` is to serve as a welcome page, and
directory with :file:`conf.py` and a root document, :file:`index.rst`. The
main function of the :term:`root document` is to serve as a welcome page, and
to contain the root of the "table of contents tree" (or *toctree*). This is one
of the main things that Sphinx adds to reStructuredText, a way to connect
multiple files to a single hierarchy of documents.
@ -74,14 +74,14 @@ multiple files to a single hierarchy of documents.
The ``toctree`` directive initially is empty, and looks like so:
.. code-block:: rest
.. code-block:: rst
.. toctree::
:maxdepth: 2
You add documents listing them in the *content* of the directive:
.. code-block:: rest
.. code-block:: rst
.. toctree::
:maxdepth: 2
@ -172,7 +172,7 @@ The most prominent domain is the Python domain. For example, to document
Python's built-in function ``enumerate()``, you would add this to one of your
source files.
.. code-block:: restructuredtext
.. code-block:: rst
.. py:function:: enumerate(sequence[, start=0])
@ -193,7 +193,7 @@ given, each in its own line.
The Python domain also happens to be the default domain, so you don't need to
prefix the markup with the domain name.
.. code-block:: restructuredtext
.. code-block:: rst
.. function:: enumerate(sequence[, start=0])

View File

@ -224,6 +224,8 @@ Internal linking is done via a special reST role provided by Sphinx, see the
section on specific markup, :ref:`ref-role`.
.. _rst-sections:
Sections
--------
@ -408,7 +410,27 @@ following the arguments and indicated by the colons). Options must be indented
to the same level as the directive content.
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

View File

@ -497,10 +497,10 @@ __ https://pygments.org/docs/lexers
Some Ruby code.
The directive's alias name :rst:dir:`sourcecode` works as well. This
directive takes a language name as an argument. It can be any lexer alias
supported by Pygments. If it is not given, the setting of
:rst:dir:`highlight` directive will be used. If not set,
:confval:`highlight_language` will be used.
directive takes a language name as an argument. It can be `any lexer alias
supported by Pygments <https://pygments.org/docs/lexers/>`_. If it is not
given, the setting of :rst:dir:`highlight` directive will be used.
If not set, :confval:`highlight_language` will be used.
.. versionchanged:: 2.0
The ``language`` argument becomes optional.

View File

@ -43,7 +43,7 @@ Most domains provide a number of :dfn:`object description directives`, used to
describe specific objects provided by modules. Each directive requires one or
more signatures to provide basic information about what is being described, and
the content should be the description. A domain will typically keep an
internal index of all entites to aid cross-referencing. Typically it will
internal index of all entities to aid cross-referencing. Typically it will
also add entries in the shown general index.
If you want to suppress the addition of an entry in the shown index, you can
give the directive option flag ``:noindexentry:``.
@ -125,6 +125,7 @@ In short:
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.
.. _python-domain:
The Python Domain
-----------------
@ -329,6 +330,13 @@ The following directives are provided for module and class contents:
Indicate the property is abstract.
.. rst:directive:option:: classmethod
:type: no value
Indicate the property is a classmethod.
.. versionaddedd: 4.2
.. rst:directive:option:: type: type of the property
:type: text
@ -670,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
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
.. c:macro:: name(arg list)
Describes a C macro, i.e., a C-language ``#define``, without the replacement
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
The function style variant.
@ -1446,14 +1497,23 @@ The ``cpp:namespace-pop`` directive undoes the most recent
Info field lists
~~~~~~~~~~~~~~~~~
The C++ directives support the following info fields (see also
:ref:`info-field-lists`):
All the C++ directives for declaring entities support the following
info fields (see also :ref:`info-field-lists`):
* `param`, `parameter`, `arg`, `argument`: Description of a parameter.
* `tparam`: Description of a template parameter.
* `returns`, `return`: Description of a return value.
* ``tparam``: Description of a template parameter.
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.
.. versionadded:: 4.3
The ``retval`` field type.
.. _cpp-roles:
Cross-referencing
@ -1673,7 +1733,7 @@ There is a set of directives allowing documenting command-line programs:
.. program:: svn
.. option:: -r revision
.. option:: -r <revision>
Specify the revision to work upon.

View File

@ -127,6 +127,10 @@ Builtin themes
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
These themes are:

View File

@ -18,8 +18,8 @@ install_requires = [
'sphinxcontrib-applehelp',
'sphinxcontrib-devhelp',
'sphinxcontrib-jsmath',
'sphinxcontrib-htmlhelp',
'sphinxcontrib-serializinghtml',
'sphinxcontrib-htmlhelp>=2.0.0',
'sphinxcontrib-serializinghtml>=1.1.5',
'sphinxcontrib-qthelp',
'Jinja2>=2.3',
'Pygments>=2.0',
@ -176,7 +176,7 @@ else:
setup(
name='Sphinx',
version=sphinx.__version__,
url='https://sphinx-doc.org/',
url='https://www.sphinx-doc.org/',
download_url='https://pypi.org/project/Sphinx/',
license='BSD',
author='Georg Brandl',
@ -207,6 +207,7 @@ setup(
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Framework :: Setuptools Plugin',

View File

@ -27,8 +27,8 @@ if 'PYTHONWARNINGS' not in os.environ:
warnings.filterwarnings('ignore', "'U' mode is deprecated",
DeprecationWarning, module='docutils.io')
__version__ = '4.1.0'
__released__ = '4.1.0' # used when Sphinx builds its own docs
__version__ = '4.4.0+'
__released__ = '4.4.0' # used when Sphinx builds its own docs
#: Version info for better programmatic use.
#:
@ -38,7 +38,7 @@ __released__ = '4.1.0' # used when Sphinx builds its own docs
#:
#: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``.
version_info = (4, 1, 0, 'final', 0)
version_info = (4, 4, 0, 'beta', 0)
package_dir = path.abspath(path.dirname(__file__))

View File

@ -528,6 +528,8 @@ class manpage(nodes.Inline, nodes.FixedTextElement):
def setup(app: "Sphinx") -> Dict[str, Any]:
from sphinx.util import docutils # lazy import
app.add_node(toctree)
app.add_node(desc)
@ -563,7 +565,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(start_of_file)
app.add_node(highlightlang)
app.add_node(tabular_col_spec)
app.add_node(meta)
app.add_node(pending_xref)
app.add_node(number_reference)
app.add_node(download_reference)
@ -571,6 +572,9 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(literal_strong)
app.add_node(manpage)
if docutils.__version_info__ < (0, 18):
app.add_node(meta)
return {
'version': 'builtin',
'parallel_read_safe': True,

View File

@ -12,7 +12,6 @@
import os
import pickle
import platform
import sys
import warnings
from collections import deque
@ -195,12 +194,6 @@ class Sphinx:
# say hello to the world
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 reason, 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
self.statuscode = 0
@ -284,7 +277,8 @@ class Sphinx:
self.config.language, self.config.source_encoding)
for catalog in repo.catalogs:
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 += [None]
@ -415,7 +409,7 @@ class Sphinx:
:param event: The name of target event
:param callback: Callback function for the event
:param priority: The priority of the callback. The callbacks will be invoked
in the order of *priority* in asending.
in order of *priority* (ascending).
:return: A listener ID. It can be used for :meth:`disconnect`.
.. versionchanged:: 3.0
@ -493,7 +487,7 @@ class Sphinx:
values accordingly.
:param name: The name of configuration value. It is recommended to be prefixed
:param name: The name of the configuration value. It is recommended to be prefixed
with the extension name (ex. ``html_logo``, ``epub_title``)
:param default: The default value of the configuration.
:param rebuild: The condition of rebuild. It must be one of those values:
@ -539,10 +533,10 @@ class Sphinx:
"""Register or override a Docutils translator class.
This is used to register a custom output translator or to replace a
builtin translator. This allows extensions to use custom translator
builtin translator. This allows extensions to use a custom translator
and define custom nodes for the translator (see :meth:`add_node`).
:param name: The name of builder for the translator
:param name: The name of the builder for the translator
:param translator_class: A translator class
:param override: If true, install the translator forcedly even if another translator
is already installed as the same name
@ -606,11 +600,11 @@ class Sphinx:
using :rst:role:`numref`.
:param node: A node class
:param figtype: The type of enumerable nodes. Each figtypes have individual numbering
sequences. As a system figtypes, ``figure``, ``table`` and
``code-block`` are defined. It is able to add custom nodes to these
default figtypes. It is also able to define new custom figtype if new
figtype is given.
:param figtype: The type of enumerable nodes. Each figtype has individual numbering
sequences. As system figtypes, ``figure``, ``table`` and
``code-block`` are defined. It is possible to add custom nodes to
these default figtypes. It is also possible to define new custom
figtype if a new figtype is given.
:param title_getter: A getter function to obtain the title of node. It takes an
instance of the enumerable node, and it must return its title as
string. The title is used to the default title of references for
@ -629,7 +623,7 @@ class Sphinx:
def add_directive(self, name: str, cls: Type[Directive], override: bool = False) -> None:
"""Register a Docutils directive.
:param name: The name of directive
:param name: The name of the directive
:param cls: A directive class
:param override: If true, install the directive forcedly even if another directive
is already installed as the same name
@ -755,9 +749,9 @@ class Sphinx:
Like :meth:`add_role`, but the role is added to the domain named
*domain*.
:param domain: The name of target domain
:param name: A name of role
:param role: A role function
:param domain: The name of the target domain
:param name: The name of the role
:param role: The role function
:param override: If true, install the role forcedly even if another role is already
installed as the same name
@ -773,8 +767,8 @@ class Sphinx:
Add a custom *index* class to the domain named *domain*.
:param domain: The name of target domain
:param index: A index class
:param domain: The name of the target domain
:param index: The index class
:param override: If true, install the index forcedly even if another index is
already installed as the same name
@ -942,8 +936,8 @@ class Sphinx:
Add *filename* to the list of JavaScript files that the default HTML
template will include in order of *priority* (ascending). The filename
must be relative to the HTML static path , or a full URI with scheme.
If the priority of JavaScript file is the same as others, the JavaScript
files will be included in order of the registration. If the keyword
If the priority of the JavaScript file is the same as others, the JavaScript
files will be included in order of registration. If the keyword
argument ``body`` is given, its value will be added between the
``<script>`` tags. Extra keyword arguments are included as attributes of
the ``<script>`` tag.
@ -971,7 +965,7 @@ class Sphinx:
* - 800
- default priority for :confval:`html_js_files`
A JavaScript file can be added to the specific HTML page when on extension
A JavaScript file can be added to the specific HTML page when an extension
calls this method on :event:`html-page-context` event.
.. versionadded:: 0.5
@ -993,8 +987,8 @@ class Sphinx:
Add *filename* to the list of CSS files that the default HTML template
will include in order of *priority* (ascending). The filename must be
relative to the HTML static path, or a full URI with scheme. If the
priority of CSS file is the same as others, the CSS files will be
included in order of the registration. The keyword arguments are also
priority of the CSS file is the same as others, the CSS files will be
included in order of registration. The keyword arguments are also
accepted for attributes of ``<link>`` tag.
Example::
@ -1022,15 +1016,15 @@ class Sphinx:
* - 800
- default priority for :confval:`html_css_files`
A CSS file can be added to the specific HTML page when on extension calls
A CSS file can be added to the specific HTML page when an extension calls
this method on :event:`html-page-context` event.
.. versionadded:: 1.0
.. versionchanged:: 1.6
Optional ``alternate`` and/or ``title`` attributes can be supplied
with the *alternate* (of boolean type) and *title* (a string)
arguments. The default is no title and *alternate* = ``False``. For
with the arguments *alternate* (a Boolean) and *title* (a string).
The default is no title and *alternate* = ``False``. For
more information, refer to the `documentation
<https://mdn.io/Web/CSS/Alternative_style_sheets>`__.
@ -1046,12 +1040,32 @@ class Sphinx:
if hasattr(self.builder, 'add_css_file'):
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,
after_hyperref: bool = False) -> None:
r"""Register a package to include in the LaTeX source code.
Add *packagename* to the list of packages that LaTeX source code will
include. If you provide *options*, it will be taken to `\usepackage`
include. If you provide *options*, it will be taken to the `\usepackage`
declaration. If you set *after_hyperref* truthy, the package will be
loaded after ``hyperref`` package.
@ -1087,7 +1101,7 @@ class Sphinx:
Add *cls* as a new documenter class for the :mod:`sphinx.ext.autodoc`
extension. It must be a subclass of
:class:`sphinx.ext.autodoc.Documenter`. This allows to auto-document
:class:`sphinx.ext.autodoc.Documenter`. This allows auto-documenting
new types of objects. See the source of the autodoc module for
examples on how to subclass :class:`Documenter`.
@ -1140,10 +1154,10 @@ class Sphinx:
"""Register a suffix of source files.
Same as :confval:`source_suffix`. The users can override this
using the setting.
using the config setting.
If *override* is True, the given *suffix* is forcedly installed even if
a same suffix is already installed.
the same suffix is already installed.
.. versionadded:: 1.8
"""
@ -1204,8 +1218,8 @@ class Sphinx:
def add_message_catalog(self, catalog: str, locale_dir: str) -> None:
"""Register a message catalog.
:param catalog: A name of catalog
:param locale_dir: The base path of message catalog
:param catalog: The name of the catalog
:param locale_dir: The base path of the message catalog
For more details, see :func:`sphinx.locale.get_translation()`.
@ -1216,7 +1230,7 @@ class Sphinx:
# ---- other methods -------------------------------------------------
def is_parallel_allowed(self, typ: str) -> bool:
"""Check parallel processing is allowed or not.
"""Check whether parallel processing is allowed or not.
:param typ: A type of processing; ``'read'`` or ``'write'``.
"""
@ -1250,6 +1264,18 @@ class Sphinx:
return True
def set_html_assets_policy(self, policy):
"""Set the policy to include assets in HTML pages.
- always: include the assets in all the pages
- per_page: include the assets only in pages where they are used
.. versionadded: 4.1
"""
if policy not in ('always', 'per_page'):
raise ValueError('policy %s is not supported' % policy)
self.registry.html_assets_policy = policy
@property
def html_themes(self) -> Dict[str, str]:
warnings.warn('app.html_themes is deprecated.',

View File

@ -68,7 +68,7 @@ class Builder:
# doctree versioning method
versioning_method = 'none'
versioning_compare = False
# allow parallel write_doc() calls
#: allow parallel write_doc() calls
allow_parallel = False
# support translation
use_message_catalog = True
@ -217,7 +217,8 @@ class Builder:
for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen",
len(catalogs), self.app.verbosity,
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:
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,

View File

@ -143,7 +143,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
embedded = True
# disable download role
download_support = False
# dont' create links to original images from images
# don't create links to original images from images
html_scaled_image_link = False
# don't generate search index or include search page
search = False
@ -323,14 +323,14 @@ class EpubBuilder(StandaloneHTMLBuilder):
# a) place them after the last existing footnote
# b) place them after an (empty) Footnotes rubric
# c) create an empty Footnotes rubric at the end of the document
fns = tree.traverse(nodes.footnote)
fns = list(tree.traverse(nodes.footnote))
if fns:
fn = fns[-1]
return fn.parent, fn.parent.index(fn) + 1
for node in tree.traverse(nodes.rubric):
if len(node) == 1 and node.astext() == FOOTNOTES_RUBRIC_NAME:
return node.parent, node.parent.index(node) + 1
doc = tree.traverse(nodes.document)[0]
doc = list(tree.traverse(nodes.document))[0]
rub = nodes.rubric()
rub.append(nodes.Text(FOOTNOTES_RUBRIC_NAME))
doc.append(rub)
@ -339,10 +339,10 @@ class EpubBuilder(StandaloneHTMLBuilder):
if show_urls == 'no':
return
if show_urls == 'footnote':
doc = tree.traverse(nodes.document)[0]
doc = list(tree.traverse(nodes.document))[0]
fn_spot, fn_idx = footnote_spot(tree)
nr = 1
for node in tree.traverse(nodes.reference):
for node in list(tree.traverse(nodes.reference)):
uri = node.get('refuri', '')
if (uri.startswith('http:') or uri.startswith('https:') or
uri.startswith('ftp:')) and uri not in node.astext():

View File

@ -26,6 +26,7 @@ from docutils.nodes import Node
from docutils.utils import relative_path
from sphinx import __display_version__, package_dir
from sphinx import version_info as sphinx_version
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.config import ENUM, Config
@ -202,7 +203,7 @@ class StandaloneHTMLBuilder(Builder):
super().__init__(app)
# CSS files
self.css_files: List[Dict[str, str]] = []
self.css_files: List[Stylesheet] = []
# JS files
self.script_files: List[JavaScript] = []
@ -286,13 +287,14 @@ class StandaloneHTMLBuilder(Builder):
if dark_style is not None:
self.dark_highlighter = PygmentsBridge('html', dark_style)
self.add_css_file('pygments_dark.css',
media='(prefers-color-scheme: dark)',
id='pygments_dark_css')
self.app.add_css_file('pygments_dark.css',
media='(prefers-color-scheme: dark)',
id='pygments_dark_css')
else:
self.dark_highlighter = None
def init_css_files(self) -> None:
self.css_files = []
self.add_css_file('pygments.css', priority=200)
self.add_css_file(self._get_style_filename(), priority=200)
@ -307,9 +309,10 @@ class StandaloneHTMLBuilder(Builder):
if '://' not in filename:
filename = posixpath.join('_static', filename)
self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore
self.css_files.append(Stylesheet(filename, **kwargs))
def init_js_files(self) -> None:
self.script_files = []
self.add_js_file('documentation_options.js', id="documentation_options",
data_url_root='', priority=200)
self.add_js_file('jquery.js', priority=200)
@ -468,8 +471,15 @@ class StandaloneHTMLBuilder(Builder):
else:
self.last_updated = None
logo = path.basename(self.config.html_logo) if self.config.html_logo else ''
favicon = path.basename(self.config.html_favicon) if self.config.html_favicon else ''
# If the logo or favicon are urls, keep them as-is, otherwise
# strip the relative path as the files will be copied into _static.
logo = self.config.html_logo or ''
favicon = self.config.html_favicon or ''
if not isurl(logo):
logo = path.basename(logo)
if not isurl(favicon):
favicon = path.basename(favicon)
self.relations = self.env.collect_relations()
@ -509,6 +519,7 @@ class StandaloneHTMLBuilder(Builder):
'language': self.config.language,
'css_files': self.css_files,
'sphinx_version': __display_version__,
'sphinx_version_tuple': sphinx_version,
'style': self._get_style_filename(),
'rellinks': rellinks,
'builder': self.name,

View File

@ -198,6 +198,9 @@ ADDITIONAL_SETTINGS: Dict[Any, Dict[str, Any]] = {
'polyglossia': '',
'babel': '\\usepackage{babel}',
'fontenc': '\\usepackage{xeCJK}',
# set formatcom=\xeCJKVerbAddon to prevent xeCJK from adding extra spaces in
# fancyvrb Verbatim environment.
'fvset': '\\fvset{fontsize=\\small,formatcom=\\xeCJKVerbAddon}',
},
('xelatex', 'el'): {
'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG,

View File

@ -45,7 +45,7 @@ class SubstitutionDefinitionsRemover(SphinxPostTransform):
formats = ('latex',)
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.substitution_definition):
for node in list(self.document.traverse(nodes.substitution_definition)):
node.parent.remove(node)
@ -81,7 +81,7 @@ class ShowUrlsTransform(SphinxPostTransform):
if show_urls is False or show_urls == 'no':
return
for node in self.document.traverse(nodes.reference):
for node in list(self.document.traverse(nodes.reference)):
uri = node.get('refuri', '')
if uri.startswith(URI_SCHEMES):
if uri.startswith('mailto:'):
@ -501,7 +501,7 @@ class BibliographyTransform(SphinxPostTransform):
def run(self, **kwargs: Any) -> None:
citations = thebibliography()
for node in self.document.traverse(nodes.citation):
for node in list(self.document.traverse(nodes.citation)):
node.parent.remove(node)
citations += node
@ -602,9 +602,9 @@ class IndexInSectionTitleTransform(SphinxPostTransform):
formats = ('latex',)
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.title):
for node in list(self.document.traverse(nodes.title)):
if isinstance(node.parent, nodes.section):
for i, index in enumerate(node.traverse(addnodes.index)):
for i, index in enumerate(list(node.traverse(addnodes.index))):
# move the index node next to the section title
node.remove(index)
node.parent.insert(i + 1, index)

View File

@ -255,7 +255,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
elif result.status == 'broken':
if self.app.quiet or self.app.warningiserror:
logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
location=(filename, result.lineno))
location=(result.docname, result.lineno))
else:
logger.info(red('broken ') + result.uri + red(' - ' + result.message))
self.write_entry('broken', result.docname, filename, result.lineno,
@ -272,8 +272,12 @@ class CheckExternalLinksBuilder(DummyBuilder):
except KeyError:
text, color = ('with unknown code', purple)
linkstat['text'] = text
logger.info(color('redirect ') + result.uri +
color(' - ' + text + ' to ' + result.message))
if self.config.linkcheck_allowed_redirects:
logger.warning('redirect ' + result.uri + ' - ' + text + ' to ' +
result.message, location=(result.docname, result.lineno))
else:
logger.info(color('redirect ') + result.uri +
color(' - ' + text + ' to ' + result.message))
self.write_entry('redirected ' + text, result.docname, filename,
result.lineno, result.uri + ' to ' + result.message)
else:
@ -374,6 +378,8 @@ class HyperlinkAvailabilityCheckWorker(Thread):
self.anchors_ignore = [re.compile(x)
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
in self.config.linkcheck_auth]
@ -496,15 +502,34 @@ class HyperlinkAvailabilityCheckWorker(Thread):
new_url = response.url
if anchor:
new_url += '#' + anchor
# history contains any redirects, get last
if response.history:
if allowed_redirect(req_url, new_url):
return 'working', '', 0
elif response.history:
# history contains any redirects, get last
code = response.history[-1].status_code
return 'redirected', new_url, code
else:
return 'redirected', new_url, 0
def allowed_redirect(url: str, new_url: str) -> bool:
for from_url, to_url in self.config.linkcheck_allowed_redirects.items():
if from_url.match(url) and to_url.match(new_url):
return True
return False
def check(docname: str) -> Tuple[str, str, int]:
# 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:')):
return 'unchecked', '', 0
elif not uri.startswith(('http:', 'https:')):
@ -667,11 +692,26 @@ def rewrite_github_anchor(app: Sphinx, uri: str) -> Optional[str]:
return None
def compile_linkcheck_allowed_redirects(app: Sphinx, config: Config) -> None:
"""Compile patterns in linkcheck_allowed_redirects to the regexp objects."""
for url, pattern in list(app.config.linkcheck_allowed_redirects.items()):
try:
app.config.linkcheck_allowed_redirects[re.compile(url)] = re.compile(pattern)
except re.error as exc:
logger.warning(__('Failed to compile regex in linkcheck_allowed_redirects: %r %s'),
exc.pattern, exc.msg)
finally:
# Remove the original regexp-string
app.config.linkcheck_allowed_redirects.pop(url)
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(CheckExternalLinksBuilder)
app.add_post_transform(HyperlinkCollector)
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_auth', [], None)
app.add_config_value('linkcheck_request_headers', {}, None)
app.add_config_value('linkcheck_retries', 1, None)
@ -684,7 +724,12 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None)
app.add_event('linkcheck-process-uri')
app.connect('linkcheck-process-uri', rewrite_github_anchor)
app.connect('config-inited', compile_linkcheck_allowed_redirects, priority=800)
# FIXME: Disable URL rewrite handler for github.com temporarily.
# ref: https://github.com/sphinx-doc/sphinx/issues/9435
# app.connect('linkcheck-process-uri', rewrite_github_anchor)
return {
'version': 'builtin',

View File

@ -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_show_urls', 'footnote', None)
app.add_config_value('texinfo_no_detailmenu', False, None)
app.add_config_value('texinfo_cross_references', True, None)
return {
'version': 'builtin',

View File

@ -101,7 +101,7 @@ def jobs_argument(value: str) -> int:
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]',
epilog=__('For more information, visit <http://sphinx-doc.org/>.'),
epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
description=__("""
Generate documentation from source files.

View File

@ -95,6 +95,12 @@ def is_path(x: str) -> str:
return x
def is_path_or_empty(x: str) -> str:
if x == '':
return x
return is_path(x)
def allow_empty(x: str) -> str:
return x
@ -223,7 +229,7 @@ def ask_user(d: Dict) -> None:
print(__('sphinx-quickstart will not overwrite existing Sphinx projects.'))
print()
d['path'] = do_prompt(__('Please enter a new root path (or just Enter to exit)'),
'', is_path)
'', is_path_or_empty)
if not d['path']:
sys.exit(1)
@ -461,7 +467,7 @@ def get_parser() -> argparse.ArgumentParser:
)
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] <PROJECT_DIR>',
epilog=__("For more information, visit <http://sphinx-doc.org/>."),
epilog=__("For more information, visit <https://www.sphinx-doc.org/>."),
description=description)
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet',

View File

@ -56,7 +56,7 @@ def is_serializable(obj: Any) -> bool:
class ENUM:
"""represents the config value should be a one of candidates.
"""Represents the candidates which a config value should be one of.
Example:
app.add_config_value('latex_show_urls', 'no', None, ENUM('no', 'footnote', 'inline'))
@ -103,6 +103,7 @@ class Config:
'language': (None, 'env', [str]),
'locale_dirs': (['locales'], 'env', []),
'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]),
'gettext_allow_fuzzy_translations': (False, 'gettext', []),
'master_doc': ('index', 'env', []),
'root_doc': (lambda config: config.master_doc, 'env', []),
@ -215,7 +216,8 @@ class Config:
def pre_init_values(self) -> None:
"""
Initialize some limited config variables before initialize i18n and loading extensions
Initialize some limited config variables before initializing i18n and loading
extensions.
"""
variables = ['needs_sphinx', 'suppress_warnings', 'language', 'locale_dirs']
for name in variables:
@ -343,7 +345,7 @@ def eval_config_file(filename: str, tags: Optional[Tags]) -> Dict[str, Any]:
def convert_source_suffix(app: "Sphinx", config: Config) -> None:
"""This converts old styled source_suffix to new styled one.
"""Convert old styled source_suffix to new styled one.
* old style: str or list
* new style: a dict which maps from fileext to filetype
@ -371,7 +373,7 @@ def convert_highlight_options(app: "Sphinx", config: Config) -> None:
"""Convert old styled highlight_options to new styled one.
* old style: options
* new style: dict that maps language names to options
* new style: a dict which maps from language name to options
"""
options = config.highlight_options
if options and not all(isinstance(v, dict) for v in options.values()):
@ -392,7 +394,7 @@ def init_numfig_format(app: "Sphinx", config: Config) -> None:
def correct_copyright_year(app: "Sphinx", config: Config) -> None:
"""correct values of copyright year that are not coherent with
"""Correct values of copyright year that are not coherent with
the SOURCE_DATE_EPOCH environment variable (if set)
See https://reproducible-builds.org/specs/source-date-epoch/
@ -405,7 +407,7 @@ def correct_copyright_year(app: "Sphinx", config: Config) -> None:
def check_confval_types(app: "Sphinx", config: Config) -> None:
"""check all values for deviation from the default value's type, since
"""Check all values for deviation from the default value's type, since
that can result in TypeErrors all over the place NB.
"""
for confval in config:
@ -414,7 +416,7 @@ def check_confval_types(app: "Sphinx", config: Config) -> None:
if hasattr(default, '__call__'):
default = default(config) # evaluate default value
if default is None and not annotations:
continue # neither inferrable nor expliclitly annotated types
continue # neither inferable nor expliclitly annotated types
if annotations is Any:
# any type of value is accepted
@ -469,7 +471,7 @@ def check_primary_domain(app: "Sphinx", config: Config) -> None:
def check_root_doc(app: "Sphinx", env: "BuildEnvironment", added: Set[str],
changed: Set[str], removed: Set[str]) -> Set[str]:
"""Adjust root_doc to 'contents' to support an old project which does not have
no root_doc setting.
any root_doc setting.
"""
if (app.config.root_doc == 'index' and
'index' not in app.project.docnames and

View File

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

View File

@ -14,7 +14,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast
from docutils import nodes
from docutils.nodes import Node, make_id, system_message
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.deprecation import RemovedInSphinx60Warning
@ -27,6 +28,15 @@ from sphinx.util.nodes import set_source_info
from sphinx.util.osutil import SEP, os_path, relpath
from sphinx.util.typing import OptionSpec
try:
from docutils.nodes import meta as meta_node # type: ignore
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
from docutils.parsers.rst.directives.html import MetaBody
meta_node = MetaBody.meta
if TYPE_CHECKING:
from sphinx.application import Sphinx
@ -60,19 +70,19 @@ class Figure(images.Figure):
return [figure_node]
class Meta(html.Meta, SphinxDirective):
class Meta(MetaBase, SphinxDirective):
def run(self) -> List[Node]:
result = super().run()
for node in result:
if (isinstance(node, nodes.pending) and
isinstance(node.details['nodes'][0], html.MetaBody.meta)):
isinstance(node.details['nodes'][0], meta_node)):
meta = node.details['nodes'][0]
meta.source = self.env.doc2path(self.env.docname)
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
meta.__class__ = addnodes.meta # type: ignore
meta.__class__ = addnodes.meta
return result
@ -152,6 +162,7 @@ class Code(SphinxDirective):
def run(self) -> List[Node]:
self.assert_has_content()
set_classes(self.options)
code = '\n'.join(self.content)
node = nodes.literal_block(code, code,
classes=self.options.get('classes', []),

View File

@ -36,7 +36,7 @@ from sphinx.util.cfamily import (ASTAttribute, ASTBaseBase, ASTBaseParenExprList
float_literal_suffix_re, hex_literal_re, identifier_re,
integer_literal_re, integers_literal_suffix_re,
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.nodes import make_refnode
from sphinx.util.typing import OptionSpec
@ -55,10 +55,15 @@ _keywords = [
'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long',
'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct',
'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
'_Alignas', 'alignas', '_Alignof', 'alignof', '_Atomic', '_Bool', 'bool',
'_Complex', 'complex', '_Generic', '_Imaginary', 'imaginary',
'_Noreturn', 'noreturn', '_Static_assert', 'static_assert',
'_Thread_local', 'thread_local',
'_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex',
'_Decimal32', '_Decimal64', '_Decimal128',
'_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local',
]
# These are only keyword'y when the corresponding headers are included.
# They are used as default value for c_extra_keywords.
_macroKeywords = [
'alignas', 'alignof', 'bool', 'complex', 'imaginary', 'noreturn', 'static_assert',
'thread_local',
]
# these are ordered by preceedence
@ -87,6 +92,34 @@ _id_prefix = [None, 'c.', 'Cv2.']
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_simple_type_specifiers_re = re.compile(r"""(?x)
\b(
void|_Bool|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
)\b
""")
class _DuplicateSymbolError(Exception):
def __init__(self, symbol: "Symbol", declaration: "ASTDeclaration") -> None:
@ -604,14 +637,20 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def __init__(self, name: str) -> None:
self.name = name
self.names = name.split()
def _stringify(self, transform: StringifyTransform) -> str:
return self.name
return ' '.join(self.names)
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += addnodes.desc_sig_keyword_type(self.name, self.name)
first = True
for n in self.names:
if not first:
signode += addnodes.desc_sig_space()
else:
first = False
signode += addnodes.desc_sig_keyword_type(n, n)
class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
@ -2118,15 +2157,6 @@ class Symbol:
class DefinitionParser(BaseParser):
# those without signedness and size modifiers
# see https://en.cppreference.com/w/cpp/language/types
_simple_fundamental_types = (
'void', '_Bool', 'bool', 'char', 'int', 'float', 'double',
'__int64',
)
_prefix_keys = ('struct', 'enum', 'union')
@property
def language(self) -> str:
return 'C'
@ -2225,7 +2255,7 @@ class DefinitionParser(BaseParser):
def _parse_initializer_list(self, name: str, open: str, close: str
) -> Tuple[List[ASTExpression], bool]:
# Parse open and close with the actual initializer-list inbetween
# Parse open and close with the actual initializer-list in between
# -> initializer-clause '...'[opt]
# | initializer-list ',' initializer-clause '...'[opt]
# TODO: designators
@ -2473,7 +2503,7 @@ class DefinitionParser(BaseParser):
def _parse_expression(self) -> ASTExpression:
# -> assignment-expression
# | expression "," assignment-expresion
# | expression "," assignment-expression
# TODO: actually parse the second production
return self._parse_assignment_expression()
@ -2536,6 +2566,12 @@ class DefinitionParser(BaseParser):
if identifier in _keywords:
self.fail("Expected identifier in nested name, "
"got keyword: %s" % identifier)
if self.matched_text in self.config.c_extra_keywords:
msg = "Expected identifier, got user-defined keyword: %s." \
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ "Currently c_extra_keywords is %s."
self.fail(msg % (self.matched_text,
str(self.config.c_extra_keywords)))
ident = ASTIdentifier(identifier)
names.append(ident)
@ -2545,40 +2581,16 @@ class DefinitionParser(BaseParser):
return ASTNestedName(names, rooted)
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
# fundamental types
# fundamental types, https://en.cppreference.com/w/c/language/type
# and extensions
self.skip_ws()
for t in self._simple_fundamental_types:
if self.skip_word(t):
return ASTTrailingTypeSpecFundamental(t)
# TODO: this could/should be more strict
elements = []
if self.skip_word_and_ws('signed'):
elements.append('signed')
elif self.skip_word_and_ws('unsigned'):
elements.append('unsigned')
while 1:
if self.skip_word_and_ws('short'):
elements.append('short')
elif self.skip_word_and_ws('long'):
elements.append('long')
else:
break
if self.skip_word_and_ws('char'):
elements.append('char')
elif self.skip_word_and_ws('int'):
elements.append('int')
elif self.skip_word_and_ws('double'):
elements.append('double')
elif self.skip_word_and_ws('__int64'):
elements.append('__int64')
if len(elements) > 0:
return ASTTrailingTypeSpecFundamental(' '.join(elements))
if self.match(_simple_type_specifiers_re):
return ASTTrailingTypeSpecFundamental(self.matched_text)
# prefixed
prefix = None
self.skip_ws()
for k in self._prefix_keys:
for k in ('struct', 'enum', 'union'):
if self.skip_word_and_ws(k):
prefix = k
break
@ -2712,6 +2724,12 @@ class DefinitionParser(BaseParser):
if self.matched_text in _keywords:
self.fail("Expected identifier, "
"got keyword: %s" % self.matched_text)
if self.matched_text in self.config.c_extra_keywords:
msg = "Expected identifier, got user-defined keyword: %s." \
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ "Currently c_extra_keywords is %s."
self.fail(msg % (self.matched_text,
str(self.config.c_extra_keywords)))
identifier = ASTIdentifier(self.matched_text)
declId = ASTNestedName([identifier], rooted=False)
else:
@ -3112,16 +3130,6 @@ class CObject(ObjectDescription[ASTDeclaration]):
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 = {
'noindexentry': directives.flag,
}
@ -3324,13 +3332,31 @@ class CMemberObject(CObject):
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):
object_type = 'function'
doc_field_types = _function_doc_field_types.copy()
class CMacroObject(CObject):
object_type = 'macro'
doc_field_types = _function_doc_field_types.copy()
class CStructObject(CObject):
object_type = 'struct'
@ -3371,13 +3397,13 @@ class CNamespaceObject(SphinxDirective):
stack: List[Symbol] = []
else:
parser = DefinitionParser(self.arguments[0],
location=self.get_source_info(),
location=self.get_location(),
config=self.env.config)
try:
name = parser.parse_namespace_object()
parser.assert_end()
except DefinitionError as e:
logger.warning(e, location=self.get_source_info())
logger.warning(e, location=self.get_location())
name = _make_phony_error_name()
symbol = rootSymbol.add_name(name)
stack = [symbol]
@ -3398,13 +3424,13 @@ class CNamespacePushObject(SphinxDirective):
if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
return []
parser = DefinitionParser(self.arguments[0],
location=self.get_source_info(),
location=self.get_location(),
config=self.env.config)
try:
name = parser.parse_namespace_object()
parser.assert_end()
except DefinitionError as e:
logger.warning(e, location=self.get_source_info())
logger.warning(e, location=self.get_location())
name = _make_phony_error_name()
oldParent = self.env.temp_data.get('c:parent_symbol', None)
if not oldParent:
@ -3428,8 +3454,8 @@ class CNamespacePopObject(SphinxDirective):
def run(self) -> List[Node]:
stack = self.env.temp_data.get('c:namespace_stack', None)
if not stack or len(stack) == 0:
logger.warning("C namespace pop on empty stack. Defaulting to gobal scope.",
location=self.get_source_info())
logger.warning("C namespace pop on empty stack. Defaulting to global scope.",
location=self.get_location())
stack = []
else:
stack.pop()
@ -3611,7 +3637,7 @@ class CAliasObject(ObjectDescription):
" Requested 'noroot' but 'maxdepth' 1."
" When skipping the root declaration,"
" need 'maxdepth' 0 for infinite or at least 2.",
location=self.get_source_info())
location=self.get_location())
signatures = self.get_signatures()
for i, sig in enumerate(signatures):
node.append(AliasNode(sig, aliasOptions, self.state.document, env=self.env))
@ -3644,7 +3670,7 @@ class CXRefRole(XRefRole):
return super().run()
text = self.text.replace('\n', ' ')
parser = DefinitionParser(text, location=self.get_source_info(),
parser = DefinitionParser(text, location=self.get_location(),
config=self.env.config)
try:
parser.parse_xref_object()
@ -3669,7 +3695,7 @@ class CXRefRole(XRefRole):
msg = "{}: Pre-v3 C type role ':c:type:`{}`' converted to ':c:expr:`{}`'."
msg += "\nThe original parsing error was:\n{}"
msg = msg.format(RemovedInSphinx50Warning.__name__, text, text, eOrig)
logger.warning(msg, location=self.get_source_info())
logger.warning(msg, location=self.get_location())
return [signode], []
@ -3685,14 +3711,14 @@ class CExprRole(SphinxRole):
def run(self) -> Tuple[List[Node], List[system_message]]:
text = self.text.replace('\n', ' ')
parser = DefinitionParser(text, location=self.get_source_info(),
parser = DefinitionParser(text, location=self.get_location(),
config=self.env.config)
# attempt to mimic XRefRole classes, except that...
try:
ast = parser.parse_expression()
except DefinitionError as ex:
logger.warning('Unparseable C expression: %r\n%s', text, ex,
location=self.get_source_info())
location=self.get_location())
# see below
return [addnodes.desc_inline('c', text, text, classes=[self.class_type])], []
parentSymbol = self.env.temp_data.get('c:parent_symbol', None)
@ -3878,6 +3904,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(CDomain)
app.add_config_value("c_id_attributes", [], 'env')
app.add_config_value("c_paren_attributes", [], 'env')
app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
app.add_post_transform(AliasTransform)
app.add_config_value("c_allow_pre_v3", False, 'env')

View File

@ -144,7 +144,7 @@ T = TypeVar('T')
simple-type-specifier ->
::[opt] nested-name-specifier[opt] type-name
| ::[opt] nested-name-specifier "template" simple-template-id
| "char" | "bool" | ect.
| "char" | "bool" | etc.
| decltype-specifier
| elaborated-type-specifier ->
class-key attribute-specifier-seq[opt] ::[opt]
@ -162,7 +162,7 @@ T = TypeVar('T')
trailing-type-specifier ->
rest-of-trailing
("class" | "struct" | "union" | "typename") rest-of-trailing
build-in -> "char" | "bool" | ect.
built-in -> "char" | "bool" | etc.
decltype-specifier
rest-of-trailing -> (with some simplification)
"::"[opt] list-of-elements-separated-by-::
@ -198,7 +198,7 @@ T = TypeVar('T')
| "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
cv-qualifier-seq[opt]
# function_object must use a parameters-and-qualifiers, the others may
# use it (e.g., function poitners)
# use it (e.g., function pointers)
parameters-and-qualifiers ->
"(" parameter-clause ")" attribute-specifier-seq[opt]
cv-qualifier-seq[opt] ref-qualifier[opt]
@ -334,6 +334,31 @@ _keywords = [
'while', 'xor', 'xor_eq'
]
_simple_type_specifiers_re = re.compile(r"""(?x)
\b(
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
)\b
""")
_max_id = 4
_id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4']
# Ids are used in lookup keys which are used across pickled files,
@ -449,11 +474,23 @@ _id_fundamental_v2 = {
'long long int': 'x',
'signed long long': 'x',
'signed long long int': 'x',
'__int64': 'x',
'unsigned long long': 'y',
'unsigned long long int': 'y',
'__int128': 'n',
'signed __int128': 'n',
'unsigned __int128': 'o',
'float': 'f',
'double': 'd',
'long double': 'e',
'__float80': 'e', '_Float64x': 'e',
'__float128': 'g', '_Float128': 'g',
'float _Complex': 'Cf', '_Complex float': 'Cf',
'double _Complex': 'Cd', '_Complex double': 'Cd',
'long double _Complex': 'Ce', '_Complex long double': 'Ce',
'float _Imaginary': 'f', '_Imaginary float': 'f',
'double _Imaginary': 'd', '_Imaginary double': 'd',
'long double _Imaginary': 'e', '_Imaginary long double': 'e',
'auto': 'Da',
'decltype(auto)': 'Dc',
'std::nullptr_t': 'Dn'
@ -1672,7 +1709,7 @@ class ASTOperatorBuildIn(ASTOperator):
else:
ids = _id_operator_v2
if self.op not in ids:
raise Exception('Internal error: Build-in operator "%s" can not '
raise Exception('Internal error: Built-in operator "%s" can not '
'be mapped to an id.' % self.op)
return ids[self.op]
@ -1817,31 +1854,38 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def __init__(self, name: str) -> None:
self.name = name
self.names = name.split()
def _stringify(self, transform: StringifyTransform) -> str:
return self.name
return ' '.join(self.names)
def get_id(self, version: int) -> str:
if version == 1:
res = []
for a in self.name.split(' '):
for a in self.names:
if a in _id_fundamental_v1:
res.append(_id_fundamental_v1[a])
else:
res.append(a)
return '-'.join(res)
if self.name not in _id_fundamental_v2:
txt = str(self)
if txt not in _id_fundamental_v2:
raise Exception(
'Semi-internal error: Fundamental type "%s" can not be mapped '
'to an id. Is it a true fundamental type? If not so, the '
'parser should have rejected it.' % self.name)
return _id_fundamental_v2[self.name]
'to an ID. Is it a true fundamental type? If not so, the '
'parser should have rejected it.' % txt)
return _id_fundamental_v2[txt]
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += addnodes.desc_sig_keyword_type(self.name, self.name)
first = True
for n in self.names:
if not first:
signode += addnodes.desc_sig_space()
else:
first = False
signode += addnodes.desc_sig_keyword_type(n, n)
class ASTTrailingTypeSpecDecltypeAuto(ASTTrailingTypeSpec):
@ -2435,7 +2479,7 @@ class ASTDeclaratorNameParamQual(ASTDeclarator):
def get_type_id(self, version: int, returnTypeId: str) -> str:
assert version >= 2
res = []
# TOOD: can we actually have both array ops and paramQual?
# TODO: can we actually have both array ops and paramQual?
res.append(self.get_ptr_suffix_id(version))
if self.paramQual:
res.append(self.get_modifiers_id(version))
@ -4996,15 +5040,6 @@ class Symbol:
class DefinitionParser(BaseParser):
# those without signedness and size modifiers
# see https://en.cppreference.com/w/cpp/language/types
_simple_fundemental_types = (
'void', 'bool', 'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
'int', 'float', 'double', 'auto'
)
_prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')
@property
def language(self) -> str:
return 'C++'
@ -5182,7 +5217,7 @@ class DefinitionParser(BaseParser):
) -> Tuple[List[Union[ASTExpression,
ASTBracedInitList]],
bool]:
# Parse open and close with the actual initializer-list inbetween
# Parse open and close with the actual initializer-list in between
# -> initializer-clause '...'[opt]
# | initializer-list ',' initializer-clause '...'[opt]
self.skip_ws()
@ -5281,7 +5316,7 @@ class DefinitionParser(BaseParser):
if cast is not None:
prefixType = "cast"
if not self.skip_string("<"):
self.fail("Expected '<' afer '%s'." % cast)
self.fail("Expected '<' after '%s'." % cast)
typ = self._parse_type(False)
self.skip_ws()
if not self.skip_string_and_ws(">"):
@ -5617,7 +5652,7 @@ class DefinitionParser(BaseParser):
def _parse_expression(self) -> ASTExpression:
# -> assignment-expression
# | expression "," assignment-expresion
# | expression "," assignment-expression
exprs = [self._parse_assignment_expression(inTemplate=False)]
while True:
self.skip_ws()
@ -5821,33 +5856,11 @@ class DefinitionParser(BaseParser):
# ==========================================================================
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
# fundemental types
# fundamental types, https://en.cppreference.com/w/cpp/language/type
# and extensions
self.skip_ws()
for t in self._simple_fundemental_types:
if self.skip_word(t):
return ASTTrailingTypeSpecFundamental(t)
# TODO: this could/should be more strict
elements = []
if self.skip_word_and_ws('signed'):
elements.append('signed')
elif self.skip_word_and_ws('unsigned'):
elements.append('unsigned')
while 1:
if self.skip_word_and_ws('short'):
elements.append('short')
elif self.skip_word_and_ws('long'):
elements.append('long')
else:
break
if self.skip_word_and_ws('char'):
elements.append('char')
elif self.skip_word_and_ws('int'):
elements.append('int')
elif self.skip_word_and_ws('double'):
elements.append('double')
if len(elements) > 0:
return ASTTrailingTypeSpecFundamental(' '.join(elements))
if self.match(_simple_type_specifiers_re):
return ASTTrailingTypeSpecFundamental(self.matched_text)
# decltype
self.skip_ws()
@ -5867,7 +5880,7 @@ class DefinitionParser(BaseParser):
# prefixed
prefix = None
self.skip_ws()
for k in self._prefix_keys:
for k in ('class', 'struct', 'enum', 'union', 'typename'):
if self.skip_word_and_ws(k):
prefix = k
break
@ -5923,13 +5936,6 @@ class DefinitionParser(BaseParser):
'Expecting "," or ")" in parameters-and-qualifiers, '
'got "%s".' % self.current_char)
# TODO: why did we have this bail-out?
# does it hurt to parse the extra stuff?
# it's needed for pointer to member functions
if paramMode != 'function' and False:
return ASTParametersQualifiers(
args, None, None, None, None, None, None, None)
self.skip_ws()
const = self.skip_word_and_ws('const')
volatile = self.skip_word_and_ws('volatile')
@ -5976,7 +5982,8 @@ class DefinitionParser(BaseParser):
self.skip_ws()
initializer = None
if self.skip_string('='):
# if this is a function pointer we should not swallow an initializer
if paramMode == 'function' and self.skip_string('='):
self.skip_ws()
valid = ('0', 'delete', 'default')
for w in valid:
@ -6351,7 +6358,7 @@ class DefinitionParser(BaseParser):
if outer in ('type', 'function'):
# We allow type objects to just be a name.
# Some functions don't have normal return types: constructors,
# destrutors, cast operators
# destructors, cast operators
prevErrors = []
startPos = self.pos
# first try without the type
@ -6534,7 +6541,7 @@ class DefinitionParser(BaseParser):
# ==========================================================================
def _parse_template_paramter(self) -> ASTTemplateParam:
def _parse_template_parameter(self) -> ASTTemplateParam:
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
@ -6555,7 +6562,7 @@ class DefinitionParser(BaseParser):
self.fail("Expected 'typename' or 'class' after "
"template template parameter list.")
else:
self.fail("Expected 'typename' or 'class' in tbe "
self.fail("Expected 'typename' or 'class' in the "
"beginning of template type parameter.")
self.skip_ws()
parameterPack = self.skip_string('...')
@ -6606,7 +6613,7 @@ class DefinitionParser(BaseParser):
pos = self.pos
err = None
try:
param = self._parse_template_paramter()
param = self._parse_template_parameter()
templateParams.append(param)
except DefinitionError as eParam:
self.pos = pos
@ -6927,18 +6934,10 @@ def _make_phony_error_name() -> ASTNestedName:
class CPPObject(ObjectDescription[ASTDeclaration]):
"""Description of a C++ language object."""
doc_field_types = [
GroupedField('parameter', label=_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
can_collapse=True),
doc_field_types: List[Field] = [
GroupedField('template parameter', label=_('Template Parameters'),
names=('tparam', 'template parameter'),
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 = {
@ -7005,7 +7004,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId):
logger.warning('Index id generation for C++ object "%s" failed, please '
'report as bug (id=%s).', ast, newestId,
location=self.get_source_info())
location=self.get_location())
name = ast.symbol.get_full_nested_name().get_display_string().lstrip(':')
# Add index entry, but not if it's a declaration inside a concept
@ -7088,7 +7087,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
logger.warning(msg.format(
str(parentSymbol.get_full_nested_name()),
self.name, self.arguments[0]
), location=self.get_source_info())
), location=self.get_location())
name = _make_phony_error_name()
symbol = parentSymbol.add_name(name)
env.temp_data['cpp:last_symbol'] = symbol
@ -7174,6 +7173,20 @@ class CPPMemberObject(CPPObject):
class CPPFunctionObject(CPPObject):
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):
object_type = 'class'
@ -7216,13 +7229,13 @@ class CPPNamespaceObject(SphinxDirective):
stack: List[Symbol] = []
else:
parser = DefinitionParser(self.arguments[0],
location=self.get_source_info(),
location=self.get_location(),
config=self.config)
try:
ast = parser.parse_namespace_object()
parser.assert_end()
except DefinitionError as e:
logger.warning(e, location=self.get_source_info())
logger.warning(e, location=self.get_location())
name = _make_phony_error_name()
ast = ASTNamespace(name, None)
symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix)
@ -7244,13 +7257,13 @@ class CPPNamespacePushObject(SphinxDirective):
if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
return []
parser = DefinitionParser(self.arguments[0],
location=self.get_source_info(),
location=self.get_location(),
config=self.config)
try:
ast = parser.parse_namespace_object()
parser.assert_end()
except DefinitionError as e:
logger.warning(e, location=self.get_source_info())
logger.warning(e, location=self.get_location())
name = _make_phony_error_name()
ast = ASTNamespace(name, None)
oldParent = self.env.temp_data.get('cpp:parent_symbol', None)
@ -7275,8 +7288,8 @@ class CPPNamespacePopObject(SphinxDirective):
def run(self) -> List[Node]:
stack = self.env.temp_data.get('cpp:namespace_stack', None)
if not stack or len(stack) == 0:
logger.warning("C++ namespace pop on empty stack. Defaulting to gobal scope.",
location=self.get_source_info())
logger.warning("C++ namespace pop on empty stack. Defaulting to global scope.",
location=self.get_location())
stack = []
else:
stack.pop()
@ -7480,7 +7493,7 @@ class CPPAliasObject(ObjectDescription):
" Requested 'noroot' but 'maxdepth' 1."
" When skipping the root declaration,"
" need 'maxdepth' 0 for infinite or at least 2.",
location=self.get_source_info())
location=self.get_location())
signatures = self.get_signatures()
for i, sig in enumerate(signatures):
node.append(AliasNode(sig, aliasOptions, env=self.env))
@ -7537,14 +7550,14 @@ class CPPExprRole(SphinxRole):
def run(self) -> Tuple[List[Node], List[system_message]]:
text = self.text.replace('\n', ' ')
parser = DefinitionParser(text,
location=self.get_source_info(),
location=self.get_location(),
config=self.config)
# attempt to mimic XRefRole classes, except that...
try:
ast = parser.parse_expression()
except DefinitionError as ex:
logger.warning('Unparseable C++ expression: %r\n%s', text, ex,
location=self.get_source_info())
location=self.get_location())
# see below
return [addnodes.desc_inline('cpp', text, text, classes=[self.class_type])], []
parentSymbol = self.env.temp_data.get('cpp:parent_symbol', None)

View File

@ -48,7 +48,7 @@ class IndexDomain(Domain):
def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None:
"""Process a document after it is read by the environment."""
entries = self.entries.setdefault(env.docname, [])
for node in document.traverse(addnodes.index):
for node in list(document.traverse(addnodes.index)):
try:
for entry in node['entries']:
split_index_msg(entry[0], entry[1])

View File

@ -41,9 +41,6 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
#: added
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
#: based on directive nesting
allow_nesting = False
@ -53,6 +50,10 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
'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]:
"""Breaks down construct signatures
@ -71,6 +72,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
# If construct is nested, prefix the current prefix
prefix = self.env.ref_context.get('js:object', None)
mod_name = self.env.ref_context.get('js:module')
name = member
try:
member_prefix, member_name = member.rsplit('.', 1)
@ -91,14 +93,22 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
signode['object'] = prefix
signode['fullname'] = fullname
if self.display_prefix:
signode += addnodes.desc_annotation(self.display_prefix,
self.display_prefix)
display_prefix = self.get_display_prefix()
if display_prefix:
signode += addnodes.desc_annotation('', '', *display_prefix)
actual_prefix = None
if prefix:
signode += addnodes.desc_addname(prefix + '.', prefix + '.')
actual_prefix = prefix
elif mod_name:
signode += addnodes.desc_addname(mod_name + '.', mod_name + '.')
signode += addnodes.desc_name(name, name)
actual_prefix = mod_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 not arglist:
signode += addnodes.desc_parameterlist()
@ -227,9 +237,13 @@ class JSCallable(JSObject):
class JSConstructor(JSCallable):
"""Like a callable but with a different prefix."""
display_prefix = 'class '
allow_nesting = True
def get_display_prefix(self) -> List[Node]:
return [addnodes.desc_sig_keyword('class', 'class'),
addnodes.desc_sig_space()]
class JSModule(SphinxDirective):
"""

View File

@ -26,7 +26,7 @@ from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.environment import BuildEnvironment
@ -80,9 +80,10 @@ class ModuleEntry(NamedTuple):
deprecated: bool
def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref:
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 text == 'None':
if target == 'None':
reftype = 'obj'
else:
reftype = 'class'
@ -93,6 +94,19 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr
else:
kwargs = {}
refspecific = False
if target.startswith('.'):
target = target[1:]
text = target
refspecific = True
elif target.startswith('~'):
target = target[1:]
text = target.split('.')[-1]
elif suppress_prefix:
text = target.split('.')[-1]
else:
text = target
if env.config.python_use_unqualified_type_names:
# 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
@ -104,7 +118,8 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr
contnodes = [nodes.Text(text)]
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]:
@ -118,22 +133,40 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
result.extend(unparse(node.right))
return result
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
if node.value is Ellipsis:
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:
return [nodes.Text(node.value)]
# 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))]
elif isinstance(node, ast.Expr):
return unparse(node.value)
elif isinstance(node, ast.Index):
return unparse(node.value)
elif isinstance(node, ast.Invert):
return [addnodes.desc_sig_punctuation('', '~')]
elif isinstance(node, ast.List):
result = [addnodes.desc_sig_punctuation('', '[')]
for elem in node.elts:
result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ', '))
result.pop()
if node.elts:
# check if there are elements in node.elts to only pop the
# last element of result if the for-loop was run at least
# once
for elem in node.elts:
result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ','))
result.append(addnodes.desc_sig_space())
result.pop()
result.pop()
result.append(addnodes.desc_sig_punctuation('', ']'))
return result
elif isinstance(node, ast.Module):
@ -145,13 +178,23 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
result.append(addnodes.desc_sig_punctuation('', '['))
result.extend(unparse(node.slice))
result.append(addnodes.desc_sig_punctuation('', ']'))
# Wrap the Text nodes inside brackets by literal node if the subscript is a Literal
if result[0] in ('Literal', 'typing.Literal'):
for i, subnode in enumerate(result[1:], start=1):
if isinstance(subnode, nodes.Text):
result[i] = nodes.literal('', '', subnode)
return result
elif isinstance(node, ast.UnaryOp):
return unparse(node.op) + unparse(node.operand)
elif isinstance(node, ast.Tuple):
if node.elts:
result = []
for elem in node.elts:
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()
else:
result = [addnodes.desc_sig_punctuation('', '('),
@ -173,10 +216,19 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
try:
tree = ast_parse(annotation)
result = unparse(tree)
for i, node in enumerate(result):
if isinstance(node, nodes.Text) and node.strip():
result[i] = type_to_xref(str(node), env)
result: List[Node] = []
for node in unparse(tree):
if isinstance(node, nodes.literal):
result.append(node[0])
elif isinstance(node, nodes.Text) and node.strip():
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
except SyntaxError:
return [type_to_xref(annotation, env)]
@ -210,13 +262,13 @@ def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_
if param.annotation is not param.empty:
children = _parse_annotation(param.annotation, env)
node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ')
node += addnodes.desc_sig_space()
node += addnodes.desc_sig_name('', '', *children) # type: ignore
if param.default 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 += nodes.Text(' ')
node += addnodes.desc_sig_space()
else:
node += addnodes.desc_sig_operator('', '=')
node += nodes.inline('', param.default, classes=['default_value'],
@ -259,7 +311,8 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
ends_open += 1
argument = argument[:-1].strip()
if argument:
stack[-1] += addnodes.desc_parameter(argument, argument)
stack[-1] += addnodes.desc_parameter(
'', '', addnodes.desc_sig_name(argument, argument))
while ends_open:
stack.append(addnodes.desc_optional())
stack[-2] += stack[-1]
@ -301,7 +354,7 @@ class PyXrefMixin:
text = target[1:]
elif prefix == '~':
text = target.split('.')[-1]
for node in result.traverse(nodes.Text):
for node in list(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:
@ -326,17 +379,21 @@ class PyXrefMixin:
split_contnode = bool(contnode and contnode.astext() == target)
in_literal = False
results = []
for sub_target in filter(None, sub_targets):
if split_contnode:
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))
else:
results.append(self.make_xref(rolename, domain, sub_target,
innernode, contnode, env, inliner, location))
if sub_target in ('Literal', 'typing.Literal'):
in_literal = True
return results
@ -406,11 +463,11 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
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
signature.
"""
return ''
return []
def needs_arglist(self) -> bool:
"""May return true if an empty argument list is to be generated even if
@ -464,7 +521,17 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
sig_prefix = self.get_signature_prefix(sig)
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:
signode += addnodes.desc_addname(prefix, prefix)
@ -495,7 +562,9 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
anno = self.options.get('annotation')
if anno:
signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
signode += addnodes.desc_annotation(' ' + anno, '',
addnodes.desc_sig_space(),
nodes.Text(anno))
return fullname, prefix
@ -597,11 +666,12 @@ class PyFunction(PyObject):
'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:
return 'async '
return [addnodes.desc_sig_keyword('', 'async'),
addnodes.desc_sig_space()]
else:
return ''
return []
def needs_arglist(self) -> bool:
return True
@ -658,11 +728,17 @@ class PyVariable(PyObject):
typ = self.options.get('type')
if typ:
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')
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
@ -686,11 +762,12 @@ class PyClasslike(PyObject):
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:
return 'final %s ' % self.objtype
return [nodes.Text('final'), addnodes.desc_sig_space(),
nodes.Text(self.objtype), addnodes.desc_sig_space()]
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:
if self.objtype == 'class':
@ -722,25 +799,27 @@ class PyMethod(PyObject):
else:
return True
def get_signature_prefix(self, sig: str) -> str:
prefix = []
def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
prefix: List[nodes.Node] = []
if 'final' in self.options:
prefix.append('final')
prefix.append(nodes.Text('final'))
prefix.append(addnodes.desc_sig_space())
if 'abstractmethod' in self.options:
prefix.append('abstract')
prefix.append(nodes.Text('abstract'))
prefix.append(addnodes.desc_sig_space())
if 'async' in self.options:
prefix.append('async')
prefix.append(nodes.Text('async'))
prefix.append(addnodes.desc_sig_space())
if 'classmethod' in self.options:
prefix.append('classmethod')
prefix.append(nodes.Text('classmethod'))
prefix.append(addnodes.desc_sig_space())
if 'property' in self.options:
prefix.append('property')
prefix.append(nodes.Text('property'))
prefix.append(addnodes.desc_sig_space())
if 'staticmethod' in self.options:
prefix.append('static')
if prefix:
return ' '.join(prefix) + ' '
else:
return ''
prefix.append(nodes.Text('static'))
prefix.append(addnodes.desc_sig_space())
return prefix
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
@ -757,7 +836,7 @@ class PyMethod(PyObject):
if 'classmethod' in self.options:
return _('%s() (%s class method)') % (methname, clsname)
elif 'property' in self.options:
return _('%s() (%s property)') % (methname, clsname)
return _('%s (%s property)') % (methname, clsname)
elif 'staticmethod' in self.options:
return _('%s() (%s static method)') % (methname, clsname)
else:
@ -819,11 +898,18 @@ class PyAttribute(PyObject):
typ = self.options.get('type')
if typ:
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')
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
@ -848,6 +934,7 @@ class PyProperty(PyObject):
option_spec = PyObject.option_spec.copy()
option_spec.update({
'abstractmethod': directives.flag,
'classmethod': directives.flag,
'type': directives.unchanged,
})
@ -856,16 +943,26 @@ class PyProperty(PyObject):
typ = self.options.get('type')
if typ:
signode += addnodes.desc_annotation(typ, ': ' + typ)
annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '',
addnodes.desc_sig_punctuation('', ':'),
addnodes.desc_sig_space(),
*annotations)
return fullname, prefix
def get_signature_prefix(self, sig: str) -> str:
prefix = ['property']
def get_signature_prefix(self, sig: str) -> List[nodes.Node]:
prefix: List[nodes.Node] = []
if 'abstractmethod' in self.options:
prefix.insert(0, 'abstract')
prefix.append(nodes.Text('abstract'))
prefix.append(addnodes.desc_sig_space())
if 'classmethod' in self.options:
prefix.append(nodes.Text('class'))
prefix.append(addnodes.desc_sig_space())
return ' '.join(prefix) + ' '
prefix.append(nodes.Text('property'))
prefix.append(addnodes.desc_sig_space())
return prefix
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
@ -1045,7 +1142,7 @@ class PythonModuleIndex(Index):
# list of all modules, sorted by module name
modules = sorted(self.domain.data['modules'].items(),
key=lambda x: x[0].lower())
# sort out collapsable modules
# sort out collapsible modules
prev_modname = ''
num_toplevels = 0
for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:

View File

@ -45,15 +45,18 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
default_settings: Dict[str, Any] = {
'auto_id_prefix': 'id',
'embed_images': False,
'embed_stylesheet': False,
'cloak_email_addresses': True,
'pep_base_url': 'https://www.python.org/dev/peps/',
'pep_references': None,
'rfc_base_url': 'https://tools.ietf.org/html/',
'rfc_base_url': 'https://datatracker.ietf.org/doc/html/',
'rfc_references': None,
'input_encoding': 'utf-8-sig',
'doctitle_xform': False,
'sectsubtitle_xform': False,
'section_self_link': False,
'halt_level': 5,
'file_insertion_enabled': True,
'smartquotes_locales': [],

View File

@ -107,12 +107,12 @@ class IndexEntries:
lckey = lckey[1:]
if lckey[0:1].isalpha() or lckey.startswith('_'):
# put non-symbol characters at the folloing group (1)
# put non-symbol characters at the following group (1)
sortkey = (1, lckey)
else:
# put symbols at the front of the index (0)
sortkey = (0, lckey)
# ensure a determinstic order *within* letters by also sorting on
# ensure a deterministic order *within* letters by also sorting on
# the entry itself
return (sortkey, entry[0])
newlist = sorted(new.items(), key=keyfunc)

View File

@ -193,13 +193,13 @@ class TocTree:
for toplevel in children:
# nodes with length 1 don't have any children anyway
if len(toplevel) > 1:
subtrees = toplevel.traverse(addnodes.toctree)
subtrees = list(toplevel.traverse(addnodes.toctree))
if subtrees:
toplevel[1][:] = subtrees # type: ignore
else:
toplevel.pop(1)
# resolve all sub-toctrees
for subtocnode in toc.traverse(addnodes.toctree):
for subtocnode in list(toc.traverse(addnodes.toctree)):
if not (subtocnode.get('hidden', False) and
not includehidden):
i = subtocnode.parent.index(subtocnode) + 1

View File

@ -33,9 +33,12 @@ class MetadataCollector(EnvironmentCollector):
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]
for node in doctree[0]:
for node in doctree[index]: # type: ignore
# nodes are multiply inherited...
if isinstance(node, nodes.authors):
authors = cast(List[nodes.author], node)
@ -58,7 +61,7 @@ class MetadataCollector(EnvironmentCollector):
value = 0
md[name] = value
doctree.pop(0)
doctree.pop(index)
def setup(app: Sphinx) -> Dict[str, Any]:

View File

@ -129,5 +129,5 @@ class NoUri(Exception):
class FiletypeNotFoundError(Exception):
"Raised by get_filetype() if a filename matches no source suffix."
"""Raised by get_filetype() if a filename matches no source suffix."""
pass

View File

@ -226,7 +226,7 @@ def walk(rootpath: str, excludes: List[str], opts: Any
def has_child_module(rootpath: str, excludes: List[str], opts: Any) -> bool:
"""Check the given directory contains child modules at least one."""
"""Check the given directory contains child module/s (at least one)."""
for root, subs, files in walk(rootpath, excludes, opts):
if files:
return True
@ -304,7 +304,7 @@ def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] -o <OUTPUT_PATH> <MODULE_PATH> '
'[EXCLUDE_PATTERN, ...]',
epilog=__('For more information, visit <http://sphinx-doc.org/>.'),
epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
description=__("""
Look recursively in <MODULE_PATH> for Python modules and packages and create
one reST file with automodule directives per package in the <OUTPUT_PATH>.

View File

@ -257,6 +257,9 @@ def between(marker: str, what: Sequence[str] = None, keepempty: bool = False,
# But we define this class here to keep compatibility (see #4538)
class Options(dict):
"""A dict/attribute hybrid that returns None on nonexisting keys."""
def copy(self) -> "Options":
return Options(super().copy())
def __getattr__(self, name: str) -> Any:
try:
return self[name.replace('_', '-')]
@ -306,7 +309,7 @@ class Documenter:
A Documenter has an *option_spec* that works like a docutils directive's;
in fact, it will be used to parse an auto directive's options that matches
the documenter.
the Documenter.
"""
#: name by which the directive is called (auto...) and the default
#: generated directive name
@ -331,7 +334,7 @@ class Documenter:
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
"""Called to see if a member can be documented by this documenter."""
"""Called to see if a member can be documented by this Documenter."""
raise NotImplementedError('must be implemented in subclasses')
def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None:
@ -552,7 +555,7 @@ class Documenter:
def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:
"""Decode and return lines of the docstring(s) for the object.
When it returns None value, autodoc-process-docstring will not be called for this
When it returns None, autodoc-process-docstring will not be called for this
object.
"""
if ignore is not None:
@ -582,8 +585,8 @@ class Documenter:
yield from docstringlines
def get_sourcename(self) -> str:
if (getattr(self.object, '__module__', None) and
getattr(self.object, '__qualname__', None)):
if (inspect.safe_getattr(self.object, '__module__', None) and
inspect.safe_getattr(self.object, '__qualname__', None)):
# Get the correct location of docstring from self.object
# to support inherited methods
fullname = '%s.%s' % (self.object.__module__, self.object.__qualname__)
@ -643,7 +646,7 @@ class Documenter:
list of `(membername, member)` pairs of the members of *self.object*.
If *want_all* is True, return all members. Else, only return those
members given by *self.options.members* (which may also be none).
members given by *self.options.members* (which may also be None).
"""
warnings.warn('The implementation of Documenter.get_object_members() will be '
'removed from Sphinx-6.0.', RemovedInSphinx60Warning)
@ -718,7 +721,7 @@ class Documenter:
isattr = False
doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
self.parent, self.object_name)
self.object, membername)
if not isinstance(doc, str):
# Ignore non-string __doc__
doc = None
@ -748,7 +751,7 @@ class Documenter:
isprivate = membername.startswith('_')
keep = False
if ismock(member):
if ismock(member) and (namespace, membername) not in attr_docs:
# mocked module or object
pass
elif self.options.exclude_members and membername in self.options.exclude_members:
@ -820,7 +823,7 @@ class Documenter:
def document_members(self, all_members: bool = False) -> None:
"""Generate reST for member documentation.
If *all_members* is True, do all members, else those given by
If *all_members* is True, document all members, else those given by
*self.options.members*.
"""
# set current namespace for finding members
@ -942,6 +945,11 @@ class Documenter:
except PycodeError:
pass
docstrings: List[str] = sum(self.get_doc() or [], [])
if ismock(self.object) and not docstrings:
logger.warning(__('A mocked object is detected: %r'),
self.name, type='autodoc')
# check __module__ of object (for members not given explicitly)
if check_module:
if not self.check_module():
@ -1287,6 +1295,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
def format_args(self, **kwargs: Any) -> str:
if self.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
try:
self.env.app.emit('autodoc-before-process-signature', self.object, False)
@ -1311,10 +1321,13 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
sourcename = self.get_sourcename()
super().add_directive_header(sig)
if inspect.iscoroutinefunction(self.object):
if inspect.iscoroutinefunction(self.object) or inspect.isasyncgenfunction(self.object):
self.add_line(' :async:', sourcename)
def format_signature(self, **kwargs: Any) -> str:
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
sigs = []
if (self.analyzer and
'.'.join(self.objpath) in self.analyzer.overloads and
@ -1417,7 +1430,7 @@ _METACLASS_CALL_BLACKLIST = [
]
# Types whose __new__ signature is a pass-thru.
# Types whose __new__ signature is a pass-through.
_CLASS_NEW_BLACKLIST = [
'typing.Generic.__new__',
]
@ -1445,9 +1458,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
super().__init__(*args)
if self.config.autodoc_class_signature == 'separated':
self.options = self.options.copy()
# show __init__() method
if self.options.special_members is None:
self.options['special-members'] = {'__new__', '__init__'}
self.options['special-members'] = ['__new__', '__init__']
else:
self.options.special_members.append('__new__')
self.options.special_members.append('__init__')
@ -1551,6 +1566,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
def format_args(self, **kwargs: Any) -> str:
if self.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
try:
self._signature_class, self._signature_method_name, sig = self._get_signature()
@ -1572,6 +1589,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# do not show signatures
return ''
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
sig = super().format_signature()
sigs = []
@ -1604,7 +1624,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if qualname in analyzer.overloads:
return analyzer.overloads.get(qualname)
elif qualname in analyzer.tagorder:
# the constructor is defined in the class, but not overrided.
# the constructor is defined in the class, but not overridden.
return []
except PycodeError:
pass
@ -1641,7 +1661,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# add inheritance info, if wanted
if not self.doc_as_attr and self.options.show_inheritance:
if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__):
if inspect.getorigbases(self.object):
# A subclass of generic types
# refs: PEP-560 <https://www.python.org/dev/peps/pep-0560/>
bases = list(self.object.__orig_bases__)
@ -1694,7 +1714,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
classdoc_from = self.options.get('class-doc-from', self.config.autoclass_content)
docstrings = []
attrdocstring = self.get_attr(self.object, '__doc__', None)
attrdocstring = getdoc(self.object, self.get_attr)
if attrdocstring:
docstrings.append(attrdocstring)
@ -1733,14 +1753,22 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
def get_variable_comment(self) -> Optional[List[str]]:
try:
key = ('', '.'.join(self.objpath))
analyzer = ModuleAnalyzer.for_module(self.get_real_modname())
if self.doc_as_attr:
analyzer = ModuleAnalyzer.for_module(self.modname)
else:
analyzer = ModuleAnalyzer.for_module(self.get_real_modname())
analyzer.analyze()
return list(self.analyzer.attr_docs.get(key, []))
return list(analyzer.attr_docs.get(key, []))
except PycodeError:
return None
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
) -> None:
if self.doc_as_attr and self.modname != self.get_real_modname():
# override analyzer to obtain doccomment around its definition.
self.analyzer = ModuleAnalyzer.for_module(self.modname)
self.analyzer.analyze()
if self.doc_as_attr and not self.get_variable_comment():
try:
more_content = StringList([_('alias of %s') % restify(self.object)], source='')
@ -1982,14 +2010,17 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
else:
# obtain annotation for this data
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
if self.config.autodoc_typehints != 'none':
# obtain annotation for this data
annotations = get_type_hints(self.parent, None,
self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
try:
if self.options.no_value or self.should_suppress_value_header():
if (self.options.no_value or self.should_suppress_value_header() or
ismock(self.object)):
pass
else:
objrepr = object_description(self.object)
@ -2089,6 +2120,8 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
def format_args(self, **kwargs: Any) -> str:
if self.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
try:
if self.object == object.__init__ and self.parent != object:
@ -2126,7 +2159,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
obj = self.parent.__dict__.get(self.object_name, self.object)
if inspect.isabstractmethod(obj):
self.add_line(' :abstractmethod:', sourcename)
if inspect.iscoroutinefunction(obj):
if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj):
self.add_line(' :async:', sourcename)
if inspect.isclassmethod(obj):
self.add_line(' :classmethod:', sourcename)
@ -2139,6 +2172,9 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
pass
def format_signature(self, **kwargs: Any) -> str:
if self.config.autodoc_unqualified_typehints:
kwargs.setdefault('unqualified_typehints', True)
sigs = []
if (self.analyzer and
'.'.join(self.objpath) in self.analyzer.overloads and
@ -2227,6 +2263,12 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return None
def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:
if self._new_docstrings is not None:
# docstring already returned previously, then modified by
# `DocstringSignatureMixin`. Just return the previously-computed
# result, so that we don't lose the processing done by
# `DocstringSignatureMixin`.
return self._new_docstrings
if self.objpath[-1] == '__init__':
docstring = getdoc(self.object, self.get_attr,
self.config.autodoc_inherit_docstrings,
@ -2241,15 +2283,13 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
else:
return []
elif self.objpath[-1] == '__new__':
__new__ = self.get_attr(self.object, '__new__', None)
if __new__:
docstring = getdoc(__new__, self.get_attr,
self.config.autodoc_inherit_docstrings,
self.parent, self.object_name)
if (docstring is not None and
(docstring == object.__new__.__doc__ or # for pypy
docstring.strip() == object.__new__.__doc__)): # for !pypy
docstring = None
docstring = getdoc(self.object, self.get_attr,
self.config.autodoc_inherit_docstrings,
self.parent, self.object_name)
if (docstring is not None and
(docstring == object.__new__.__doc__ or # for pypy
docstring.strip() == object.__new__.__doc__)): # for !pypy
docstring = None
if docstring:
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, tabsize=tab_width)]
@ -2313,12 +2353,11 @@ class SlotsMixin(DataDocumenterMixinBase):
return ret
def should_suppress_directive_header(self) -> bool:
def should_suppress_value_header(self) -> bool:
if self.object is SLOTSATTR:
self._datadescriptor = True
return True
else:
return super().should_suppress_directive_header()
return super().should_suppress_value_header()
def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:
if self.object is SLOTSATTR:
@ -2336,6 +2375,15 @@ class SlotsMixin(DataDocumenterMixinBase):
else:
return super().get_doc(ignore) # type: ignore
@property
def _datadescriptor(self) -> bool:
warnings.warn('AttributeDocumenter._datadescriptor() is deprecated.',
RemovedInSphinx60Warning)
if self.object is SLOTSATTR:
return True
else:
return False
class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase):
"""
@ -2380,7 +2428,7 @@ class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase):
return None
def import_object(self, raiseerror: bool = False) -> bool:
"""Check the existence of runtime instance attribute when failed to import the
"""Check the existence of runtime instance attribute after failing to import the
attribute."""
try:
return super().import_object(raiseerror=True) # type: ignore
@ -2496,11 +2544,11 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
if inspect.isattributedescriptor(member):
if isinstance(parent, ModuleDocumenter):
return False
elif inspect.isattributedescriptor(member):
return True
elif (not isinstance(parent, ModuleDocumenter) and
not inspect.isroutine(member) and
not isinstance(member, type)):
elif not inspect.isroutine(member) and not isinstance(member, type):
return True
else:
return False
@ -2584,14 +2632,17 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
elif self.options.annotation:
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
else:
# obtain type annotation for this attribute
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
if self.config.autodoc_typehints != 'none':
# obtain type annotation for this attribute
annotations = get_type_hints(self.parent, None,
self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
try:
if self.options.no_value or self.should_suppress_value_header():
if (self.options.no_value or self.should_suppress_value_header() or
ismock(self.object)):
pass
else:
objrepr = object_description(self.object)
@ -2657,7 +2708,32 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return inspect.isproperty(member) and isinstance(parent, ClassDocumenter)
if isinstance(parent, ClassDocumenter):
if inspect.isproperty(member):
return True
else:
__dict__ = safe_getattr(parent.object, '__dict__', {})
obj = __dict__.get(membername)
return isinstance(obj, classmethod) and inspect.isproperty(obj.__func__)
else:
return False
def import_object(self, raiseerror: bool = False) -> bool:
"""Check the exisitence of uninitialized instance attribute when failed to import
the attribute."""
ret = super().import_object(raiseerror)
if ret and not inspect.isproperty(self.object):
__dict__ = safe_getattr(self.parent, '__dict__', {})
obj = __dict__.get(self.objpath[-1])
if isinstance(obj, classmethod) and inspect.isproperty(obj.__func__):
self.object = obj.__func__
self.isclassmethod = True
return True
else:
return False
self.isclassmethod = False
return ret
def document_members(self, all_members: bool = False) -> None:
pass
@ -2671,10 +2747,19 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
sourcename = self.get_sourcename()
if inspect.isabstractmethod(self.object):
self.add_line(' :abstractmethod:', sourcename)
if self.isclassmethod:
self.add_line(' :classmethod:', sourcename)
if safe_getattr(self.object, 'fget', None):
if safe_getattr(self.object, 'fget', None): # property
func = self.object.fget
elif safe_getattr(self.object, 'func', None): # cached_property
func = self.object.func
else:
func = None
if func and self.config.autodoc_typehints != 'none':
try:
signature = inspect.signature(self.object.fget,
signature = inspect.signature(func,
type_aliases=self.config.autodoc_type_aliases)
if signature.return_annotation is not Parameter.empty:
objrepr = stringify_typehint(signature.return_annotation)
@ -2763,6 +2848,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autodoc_typehints_description_target', 'all', True,
ENUM('all', 'documented'))
app.add_config_value('autodoc_type_aliases', {}, True)
app.add_config_value('autodoc_unqualified_typehints', False, 'env')
app.add_config_value('autodoc_warningiserror', True, True)
app.add_config_value('autodoc_inherit_docstrings', True, True)
app.add_event('autodoc-before-process-signature')

View File

@ -61,7 +61,7 @@ class DocumenterBridge:
self.state = state
def warn(self, msg: str) -> None:
warnings.warn('DocumenterBridge.warn is deprecated. Plase use sphinx.util.logging '
warnings.warn('DocumenterBridge.warn is deprecated. Please use sphinx.util.logging '
'module instead.',
RemovedInSphinx60Warning, stacklevel=2)
logger.warning(msg, location=(self.env.docname, self.lineno))
@ -107,7 +107,7 @@ def process_documenter_options(documenter: Type[Documenter], config: Config, opt
def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter
) -> List[Node]:
"""Parse a generated content by Documenter."""
"""Parse an item of content generated by Documenter."""
with switch_source_input(state, content):
if documenter.titles_allowed:
node: Element = nodes.section()
@ -125,8 +125,8 @@ def parse_generated_content(state: RSTState, content: StringList, documenter: Do
class AutodocDirective(SphinxDirective):
"""A directive class for all autodoc directives. It works as a dispatcher of Documenters.
It invokes a Documenter on running. After the processing, it parses and returns
the generated content by Documenter.
It invokes a Documenter upon running. After the processing, it parses and returns
the content generated by Documenter.
"""
option_spec = DummyOptionSpec()
has_content = True

View File

@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
def mangle(subject: Any, name: str) -> str:
"""mangle the given name."""
"""Mangle the given name."""
try:
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
return "_%s%s" % (subject.__name__, name)
@ -41,7 +41,7 @@ def mangle(subject: Any, name: str) -> str:
def unmangle(subject: Any, name: str) -> Optional[str]:
"""unmangle the given name."""
"""Unmangle the given name."""
try:
if isclass(subject) and not name.endswith('__'):
prefix = "_%s__" % subject.__name__

View File

@ -26,6 +26,7 @@ class _MockObject:
"""Used by autodoc_mock_imports."""
__display_name__ = '_MockObject'
__name__ = ''
__sphinx_mock__ = True
__sphinx_decorator_args__: Tuple[Any, ...] = ()
@ -40,7 +41,7 @@ class _MockObject:
return super().__new__(cls)
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.__qualname__ = ''
self.__qualname__ = self.__name__
def __len__(self) -> int:
return 0
@ -73,6 +74,7 @@ def _make_subclass(name: str, module: str, superclass: Any = _MockObject,
attributes: Any = None, decorator_args: Tuple = ()) -> Any:
attrs = {'__module__': module,
'__display_name__': module + '.' + name,
'__name__': name,
'__sphinx_decorator_args__': decorator_args}
attrs.update(attributes or {})
@ -168,7 +170,8 @@ def ismock(subject: Any) -> bool:
try:
# check the object is mocked object
__mro__ = safe_getattr(type(subject), '__mro__', [])
if len(__mro__) > 2 and __mro__[1] is _MockObject:
if len(__mro__) > 2 and __mro__[-2] is _MockObject:
# A mocked object has a MRO that ends with (..., _MockObject, object).
return True
except AttributeError:
pass

View File

@ -11,7 +11,8 @@
import ast
import inspect
from typing import Any, Dict
import sys
from typing import Any, Dict, List, Optional
from sphinx.application import Sphinx
from sphinx.locale import __
@ -49,11 +50,32 @@ def get_function_def(obj: Any) -> ast.FunctionDef:
return None
def get_default_value(lines: List[str], position: ast.AST) -> Optional[str]:
try:
if sys.version_info < (3, 8): # only for py38+
return None
elif position.lineno == position.end_lineno:
line = lines[position.lineno - 1]
return line[position.col_offset:position.end_col_offset]
else:
# multiline value is not supported now
return None
except (AttributeError, IndexError):
return None
def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
"""Update defvalue info of *obj* using type_comments."""
if not app.config.autodoc_preserve_defaults:
return
try:
lines = inspect.getsource(obj).splitlines()
if lines[0].startswith((' ', r'\t')):
lines.insert(0, '') # insert a dummy line to follow what get_function_def() does.
except (OSError, TypeError):
lines = []
try:
function = get_function_def(obj)
if function.args.defaults or function.args.kw_defaults:
@ -64,11 +86,17 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
for i, param in enumerate(parameters):
if param.default is not param.empty:
if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
value = DefaultValue(ast_unparse(defaults.pop(0))) # type: ignore
parameters[i] = param.replace(default=value)
default = defaults.pop(0)
value = get_default_value(lines, default)
if value is None:
value = ast_unparse(default) # type: ignore
parameters[i] = param.replace(default=DefaultValue(value))
else:
value = DefaultValue(ast_unparse(kw_defaults.pop(0))) # type: ignore
parameters[i] = param.replace(default=value)
default = kw_defaults.pop(0)
value = get_default_value(lines, default)
if value is None:
value = ast_unparse(default) # type: ignore
parameters[i] = param.replace(default=DefaultValue(value))
sig = sig.replace(parameters=parameters)
obj.__signature__ = sig
except (AttributeError, TypeError):

View File

@ -149,14 +149,14 @@ def augment_descriptions_with_types(
elif parts[0] == 'type':
name = ' '.join(parts[1:])
has_type.add(name)
elif parts[0] == 'return':
elif parts[0] in ('return', 'returns'):
has_description.add('return')
elif parts[0] == 'rtype':
has_type.add('return')
# Add 'type' for parameters with a description but no declared type.
for name in annotations:
if name == 'return':
if name in ('return', 'returns'):
continue
if name in has_description and name not in has_type:
field = nodes.field()

View File

@ -58,6 +58,7 @@ import posixpath
import re
import sys
import warnings
from inspect import Parameter
from os import path
from types import ModuleType
from typing import Any, Dict, List, Optional, Tuple, Type, cast
@ -87,6 +88,7 @@ from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging, rst
from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new_document,
switch_source_input)
from sphinx.util.inspect import signature_from_str
from sphinx.util.matching import Matcher
from sphinx.util.typing import OptionSpec
from sphinx.writers.html import HTMLTranslator
@ -222,7 +224,7 @@ def get_documenter(app: Sphinx, obj: Any, parent: Any) -> Type[Documenter]:
else:
parent_doc = parent_doc_cls(FakeDirective(), "")
# Get the corrent documenter class for *obj*
# Get the correct documenter class for *obj*
classes = [cls for cls in app.registry.documenters.values()
if cls.can_document_member(obj, '', False, parent_doc)]
if classes:
@ -280,7 +282,7 @@ class Autosummary(SphinxDirective):
msg = __('autosummary: stub file not found %r. '
'Check your autosummary_generate setting.')
logger.warning(msg, real_name, location=self.get_source_info())
logger.warning(msg, real_name, location=self.get_location())
continue
docnames.append(docname)
@ -344,7 +346,7 @@ class Autosummary(SphinxDirective):
real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
except ImportError:
logger.warning(__('autosummary: failed to import %s'), name,
location=self.get_source_info())
location=self.get_location())
continue
self.bridge.result = StringList() # initialize for each documenter
@ -358,12 +360,12 @@ class Autosummary(SphinxDirective):
documenter = self.create_documenter(self.env.app, obj, parent, full_name)
if not documenter.parse_name():
logger.warning(__('failed to parse name %s'), real_name,
location=self.get_source_info())
location=self.get_location())
items.append((display_name, '', '', real_name))
continue
if not documenter.import_object():
logger.warning(__('failed to import object %s'), real_name,
location=self.get_source_info())
location=self.get_location())
items.append((display_name, '', '', real_name))
continue
if documenter.options.members and not documenter.check_module():
@ -442,9 +444,9 @@ class Autosummary(SphinxDirective):
for name, sig, summary, real_name in items:
qualifier = 'obj'
if 'nosignatures' not in self.options:
col1 = ':%s:`%s <%s>`\\ %s' % (qualifier, name, real_name, rst.escape(sig))
col1 = ':py:%s:`%s <%s>`\\ %s' % (qualifier, name, real_name, rst.escape(sig))
else:
col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name)
col1 = ':py:%s:`%s <%s>`' % (qualifier, name, real_name)
col2 = summary
append_row(col1, col2)
@ -456,10 +458,32 @@ def strip_arg_typehint(s: str) -> str:
return s.split(':')[0].strip()
def _cleanup_signature(s: str) -> str:
"""Clean up signature using inspect.signautre() for mangle_signature()"""
try:
sig = signature_from_str(s)
parameters = list(sig.parameters.values())
for i, param in enumerate(parameters):
if param.annotation is not Parameter.empty:
# Remove typehints
param = param.replace(annotation=Parameter.empty)
if param.default is not Parameter.empty:
# Replace default value by "None"
param = param.replace(default=None)
parameters[i] = param
sig = sig.replace(parameters=parameters, return_annotation=Parameter.empty)
return str(sig)
except Exception:
# Return the original signature string if failed to clean (ex. parsing error)
return s
def mangle_signature(sig: str, max_chars: int = 30) -> str:
"""Reformat a function signature to a more compact form."""
s = _cleanup_signature(sig)
# Strip return type annotation
s = re.sub(r"\)\s*->\s.*$", ")", sig)
s = re.sub(r"\)\s*->\s.*$", ")", s)
# Remove parenthesis
s = re.sub(r"^\((.*)\)$", r"\1", s).strip()
@ -540,7 +564,10 @@ def extract_summary(doc: List[str], document: Any) -> str:
# parse the docstring
node = parse(doc, document.settings)
if not isinstance(node[0], nodes.paragraph):
if isinstance(node[0], nodes.section):
# document starts with a section heading, so use that.
summary = node[0].astext().strip()
elif not isinstance(node[0], nodes.paragraph):
# document starts with non-paragraph: pick up the first line
summary = doc[0].strip()
else:
@ -556,7 +583,7 @@ def extract_summary(doc: List[str], document: Any) -> str:
node = parse(doc, document.settings)
if summary.endswith(WELL_KNOWN_ABBREVIATIONS):
pass
elif not node.traverse(nodes.system_message):
elif not list(node.traverse(nodes.system_message)):
# considered as that splitting by period does not break inline markups
break
@ -568,7 +595,7 @@ def extract_summary(doc: List[str], document: Any) -> str:
def limited_join(sep: str, items: List[str], max_chars: int = 30,
overflow_marker: str = "...") -> str:
"""Join a number of strings to one, limiting the length to *max_chars*.
"""Join a number of strings into one, limiting the length to *max_chars*.
If the string overflows this limit, replace the last fitting item by
*overflow_marker*.
@ -799,5 +826,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autosummary_mock_imports',
lambda config: config.autodoc_mock_imports, 'env')
app.add_config_value('autosummary_imported_members', [], False, [bool])
app.add_config_value('autosummary_ignore_module_all', True, 'env', bool)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -28,7 +28,7 @@ import sys
import warnings
from gettext import NullTranslations
from os import path
from typing import Any, Dict, List, NamedTuple, Set, Tuple, Type, Union
from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union
from jinja2 import TemplateNotFound
from jinja2.sandbox import SandboxedEnvironment
@ -46,7 +46,7 @@ from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging, rst, split_full_qualified_name
from sphinx.util.inspect import safe_getattr
from sphinx.util.inspect import getall, safe_getattr
from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxTemplateLoader
@ -68,6 +68,7 @@ class DummyApplication:
self.config.add('autosummary_context', {}, True, None)
self.config.add('autosummary_filename_map', {}, True, None)
self.config.add('autosummary_ignore_module_all', True, 'env', bool)
self.config.init_values()
def emit_firstresult(self, *args: Any) -> None:
@ -192,7 +193,7 @@ class ModuleScanner:
def scan(self, imported_members: bool) -> List[str]:
members = []
for name in dir(self.object):
for name in members_of(self.object, self.app.config):
try:
value = safe_getattr(self.object, name)
except AttributeError:
@ -212,16 +213,31 @@ class ModuleScanner:
except AttributeError:
imported = False
respect_module_all = not self.app.config.autosummary_ignore_module_all
if imported_members:
# list all members up
members.append(name)
elif imported is False:
# list not-imported members up
# list not-imported members
members.append(name)
elif '__all__' in dir(self.object) and respect_module_all:
# list members that have __all__ set
members.append(name)
return members
def members_of(obj: Any, conf: Config) -> Sequence[str]:
"""Get the members of ``obj``, possibly ignoring the ``__all__`` module attribute
Follows the ``conf.autosummary_ignore_module_all`` setting."""
if conf.autosummary_ignore_module_all:
return dir(obj)
else:
return getall(obj) or dir(obj)
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any,
@ -245,7 +261,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
def get_module_members(obj: Any) -> Dict[str, Any]:
members = {}
for name in dir(obj):
for name in members_of(obj, app.config):
try:
members[name] = safe_getattr(obj, name)
except AttributeError:
@ -595,7 +611,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] <SOURCE_FILE>...',
epilog=__('For more information, visit <http://sphinx-doc.org/>.'),
epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
description=__("""
Generate ReStructuredText using autosummary directives.
@ -630,6 +646,10 @@ The format of the autosummary directive is documented in the
dest='imported_members', default=False,
help=__('document imported members (default: '
'%(default)s)'))
parser.add_argument('-a', '--respect-module-all', action='store_true',
dest='respect_module_all', default=False,
help=__('document exactly the members in module __all__ attribute. '
'(default: %(default)s)'))
return parser
@ -646,6 +666,7 @@ def main(argv: List[str] = sys.argv[1:]) -> None:
if args.templates:
app.config.templates_path.append(path.abspath(args.templates))
app.config.autosummary_ignore_module_all = not args.respect_module_all # type: ignore
generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,

View File

@ -25,6 +25,7 @@
:license: BSD, see LICENSE for details.
"""
import re
import warnings
from typing import Any, Dict, List, Tuple
@ -35,9 +36,48 @@ from docutils.parsers.rst.states import Inliner
import sphinx
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx60Warning
from sphinx.locale import __
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
from sphinx.util.nodes import split_explicit_title
from sphinx.util.typing import RoleFunction
logger = logging.getLogger(__name__)
class ExternalLinksChecker(SphinxPostTransform):
"""
For each external link, check if it can be replaced by an extlink.
We treat each ``reference`` node without ``internal`` attribute as an external link.
"""
default_priority = 500
def run(self, **kwargs: Any) -> None:
for refnode in self.document.traverse(nodes.reference):
self.check_uri(refnode)
def check_uri(self, refnode: nodes.reference) -> None:
"""
If the URI in ``refnode`` has a replacement in ``extlinks``,
emit a warning with a replacement suggestion.
"""
if 'internal' in refnode or 'refuri' not in refnode:
return
uri = refnode['refuri']
for alias, (base_uri, caption) in self.app.config.extlinks.items():
uri_pattern = re.compile(base_uri.replace('%s', '(?P<value>.+)'))
match = uri_pattern.match(uri)
if match and match.groupdict().get('value'):
# build a replacement suggestion
msg = __('hardcoded link %r could be replaced by an extlink '
'(try using %r instead)')
replacement = f":{alias}:`{match.groupdict().get('value')}`"
logger.warning(msg, uri, replacement, location=refnode)
def make_link_role(name: str, base_url: str, caption: str) -> RoleFunction:
# Check whether we have base_url and caption strings have an '%s' for
@ -85,4 +125,5 @@ def setup_link_roles(app: Sphinx) -> None:
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('extlinks', {}, 'env')
app.connect('builder-inited', setup_link_roles)
app.add_post_transform(ExternalLinksChecker)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -12,7 +12,6 @@ import posixpath
import re
import shutil
import subprocess
import sys
import tempfile
from os import path
from subprocess import PIPE, CalledProcessError
@ -43,11 +42,11 @@ templates_path = path.join(package_dir, 'templates', 'imgmath')
class MathExtError(SphinxError):
category = 'Math extension error'
def __init__(self, msg: str, stderr: bytes = None, stdout: bytes = None) -> None:
def __init__(self, msg: str, stderr: str = None, stdout: str = None) -> None:
if stderr:
msg += '\n[stderr]\n' + stderr.decode(sys.getdefaultencoding(), 'replace')
msg += '\n[stderr]\n' + stderr
if stdout:
msg += '\n[stdout]\n' + stdout.decode(sys.getdefaultencoding(), 'replace')
msg += '\n[stdout]\n' + stdout
super().__init__(msg)
@ -135,7 +134,8 @@ def compile_math(latex: str, builder: Builder) -> str:
command.append('math.tex')
try:
subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True)
subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True,
encoding='ascii')
return path.join(tempdir, 'math.dvi')
except OSError as exc:
logger.warning(__('LaTeX command %r cannot be run (needed for math '

View File

@ -29,11 +29,11 @@ import posixpath
import sys
import time
from os import path
from typing import IO, Any, Dict, List, Tuple
from typing import IO, Any, Dict, List, Optional, Tuple
from urllib.parse import urlsplit, urlunsplit
from docutils import nodes
from docutils.nodes import TextElement
from docutils.nodes import Element, TextElement
from docutils.utils import relative_path
import sphinx
@ -41,11 +41,12 @@ from sphinx.addnodes import pending_xref
from sphinx.application import Sphinx
from sphinx.builders.html import INVENTORY_FILENAME
from sphinx.config import Config
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.util import logging, requests
from sphinx.util.inventory import InventoryFile
from sphinx.util.typing import Inventory
from sphinx.util.typing import Inventory, InventoryItem
logger = logging.getLogger(__name__)
@ -258,86 +259,211 @@ def load_mappings(app: Sphinx) -> None:
inventories.main_inventory.setdefault(type, {}).update(objects)
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,
contnode: TextElement) -> nodes.reference:
"""Attempt to resolve a missing reference via intersphinx references."""
target = node['reftarget']
inventories = InventoryAdapter(env)
objtypes: List[str] = None
if node['reftype'] == 'any':
# we search anything!
objtypes = ['%s:%s' % (domain.name, objtype)
for domain in env.domains.values()
for objtype in domain.object_types]
domain = None
def _create_element_from_result(domain: Domain, inv_name: Optional[str],
data: InventoryItem,
node: pending_xref, contnode: TextElement) -> Element:
proj, version, uri, dispname = data
if '://' not in uri and node.get('refdoc'):
# get correct path in case of subdirectories
uri = path.join(relative_path(node['refdoc'], '.'), uri)
if version:
reftitle = _('(in %s v%s)') % (proj, version)
else:
domain = node.get('refdomain')
if not domain:
reftitle = _('(in %s)') % (proj,)
newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle)
if node.get('refexplicit'):
# use whatever title was given
newnode.append(contnode)
elif dispname == '-' or \
(domain.name == 'std' and node['reftype'] == 'keyword'):
# use whatever title was given, but strip prefix
title = contnode.astext()
if inv_name is not None and title.startswith(inv_name + ':'):
newnode.append(contnode.__class__(title[len(inv_name) + 1:],
title[len(inv_name) + 1:]))
else:
newnode.append(contnode)
else:
# else use the given display name (used for :ref:)
newnode.append(contnode.__class__(dispname, dispname))
return newnode
def _resolve_reference_in_domain_by_target(
inv_name: Optional[str], inventory: Inventory,
domain: Domain, objtypes: List[str],
target: str,
node: pending_xref, contnode: TextElement) -> Optional[Element]:
for objtype in objtypes:
if objtype not in inventory:
# Continue if there's nothing of this kind in the inventory
continue
if target in inventory[objtype]:
# Case sensitive match, use it
data = inventory[objtype][target]
elif objtype == 'std:term':
# Check for potential case insensitive matches for terms only
target_lower = target.lower()
insensitive_matches = list(filter(lambda k: k.lower() == target_lower,
inventory[objtype].keys()))
if insensitive_matches:
data = inventory[objtype][insensitive_matches[0]]
else:
# No case insensitive match either, continue to the next candidate
continue
else:
# Could reach here if we're not a term but have a case insensitive match.
# This is a fix for terms specifically, but potentially should apply to
# other types.
continue
return _create_element_from_result(domain, inv_name, data, node, contnode)
return None
def _resolve_reference_in_domain(env: BuildEnvironment,
inv_name: Optional[str], inventory: Inventory,
honor_disabled_refs: bool,
domain: Domain, objtypes: List[str],
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
# we adjust the object types for backwards compatibility
if domain.name == 'std' and 'cmdoption' in objtypes:
# until Sphinx-1.6, cmdoptions are stored as std:option
objtypes.append('option')
if domain.name == 'py' and 'attribute' in objtypes:
# Since Sphinx-2.1, properties are stored as py:method
objtypes.append('method')
# the inventory contains domain:type as objtype
objtypes = ["{}:{}".format(domain.name, t) for t in objtypes]
# now that the objtypes list is complete we can remove the disabled ones
if honor_disabled_refs:
disabled = env.config.intersphinx_disabled_reftypes
objtypes = [o for o in objtypes if o not in disabled]
# without qualification
res = _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,
node['reftarget'], node, contnode)
if res is not None:
return res
# try with qualification of the current scope instead
full_qualified_name = domain.get_full_qualified_name(node)
if full_qualified_name is None:
return None
return _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,
full_qualified_name, node, contnode)
def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement) -> Optional[Element]:
# disabling should only be done if no inventory is given
honor_disabled_refs = honor_disabled_refs and inv_name is None
if honor_disabled_refs and '*' in env.config.intersphinx_disabled_reftypes:
return None
typ = node['reftype']
if typ == 'any':
for domain_name, domain in env.domains.items():
if honor_disabled_refs \
and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes:
continue
objtypes = list(domain.object_types)
res = _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)
if res is not None:
return res
return None
else:
domain_name = node.get('refdomain')
if not domain_name:
# only objects in domains are in the inventory
return None
objtypes = env.get_domain(domain).objtypes_for_role(node['reftype'])
if honor_disabled_refs \
and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes:
return None
domain = env.get_domain(domain_name)
objtypes = domain.objtypes_for_role(typ)
if not objtypes:
return None
objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes]
if 'std:cmdoption' in objtypes:
# until Sphinx-1.6, cmdoptions are stored as std:option
objtypes.append('std:option')
if 'py:attribute' in objtypes:
# Since Sphinx-2.1, properties are stored as py:method
objtypes.append('py:method')
return _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)
to_try = [(inventories.main_inventory, target)]
if domain:
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)
if full_qualified_name:
to_try.append((inventories.main_inventory, full_qualified_name))
in_set = None
if ':' in target:
# first part may be the foreign doc set name
setname, newtarget = target.split(':', 1)
if setname in inventories.named_inventory:
in_set = setname
to_try.append((inventories.named_inventory[setname], newtarget))
if domain:
node['reftarget'] = newtarget
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)
if full_qualified_name:
to_try.append((inventories.named_inventory[setname], full_qualified_name))
for inventory, target in to_try:
for objtype in objtypes:
if objtype not in inventory or target not in inventory[objtype]:
continue
proj, version, uri, dispname = inventory[objtype][target]
if '://' not in uri and node.get('refdoc'):
# get correct path in case of subdirectories
uri = path.join(relative_path(node['refdoc'], '.'), uri)
if version:
reftitle = _('(in %s v%s)') % (proj, version)
else:
reftitle = _('(in %s)') % (proj,)
newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle)
if node.get('refexplicit'):
# use whatever title was given
newnode.append(contnode)
elif dispname == '-' or \
(domain == 'std' and node['reftype'] == 'keyword'):
# use whatever title was given, but strip prefix
title = contnode.astext()
if in_set and title.startswith(in_set + ':'):
newnode.append(contnode.__class__(title[len(in_set) + 1:],
title[len(in_set) + 1:]))
else:
newnode.append(contnode)
else:
# else use the given display name (used for :ref:)
newnode.append(contnode.__class__(dispname, dispname))
return newnode
# at least get rid of the ':' in the target if no explicit title given
if in_set is not None and not node.get('refexplicit', True):
if len(contnode) and isinstance(contnode[0], nodes.Text):
contnode[0] = nodes.Text(newtarget, contnode[0].rawsource)
return None
def inventory_exists(env: BuildEnvironment, inv_name: str) -> bool:
return inv_name in InventoryAdapter(env).named_inventory
def resolve_reference_in_inventory(env: BuildEnvironment,
inv_name: str,
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried in the given inventory with the target as is.
Requires ``inventory_exists(env, inv_name)``.
"""
assert inventory_exists(env, inv_name)
return _resolve_reference(env, inv_name, InventoryAdapter(env).named_inventory[inv_name],
False, node, contnode)
def resolve_reference_any_inventory(env: BuildEnvironment,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried with the target as is in any inventory.
"""
return _resolve_reference(env, None, InventoryAdapter(env).main_inventory,
honor_disabled_refs,
node, contnode)
def resolve_reference_detect_inventory(env: BuildEnvironment,
node: pending_xref, contnode: TextElement
) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried first with the target as is in any inventory.
If this does not succeed, then the target is split by the first ``:``,
to form ``inv_name:newtarget``. If ``inv_name`` is a named inventory, then resolution
is tried in that inventory with the new target.
"""
# ordinary direct lookup, use data as is
res = resolve_reference_any_inventory(env, True, node, contnode)
if res is not None:
return res
# try splitting the target into 'inv_name:target'
target = node['reftarget']
if ':' not in target:
return None
inv_name, newtarget = target.split(':', 1)
if not inventory_exists(env, inv_name):
return None
node['reftarget'] = newtarget
res_inv = resolve_reference_in_inventory(env, inv_name, node, contnode)
node['reftarget'] = target
return res_inv
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,
contnode: TextElement) -> Optional[Element]:
"""Attempt to resolve a missing reference via intersphinx references."""
return resolve_reference_detect_inventory(env, node, contnode)
def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:
@ -368,6 +494,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('intersphinx_mapping', {}, True)
app.add_config_value('intersphinx_cache_limit', 5, False)
app.add_config_value('intersphinx_timeout', None, False)
app.add_config_value('intersphinx_disabled_reftypes', [], True)
app.connect('config-inited', normalize_intersphinx_mapping, priority=800)
app.connect('builder-inited', load_mappings)
app.connect('missing-reference', missing_reference)

View File

@ -39,7 +39,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
'js': ['object', 'fullname'],
}
for objnode in doctree.traverse(addnodes.desc):
for objnode in list(doctree.traverse(addnodes.desc)):
domain = objnode.get('domain')
uris: Set[str] = set()
for signode in objnode:

View File

@ -79,13 +79,8 @@ def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict
'mathjax extension to work')
domain = cast(MathDomain, app.env.get_domain('math'))
if domain.has_equations(pagename):
if app.registry.html_assets_policy == 'always' or domain.has_equations(pagename):
# Enable mathjax only if equations exists
options = {'async': 'async'}
if app.config.mathjax_options:
options.update(app.config.mathjax_options)
app.add_js_file(app.config.mathjax_path, **options) # type: ignore
if app.config.mathjax2_config:
if app.config.mathjax_path == MATHJAX_URL:
logger.warning(
@ -97,6 +92,18 @@ def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict
body = 'window.MathJax = %s' % json.dumps(app.config.mathjax3_config)
app.add_js_file(None, body=body)
options = {}
if app.config.mathjax_options:
options.update(app.config.mathjax_options)
if 'async' not in options and 'defer' not in options:
if app.config.mathjax3_config:
# Load MathJax v3 via "defer" method
options['defer'] = 'defer'
else:
# Load other MathJax via "async" method
options['async'] = 'async'
app.add_js_file(app.config.mathjax_path, **options)
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_html_math_renderer('mathjax',

View File

@ -309,11 +309,11 @@ def setup(app: Sphinx) -> Dict[str, Any]:
See Also
--------
`The Sphinx documentation on Extensions
<http://sphinx-doc.org/extensions.html>`_
<https://www.sphinx-doc.org/extensions.html>`_
`The Extension Tutorial <http://sphinx-doc.org/extdev/tutorial.html>`_
`The Extension Tutorial <https://www.sphinx-doc.org/extdev/tutorial.html>`_
`The Extension API <http://sphinx-doc.org/extdev/appapi.html>`_
`The Extension API <https://www.sphinx-doc.org/extdev/appapi.html>`_
"""
if not isinstance(app, Sphinx):

View File

@ -131,7 +131,7 @@ class TodoListProcessor:
def process(self, doctree: nodes.document, docname: str) -> None:
todos: List[todo_node] = sum(self.domain.todos.values(), [])
for node in doctree.traverse(todolist):
for node in list(doctree.traverse(todolist)):
if not self.config.todo_include_todos:
node.parent.remove(node)
continue

View File

@ -108,7 +108,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
return False
for objnode in doctree.traverse(addnodes.desc):
for objnode in list(doctree.traverse(addnodes.desc)):
if objnode.get('domain') != 'py':
continue
names: Set[str] = set()
@ -191,7 +191,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform):
node.replace_self(refnode)
def remove_viewcode_anchors(self) -> None:
for node in self.document.traverse(viewcode_anchor):
for node in list(self.document.traverse(viewcode_anchor)):
node.parent.remove(node)

View File

@ -10,6 +10,8 @@
from typing import TYPE_CHECKING, Any, Dict
from packaging.version import InvalidVersion, Version
from sphinx.config import Config
from sphinx.errors import VersionRequirementError
from sphinx.locale import __
@ -51,7 +53,18 @@ def verify_needs_extensions(app: "Sphinx", config: Config) -> None:
'but it is not loaded.'), extname)
continue
if extension.version == 'unknown version' or reqversion > extension.version:
fulfilled = True
if extension.version == 'unknown version':
fulfilled = False
else:
try:
if Version(reqversion) > Version(extension.version):
fulfilled = False
except InvalidVersion:
if reqversion > extension.version:
fulfilled = False
if not fulfilled:
raise VersionRequirementError(__('This project needs the extension %s at least in '
'version %s and therefore cannot be built with '
'the loaded version (%s).') %

View File

@ -8,11 +8,11 @@
:license: BSD, see LICENSE for details.
"""
from distutils.version import LooseVersion
from functools import partial
from importlib import import_module
from typing import Any, Dict
from packaging import version
from pygments import __version__ as pygmentsversion
from pygments import highlight
from pygments.filters import ErrorToken
@ -64,7 +64,7 @@ _LATEX_ADD_STYLES_FIXPYG = r'''
{\let\fcolorbox\spx@fixpyg@fcolorbox\PYG@do{#2}}}
\makeatother
'''
if tuple(LooseVersion(pygmentsversion).version) <= (2, 7, 4):
if version.parse(pygmentsversion).release <= (2, 7, 4):
_LATEX_ADD_STYLES += _LATEX_ADD_STYLES_FIXPYG

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