Merge branch '5.x'

# Conflicts:
#	setup.py
#	sphinx/__init__.py
This commit is contained in:
Adam Turner
2022-09-23 17:27:00 +01:00
53 changed files with 1037 additions and 526 deletions

View File

@@ -21,8 +21,6 @@ jobs:
python-version: 3
- 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

View File

@@ -20,6 +20,6 @@ jobs:
with:
python-version: 3
- name: Install dependencies
run: pip install -U tox
run: python -m pip install -U tox pip
- name: Run Tox
run: tox -e ${{ matrix.tool }}

11
CHANGES
View File

@@ -27,6 +27,10 @@ Release 5.2.0 (in development)
Dependencies
------------
* #10356: Sphinx now uses declarative metadata with ``pyproject.toml`` to
create packages, using PyPA's ``build`` project as a build backend. Patch by
Adam Turner.
Incompatible changes
--------------------
@@ -41,9 +45,14 @@ Features added
* #10755: linkcheck: Check the source URL of raw directives that use the ``url``
option.
* #10781: Allow :rst:role:`ref` role to be used with definitions and fields.
* #10717: HTML Search: Increase priority for full title and
* #10717: HTML Search: Increase priority for full title and
subtitle matches in search results
* #10718: HTML Search: Save search result score to the HTML element for debugging
* #10673: Make toctree accept 'genindex', 'modindex' and 'search' docnames
* #6316, #10804: Add domain objects to the table of contents. Patch by Adam Turner
* #6692: HTML Search: Include explicit :rst:dir:`index` directive index entries
in the search index and search results. Patch by Adam Turner
* #10816: imgmath: Allow embedding images in HTML as base64
Bugs fixed
----------

View File

@@ -1,33 +0,0 @@
include README.rst
include LICENSE
include AUTHORS
include CHANGES
include CHANGES.old
include CODE_OF_CONDUCT
include CONTRIBUTING.rst
include EXAMPLES
include babel.cfg
include Makefile
include sphinx-autogen.py
include sphinx-build.py
include sphinx-quickstart.py
include sphinx-apidoc.py
include tox.ini
include sphinx/locale/.tx/config
include sphinx/py.typed
recursive-include sphinx/templates *
recursive-include sphinx/texinputs *
recursive-include sphinx/texinputs_win *
recursive-include sphinx/themes *
recursive-include sphinx/locale *.js *.pot *.po *.mo
recursive-include sphinx/search/non-minified-js *.js
recursive-include sphinx/search/minified-js *.js
recursive-include sphinx/ext/autosummary/templates *
recursive-include tests *
recursive-include utils *
recursive-include doc *
prune doc/_build
prune sphinx/locale/.tx

View File

@@ -74,7 +74,7 @@ covertest:
.PHONY: build
build:
@$(PYTHON) setup.py build
@$(PYTHON) -m build .
.PHONY: docs
docs:

View File

