mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '4.x' into fix-footnote-in-info
This commit is contained in:
commit
5563f672ec
93
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
93
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal 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]
|
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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
18
.github/workflows/create-release.yml
vendored
Normal 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
25
.github/workflows/docutils-latest.yml
vendored
Normal 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
19
.github/workflows/lock.yml
vendored
Normal 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'
|
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@ -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
|
||||
|
5
.github/workflows/transifex.yml
vendored
5
.github/workflows/transifex.yml
vendored
@ -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
376
CHANGES
@ -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)
|
||||
=====================================
|
||||
|
||||
|
2
EXAMPLES
2
EXAMPLES
@ -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
|
||||
|
8
doc/_static/conf.py.txt
vendored
8
doc/_static/conf.py.txt
vendored
@ -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
|
||||
|
BIN
doc/_static/tutorial/lumache-autosummary.png
vendored
Normal file
BIN
doc/_static/tutorial/lumache-autosummary.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
doc/_static/tutorial/lumache-furo.png
vendored
Normal file
BIN
doc/_static/tutorial/lumache-furo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
BIN
doc/_static/tutorial/lumache-py-function-full.png
vendored
Normal file
BIN
doc/_static/tutorial/lumache-py-function-full.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
BIN
doc/_static/tutorial/lumache-py-function.png
vendored
Normal file
BIN
doc/_static/tutorial/lumache-py-function.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
6
doc/_templates/index.html
vendored
6
doc/_templates/index.html
vendored
@ -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 Contributors’s 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 %}
|
||||
|
@ -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:
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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(',')]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()`
|
||||
|
@ -13,6 +13,10 @@ Domain API
|
||||
.. autoclass:: Index
|
||||
:members:
|
||||
|
||||
.. module:: sphinx.directives
|
||||
|
||||
.. autoclass:: ObjectDescription
|
||||
:members:
|
||||
|
||||
Python Domain
|
||||
-------------
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -39,6 +39,10 @@ Options
|
||||
|
||||
Document imported members.
|
||||
|
||||
.. option:: -a, --respect-module-all
|
||||
|
||||
Document exactly the members in a module's ``__all__`` attribute.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
166
doc/tutorial/automatic-doc-generation.rst
Normal file
166
doc/tutorial/automatic-doc-generation.rst
Normal 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
279
doc/tutorial/deploying.rst
Normal 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.
|
275
doc/tutorial/describing-code.rst
Normal file
275
doc/tutorial/describing-code.rst
Normal 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
6
doc/tutorial/end.rst
Normal 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>`.
|
92
doc/tutorial/first-steps.rst
Normal file
92
doc/tutorial/first-steps.rst
Normal 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.
|
120
doc/tutorial/getting-started.rst
Normal file
120
doc/tutorial/getting-started.rst
Normal 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>`.
|
@ -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
|
||||
|
78
doc/tutorial/more-sphinx-customization.rst
Normal file
78
doc/tutorial/more-sphinx-customization.rst
Normal 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>`.
|
130
doc/tutorial/narrative-documentation.rst
Normal file
130
doc/tutorial/narrative-documentation.rst
Normal 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!
|
@ -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``:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
----------------
|
||||
|
@ -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
|
||||
---------------------
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
@ -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
|
||||
---------------------------------------------------------------
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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])
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
7
setup.py
7
setup.py
@ -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',
|
||||
|
@ -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__))
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.',
|
||||
|
@ -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,
|
||||
|
@ -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():
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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', []),
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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': [],
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]:
|
||||
|
@ -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
|
||||
|
@ -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>.
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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__
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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 '
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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',
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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).') %
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user