@@ -273,7 +273,7 @@ div.quotebar {
margin-left: 1em;
}
blockquote {
blockquote.epigraph {
font-size: 1.5em;
padding-left: 1rem;
margin-left: 0;

View File

@@ -120,8 +120,7 @@ class RecipeDomain(Domain):
return '{}.{}'.format('recipe', node.arguments[0])
def get_objects(self):
for obj in self.data['recipes']:
yield obj
yield from self.data['recipes']
def resolve_xref(self, env, fromdocname, builder, typ, target, node,
contnode):

View File

@@ -17,6 +17,7 @@ Domain API
.. autoclass:: ObjectDescription
:members:
:private-members: _toc_entry_name, _object_hierarchy_parts
Python Domain
-------------

View File

@@ -97,49 +97,49 @@ in which a Sphinx project is built: this works in several phases.
**Phase 0: Initialization**
In this phase, almost nothing of interest to us happens. The source
directory is searched for source files, and extensions are initialized.
Should a stored build environment exist, it is loaded, otherwise a new one is
created.
In this phase, almost nothing of interest to us happens. The source
directory is searched for source files, and extensions are initialized.
Should a stored build environment exist, it is loaded, otherwise a new one is
created.
**Phase 1: Reading**
In Phase 1, all source files (and on subsequent builds, those that are new or
changed) are read and parsed. This is the phase where directives and roles
are encountered by docutils, and the corresponding code is executed. The
output of this phase is a *doctree* for each source file; that is a tree of
docutils nodes. For document elements that aren't fully known until all
existing files are read, temporary nodes are created.
In Phase 1, all source files (and on subsequent builds, those that are new or
changed) are read and parsed. This is the phase where directives and roles
are encountered by docutils, and the corresponding code is executed. The
output of this phase is a *doctree* for each source file; that is a tree of
docutils nodes. For document elements that aren't fully known until all
existing files are read, temporary nodes are created.
There are nodes provided by docutils, which are documented `in the docutils
documentation <https://docutils.sourceforge.io/docs/ref/doctree.html>`__.
Additional nodes are provided by Sphinx and :ref:`documented here <nodes>`.
There are nodes provided by docutils, which are documented `in the docutils
documentation <https://docutils.sourceforge.io/docs/ref/doctree.html>`__.
Additional nodes are provided by Sphinx and :ref:`documented here <nodes>`.
During reading, the build environment is updated with all meta- and cross
reference data of the read documents, such as labels, the names of headings,
described Python objects and index entries. This will later be used to
replace the temporary nodes.
During reading, the build environment is updated with all meta- and cross
reference data of the read documents, such as labels, the names of headings,
described Python objects and index entries. This will later be used to
replace the temporary nodes.
The parsed doctrees are stored on the disk, because it is not possible to
hold all of them in memory.
The parsed doctrees are stored on the disk, because it is not possible to
hold all of them in memory.
**Phase 2: Consistency checks**
Some checking is done to ensure no surprises in the built documents.
Some checking is done to ensure no surprises in the built documents.
**Phase 3: Resolving**
Now that the metadata and cross-reference data of all existing documents is
known, all temporary nodes are replaced by nodes that can be converted into
output using components called transforms. For example, links are created
for object references that exist, and simple literal nodes are created for
those that don't.
Now that the metadata and cross-reference data of all existing documents is
known, all temporary nodes are replaced by nodes that can be converted into
output using components called transforms. For example, links are created
for object references that exist, and simple literal nodes are created for
those that don't.
**Phase 4: Writing**
This phase converts the resolved doctrees to the desired output format, such
as HTML or LaTeX. This happens via a so-called docutils writer that visits
the individual nodes of each doctree and produces some output in the process.
This phase converts the resolved doctrees to the desired output format, such
as HTML or LaTeX. This happens via a so-called docutils writer that visits
the individual nodes of each doctree and produces some output in the process.
.. note::

View File

@@ -407,12 +407,16 @@ Keys that don't need to be overridden unless in special cases are:
``'geometry'``
"geometry" package inclusion, the default definition is:
``'\\usepackage{geometry}'``
.. code:: latex
'\\usepackage{geometry}'
with an additional ``[dvipdfm]`` for Japanese documents.
The Sphinx LaTeX style file executes:
``\PassOptionsToPackage{hmargin=1in,vmargin=1in,marginpar=0.5in}{geometry}``
.. code:: latex
\PassOptionsToPackage{hmargin=1in,vmargin=1in,marginpar=0.5in}{geometry}
which can be customized via corresponding :ref:`'sphinxsetup' options
<latexsphinxsetup>`.

View File

@@ -14,12 +14,12 @@ This file (containing Python code) is called the "build configuration file"
and contains (almost) all configuration needed to customize Sphinx input
and output behavior.
An optional file `docutils.conf`_ can be added to the configuration
directory to adjust `Docutils`_ configuration if not otherwise overridden or
set by Sphinx.
An optional file `docutils.conf`_ can be added to the configuration
directory to adjust `Docutils`_ configuration if not otherwise overridden or
set by Sphinx.
.. _`docutils`: https://docutils.sourceforge.io/
.. _`docutils.conf`: https://docutils.sourceforge.io/docs/user/config.html
.. _`docutils`: https://docutils.sourceforge.io/
.. _`docutils.conf`: https://docutils.sourceforge.io/docs/user/config.html
The configuration file is executed as Python code at build time (using
:func:`importlib.import_module`, and with the current directory set to its
@@ -419,9 +419,9 @@ General configuration
:literal:`:manpage:`man(1)`` role will link to
<https://manpages.debian.org/man(1)>. The patterns available are:
* ``page`` - the manual page (``man``)
* ``section`` - the manual section (``1``)
* ``path`` - the original manual page and section specified (``man(1)``)
* ``page`` - the manual page (``man``)
* ``section`` - the manual section (``1``)
* ``path`` - the original manual page and section specified (``man(1)``)
This also supports manpages specified as ``man.1``.
@@ -678,6 +678,24 @@ General configuration
:term:`object` names (for object types where a "module" of some kind is
defined), e.g. for :rst:dir:`py:function` directives. Default is ``True``.
.. confval:: toc_object_entries_show_parents
A string that determines how domain objects (e.g. functions, classes,
attributes, etc.) are displayed in their table of contents entry.
Use ``domain`` to allow the domain to determine the appropriate number of
parents to show. For example, the Python domain would show ``Class.method()``
and ``function()``, leaving out the ``module.`` level of parents.
This is the default setting.
Use ``hide`` to only show the name of the element without any parents
(i.e. ``method()``).
Use ``all`` to show the fully-qualified name for the object
(i.e. ``module.Class.method()``), displaying all parents.
.. versionadded:: 5.2
.. confval:: show_authors
A boolean that decides whether :rst:dir:`codeauthor` and

View File

@@ -48,14 +48,14 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts:
produces a table like this:
.. currentmodule:: sphinx
.. currentmodule:: sphinx
.. autosummary::
.. autosummary::
environment.BuildEnvironment
util.relative_uri
environment.BuildEnvironment
util.relative_uri
.. currentmodule:: sphinx.ext.autosummary
.. currentmodule:: sphinx.ext.autosummary
Autosummary preprocesses the docstrings and signatures with the same
:event:`autodoc-process-docstring` and :event:`autodoc-process-signature`

View File

@@ -133,6 +133,12 @@ are built:
elements (cf the `dvisvgm FAQ`_). This option is used only when
``imgmath_image_format`` is ``'svg'``.
.. confval:: imgmath_embed
Default: ``False``. If true, encode LaTeX output images within HTML files
(base64 encoded) and do not save separate png/svg files to disk.
.. versionadded:: 5.2
:mod:`sphinx.ext.mathjax` -- Render math via JavaScript
-------------------------------------------------------

View File

@@ -94,38 +94,38 @@ Docstring Sections
All of the following section headers are supported:
* ``Args`` *(alias of Parameters)*
* ``Arguments`` *(alias of Parameters)*
* ``Attention``
* ``Attributes``
* ``Caution``
* ``Danger``
* ``Error``
* ``Example``
* ``Examples``
* ``Hint``
* ``Important``
* ``Keyword Args`` *(alias of Keyword Arguments)*
* ``Keyword Arguments``
* ``Methods``
* ``Note``
* ``Notes``
* ``Other Parameters``
* ``Parameters``
* ``Return`` *(alias of Returns)*
* ``Returns``
* ``Raise`` *(alias of Raises)*
* ``Raises``
* ``References``
* ``See Also``
* ``Tip``
* ``Todo``
* ``Warning``
* ``Warnings`` *(alias of Warning)*
* ``Warn`` *(alias of Warns)*
* ``Warns``
* ``Yield`` *(alias of Yields)*
* ``Yields``
* ``Args`` *(alias of Parameters)*
* ``Arguments`` *(alias of Parameters)*
* ``Attention``
* ``Attributes``
* ``Caution``
* ``Danger``
* ``Error``
* ``Example``
* ``Examples``
* ``Hint``
* ``Important``
* ``Keyword Args`` *(alias of Keyword Arguments)*
* ``Keyword Arguments``
* ``Methods``
* ``Note``
* ``Notes``
* ``Other Parameters``
* ``Parameters``
* ``Return`` *(alias of Returns)*
* ``Returns``
* ``Raise`` *(alias of Raises)*
* ``Raises``
* ``References``
* ``See Also``
* ``Tip``
* ``Todo``
* ``Warning``
* ``Warnings`` *(alias of Warning)*
* ``Warn`` *(alias of Warns)*
* ``Warns``
* ``Yield`` *(alias of Yields)*
* ``Yields``
Google vs NumPy
~~~~~~~~~~~~~~~

View File

@@ -208,9 +208,9 @@ You can read more about them in the `Python Packaging User Guide`_.
Note that in some Linux distributions, such as Debian and Ubuntu,
this might require an extra installation step as follows.
.. code-block:: console
.. code-block:: console
$ apt-get install python3-venv
$ apt-get install python3-venv
Docker
------

View File

@@ -137,11 +137,15 @@ declarations:
This directive marks the beginning of the description of a module (or package
submodule, in which case the name should be fully qualified, including the
package name). It does not create content (like e.g. :rst:dir:`py:class`
does).
package name). A description of the module such as the docstring can be
placed in the body of the directive.
This directive will also cause an entry in the global module index.
.. versionchanged:: 5.2
Module directives support body content.
.. rubric:: options
.. rst:directive:option:: platform: platforms
@@ -165,6 +169,8 @@ declarations:
Mark a module as deprecated; it will be designated as such in various
locations then.
.. rst:directive:: .. py:currentmodule:: name
This directive tells Sphinx that the classes, functions etc. documented from
@@ -505,7 +511,8 @@ For functions with optional parameters that don't have default values
(typically functions implemented in C extension modules without keyword
argument support), you can use brackets to specify the optional parts:
.. py:function:: compile(source[, filename[, symbol]])
.. py:function:: compile(source[, filename[, symbol]])
:noindex:
It is customary to put the opening bracket before the comma.
@@ -561,20 +568,20 @@ explained by an example::
This will render like this:
.. py:function:: send_message(sender, recipient, message_body, [priority=1])
:noindex:
.. py:function:: send_message(sender, recipient, message_body, [priority=1])
:noindex:
Send a message to a recipient
Send a message to a recipient
:param str sender: The person sending the message
:param str recipient: The recipient of the message
:param str message_body: The body of the message
:param priority: The priority of the message, can be a number 1-5
:type priority: integer or None
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
:raises TypeError: if the message_body is not a basestring
:param str sender: The person sending the message
:param str recipient: The recipient of the message
:param str message_body: The body of the message
:param priority: The priority of the message, can be a number 1-5
:type priority: integer or None
:return: the message id
:rtype: int
:raises ValueError: if the message_body exceeds 160 characters
:raises TypeError: if the message_body is not a basestring
It is also possible to combine parameter type and description, if the type is a
single word, like this::
@@ -844,12 +851,16 @@ Example::
This will be rendered as:
.. c:struct:: Data
:noindexentry:
.. c:union:: @data
:noindexentry:
.. c:var:: int a
:noindexentry:
.. c:var:: double b
:noindexentry:
Explicit ref: :c:var:`Data.@data.a`. Short-hand ref: :c:var:`Data.a`.
@@ -931,8 +942,10 @@ Inline Expressions and Types
will be rendered as follows:
.. c:var:: int a = 42
:noindexentry:
.. c:function:: int f(int i)
:noindexentry:
An expression: :c:expr:`a * f(a)` (or as text: :c:texpr:`a * f(a)`).
@@ -1142,19 +1155,23 @@ visibility statement (``public``, ``private`` or ``protected``).
The example are rendered as follows.
.. cpp:type:: std::vector<int> MyList
:noindex:
A typedef-like declaration of a type.
.. cpp:type:: MyContainer::const_iterator
:noindex:
Declaration of a type alias with unspecified type.
.. cpp:type:: MyType = std::unordered_map<int, std::string>
:noindex:
Declaration of a type alias.
.. cpp:type:: template<typename T> \
MyContainer = std::vector<T>
:noindex:
.. rst:directive:: .. cpp:enum:: unscoped enum declaration
.. cpp:enum-struct:: scoped enum declaration
@@ -1281,12 +1298,16 @@ Example::
This will be rendered as:
.. cpp:class:: Data
:noindexentry:
.. cpp:union:: @data
:noindexentry:
.. cpp:var:: int a
:noindexentry:
.. cpp:var:: double b
:noindexentry:
Explicit ref: :cpp:var:`Data::@data::a`. Short-hand ref: :cpp:var:`Data::a`.
@@ -1392,10 +1413,12 @@ introduction` instead of a template parameter list::
They are rendered as follows.
.. cpp:function:: std::Iterator{It} void advance(It &it)
:noindexentry:
A function template with a template parameter constrained to be an Iterator.
.. cpp:class:: std::LessThanComparable{T} MySortedContainer
:noindexentry:
A class template with a template parameter constrained to be
LessThanComparable.
@@ -1425,8 +1448,10 @@ Inline Expressions and Types
will be rendered as follows:
.. cpp:var:: int a = 42
:noindexentry:
.. cpp:function:: int f(int i)
:noindexentry:
An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
@@ -1815,6 +1840,9 @@ The JavaScript domain (name **js**) provides the following directives:
current module name.
.. versionadded:: 1.6
.. versionchanged:: 5.2
Module directives support body content.
.. rst:directive:: .. js:function:: name(signature)
@@ -1838,15 +1866,16 @@ The JavaScript domain (name **js**) provides the following directives:
This is rendered as:
.. js:function:: $.getJSON(href, callback[, errback])
.. js:function:: $.getJSON(href, callback[, errback])
:noindex:
:param string href: An URI to the location of the resource.
:param callback: Gets called with the object.
:param errback:
Gets called in case the request fails. And a lot of other
text so we need multiple lines.
:throws SomeError: For whatever reason in that case.
:returns: Something.
:param string href: An URI to the location of the resource.
:param callback: Gets called with the object.
:param errback:
Gets called in case the request fails. And a lot of other
text so we need multiple lines.
:throws SomeError: For whatever reason in that case.
:returns: Something.
.. rst:directive:: .. js:method:: name(signature)
@@ -1867,10 +1896,11 @@ The JavaScript domain (name **js**) provides the following directives:
This is rendered as:
.. js:class:: MyAnimal(name[, age])
.. js:class:: MyAnimal(name[, age])
:noindex:
:param string name: The name of the animal
:param number age: an optional age for the animal
:param string name: The name of the animal
:param number age: an optional age for the animal
.. rst:directive:: .. js:data:: name
@@ -1913,13 +1943,15 @@ The reStructuredText domain (name **rst**) provides the following directives:
will be rendered as:
.. rst:directive:: foo
.. rst:directive:: foo
:noindex:
Foo description.
Foo description.
.. rst:directive:: .. bar:: baz
.. rst:directive:: .. bar:: baz
:noindex:
Bar description.
Bar description.
.. rst:directive:: .. rst:directive:option:: name
@@ -1935,12 +1967,14 @@ The reStructuredText domain (name **rst**) provides the following directives:
will be rendered as:
.. rst:directive:: toctree
:noindex:
.. rst:directive:: toctree
:noindex:
.. rst:directive:option:: caption: caption of ToC
.. rst:directive:option:: caption: caption of ToC
:noindex:
.. rst:directive:option:: glob
.. rst:directive:option:: glob
:noindex:
.. rubric:: options
@@ -1968,9 +2002,10 @@ The reStructuredText domain (name **rst**) provides the following directives:
will be rendered as:
.. rst:role:: foo
.. rst:role:: foo
:noindex:
Foo description.
Foo description.
.. _rst-roles:

196
pyproject.toml Normal file
View File

@@ -0,0 +1,196 @@
[build-system]
requires = ["flit_core>=3.7"]
build-backend = "flit_core.buildapi"
# project metadata
[project]
name = "Sphinx"
description = "Python documentation generator"
readme = "README.rst"
urls.Changelog = "https://www.sphinx-doc.org/en/master/changes.html"
urls.Code = "https://github.com/sphinx-doc/sphinx"
urls.Download = "https://pypi.org/project/Sphinx/"
urls.Homepage = "https://www.sphinx-doc.org/"
urls."Issue tracker" = "https://github.com/sphinx-doc/sphinx/issues"
license.text = "BSD"
requires-python = ">=3.6"
# Classifiers list: https://pypi.org/classifiers/
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: Science/Research",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"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",
"Framework :: Sphinx",
"Framework :: Sphinx :: Extension",
"Framework :: Sphinx :: Theme",
"Topic :: Documentation",
"Topic :: Documentation :: Sphinx",
"Topic :: Internet :: WWW/HTTP :: Site Management",
"Topic :: Printing",
"Topic :: Software Development",
"Topic :: Software Development :: Documentation",
"Topic :: Text Processing",
"Topic :: Text Processing :: General",
"Topic :: Text Processing :: Indexing",
"Topic :: Text Processing :: Markup",
"Topic :: Text Processing :: Markup :: HTML",
"Topic :: Text Processing :: Markup :: LaTeX",
"Topic :: Utilities",
]
dependencies = [
"sphinxcontrib-applehelp",
"sphinxcontrib-devhelp",
"sphinxcontrib-jsmath",
"sphinxcontrib-htmlhelp>=2.0.0",
"sphinxcontrib-serializinghtml>=1.1.5",
"sphinxcontrib-qthelp",
"Jinja2>=3.0",
"Pygments>=2.12",
"docutils>=0.14,<0.20",
"snowballstemmer>=2.0",
"babel>=2.9",
"alabaster>=0.7,<0.8",
"imagesize>=1.3",
"requests>=2.5.0",
"packaging>=21.0",
"importlib-metadata>=4.8; python_version < '3.10'",
"colorama>=0.4.5; sys_platform == 'win32'",
]
dynamic = ["version"]
[project.optional-dependencies]
docs = [
"sphinxcontrib-websupport",
]
lint = [
"flake8>=3.5.0",
"flake8-comprehensions",
"flake8-bugbear",
"flake8-simplify",
"isort",
"mypy>=0.971",
"sphinx-lint",
"docutils-stubs",
"types-typed-ast",
"types-requests",
]
test = [
"pytest>=4.6",
"html5lib",
"typed_ast; python_version < '3.8'",
"cython",
]
[[project.authors]]
name = "Georg Brandl"
email = "georg@python.org"
[project.scripts]
sphinx-build = "sphinx.cmd.build:main"
sphinx-quickstart = "sphinx.cmd.quickstart:main"
sphinx-apidoc = "sphinx.ext.apidoc:main"
sphinx-autogen = "sphinx.ext.autosummary.generate:main"
[project.entry-points."distutils.commands"]
build_sphinx = 'sphinx.setup_command:BuildDoc'
[tool.flit.module]
name = "sphinx"
[tool.flit.sdist]
include = [
"LICENSE",
"AUTHORS",
"CHANGES",
# Documentation
"doc/",
"CODE_OF_CONDUCT", # used as an include in the Documentation
"EXAMPLES", # used as an include in the Documentation
# Tests
"tests/",
"tox.ini",
# Utilities
"utils/",
"babel.cfg",
]
exclude = [
"doc/_build",
]
[tool.isort]
line_length = 95
[tool.mypy]
check_untyped_defs = true
disallow_incomplete_defs = true
follow_imports = "skip"
ignore_missing_imports = true
no_implicit_optional = true
python_version = "3.6"
show_column_numbers = true
show_error_codes = true
show_error_context = true
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
[[tool.mypy.overrides]]
module = [
"sphinx.application",
"sphinx.builders",
"sphinx.builders.html",
"sphinx.builders.latex",
"sphinx.builders.linkcheck",
"sphinx.directives.code",
"sphinx.domains.*",
"sphinx.environment",
"sphinx.environment.adapters.toctree",
"sphinx.environment.adapters.indexentries",
"sphinx.ext.*",
"sphinx.pycode.parser",
"sphinx.registry",
"sphinx.setup_command",
"sphinx.testing.util",
"sphinx.transforms.i18n",
"sphinx.transforms.post_transforms.images",
"sphinx.util.cfamily",
"sphinx.util.docfields",
"sphinx.util.docutils",
"sphinx.util.nodes",
"sphinx.util.typing",
"sphinx.writers.latex",
"sphinx.writers.text",
"sphinx.writers.xml"
]
strict_optional = false
[tool.pytest.ini_options]
filterwarnings = [
"all",
"ignore::DeprecationWarning:docutils.io",
"ignore::DeprecationWarning:pyximport.pyximport",
"ignore::ImportWarning:importlib._bootstrap",
]
markers = [
"apidoc",
"setup_command",
]
testpaths = ["tests"]

122
setup.cfg
View File

@@ -1,132 +1,12 @@
[metadata]
license_files = LICENSE
[egg_info]
tag_build = .dev
tag_date = true
[aliases]
release = egg_info -Db ''
upload = upload --sign --identity=36580288
[flake8]
max-line-length = 95
ignore = E116,E241,E251,E741,W504,I101,B006,B023
ignore = E116,E241,E251,E741,W504,I101,B006,B023,SIM102,SIM103,SIM105,SIM110,SIM113,SIM114,SIM115,SIM117,SIM223,SIM300,SIM401,SIM904,SIM905,SIM907
exclude = .git,.tox,.venv,tests/roots/*,build/*,doc/_build/*,sphinx/search/*,doc/usage/extensions/example*.py
application-import-names = sphinx
import-order-style = smarkets
per-file-ignores =
tests/*: E501
[isort]
line_length = 95
[mypy]
python_version = 3.6
disallow_incomplete_defs = True
show_column_numbers = True
show_error_context = True
show_error_codes = true
ignore_missing_imports = True
follow_imports = skip
check_untyped_defs = True
warn_unused_ignores = True
strict_optional = True
no_implicit_optional = True
warn_redundant_casts = True
[mypy-sphinx.application]
strict_optional = False
[mypy-sphinx.builders]
strict_optional = False
[mypy-sphinx.builders.html]
strict_optional = False
[mypy-sphinx.builders.latex]
strict_optional = False
[mypy-sphinx.builders.linkcheck]
strict_optional = False
[mypy-sphinx.directives.code]
strict_optional = False
[mypy-sphinx.domains.*]
strict_optional = False
[mypy-sphinx.environment]
strict_optional = False
[mypy-sphinx.environment.adapters.toctree]
strict_optional = False
[mypy-sphinx.environment.adapters.indexentries]
strict_optional = False
[mypy-sphinx.ext.*]
strict_optional = False
[mypy-sphinx.pycode.parser]
strict_optional = False
[mypy-sphinx.registry]
strict_optional = False
[mypy-sphinx.setup_command]
strict_optional = False
[mypy-sphinx.testing.util]
strict_optional = False
[mypy-sphinx.transforms.i18n]
strict_optional = False
[mypy-sphinx.transforms.post_transforms.images]
strict_optional = False
[mypy-sphinx.util.cfamily]
strict_optional = False
[mypy-sphinx.util.docfields]
strict_optional = False
[mypy-sphinx.util.docutils]
strict_optional = False
[mypy-sphinx.util.nodes]
strict_optional = False
[mypy-sphinx.util.typing]
strict_optional = False
[mypy-sphinx.writers.html]
strict_optional = False
[mypy-sphinx.writers.html5]
strict_optional = False
[mypy-sphinx.writers.latex]
strict_optional = False
[mypy-sphinx.writers.text]
strict_optional = False
[mypy-sphinx.writers.xml]
strict_optional = False
[tool:pytest]
filterwarnings =
all
ignore::DeprecationWarning:docutils.io
ignore::DeprecationWarning:pyximport.pyximport
ignore::ImportWarning:importlib._bootstrap
markers =
apidoc
setup_command
testpaths = tests
[coverage:run]
branch = True
parallel = True

122
setup.py
View File

@@ -1,122 +0,0 @@
from setuptools import find_packages, setup
import sphinx
with open('README.rst', encoding='utf-8') as f:
long_desc = f.read()
setup(
name='Sphinx',
version=sphinx.__version__,
url='https://www.sphinx-doc.org/',
download_url='https://pypi.org/project/Sphinx/',
license='BSD',
author='Georg Brandl',
author_email='georg@python.org',
description='Python documentation generator',
long_description=long_desc,
long_description_content_type='text/x-rst',
project_urls={
"Code": "https://github.com/sphinx-doc/sphinx",
"Changelog": "https://www.sphinx-doc.org/en/master/changes.html",
"Issue tracker": "https://github.com/sphinx-doc/sphinx/issues",
},
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Science/Research',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'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',
'Framework :: Sphinx',
'Framework :: Sphinx :: Extension',
'Framework :: Sphinx :: Theme',
'Topic :: Documentation',
'Topic :: Documentation :: Sphinx',
'Topic :: Internet :: WWW/HTTP :: Site Management',
'Topic :: Printing',
'Topic :: Software Development',
'Topic :: Software Development :: Documentation',
'Topic :: Text Processing',
'Topic :: Text Processing :: General',
'Topic :: Text Processing :: Indexing',
'Topic :: Text Processing :: Markup',
'Topic :: Text Processing :: Markup :: HTML',
'Topic :: Text Processing :: Markup :: LaTeX',
'Topic :: Utilities',
],
platforms='any',
packages=find_packages(exclude=['tests', 'utils']),
package_data = {
'sphinx': ['py.typed'],
},
include_package_data=True,
entry_points={
'console_scripts': [
'sphinx-build = sphinx.cmd.build:main',
'sphinx-quickstart = sphinx.cmd.quickstart:main',
'sphinx-apidoc = sphinx.ext.apidoc:main',
'sphinx-autogen = sphinx.ext.autosummary.generate:main',
],
'distutils.commands': [
'build_sphinx = sphinx.setup_command:BuildDoc',
],
},
python_requires=">=3.7",
install_requires=[
'sphinxcontrib-applehelp',
'sphinxcontrib-devhelp',
'sphinxcontrib-jsmath',
'sphinxcontrib-htmlhelp>=2.0.0',
'sphinxcontrib-serializinghtml>=1.1.5',
'sphinxcontrib-qthelp',
'Jinja2>=3.0',
'Pygments>=2.12',
'docutils>=0.14,<0.20',
'snowballstemmer>=2.0',
'babel>=2.9',
'alabaster>=0.7,<0.8',
'imagesize>=1.3',
'requests>=2.5.0',
'packaging>=21.0',
"importlib-metadata>=4.8; python_version < '3.10'",
"colorama>=0.4.5; sys_platform == 'win32'",
],
extras_require={
'docs': [
'sphinxcontrib-websupport',
],
'lint': [
'flake8>=3.5.0',
'flake8-comprehensions',
'flake8-bugbear',
'isort',
'mypy>=0.971',
'sphinx-lint',
'docutils-stubs',
"types-typed-ast",
"types-requests",
],
'test': [
'pytest>=4.6',
'html5lib',
"typed_ast; python_version < '3.8'",
'cython',
],
},
)

View File

@@ -1,13 +1,11 @@
"""The Sphinx documentation toolchain."""
# Keep this file executable as-is in Python 3!
# (Otherwise getting the version out of it from setup.py is impossible.)
# (Otherwise getting the version out of it when packaging is impossible.)
import os
import subprocess
import warnings
from os import path
from subprocess import PIPE
from .deprecation import RemovedInNextVersionWarning
@@ -22,7 +20,7 @@ warnings.filterwarnings('ignore', 'The frontend.Option class .*',
DeprecationWarning, module='docutils.frontend')
__version__ = '6.0.0'
__released__ = '6.0.0' # used when Sphinx builds its own docs
__display_version__ = __version__ # used for command line version
#: Version info for better programmatic use.
#:
@@ -36,18 +34,22 @@ version_info = (6, 0, 0, 'final', 0)
package_dir = path.abspath(path.dirname(__file__))
__display_version__ = __version__ # used for command line version
if __version__.endswith('+'):
# try to find out the commit hash if checked out from git, and append
# it to __version__ (since we use this value from setup.py, it gets
# automatically propagated to an installed copy as well)
__display_version__ = __version__
__version__ = __version__[:-1] # remove '+' for PEP-440 version spec.
_in_development = True
if _in_development:
# Only import subprocess if needed
import subprocess
try:
ret = subprocess.run(['git', 'show', '-s', '--pretty=format:%h'],
cwd=package_dir,
stdout=PIPE, stderr=PIPE, encoding='ascii')
if ret.stdout:
__display_version__ += '/' + ret.stdout.strip()
except Exception:
pass
ret = subprocess.run(
['git', 'show', '-s', '--pretty=format:%h'],
cwd=package_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding='ascii',
).stdout
if ret:
__display_version__ += '+/' + ret.strip()
del ret
finally:
del subprocess
del _in_development

View File

@@ -106,6 +106,8 @@ class Config:
'default_role': (None, 'env', [str]),
'add_function_parentheses': (True, 'env', []),
'add_module_names': (True, 'env', []),
'toc_object_entries_show_parents': ('domain', 'env',
ENUM('domain', 'all', 'hide')),
'trim_footnote_reference_space': (False, 'env', []),
'show_authors': (False, 'env', []),
'pygments_style': (None, 'html', [str]),

View File

@@ -131,6 +131,44 @@ class ObjectDescription(SphinxDirective, Generic[T]):
"""
pass
def _object_hierarchy_parts(self, sig_node: desc_signature) -> Tuple[str, ...]:
"""
Returns a tuple of strings, one entry for each part of the object's
hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The
returned tuple is used to properly nest children within parents in the
table of contents, and can also be used within the
:py:meth:`_toc_entry_name` method.
This method must not be used outwith table of contents generation.
"""
return ()
def _toc_entry_name(self, sig_node: desc_signature) -> str:
"""
Returns the text of the table of contents entry for the object.
This function is called once, in :py:meth:`run`, to set the name for the
table of contents entry (a special attribute ``_toc_name`` is set on the
object node, later used in
``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()``
when the table of contents entries are collected).
To support table of contents entries for their objects, domains must
override this method, also respecting the configuration setting
``toc_object_entries_show_parents``. Domains must also override
:py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the
object's hierarchy. The result of this method is set on the signature
node, and can be accessed as ``sig_node['_toc_parts']`` for use within
this method. The resulting tuple is also used to properly nest children
within parents in the table of contents.
An example implementations of this method is within the python domain
(:meth:`PyObject._toc_entry_name`). The python domain sets the
``_toc_parts`` attribute within the :py:meth:`handle_signature()`
method.
"""
return ''
def run(self) -> List[Node]:
"""
Main directive entry function, called by docutils upon encountering the
@@ -172,6 +210,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = noindex = ('noindex' in self.options)
node['noindexentry'] = ('noindexentry' in self.options)
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
@@ -194,6 +233,11 @@ class ObjectDescription(SphinxDirective, Generic[T]):
signode.clear()
signode += addnodes.desc_name(sig, sig)
continue # we don't want an index entry here
finally:
# Private attributes for ToC generation. Will be modified or removed
# without notice.
signode['_toc_parts'] = self._object_hierarchy_parts(signode)
signode['_toc_name'] = self._toc_entry_name(signode)
if name not in self.names:
self.names.append(name)
if not noindex:
@@ -203,6 +247,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
contentnode = addnodes.desc_content()
node.append(contentnode)
if self.names:
# needed for association of version{added,changed} directives
self.env.temp_data['object'] = self.names[0]

View File

@@ -77,10 +77,11 @@ class TocTree(SphinxDirective):
return ret
def parse_content(self, toctree: addnodes.toctree) -> List[Node]:
generated_docnames = frozenset(self.env.domains['std'].initial_data['labels'].keys())
suffixes = self.config.source_suffix
# glob target documents
all_docnames = self.env.found_docs.copy()
all_docnames = self.env.found_docs.copy() | generated_docnames
all_docnames.remove(self.env.docname) # remove current document
ret: List[Node] = []
@@ -95,6 +96,9 @@ class TocTree(SphinxDirective):
patname = docname_join(self.env.docname, entry)
docnames = sorted(patfilter(all_docnames, patname))
for docname in docnames:
if docname in generated_docnames:
# don't include generated documents in globs
continue
all_docnames.remove(docname) # don't include it again
toctree['entries'].append((None, docname))
toctree['includefiles'].append(docname)
@@ -118,7 +122,7 @@ class TocTree(SphinxDirective):
docname = docname_join(self.env.docname, docname)
if url_re.match(ref) or ref == 'self':
toctree['entries'].append((title, ref))
elif docname not in self.env.found_docs:
elif docname not in self.env.found_docs | generated_docnames:
if excluded(self.env.doc2path(docname, False)):
message = __('toctree contains reference to excluded document %r')
subtype = 'excluded'

View File

@@ -3142,6 +3142,7 @@ class CObject(ObjectDescription[ASTDeclaration]):
"""
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
}

View File

@@ -7186,6 +7186,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
]
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
'tparam-line-spec': directives.flag,
}

View File

@@ -18,8 +18,8 @@ from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_id, make_refnode
from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.nodes import make_id, make_refnode, nested_parse_with_titles
from sphinx.util.typing import OptionSpec
logger = logging.getLogger(__name__)
@@ -108,6 +108,17 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
_pseudo_parse_arglist(signode, arglist)
return fullname, prefix
def _object_hierarchy_parts(self, sig_node: desc_signature) -> Tuple[str, ...]:
if 'fullname' not in sig_node:
return ()
modname = sig_node.get('module')
fullname = sig_node['fullname']
if modname:
return (modname, *fullname.split('.'))
else:
return tuple(fullname.split('.'))
def add_target_and_index(self, name_obj: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
mod_name = self.env.ref_context.get('js:module')
@@ -201,6 +212,25 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
"""
return fullname.replace('$', '_S_')
def _toc_entry_name(self, sig_node: desc_signature) -> str:
if not sig_node.get('_toc_parts'):
return ''
config = self.env.app.config
objtype = sig_node.parent.get('objtype')
if config.add_function_parentheses and objtype in {'function', 'method'}:
parens = '()'
else:
parens = ''
*parents, name = sig_node['_toc_parts']
if config.toc_object_entries_show_parents == 'domain':
return sig_node.get('fullname', name) + parens
if config.toc_object_entries_show_parents == 'hide':
return name + parens
if config.toc_object_entries_show_parents == 'all':
return '.'.join(parents + [name + parens])
return ''
class JSCallable(JSObject):
"""Description of a JavaScript function, method or constructor."""
@@ -249,7 +279,7 @@ class JSModule(SphinxDirective):
:param mod_name: Module name
"""
has_content = False
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
@@ -261,7 +291,14 @@ class JSModule(SphinxDirective):
mod_name = self.arguments[0].strip()
self.env.ref_context['js:module'] = mod_name
noindex = 'noindex' in self.options
ret: List[Node] = []
content_node: Element = nodes.section()
with switch_source_input(self.state, self.content):
# necessary so that the child nodes get the right source/line set
content_node.document = self.state.document
nested_parse_with_titles(self.state, self.content, content_node)
ret: List[Node] = [*content_node.children]
if not noindex:
domain = cast(JavaScriptDomain, self.env.get_domain('js'))

View File

@@ -26,9 +26,10 @@ from sphinx.pycode.ast import parse as ast_parse
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.docutils import SphinxDirective
from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.inspect import signature_from_str
from sphinx.util.nodes import find_pending_xref_condition, make_id, make_refnode
from sphinx.util.nodes import (find_pending_xref_condition, make_id, make_refnode,
nested_parse_with_titles)
from sphinx.util.typing import OptionSpec, TextlikeNode
logger = logging.getLogger(__name__)
@@ -551,6 +552,17 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
return fullname, prefix
def _object_hierarchy_parts(self, sig_node: desc_signature) -> Tuple[str, ...]:
if 'fullname' not in sig_node:
return ()
modname = sig_node.get('module')
fullname = sig_node['fullname']
if modname:
return (modname, *fullname.split('.'))
else:
return tuple(fullname.split('.'))
def get_index_text(self, modname: str, name: Tuple[str, str]) -> str:
"""Return the text for the index entry of the object."""
raise NotImplementedError('must be implemented in subclasses')
@@ -634,6 +646,25 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
else:
self.env.ref_context.pop('py:module')
def _toc_entry_name(self, sig_node: desc_signature) -> str:
if not sig_node.get('_toc_parts'):
return ''
config = self.env.app.config
objtype = sig_node.parent.get('objtype')
if config.add_function_parentheses and objtype in {'function', 'method'}:
parens = '()'
else:
parens = ''
*parents, name = sig_node['_toc_parts']
if config.toc_object_entries_show_parents == 'domain':
return sig_node.get('fullname', name) + parens
if config.toc_object_entries_show_parents == 'hide':
return name + parens
if config.toc_object_entries_show_parents == 'all':
return '.'.join(parents + [name + parens])
return ''
class PyFunction(PyObject):
"""Description of a function."""
@@ -952,7 +983,7 @@ class PyModule(SphinxDirective):
Directive to mark description of a new module.
"""
has_content = False
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
@@ -969,7 +1000,14 @@ class PyModule(SphinxDirective):
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
self.env.ref_context['py:module'] = modname
ret: List[Node] = []
content_node: Element = nodes.section()
with switch_source_input(self.state, self.content):
# necessary so that the child nodes get the right source/line set
content_node.document = self.state.document
nested_parse_with_titles(self.state, self.content, content_node)
ret: List[Node] = [*content_node.children]
if not noindex:
# note module to the domain
node_id = make_id(self.env, self.state.document, 'module', modname)

View File

@@ -28,6 +28,10 @@ class ReSTMarkup(ObjectDescription[str]):
"""
Description of generic reST markup.
"""
option_spec: OptionSpec = {
'noindex': directives.flag,
'noindexentry': directives.flag,
}
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
node_id = make_id(self.env, self.state.document, self.objtype, name)
@@ -37,9 +41,10 @@ class ReSTMarkup(ObjectDescription[str]):
domain = cast(ReSTDomain, self.env.get_domain('rst'))
domain.note_object(self.objtype, name, node_id, location=signode)
indextext = self.get_index_text(self.objtype, name)
if indextext:
self.indexnode['entries'].append(('single', indextext, node_id, '', None))
if 'noindexentry' not in self.options:
indextext = self.get_index_text(self.objtype, name)
if indextext:
self.indexnode['entries'].append(('single', indextext, node_id, '', None))
def get_index_text(self, objectname: str, name: str) -> str:
return ''
@@ -52,6 +57,32 @@ class ReSTMarkup(ObjectDescription[str]):
"""
return self.objtype + '-' + name
def _object_hierarchy_parts(self, sig_node: desc_signature) -> Tuple[str, ...]:
if 'fullname' not in sig_node:
return ()
directive_names = []
for parent in self.env.ref_context.get('rst:directives', ()):
directive_names += parent.split(':')
name = sig_node['fullname']
return tuple(directive_names + name.split(':'))
def _toc_entry_name(self, sig_node: desc_signature) -> str:
if not sig_node.get('_toc_parts'):
return ''
config = self.env.app.config
objtype = sig_node.parent.get('objtype')
*parents, name = sig_node['_toc_parts']
if objtype == 'directive:option':
return f':{name}:'
if config.toc_object_entries_show_parents in {'domain', 'all'}:
name = ':'.join(sig_node['_toc_parts'])
if objtype == 'role':
return f':{name}:'
if objtype == 'directive':
return f'.. {name}::'
return ''
def parse_directive(d: str) -> Tuple[str, str]:
"""Parse a directive signature.
@@ -79,7 +110,8 @@ class ReSTDirective(ReSTMarkup):
"""
def handle_signature(self, sig: str, signode: desc_signature) -> str:
name, args = parse_directive(sig)
desc_name = '.. %s::' % name
desc_name = f'.. {name}::'
signode['fullname'] = name.strip()
signode += addnodes.desc_name(desc_name, desc_name)
if len(args) > 0:
signode += addnodes.desc_addname(args, args)
@@ -114,7 +146,9 @@ class ReSTDirectiveOption(ReSTMarkup):
except ValueError:
name, argument = sig, None
signode += addnodes.desc_name(':%s:' % name, ':%s:' % name)
desc_name = f':{name}:'
signode['fullname'] = name.strip()
signode += addnodes.desc_name(desc_name, desc_name)
if argument:
signode += addnodes.desc_annotation(' ' + argument, ' ' + argument)
if self.options.get('type'):
@@ -170,7 +204,9 @@ class ReSTRole(ReSTMarkup):
Description of a reST role.
"""
def handle_signature(self, sig: str, signode: desc_signature) -> str:
signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig)
desc_name = f':{sig}:'
signode['fullname'] = sig.strip()
signode += addnodes.desc_name(desc_name, desc_name)
return sig
def get_index_text(self, objectname: str, name: str) -> str:

View File

@@ -1,6 +1,7 @@
"""The standard domain."""
import re
import sys
from copy import copy
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional,
Tuple, Type, Union, cast)
@@ -28,6 +29,10 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
if sys.version_info[:2] >= (3, 8):
from typing import Final
else:
Final = Any
# RE for option descriptions
option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
@@ -584,7 +589,7 @@ class StandardDomain(Domain):
'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline),
}
initial_data = {
initial_data: Final = {
'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid
'labels': { # labelname -> docname, labelid, sectionname

View File

@@ -77,6 +77,60 @@ versioning_conditions: Dict[str, Union[bool, Callable]] = {
'text': is_translatable,
}
if TYPE_CHECKING:
from collections.abc import MutableMapping
from typing_extensions import Literal, overload
from sphinx.domains.c import CDomain
from sphinx.domains.changeset import ChangeSetDomain
from sphinx.domains.citation import CitationDomain
from sphinx.domains.cpp import CPPDomain
from sphinx.domains.index import IndexDomain
from sphinx.domains.javascript import JavaScriptDomain
from sphinx.domains.math import MathDomain
from sphinx.domains.python import PythonDomain
from sphinx.domains.rst import ReSTDomain
from sphinx.domains.std import StandardDomain
from sphinx.ext.duration import DurationDomain
from sphinx.ext.todo import TodoDomain
class _DomainsType(MutableMapping[str, Domain]):
@overload
def __getitem__(self, key: Literal["c"]) -> CDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["cpp"]) -> CPPDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["changeset"]) -> ChangeSetDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["citation"]) -> CitationDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["index"]) -> IndexDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["js"]) -> JavaScriptDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["math"]) -> MathDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["py"]) -> PythonDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["rst"]) -> ReSTDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["std"]) -> StandardDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["duration"]) -> DurationDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: Literal["todo"]) -> TodoDomain: ... # NoQA: E704
@overload
def __getitem__(self, key: str) -> Domain: ... # NoQA: E704
def __getitem__(self, key): raise NotImplementedError # NoQA: E704
def __setitem__(self, key, value): raise NotImplementedError # NoQA: E704
def __delitem__(self, key): raise NotImplementedError # NoQA: E704
def __iter__(self): raise NotImplementedError # NoQA: E704
def __len__(self): raise NotImplementedError # NoQA: E704
else:
_DomainsType = dict
class BuildEnvironment:
"""
@@ -85,7 +139,7 @@ class BuildEnvironment:
transformations to resolve links to them.
"""
domains: Dict[str, Domain]
domains: _DomainsType
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
@@ -105,7 +159,7 @@ class BuildEnvironment:
self.versioning_compare: bool = None
# all the registered domains, set by the application
self.domains = {}
self.domains = _DomainsType()
# the docutils settings for building
self.settings = default_settings.copy()
@@ -206,7 +260,7 @@ class BuildEnvironment:
self.version = app.registry.get_envversion(app)
# initialize domains
self.domains = {}
self.domains = _DomainsType()
for domain in app.registry.create_domains(self):
self.domains[domain.name] = domain

View File

@@ -1,6 +1,6 @@
"""Toctree adapter for sphinx.environment."""
from typing import TYPE_CHECKING, Any, Iterable, List, Optional, cast
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, cast
from docutils import nodes
from docutils.nodes import Element, Node
@@ -54,6 +54,7 @@ class TocTree:
"""
if toctree.get('hidden', False) and not includehidden:
return None
generated_docnames: Dict[str, Tuple[str, str, str]] = self.env.domains['std'].initial_data['labels'].copy() # NoQA: E501
# For reading the following two helper function, it is useful to keep
# in mind the node structure of a toctree (using HTML-like node names
@@ -139,6 +140,16 @@ class TocTree:
item = nodes.list_item('', para)
# don't show subitems
toc = nodes.bullet_list('', item)
elif ref in generated_docnames:
docname, _, sectionname = generated_docnames[ref]
if not title:
title = sectionname
reference = nodes.reference('', title, internal=True,
refuri=docname, anchorname='')
para = addnodes.compact_paragraph('', '', reference)
item = nodes.list_item('', para)
# don't show subitems
toc = nodes.bullet_list('', item)
else:
if ref in parents:
logger.warning(__('circular toctree references '

View File

@@ -1,6 +1,6 @@
"""Toctree collector for sphinx.environment."""
from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar, cast
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, TypeVar, Union, cast
from docutils import nodes
from docutils.nodes import Element, Node
@@ -54,20 +54,14 @@ class TocTreeCollector(EnvironmentCollector):
docname = app.env.docname
numentries = [0] # nonlocal again...
def traverse_in_section(node: Element, cls: Type[N]) -> List[N]:
"""Like traverse(), but stay within the same section."""
result: List[N] = []
if isinstance(node, cls):
result.append(node)
for child in node.children:
if isinstance(child, nodes.section):
continue
elif isinstance(child, nodes.Element):
result.extend(traverse_in_section(child, cls))
return result
def build_toc(node: Element, depth: int = 1) -> Optional[nodes.bullet_list]:
def build_toc(
node: Union[Element, Sequence[Element]],
depth: int = 1
) -> Optional[nodes.bullet_list]:
# list of table of contents entries
entries: List[Element] = []
# cache of parents -> list item
memo_parents: Dict[Tuple[str, ...], nodes.list_item] = {}
for sectionnode in node:
# find all toctree nodes in this section and add them
# to the toc (just copying the toctree node which is then
@@ -79,13 +73,7 @@ class TocTreeCollector(EnvironmentCollector):
visitor = SphinxContentsFilter(doctree)
title.walkabout(visitor)
nodetext = visitor.get_entry_text()
if not numentries[0]:
# for the very first toc entry, don't add an anchor
# as it is the file's title anyway
anchorname = ''
else:
anchorname = '#' + sectionnode['ids'][0]
numentries[0] += 1
anchorname = _make_anchor_name(sectionnode['ids'], numentries)
# make these nodes:
# list_item -> compact_paragraph -> reference
reference = nodes.reference(
@@ -97,22 +85,67 @@ class TocTreeCollector(EnvironmentCollector):
if sub_item:
item += sub_item
entries.append(item)
# Wrap items under an ``.. only::`` directive in a node for
# post-processing
elif isinstance(sectionnode, addnodes.only):
onlynode = addnodes.only(expr=sectionnode['expr'])
blist = build_toc(sectionnode, depth)
if blist:
onlynode += blist.children
entries.append(onlynode)
# check within the section for other node types
elif isinstance(sectionnode, nodes.Element):
for toctreenode in traverse_in_section(sectionnode,
addnodes.toctree):
item = toctreenode.copy()
entries.append(item)
# important: do the inventory stuff
TocTree(app.env).note(docname, toctreenode)
toctreenode: nodes.Node
for toctreenode in sectionnode.findall():
if isinstance(toctreenode, nodes.section):
continue
if isinstance(toctreenode, addnodes.toctree):
item = toctreenode.copy()
entries.append(item)
# important: do the inventory stuff
TocTree(app.env).note(docname, toctreenode)
# add object signatures within a section to the ToC
elif isinstance(toctreenode, addnodes.desc):
for sig_node in toctreenode:
if not isinstance(sig_node, addnodes.desc_signature):
continue
# Skip if no name set
if not sig_node.get('_toc_name', ''):
continue
# Skip entries with no ID (e.g. with :noindex: set)
ids = sig_node['ids']
if not ids or sig_node.parent.get('noindexentry'):
continue
anchorname = _make_anchor_name(ids, numentries)
reference = nodes.reference(
'', '', nodes.literal('', sig_node['_toc_name']),
internal=True, refuri=docname, anchorname=anchorname)
para = addnodes.compact_paragraph('', '', reference,
skip_section_number=True)
entry = nodes.list_item('', para)
*parents, _ = sig_node['_toc_parts']
parents = tuple(parents)
# Cache parents tuple
memo_parents[sig_node['_toc_parts']] = entry
# Nest children within parents
if parents and parents in memo_parents:
root_entry = memo_parents[parents]
if isinstance(root_entry[-1], nodes.bullet_list):
root_entry[-1].append(entry)
else:
root_entry.append(nodes.bullet_list('', entry))
continue
entries.append(entry)
if entries:
return nodes.bullet_list('', *entries)
return None
toc = build_toc(doctree)
if toc:
app.env.tocs[docname] = toc
@@ -153,6 +186,8 @@ class TocTreeCollector(EnvironmentCollector):
_walk_toc(subnode, secnums, depth, titlenode)
titlenode = None
elif isinstance(subnode, addnodes.compact_paragraph):
if 'skip_section_number' in subnode:
continue
numstack[-1] += 1
reference = cast(nodes.reference, subnode[0])
if depth > 0:
@@ -201,6 +236,7 @@ class TocTreeCollector(EnvironmentCollector):
def assign_figure_numbers(self, env: BuildEnvironment) -> List[str]:
"""Assign a figure number to each figure under a numbered toctree."""
generated_docnames = frozenset(env.domains['std'].initial_data['labels'].keys())
rewrite_needed = []
@@ -212,7 +248,8 @@ class TocTreeCollector(EnvironmentCollector):
def get_figtype(node: Node) -> Optional[str]:
for domain in env.domains.values():
figtype = domain.get_enumerable_node_type(node)
if domain.name == 'std' and not domain.get_numfig_title(node): # type: ignore
if (domain.name == 'std'
and not domain.get_numfig_title(node)): # type: ignore[attr-defined] # NoQA: E501,W503
# Skip if uncaptioned node
continue
@@ -247,6 +284,7 @@ class TocTreeCollector(EnvironmentCollector):
fignumbers[figure_id] = get_next_fignumber(figtype, secnum)
def _walk_doctree(docname: str, doctree: Element, secnum: Tuple[int, ...]) -> None:
nonlocal generated_docnames
for subnode in doctree.children:
if isinstance(subnode, nodes.section):
next_secnum = get_section_number(docname, subnode)
@@ -259,6 +297,9 @@ class TocTreeCollector(EnvironmentCollector):
if url_re.match(subdocname) or subdocname == 'self':
# don't mess with those
continue
if subdocname in generated_docnames:
# or these
continue
_walk_doc(subdocname, secnum)
elif isinstance(subnode, nodes.Element):
@@ -283,6 +324,17 @@ class TocTreeCollector(EnvironmentCollector):
return rewrite_needed
def _make_anchor_name(ids: List[str], num_entries: List[int]) -> str:
if not num_entries[0]:
# for the very first toc entry, don't add an anchor
# as it is the file's title anyway
anchorname = ''
else:
anchorname = '#' + ids[0]
num_entries[0] += 1
return anchorname
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_env_collector(TocTreeCollector)

View File

@@ -956,6 +956,15 @@ class ModuleDocumenter(Documenter):
merge_members_option(self.options)
self.__all__: Optional[Sequence[str]] = None
def add_content(self, more_content: Optional[StringList]) -> None:
old_indent = self.indent
self.indent += ' '
super().add_content(None)
self.indent = old_indent
if more_content:
for line, src in zip(more_content.data, more_content.items):
self.add_line(line, src[0], src[1])
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:

View File

@@ -1,5 +1,6 @@
"""Render math in HTML via dvipng or dvisvgm."""
import base64
import posixpath
import re
import shutil
@@ -30,6 +31,8 @@ logger = logging.getLogger(__name__)
templates_path = path.join(package_dir, 'templates', 'imgmath')
__all__ = ()
class MathExtError(SphinxError):
category = 'Math extension error'
@@ -204,13 +207,17 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder) -> Tuple[str, Optional[in
return filename, depth
def render_math(self: HTMLTranslator, math: str) -> Tuple[Optional[str], Optional[int]]:
def render_math(
self: HTMLTranslator,
math: str,
) -> Tuple[Optional[str], Optional[int], Optional[str], Optional[str]]:
"""Render the LaTeX math expression *math* using latex and dvipng or
dvisvgm.
Return the filename relative to the built document and the "depth",
that is, the distance of image bottom and baseline in pixels, if the
option to use preview_latex is switched on.
Also return the temporary and destination files.
Error handling may seem strange, but follows a pattern: if LaTeX or dvipng
(dvisvgm) aren't available, only a warning is generated (since that enables
@@ -235,19 +242,19 @@ def render_math(self: HTMLTranslator, math: str) -> Tuple[Optional[str], Optiona
depth = read_png_depth(outfn)
elif image_format == 'svg':
depth = read_svg_depth(outfn)
return relfn, depth
return relfn, depth, None, outfn
# if latex or dvipng (dvisvgm) has failed once, don't bother to try again
if hasattr(self.builder, '_imgmath_warned_latex') or \
hasattr(self.builder, '_imgmath_warned_image_translator'):
return None, None
return None, None, None, None
# .tex -> .dvi
try:
dvipath = compile_math(latex, self.builder)
except InvokeError:
self.builder._imgmath_warned_latex = True # type: ignore
return None, None
return None, None, None, None
# .dvi -> .png/.svg
try:
@@ -257,13 +264,19 @@ def render_math(self: HTMLTranslator, math: str) -> Tuple[Optional[str], Optiona
imgpath, depth = convert_dvi_to_svg(dvipath, self.builder)
except InvokeError:
self.builder._imgmath_warned_image_translator = True # type: ignore
return None, None
return None, None, None, None
# Move generated image on tempdir to build dir
ensuredir(path.dirname(outfn))
shutil.move(imgpath, outfn)
return relfn, depth, imgpath, outfn
return relfn, depth
def render_maths_to_base64(image_format: str, outfn: Optional[str]) -> str:
with open(outfn, "rb") as f:
encoded = base64.b64encode(f.read()).decode(encoding='utf-8')
if image_format == 'png':
return f'data:image/png;base64,{encoded}'
if image_format == 'svg':
return f'data:image/svg+xml;base64,{encoded}'
raise MathExtError('imgmath_image_format must be either "png" or "svg"')
def cleanup_tempdir(app: Sphinx, exc: Exception) -> None:
@@ -285,7 +298,7 @@ def get_tooltip(self: HTMLTranslator, node: Element) -> str:
def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
try:
fname, depth = render_math(self, '$' + node.astext() + '$')
fname, depth, imgpath, outfn = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc:
msg = str(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
@@ -293,14 +306,23 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
sm.walkabout(self)
logger.warning(__('display latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode from exc
if fname is None:
if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower()
img_src = render_maths_to_base64(image_format, outfn)
else:
# Move generated image on tempdir to build dir
if imgpath is not None:
ensuredir(path.dirname(outfn))
shutil.move(imgpath, outfn)
img_src = fname
if img_src is None:
# something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span>' %
self.encode(node.astext()).strip())
else:
c = ('<img class="math" src="%s"' % fname) + get_tooltip(self, node)
c = f'<img class="math" src="{img_src}"' + get_tooltip(self, node)
if depth is not None:
c += ' style="vertical-align: %dpx"' % (-depth)
c += f' style="vertical-align: {-depth:d}px"'
self.body.append(c + '/>')
raise nodes.SkipNode
@@ -311,7 +333,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
else:
latex = wrap_displaymath(node.astext(), None, False)
try:
fname, depth = render_math(self, latex)
fname, depth, imgpath, outfn = render_math(self, latex)
except MathExtError as exc:
msg = str(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
@@ -326,12 +348,21 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
self.body.append('<span class="eqno">(%s)' % number)
self.add_permalink_ref(node, _('Permalink to this equation'))
self.body.append('</span>')
if fname is None:
if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower()
img_src = render_maths_to_base64(image_format, outfn)
else:
# Move generated image on tempdir to build dir
if imgpath is not None:
ensuredir(path.dirname(outfn))
shutil.move(imgpath, outfn)
img_src = fname
if img_src is None:
# something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span></p>\n</div>' %
self.encode(node.astext()).strip())
else:
self.body.append(('<img src="%s"' % fname) + get_tooltip(self, node) +
self.body.append(f'<img src="{img_src}"' + get_tooltip(self, node) +
'/></p>\n</div>')
raise nodes.SkipNode
@@ -354,5 +385,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('imgmath_latex_preamble', '', 'html')
app.add_config_value('imgmath_add_tooltips', True, 'html')
app.add_config_value('imgmath_font_size', 12, 'html')
app.add_config_value('imgmath_embed', False, 'html', [bool])
app.connect('build-finished', cleanup_tempdir)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@@ -14,6 +14,7 @@ from docutils.nodes import Element, Node
from sphinx import addnodes, package_dir
from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.environment import BuildEnvironment
from sphinx.util import split_into
class SearchLanguage:
@@ -242,6 +243,7 @@ class IndexBuilder:
# stemmed words in titles -> set(docname)
self._title_mapping: Dict[str, Set[str]] = {}
self._all_titles: Dict[str, List[Tuple[str, str]]] = {} # docname -> all titles
self._index_entries: Dict[str, List[Tuple[str, str, str]]] = {} # docname -> index entry
self._stem_cache: Dict[str, str] = {} # word -> stemmed word
self._objtypes: Dict[Tuple[str, str], int] = {} # objtype -> index
# objtype index -> (domain, type, objname (localized))
@@ -378,12 +380,17 @@ class IndexBuilder:
alltitles: Dict[str, List[Tuple[int, str]]] = {}
for docname, titlelist in self._all_titles.items():
for title, titleid in titlelist:
alltitles.setdefault(title.lower(), []).append((fn2index[docname], titleid))
alltitles.setdefault(title, []).append((fn2index[docname], titleid))
index_entries: Dict[str, List[Tuple[int, str]]] = {}
for docname, entries in self._index_entries.items():
for entry, entry_id, main_entry in entries:
index_entries.setdefault(entry.lower(), []).append((fn2index[docname], entry_id))
return dict(docnames=docnames, filenames=filenames, titles=titles, terms=terms,
objects=objects, objtypes=objtypes, objnames=objnames,
titleterms=title_terms, envversion=self.env.version,
alltitles=alltitles)
alltitles=alltitles, indexentries=index_entries)
def label(self) -> str:
return "%s (code: %s)" % (self.lang.language_name, self.lang.lang)
@@ -441,6 +448,38 @@ class IndexBuilder:
if _filter(stemmed_word) and not already_indexed:
self._mapping.setdefault(stemmed_word, set()).add(docname)
# find explicit entries within index directives
_index_entries: Set[Tuple[str, str, str]] = set()
for node in doctree.findall(addnodes.index):
for entry_type, value, tid, main, *index_key in node['entries']:
tid = tid or ''
try:
if entry_type == 'single':
try:
entry, subentry = split_into(2, 'single', value)
except ValueError:
entry, = split_into(1, 'single', value)
subentry = ''
_index_entries.add((entry, tid, main))
if subentry:
_index_entries.add((subentry, tid, main))
elif entry_type == 'pair':
first, second = split_into(2, 'pair', value)
_index_entries.add((first, tid, main))
_index_entries.add((second, tid, main))
elif entry_type == 'triple':
first, second, third = split_into(3, 'triple', value)
_index_entries.add((first, tid, main))
_index_entries.add((second, tid, main))
_index_entries.add((third, tid, main))
elif entry_type in {'see', 'seealso'}:
first, second = split_into(2, 'see', value)
_index_entries.add((first, tid, main))
except ValueError:
pass
self._index_entries[docname] = sorted(_index_entries)
def context_for_searchtool(self) -> Dict[str, Any]:
if self.lang.js_splitter_code:
js_splitter_code = self.lang.js_splitter_code

View File

@@ -242,6 +242,7 @@ const Search = {
const docNames = Search._index.docnames;
const titles = Search._index.titles;
const allTitles = Search._index.alltitles;
const indexEntries = Search._index.indexentries;
// stem the search terms and add them to the correct list
const stemmer = new Stemmer();
@@ -280,12 +281,12 @@ const Search = {
const queryLower = query.toLowerCase();
for (const [title, foundTitles] of Object.entries(allTitles)) {
if (title.includes(queryLower) && (queryLower.length >= title.length/2)) {
if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) {
for (const [file, id] of foundTitles) {
let score = Math.round(100 * queryLower.length / title.length)
results.push([
docNames[file],
titles[file],
`${titles[file]} > ${title}`,
id !== null ? "#" + id : "",
null,
score,
@@ -295,6 +296,23 @@ const Search = {
}
}
// search for explicit entries in index directives
for (const [entry, foundEntries] of Object.entries(indexEntries)) {
if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
for (const [file, id] of foundEntries) {
let score = Math.round(100 * queryLower.length / entry.length)
results.push([
docNames[file],
titles[file],
id ? "#" + id : "",
null,
score,
filenames[file],
]);
}
}
}
// lookup as object
objectTerms.forEach((term) =>
results.push(...Search.performObjectSearch(term, objectTerms))

View File

@@ -2,6 +2,7 @@
import os
import re
import shutil
import sys
from typing import Dict, Pattern
@@ -22,21 +23,8 @@ def terminal_safe(s: str) -> str:
def get_terminal_width() -> int:
"""Borrowed from the py lib."""
if sys.platform == "win32":
# For static typing, as fcntl & termios never exist on Windows.
return int(os.environ.get('COLUMNS', 80)) - 1
try:
import fcntl
import struct
import termios
call = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hhhh', 0, 0, 0, 0))
height, width = struct.unpack('hhhh', call)[:2]
terminal_width = width
except Exception:
# FALLBACK
terminal_width = int(os.environ.get('COLUMNS', 80)) - 1
return terminal_width
"""Return the width of the terminal in columns."""
return shutil.get_terminal_size().columns - 1
_tw: int = get_terminal_width()

View File

@@ -70,7 +70,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
Our custom HTML translator.
"""
builder: "StandaloneHTMLBuilder" = None
builder: "StandaloneHTMLBuilder"
def __init__(self, document: nodes.document, builder: Builder) -> None:
super().__init__(document, builder)
@@ -280,7 +280,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def depart_seealso(self, node: Element) -> None:
self.depart_admonition(node)
def get_secnumber(self, node: Element) -> Tuple[int, ...]:
def get_secnumber(self, node: Element) -> Optional[Tuple[int, ...]]:
if node.get('secnumber'):
return node['secnumber']
elif isinstance(node.parent, nodes.section):

View File

@@ -45,7 +45,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
Our custom HTML translator.
"""
builder: "StandaloneHTMLBuilder" = None
builder: "StandaloneHTMLBuilder"
# Override docutils.writers.html5_polyglot:HTMLTranslator
# otherwise, nodes like <inline classes="s">...</inline> will be
# converted to <s>...</s> by `visit_inline`.
@@ -258,7 +258,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def depart_seealso(self, node: Element) -> None:
self.depart_admonition(node)
def get_secnumber(self, node: Element) -> Tuple[int, ...]:
def get_secnumber(self, node: Element) -> Optional[Tuple[int, ...]]:
if node.get('secnumber'):
return node['secnumber']

View File

@@ -259,7 +259,7 @@ def rstdim_to_latexdim(width_str: str, scale: int = 100) -> str:
class LaTeXTranslator(SphinxTranslator):
builder: "LaTeXBuilder" = None
builder: "LaTeXBuilder"
secnumdepth = 2 # legacy sphinxhowto.cls uses this, whereas article.cls
# default is originally 3. For book/report, 2 is already LaTeX default.

View File

@@ -0,0 +1,39 @@
test-domain-objects
===================
.. py:module:: hello
.. py:function:: world() -> str
Prints "Hello, World!" to stdout
.. py:class:: HelloWorldPrinter
Controls printing of hello world
.. py:method:: set_language()
Sets the language of the HelloWorldPrinter instance
.. py:attribute:: output_count
Count of outputs of "Hello, World!"
.. py:method:: print_normal()
:async:
:classmethod:
Prints the normal form of "Hello, World!"
.. py:method:: print()
Prints "Hello, World!", including in the chosen language
.. py:function:: exit()
:module: sys
Quits the interpreter
.. js:function:: fetch(resource)
Fetches the given resource, returns a Promise

View File

@@ -0,0 +1,6 @@
.. toctree::
:numbered:
:caption: Table of Contents
:name: mastertoc
domains

View File

View File

@@ -0,0 +1,8 @@
foo
===
:index:`word`
.. py:module:: pymodule
.. py:function:: Timer.repeat(repeat=3, number=1000000)

View File

@@ -0,0 +1,15 @@
test-toctree-index
==================
.. toctree::
foo
.. toctree::
:caption: Indices
genindex
modindex
search

View File

@@ -2,7 +2,7 @@
import pytest
from docutils import nodes
from docutils.nodes import bullet_list, comment, list_item, reference, title
from docutils.nodes import bullet_list, comment, list_item, literal, reference, title
from sphinx import addnodes
from sphinx.addnodes import compact_paragraph, only
@@ -126,6 +126,44 @@ def test_glob(app):
assert app.env.numbered_toctrees == set()
@pytest.mark.sphinx('dummy', testroot='toctree-domain-objects')
def test_domain_objects(app):
includefiles = ['domains']
app.build()
assert app.env.toc_num_entries['index'] == 0
assert app.env.toc_num_entries['domains'] == 9
assert app.env.toctree_includes['index'] == includefiles
for file in includefiles:
assert 'index' in app.env.files_to_rebuild[file]
assert app.env.glob_toctrees == set()
assert app.env.numbered_toctrees == {'index'}
# tocs
toctree = app.env.tocs['domains']
assert_node(toctree,
[bullet_list, list_item, (compact_paragraph, # [0][0]
[bullet_list, (list_item, # [0][1][0]
[list_item, # [0][1][1]
(compact_paragraph, # [0][1][1][0]
[bullet_list, (list_item, # [0][1][1][1][0]
list_item,
list_item,
list_item)])], # [0][1][1][1][3]
list_item,
list_item)])]) # [0][1][1]
assert_node(toctree[0][0],
[compact_paragraph, reference, "test-domain-objects"])
assert_node(toctree[0][1][0],
[list_item, ([compact_paragraph, reference, literal, "world()"])])
assert_node(toctree[0][1][1][1][3],
[list_item, ([compact_paragraph, reference, literal, "HelloWorldPrinter.print()"])])
@pytest.mark.sphinx('xml', testroot='toctree')
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_get_toc_for(app):
@@ -346,3 +384,17 @@ def test_get_toctree_for_includehidden(app):
assert_node(toctree[2],
[bullet_list, list_item, compact_paragraph, reference, "baz"])
@pytest.mark.sphinx('xml', testroot='toctree-index')
def test_toctree_index(app):
app.build()
toctree = app.env.tocs['index']
assert_node(toctree,
[bullet_list, ([list_item, (compact_paragraph, # [0][0]
[bullet_list, (addnodes.toctree, # [0][1][0]
addnodes.toctree)])])]) # [0][1][1]
assert_node(toctree[0][1][1], addnodes.toctree,
caption="Indices", glob=False, hidden=False,
titlesonly=False, maxdepth=-1, numbered=0,
entries=[(None, 'genindex'), (None, 'modindex'), (None, 'search')])

View File

@@ -19,7 +19,7 @@ def test_empty_all(app):
'',
'.. py:module:: target.empty_all',
'',
'docsting of empty_all module.',
' docsting of empty_all module.',
'',
]

View File

@@ -56,6 +56,24 @@ def test_imgmath_svg(app, status, warning):
assert re.search(html, content, re.S)
@pytest.mark.skipif(not has_binary('dvisvgm'),
reason='Requires dvisvgm" binary')
@pytest.mark.sphinx('html', testroot='ext-math-simple',
confoverrides={'extensions': ['sphinx.ext.imgmath'],
'imgmath_image_format': 'svg',
'imgmath_embed': True})
def test_imgmath_svg_embed(app, status, warning):
app.builder.build_all()
if "LaTeX command 'latex' cannot be run" in warning.getvalue():
pytest.skip('LaTeX command "latex" is not available')
if "dvisvgm command 'dvisvgm' cannot be run" in warning.getvalue():
pytest.skip('dvisvgm command "dvisvgm" is not available')
content = (app.outdir / 'index.html').read_text(encoding='utf8')
html = r'<img src="data:image/svg\+xml;base64,[\w\+/=]+"'
assert re.search(html, content, re.DOTALL)
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax'],
'mathjax_options': {'integrity': 'sha384-0123456789'}})

View File

@@ -178,7 +178,8 @@ def test_IndexBuilder():
'test': [0, 1, 2, 3]},
'titles': ('title1_1', 'title1_2', 'title2_1', 'title2_2'),
'titleterms': {'section_titl': [0, 1, 2, 3]},
'alltitles': {'section_title': [(0, 'section-title'), (1, 'section-title'), (2, 'section-title'), (3, 'section-title')]}
'alltitles': {'section_title': [(0, 'section-title'), (1, 'section-title'), (2, 'section-title'), (3, 'section-title')]},
'indexentries': {},
}
assert index._objtypes == {('dummy1', 'objtype1'): 0, ('dummy2', 'objtype1'): 1}
assert index._objnames == {0: ('dummy1', 'objtype1', 'objtype1'),
@@ -236,7 +237,8 @@ def test_IndexBuilder():
'test': [0, 1]},
'titles': ('title1_2', 'title2_2'),
'titleterms': {'section_titl': [0, 1]},
'alltitles': {'section_title': [(0, 'section-title'), (1, 'section-title')]}
'alltitles': {'section_title': [(0, 'section-title'), (1, 'section-title')]},
'indexentries': {},
}
assert index._objtypes == {('dummy1', 'objtype1'): 0, ('dummy2', 'objtype1'): 1}
assert index._objnames == {0: ('dummy1', 'objtype1', 'objtype1'),

View File

@@ -1,6 +1,7 @@
[tox]
minversion = 2.4.0
envlist = docs,flake8,mypy,twine,py{36,37,38,39,310},du{14,15,16,17,18,19}
isolated_build = True
[testenv]
usedevelop = True
@@ -34,7 +35,7 @@ commands=
[testenv:du-latest]
commands =
python -m pip install "git+https://repo.or.cz/docutils.git#subdirectory=docutils"
python -m pip install "git+https://repo.or.cz/docutils.git#subdirectory=docutils" --no-warn-conflicts
{[testenv]commands}
[testenv:flake8]
@@ -92,7 +93,7 @@ description =
Build documentation.
extras =
docs
deps =
deps =
sphinx-autobuild
commands =
sphinx-autobuild ./doc ./build/sphinx/
@@ -103,8 +104,9 @@ description =
Lint package.
deps =
twine
build
commands =
python setup.py release bdist_wheel sdist
python -m build .
twine check dist/*
[testenv:bindep]

View File

@@ -23,19 +23,23 @@ def stringify_version(version_info, in_develop=True):
def bump_version(path, version_info, in_develop=True):
version = stringify_version(version_info, in_develop)
release = version
if in_develop:
version += '+'
with open(path, 'r+', encoding='utf-8') as f:
body = f.read()
body = re.sub(r"(?<=__version__ = ')[^']+", version, body)
body = re.sub(r"(?<=__released__ = ')[^']+", release, body)
body = re.sub(r"(?<=version_info = )\(.*\)", str(version_info), body)
with open(path, 'r', encoding='utf-8') as f:
lines = f.read().splitlines()
f.seek(0)
f.truncate(0)
f.write(body)
for i, line in enumerate(lines):
if line.startswith('__version__ = '):
lines[i] = f"__version__ = '{version}'"
continue
if line.startswith('version_info = '):
lines[i] = f'version_info = {version_info}'
continue
if line.startswith('_in_development = '):
lines[i] = f'_in_development = {in_develop}'
continue
with open(path, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines) + '\n')
def parse_version(version):

View File

@@ -10,7 +10,7 @@ for stable releases
* Check diff by ``git diff``
* ``git commit -am 'Bump to X.Y.Z final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``
@@ -34,7 +34,7 @@ for first beta releases
* Check diff by ``git diff``
* ``git commit -am 'Bump to X.Y.0 beta1'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0b1``
@@ -62,7 +62,7 @@ for other beta releases
* Check diff by ``git diff``
* ``git commit -am 'Bump to X.Y.0 betaN'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0bN``
@@ -87,7 +87,7 @@ for major releases
* Check diff by ``git diff``
* ``git commit -am 'Bump to X.Y.0 final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``