Merge branch 'master' into feature-autosummary-packages

This commit is contained in:
woutdenolf 2019-02-24 12:16:52 +01:00 committed by GitHub
commit 33e7b76c32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 2688 additions and 2742 deletions

View File

@ -13,13 +13,14 @@ matrix:
include: include:
- python: '3.5' - python: '3.5'
env: env:
- TOXENV=du13 - TOXENV=du12
- python: '3.6' - python: '3.6'
env: env:
- TOXENV=py36 - TOXENV=du13
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
- python: '3.7' - python: '3.7'
env: TOXENV=py37 env:
- TOXENV=py37
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
- python: 'nightly' - python: 'nightly'
env: TOXENV=py38 env: TOXENV=py38
- python: '3.6' - python: '3.6'

80
CHANGES
View File

@ -1,9 +1,51 @@
Release 2.0.0 (in development) Release 2.1.0 (in development)
============================== ==============================
Dependencies Dependencies
------------ ------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 2.0.0 beta2 (in development)
====================================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 2.0.0 beta1 (in development)
====================================
Dependencies
------------
* LaTeX builder now depends on TeX Live 2015 or above. * LaTeX builder now depends on TeX Live 2015 or above.
* LaTeX builder (with ``'pdflatex'`` :confval:`latex_engine`) will process * LaTeX builder (with ``'pdflatex'`` :confval:`latex_engine`) will process
Unicode Greek letters in text (not in math mark-up) via the text font and Unicode Greek letters in text (not in math mark-up) via the text font and
@ -23,7 +65,9 @@ Dependencies
- sphinxcontrib.applehelp - sphinxcontrib.applehelp
- sphinxcontrib.devhelp - sphinxcontrib.devhelp
- sphinxcontrib.htmlhelp
- sphinxcontrib.jsmath - sphinxcontrib.jsmath
- sphinxcontrib.serializinghtml
- sphinxcontrib.qthelp - sphinxcontrib.qthelp
Incompatible changes Incompatible changes
@ -59,12 +103,17 @@ Incompatible changes
from LaTeX preamble now get overwritten. Use ``\sphinxtableofcontentshook`` from LaTeX preamble now get overwritten. Use ``\sphinxtableofcontentshook``
to insert custom user definitions. See :ref:`latex-macros`. to insert custom user definitions. See :ref:`latex-macros`.
* quickstart: Simplify generated ``conf.py`` * quickstart: Simplify generated ``conf.py``
* #4148: quickstart: some questions are removed. They are still able to specify
via command line options
* websupport: unbundled from sphinx core. Please use sphinxcontrib-websupport * websupport: unbundled from sphinx core. Please use sphinxcontrib-websupport
* C++, the visibility of base classes is now always rendered as present in the * C++, the visibility of base classes is now always rendered as present in the
input. That is, ``private`` is now shown, where it was ellided before. input. That is, ``private`` is now shown, where it was ellided before.
* LaTeX: graphics inclusion of oversized images rescales to not exceed * LaTeX: graphics inclusion of oversized images rescales to not exceed
the text width and height, even if width and/or height option were used. the text width and height, even if width and/or height option were used.
(refs: #5956) (refs: #5956)
* epub: ``epub_title`` defaults to the :confval:`project` option
* #4550: All tables and figures without ``align`` option are displayed to center
* #4587: html: Output HTML5 by default
Deprecated Deprecated
---------- ----------
@ -75,6 +124,10 @@ Deprecated
``EpubBuilder.build_container()``, ``EpubBuilder.bulid_content()``, ``EpubBuilder.build_container()``, ``EpubBuilder.bulid_content()``,
``EpubBuilder.build_toc()`` and ``EpubBuilder.build_epub()`` ``EpubBuilder.build_toc()`` and ``EpubBuilder.build_epub()``
* The arguments of ``Epub3Builder.build_navigation_doc()`` * The arguments of ``Epub3Builder.build_navigation_doc()``
* The config variables
- :confval:`html_experimental_html5_writer`
* The ``encoding`` argument of ``autodoc.Documenter.get_doc()``, * The ``encoding`` argument of ``autodoc.Documenter.get_doc()``,
``autodoc.DocstringSignatureMixin.get_doc()``, ``autodoc.DocstringSignatureMixin.get_doc()``,
``autodoc.DocstringSignatureMixin._find_signature()``, and ``autodoc.DocstringSignatureMixin._find_signature()``, and
@ -108,21 +161,30 @@ Deprecated
* ``sphinx.io.SphinxFileInput.supported`` * ``sphinx.io.SphinxFileInput.supported``
* ``sphinx.io.SphinxRSTFileInput`` * ``sphinx.io.SphinxRSTFileInput``
* ``sphinx.registry.SphinxComponentRegistry.add_source_input()`` * ``sphinx.registry.SphinxComponentRegistry.add_source_input()``
* ``sphinx.roles.abbr_role()``
* ``sphinx.roles.emph_literal_role()``
* ``sphinx.roles.menusel_role()``
* ``sphinx.roles.index_role()``
* ``sphinx.roles.indexmarkup_role()``
* ``sphinx.testing.util.remove_unicode_literal()`` * ``sphinx.testing.util.remove_unicode_literal()``
* ``sphinx.util.attrdict`` * ``sphinx.util.attrdict``
* ``sphinx.util.force_decode()`` * ``sphinx.util.force_decode()``
* ``sphinx.util.get_matching_docs()`` * ``sphinx.util.get_matching_docs()``
* ``sphinx.util.inspect.Parameter`` * ``sphinx.util.inspect.Parameter``
* ``sphinx.util.jsonimpl``
* ``sphinx.util.osutil.EEXIST`` * ``sphinx.util.osutil.EEXIST``
* ``sphinx.util.osutil.EINVAL`` * ``sphinx.util.osutil.EINVAL``
* ``sphinx.util.osutil.ENOENT`` * ``sphinx.util.osutil.ENOENT``
* ``sphinx.util.osutil.EPIPE`` * ``sphinx.util.osutil.EPIPE``
* ``sphinx.util.osutil.walk()`` * ``sphinx.util.osutil.walk()``
* ``sphinx.util.PeekableIterator`` * ``sphinx.util.PeekableIterator``
* ``sphinx.util.pycompat.NoneType``
* ``sphinx.util.pycompat.TextIOWrapper`` * ``sphinx.util.pycompat.TextIOWrapper``
* ``sphinx.util.pycompat.UnicodeMixin`` * ``sphinx.util.pycompat.UnicodeMixin``
* ``sphinx.util.pycompat.htmlescape`` * ``sphinx.util.pycompat.htmlescape``
* ``sphinx.util.pycompat.indent`` * ``sphinx.util.pycompat.indent``
* ``sphinx.util.pycompat.sys_encoding``
* ``sphinx.util.pycompat.terminal_safe()``
* ``sphinx.util.pycompat.u`` * ``sphinx.util.pycompat.u``
* ``sphinx.writers.latex.ExtBabel`` * ``sphinx.writers.latex.ExtBabel``
* ``sphinx.writers.latex.LaTeXTranslator._make_visit_admonition()`` * ``sphinx.writers.latex.LaTeXTranslator._make_visit_admonition()``
@ -154,6 +216,11 @@ Features added
* #4182: autodoc: Support :confval:`suppress_warnings` * #4182: autodoc: Support :confval:`suppress_warnings`
* #5533: autodoc: :confval:`autodoc_default_options` supports ``member-order`` * #5533: autodoc: :confval:`autodoc_default_options` supports ``member-order``
* #5394: autodoc: Display readable names in type annotations for mocked objects
* #5459: autodoc: :confval:`autodoc_default_options` accepts ``True`` as a value
* #1148: autodoc: Add :rst:dir:`autodecorator` directive for decorators
* #5635: autosummary: Add :confval:`autosummary_mock_imports` to mock external
libraries on importing targets
* #4018: htmlhelp: Add :confval:`htmlhelp_file_suffix` and * #4018: htmlhelp: Add :confval:`htmlhelp_file_suffix` and
:confval:`htmlhelp_link_suffix` :confval:`htmlhelp_link_suffix`
* #5559: text: Support complex tables (colspan and rowspan) * #5559: text: Support complex tables (colspan and rowspan)
@ -173,9 +240,16 @@ Features added
* #4611: epub: Show warning for duplicated ToC entries * #4611: epub: Show warning for duplicated ToC entries
* #1851: Allow to omit an argument for :rst:dir:`code-block` directive. If * #1851: Allow to omit an argument for :rst:dir:`code-block` directive. If
omitted, it follows :rst:dir:`highlight` or :confval:`highlight_language` omitted, it follows :rst:dir:`highlight` or :confval:`highlight_language`
* #4587: html: Add :confval:`html4_writer` to use old HTML4 writer
* #6016: HTML search: A placeholder for the search summary prevents search * #6016: HTML search: A placeholder for the search summary prevents search
result links from changing their position when the search terminates. This result links from changing their position when the search terminates. This
makes navigating search results easier. makes navigating search results easier.
* #5196: linkcheck also checks remote images exist
* #5924: githubpages: create CNAME file for custom domains when
:confval:`html_baseurl` set
* #4261: autosectionlabel: restrict the labeled sections by new config value;
:confval:`autosectionlabel_maxdepth`
Bugs fixed Bugs fixed
---------- ----------
@ -225,6 +299,10 @@ Bugs fixed
---------- ----------
* LaTeX: Remove extraneous space after author names on PDF title page (refs: #6004) * LaTeX: Remove extraneous space after author names on PDF title page (refs: #6004)
* #6026: LaTeX: A cross reference to definition list does not work
* #6046: LaTeX: ``TypeError`` is raised when invalid latex_elements given
* #6019: imgconverter: Including multipage PDF fails
* #6047: autodoc: ``autofunction`` emits a warning for method objects
Testing Testing
-------- --------

15
bindep.txt Normal file
View File

@ -0,0 +1,15 @@
texlive [platform:rpm]
texlive-fncychap [platform:rpm]
texlive-titlesec [platform:rpm]
texlive-tabulary [platform:rpm]
texlive-framed [platform:rpm]
texlive-wrapfig [platform:rpm]
texlive-upquote [platform:rpm]
texlive-capt-of [platform:rpm]
texlive-needspace [platform:rpm]
texlive-polyglossia [platform:rpm]
texlive-luatex85 [platform:rpm]
texlive-anyfontsize [platform:rpm]
texlive-ctablestack [platform:rpm]
texlive-gnu-freefont [platform:rpm]
latexmk [platform:rpm]

View File

@ -337,7 +337,7 @@ a tt:hover {
} }
pre { pre {
font-family: 'Consolas', 'DejaVu Sans Mono', font-family: 'Consolas', 'Courier New', 'DejaVu Sans Mono',
'Bitstream Vera Sans Mono', monospace; 'Bitstream Vera Sans Mono', monospace;
font-size: 13px; font-size: 13px;
letter-spacing: 0.015em; letter-spacing: 0.015em;
@ -388,32 +388,29 @@ div.admonition, div.warning {
padding: 0; padding: 0;
} }
div.admonition p, div.warning p { div.admonition > p, div.warning > p {
margin: 0.5em 1em 0.5em 1em; margin: 0.5em 1em 0.5em 1em;
padding: 0; padding: 0;
} }
div.admonition pre, div.warning pre { div.admonition > pre, div.warning > pre {
margin: 0.4em 1em 0.4em 1em; margin: 0.4em 1em 0.4em 1em;
} }
div.admonition p.admonition-title, div.admonition > p.admonition-title,
div.warning p.admonition-title { div.warning > p.admonition-title {
margin-top: 1em; margin-top: 0.5em;
padding-top: 0.5em;
font-weight: bold; font-weight: bold;
} }
div.warning { div.warning {
border: 1px solid #940000; border: 1px solid #940000;
/* background-color: #FFCCCF;*/
} }
div.warning p.admonition-title { div.admonition > ul,
} div.admonition > ol,
div.warning > ul,
div.admonition ul, div.admonition ol, div.warning > ol {
div.warning ul, div.warning ol {
margin: 0.1em 0.5em 0.5em 3em; margin: 0.1em 0.5em 0.5em 3em;
padding: 0; padding: 0;
} }

View File

@ -290,11 +290,36 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinx.builders.epub3.validate_config_values()`` - ``sphinx.builders.epub3.validate_config_values()``
* - ``sphinx.builders.html.JSONHTMLBuilder``
- 2.0
- 4.0
- ``sphinx.builders.serializinghtml.JSONHTMLBuilder``
* - ``sphinx.builders.html.PickleHTMLBuilder``
- 2.0
- 4.0
- ``sphinx.builders.serializinghtml.PickleHTMLBuilder``
* - ``sphinx.builders.html.SerializingHTMLBuilder``
- 2.0
- 4.0
- ``sphinx.builders.serializinghtml.SerializingHTMLBuilder``
* - ``sphinx.builders.html.SingleFileHTMLBuilder`` * - ``sphinx.builders.html.SingleFileHTMLBuilder``
- 2.0 - 2.0
- 4.0 - 4.0
- ``sphinx.builders.singlehtml.SingleFileHTMLBuilder`` - ``sphinx.builders.singlehtml.SingleFileHTMLBuilder``
* - ``sphinx.builders.html.WebHTMLBuilder``
- 2.0
- 4.0
- ``sphinx.builders.serializinghtml.PickleHTMLBuilder``
* - ``sphinx.builders.htmlhelp``
- 2.0
- 4.0
- ``sphinxcontrib.htmlhelp``
* - ``sphinx.builders.htmlhelp.HTMLHelpBuilder.open_file()`` * - ``sphinx.builders.htmlhelp.HTMLHelpBuilder.open_file()``
- 2.0 - 2.0
- 4.0 - 4.0
@ -370,6 +395,31 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinxcontrib.jsmath`` - ``sphinxcontrib.jsmath``
* - ``sphinx.roles.abbr_role()``
- 2.0
- 4.0
- ``sphinx.roles.Abbreviation``
* - ``sphinx.roles.emph_literal_role()``
- 2.0
- 4.0
- ``sphinx.roles.EmphasizedLiteral``
* - ``sphinx.roles.menusel_role()``
- 2.0
- 4.0
- ``sphinx.roles.GUILabel`` or ``sphinx.roles.MenuSelection``
* - ``sphinx.roles.index_role()``
- 2.0
- 4.0
- ``sphinx.roles.Index``
* - ``sphinx.roles.indexmarkup_role()``
- 2.0
- 4.0
- ``sphinx.roles.PEP`` or ``sphinx.roles.RFC``
* - ``sphinx.testing.util.remove_unicode_literal()`` * - ``sphinx.testing.util.remove_unicode_literal()``
- 2.0 - 2.0
- 4.0 - 4.0
@ -395,6 +445,11 @@ The following is a list of deprecated interfaces.
- 3.0 - 3.0
- N/A - N/A
* - ``sphinx.util.jsonimpl``
- 2.0
- 4.0
- ``sphinxcontrib.serializinghtml.jsonimpl``
* - ``sphinx.util.osutil.EEXIST`` * - ``sphinx.util.osutil.EEXIST``
- 2.0 - 2.0
- 4.0 - 4.0
@ -420,6 +475,11 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``os.walk()`` - ``os.walk()``
* - ``sphinx.util.pycompat.NoneType``
- 2.0
- 4.0
- ``sphinx.util.typing.NoneType``
* - ``sphinx.util.pycompat.TextIOWrapper`` * - ``sphinx.util.pycompat.TextIOWrapper``
- 2.0 - 2.0
- 4.0 - 4.0
@ -440,6 +500,16 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``textwrap.indent()`` - ``textwrap.indent()``
* - ``sphinx.util.pycompat.sys_encoding``
- 2.0
- 4.0
- ``sys.getdefaultencoding()``
* - ``sphinx.util.pycompat.terminal_safe()``
- 2.0
- 4.0
- ``sphinx.util.console.terminal_safe()``
* - ``sphinx.util.pycompat.u`` * - ``sphinx.util.pycompat.u``
- 2.0 - 2.0
- 4.0 - 4.0

View File

@ -18,5 +18,11 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
.. autoclass:: sphinx.util.docutils.SphinxDirective .. autoclass:: sphinx.util.docutils.SphinxDirective
:members: :members:
.. autoclass:: sphinx.util.docutils.SphinxRole
:members:
.. autoclass:: sphinx.util.docutils.ReferenceRole
:members:
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter .. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
:members: :members:

View File

@ -28,6 +28,7 @@ The builder's "name" must be given to the **-b** command-line option of
.. autoattribute:: supported_image_types .. autoattribute:: supported_image_types
.. module:: sphinx.builders.dirhtml
.. class:: DirectoryHTMLBuilder .. class:: DirectoryHTMLBuilder
This is a subclass of the standard HTML builder. Its output is a directory This is a subclass of the standard HTML builder. Its output is a directory
@ -45,6 +46,7 @@ The builder's "name" must be given to the **-b** command-line option of
.. versionadded:: 0.6 .. versionadded:: 0.6
.. module:: sphinx.builders.singlehtml
.. class:: SingleFileHTMLBuilder .. class:: SingleFileHTMLBuilder
This is an HTML builder that combines the whole project in one output file. This is an HTML builder that combines the whole project in one output file.
@ -297,7 +299,7 @@ name is ``rinoh``. Refer to the `rinohtype manual`_ for details.
.. versionadded:: 1.1 .. versionadded:: 1.1
.. currentmodule:: sphinx.builders.html .. currentmodule:: sphinxcontrib.serializinghtml
.. class:: SerializingHTMLBuilder .. class:: SerializingHTMLBuilder
This builder uses a module that implements the Python serialization API This builder uses a module that implements the Python serialization API

View File

@ -1331,6 +1331,12 @@ that use Sphinx's HTMLWriter class.
.. versionadded:: 1.6 .. versionadded:: 1.6
.. deprecated:: 2.0
.. confval:: html4_writer
Output is processed with HTML4 writer. Default is ``False``.
Options for Single HTML output Options for Single HTML output
------------------------------- -------------------------------
@ -1561,7 +1567,11 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
.. confval:: epub_title .. confval:: epub_title
The title of the document. It defaults to the :confval:`html_title` option The title of the document. It defaults to the :confval:`html_title` option
but can be set independently for epub creation. but can be set independently for epub creation. It defaults to the
:confval:`project` option.
.. versionchanged:: 2.0
It defaults to the ``project`` option.
.. confval:: epub_description .. confval:: epub_description

View File

@ -237,6 +237,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. rst:directive:: autofunction .. rst:directive:: autofunction
autodecorator
autodata autodata
automethod automethod
autoattribute autoattribute
@ -293,10 +294,11 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
docstrings. docstrings.
.. versionchanged:: 1.1 .. versionchanged:: 1.1
Comment docs are now allowed on the same line after an assignment. Comment docs are now allowed on the same line after an assignment.
.. versionchanged:: 1.2 .. versionchanged:: 1.2
:rst:dir:`autodata` and :rst:dir:`autoattribute` have an ``annotation`` :rst:dir:`autodata` and :rst:dir:`autoattribute` have an ``annotation``
option. option.
.. versionchanged:: 2.0
:rst:dir:`autodecorator` added.
.. note:: .. note::
@ -376,12 +378,12 @@ There are also new config values that you can set:
'members': 'var1, var2', 'members': 'var1, var2',
'member-order': 'bysource', 'member-order': 'bysource',
'special-members': '__init__', 'special-members': '__init__',
'undoc-members': None, 'undoc-members': True,
'exclude-members': '__weakref__' 'exclude-members': '__weakref__'
} }
Setting ``None`` is equivalent to giving the option name in the list format Setting ``None`` or ``True`` to the value is equivalent to giving only the
(i.e. it means "yes/true/on"). option name to the directives.
The supported options are ``'members'``, ``'member-order'``, The supported options are ``'members'``, ``'member-order'``,
``'undoc-members'``, ``'private-members'``, ``'special-members'``, ``'undoc-members'``, ``'private-members'``, ``'special-members'``,
@ -390,6 +392,9 @@ There are also new config values that you can set:
.. versionadded:: 1.8 .. versionadded:: 1.8
.. versionchanged:: 2.0
Accepts ``True`` as a value.
.. confval:: autodoc_docstring_signature .. confval:: autodoc_docstring_signature
Functions imported from C modules cannot be introspected, and therefore the Functions imported from C modules cannot be introspected, and therefore the

View File

@ -38,3 +38,10 @@ Configuration
called ``Introduction`` that appears in document ``index.rst``. Useful for called ``Introduction`` that appears in document ``index.rst``. Useful for
avoiding ambiguity when the same section heading appears in different avoiding ambiguity when the same section heading appears in different
documents. documents.
.. confval:: autosectionlabel_maxdepth
If set, autosectionlabel chooses the sections for labeling by its depth. For
example, when set 1 to ``autosectionlabel_maxdepth``, labels are generated
only for top level sections, and deeper sections are not labeled. It
defaults to ``None`` (disabled).

View File

@ -181,6 +181,12 @@ Packages can be explored recursively when generating stub pages.
.. versionadded:: 2.0 .. versionadded:: 2.0
.. confval:: autosummary_mock_imports
This value contains a list of modules to be mocked up. See
:confval:`autodoc_mock_imports` for more details. It defaults to
:confval:`autodoc_mock_imports`.
Customizing templates Customizing templates
--------------------- ---------------------

View File

@ -6,5 +6,11 @@
.. versionadded:: 1.4 .. versionadded:: 1.4
.. versionchanged:: 2.0
Support ``CNAME`` file
This extension creates ``.nojekyll`` file on generated HTML directory to publish This extension creates ``.nojekyll`` file on generated HTML directory to publish
the document on GitHub Pages. the document on GitHub Pages.
It also creates a ``CNAME`` file for custom domains when :confval:`html_baseurl`
set.

View File

@ -18,6 +18,8 @@ install_requires = [
'sphinxcontrib-applehelp', 'sphinxcontrib-applehelp',
'sphinxcontrib-devhelp', 'sphinxcontrib-devhelp',
'sphinxcontrib-jsmath', 'sphinxcontrib-jsmath',
'sphinxcontrib-htmlhelp',
'sphinxcontrib-serializinghtml',
'sphinxcontrib-qthelp', 'sphinxcontrib-qthelp',
'Jinja2>=2.3', 'Jinja2>=2.3',
'Pygments>=2.0', 'Pygments>=2.0',

View File

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

View File

@ -37,7 +37,6 @@ from sphinx.registry import SphinxComponentRegistry
from sphinx.util import docutils from sphinx.util import docutils
from sphinx.util import import_object, progress_message from sphinx.util import import_object, progress_message
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import pycompat # noqa: F401
from sphinx.util.build_phase import BuildPhase from sphinx.util.build_phase import BuildPhase
from sphinx.util.console import bold # type: ignore from sphinx.util.console import bold # type: ignore
from sphinx.util.docutils import directive_helper from sphinx.util.docutils import directive_helper
@ -68,7 +67,6 @@ builtin_extensions = (
'sphinx.builders.dummy', 'sphinx.builders.dummy',
'sphinx.builders.gettext', 'sphinx.builders.gettext',
'sphinx.builders.html', 'sphinx.builders.html',
'sphinx.builders.htmlhelp',
'sphinx.builders.latex', 'sphinx.builders.latex',
'sphinx.builders.linkcheck', 'sphinx.builders.linkcheck',
'sphinx.builders.manpage', 'sphinx.builders.manpage',
@ -108,6 +106,8 @@ builtin_extensions = (
# 1st party extensions # 1st party extensions
'sphinxcontrib.applehelp', 'sphinxcontrib.applehelp',
'sphinxcontrib.devhelp', 'sphinxcontrib.devhelp',
'sphinxcontrib.htmlhelp',
'sphinxcontrib.serializinghtml',
'sphinxcontrib.qthelp', 'sphinxcontrib.qthelp',
# Strictly, alabaster theme is not a builtin extension, # Strictly, alabaster theme is not a builtin extension,
# but it is loaded automatically to use it as default theme. # but it is loaded automatically to use it as default theme.

View File

@ -19,7 +19,7 @@ from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.io import read_doc from sphinx.io import read_doc
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import i18n, import_object, logging, rst, status_iterator from sphinx.util import i18n, import_object, logging, rst, progress_message, status_iterator
from sphinx.util.build_phase import BuildPhase from sphinx.util.build_phase import BuildPhase
from sphinx.util.console import bold # type: ignore from sphinx.util.console import bold # type: ignore
from sphinx.util.docutils import sphinx_domains from sphinx.util.docutils import sphinx_domains
@ -351,16 +351,14 @@ class Builder:
if updated_docnames: if updated_docnames:
# save the environment # save the environment
from sphinx.application import ENV_PICKLE_FILENAME from sphinx.application import ENV_PICKLE_FILENAME
logger.info(bold(__('pickling environment... ')), nonl=True) with progress_message(__('pickling environment')):
with open(path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f: with open(path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f:
pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL) pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL)
logger.info(__('done'))
# global actions # global actions
self.app.phase = BuildPhase.CONSISTENCY_CHECK self.app.phase = BuildPhase.CONSISTENCY_CHECK
logger.info(bold(__('checking consistency... ')), nonl=True) with progress_message(__('checking consistency')):
self.env.check_consistency() self.env.check_consistency()
logger.info(__('done'))
else: else:
if method == 'update' and not docnames: if method == 'update' and not docnames:
logger.info(bold(__('no targets are out of date.'))) logger.info(bold(__('no targets are out of date.')))
@ -559,9 +557,8 @@ class Builder:
docnames.add(tocdocname) docnames.add(tocdocname)
docnames.add(self.config.master_doc) docnames.add(self.config.master_doc)
logger.info(bold(__('preparing documents... ')), nonl=True) with progress_message(__('preparing documents')):
self.prepare_writing(docnames) self.prepare_writing(docnames)
logger.info(__('done'))
if self.parallel_ok: if self.parallel_ok:
# number of subprocesses is parallel-1 because the main process # number of subprocesses is parallel-1 because the main process

View File

@ -134,8 +134,6 @@ class EpubBuilder(StandaloneHTMLBuilder):
html_scaled_image_link = False html_scaled_image_link = False
# don't generate search index or include search page # don't generate search index or include search page
search = False search = False
# use html5 translator by default
default_html5_translator = True
coverpage_name = COVERPAGE_NAME coverpage_name = COVERPAGE_NAME
toctree_template = TOCTREE_TEMPLATE toctree_template = TOCTREE_TEMPLATE
@ -655,7 +653,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
if incr: if incr:
self.playorder += 1 self.playorder += 1
self.tocid += 1 self.tocid += 1
return NavPoint(self.esc('navPoint%d' % self.tocid), self.playorder, return NavPoint('navPoint%d' % self.tocid, self.playorder,
node['text'], node['refuri'], []) node['text'], node['refuri'], [])
def build_navpoints(self, nodes): def build_navpoints(self, nodes):

View File

@ -269,7 +269,7 @@ def setup(app):
app.add_config_value('epub_version', 3.0, 'epub') # experimental app.add_config_value('epub_version', 3.0, 'epub') # experimental
app.add_config_value('epub_theme', 'epub', 'epub') app.add_config_value('epub_theme', 'epub', 'epub')
app.add_config_value('epub_theme_options', {}, 'epub') app.add_config_value('epub_theme_options', {}, 'epub')
app.add_config_value('epub_title', lambda self: self.html_title, 'epub') app.add_config_value('epub_title', lambda self: self.project, 'epub')
app.add_config_value('epub_author', lambda self: self.author, 'epub') app.add_config_value('epub_author', lambda self: self.author, 'epub')
app.add_config_value('epub_language', lambda self: self.language or 'en', 'epub') app.add_config_value('epub_language', lambda self: self.language or 'en', 'epub')
app.add_config_value('epub_publisher', lambda self: self.author, 'epub') app.add_config_value('epub_publisher', lambda self: self.author, 'epub')

View File

@ -9,16 +9,13 @@
""" """
import html import html
import pickle
import posixpath import posixpath
import re import re
import sys import sys
import types
import warnings import warnings
from hashlib import md5 from hashlib import md5
from os import path from os import path
import docutils
from docutils import nodes from docutils import nodes
from docutils.core import publish_parts from docutils.core import publish_parts
from docutils.frontend import OptionParser from docutils.frontend import OptionParser
@ -26,7 +23,6 @@ from docutils.io import DocTreeInput, StringOutput
from docutils.utils import relative_path from docutils.utils import relative_path
from sphinx import package_dir, __display_version__ from sphinx import package_dir, __display_version__
from sphinx.application import ENV_PICKLE_FILENAME
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.deprecation import ( from sphinx.deprecation import (
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
@ -39,15 +35,14 @@ from sphinx.highlighting import PygmentsBridge
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.search import js_index from sphinx.search import js_index
from sphinx.theming import HTMLThemeFactory from sphinx.theming import HTMLThemeFactory
from sphinx.util import jsonimpl, logging, status_iterator from sphinx.util import logging, status_iterator
from sphinx.util.console import bold # type: ignore from sphinx.util.console import bold # type: ignore
from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.docutils import is_html5_writer_available, new_document
from sphinx.util.fileutil import copy_asset from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile
from sphinx.util.matching import patmatch, Matcher, DOTFILES from sphinx.util.matching import patmatch, Matcher, DOTFILES
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \ from sphinx.util.osutil import os_path, relative_uri, ensuredir, movefile, copyfile
movefile, copyfile
from sphinx.writers.html import HTMLWriter, HTMLTranslator from sphinx.writers.html import HTMLWriter, HTMLTranslator
if False: if False:
@ -58,7 +53,7 @@ if False:
from sphinx.domains import Domain, Index, IndexEntry # NOQA from sphinx.domains import Domain, Index, IndexEntry # NOQA
from sphinx.util.tags import Tags # NOQA from sphinx.util.tags import Tags # NOQA
# Experimental HTML5 Writer # HTML5 Writer is avialable or not
if is_html5_writer_available(): if is_html5_writer_available():
from sphinx.writers.html5 import HTML5Translator from sphinx.writers.html5 import HTML5Translator
html5_ready = True html5_ready = True
@ -67,8 +62,6 @@ else:
#: the filename for the inventory of objects #: the filename for the inventory of objects
INVENTORY_FILENAME = 'objects.inv' INVENTORY_FILENAME = 'objects.inv'
#: the filename for the "last build" file (for serializing builders)
LAST_BUILD_FILENAME = 'last_build'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
return_codes_re = re.compile('[\r\n]+') return_codes_re = re.compile('[\r\n]+')
@ -116,24 +109,24 @@ class JSContainer(list):
"""The container for JavaScript scripts.""" """The container for JavaScript scripts."""
def insert(self, index, obj): def insert(self, index, obj):
# type: (int, str) -> None # type: (int, str) -> None
warnings.warn('builder.script_files is deprecated. ' warnings.warn('To modify script_files in the theme is deprecated. '
'Please use app.add_js_file() instead.', 'Please insert a <script> tag directly in your theme instead.',
RemovedInSphinx30Warning, stacklevel=2) RemovedInSphinx30Warning, stacklevel=3)
super().insert(index, obj) super().insert(index, obj)
def extend(self, other): # type: ignore def extend(self, other): # type: ignore
# type: (List[str]) -> None # type: (List[str]) -> None
warnings.warn('builder.script_files is deprecated. ' warnings.warn('To modify script_files in the theme is deprecated. '
'Please use app.add_js_file() instead.', 'Please insert a <script> tag directly in your theme instead.',
RemovedInSphinx30Warning, stacklevel=2) RemovedInSphinx30Warning, stacklevel=3)
for item in other: for item in other:
self.append(item) self.append(item)
def __iadd__(self, other): # type: ignore def __iadd__(self, other): # type: ignore
# type: (List[str]) -> JSContainer # type: (List[str]) -> JSContainer
warnings.warn('builder.script_files is deprecated. ' warnings.warn('To modify script_files in the theme is deprecated. '
'Please use app.add_js_file() instead.', 'Please insert a <script> tag directly in your theme instead.',
RemovedInSphinx30Warning, stacklevel=2) RemovedInSphinx30Warning, stacklevel=3)
for item in other: for item in other:
self.append(item) self.append(item)
return self return self
@ -242,8 +235,6 @@ class StandaloneHTMLBuilder(Builder):
search = True # for things like HTML help and Apple help: suppress search search = True # for things like HTML help and Apple help: suppress search
use_index = False use_index = False
download_support = True # enable download role download_support = True # enable download role
# use html5 translator by default
default_html5_translator = False
imgpath = None # type: str imgpath = None # type: str
domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA
@ -285,11 +276,6 @@ class StandaloneHTMLBuilder(Builder):
self.use_index = self.get_builder_config('use_index', 'html') self.use_index = self.get_builder_config('use_index', 'html')
if self.config.html_experimental_html5_writer and not html5_ready:
logger.warning(__('html_experimental_html5_writer is set, but current version '
'is old. Docutils\' version should be 0.13 or newer, but %s.'),
docutils.__version__)
def create_build_info(self): def create_build_info(self):
# type: () -> BuildInfo # type: () -> BuildInfo
return BuildInfo(self.config, self.tags, ['html']) return BuildInfo(self.config, self.tags, ['html'])
@ -374,14 +360,10 @@ class StandaloneHTMLBuilder(Builder):
@property @property
def default_translator_class(self): # type: ignore def default_translator_class(self): # type: ignore
# type: () -> Type[nodes.NodeVisitor] # type: () -> Type[nodes.NodeVisitor]
use_html5_writer = self.config.html_experimental_html5_writer if not html5_ready or self.config.html4_writer:
if use_html5_writer is None:
use_html5_writer = self.default_html5_translator
if use_html5_writer and html5_ready:
return HTML5Translator
else:
return HTMLTranslator return HTMLTranslator
else:
return HTML5Translator
@property @property
def math_renderer_name(self): def math_renderer_name(self):
@ -562,7 +544,7 @@ class StandaloneHTMLBuilder(Builder):
'parents': [], 'parents': [],
'logo': logo, 'logo': logo,
'favicon': favicon, 'favicon': favicon,
'html5_doctype': self.config.html_experimental_html5_writer and html5_ready, 'html5_doctype': html5_ready and not self.config.html4_writer
} }
if self.theme: if self.theme:
self.globalcontext.update( self.globalcontext.update(
@ -1169,140 +1151,6 @@ class StandaloneHTMLBuilder(Builder):
logger.info(__('done')) logger.info(__('done'))
class SerializingHTMLBuilder(StandaloneHTMLBuilder):
"""
An abstract builder that serializes the generated HTML.
"""
#: the serializing implementation to use. Set this to a module that
#: implements a `dump`, `load`, `dumps` and `loads` functions
#: (pickle, simplejson etc.)
implementation = None # type: Any
implementation_dumps_unicode = False
#: additional arguments for dump()
additional_dump_args = () # type: Tuple
#: the filename for the global context file
globalcontext_filename = None # type: str
supported_image_types = ['image/svg+xml', 'image/png',
'image/gif', 'image/jpeg']
def init(self):
# type: () -> None
self.build_info = BuildInfo(self.config, self.tags)
self.imagedir = '_images'
self.current_docname = None
self.theme = None # no theme necessary
self.templates = None # no template bridge necessary
self.init_templates()
self.init_highlighter()
self.init_css_files()
self.init_js_files()
self.use_index = self.get_builder_config('use_index', 'html')
def get_target_uri(self, docname, typ=None):
# type: (str, str) -> str
if docname == 'index':
return ''
if docname.endswith(SEP + 'index'):
return docname[:-5] # up to sep
return docname + SEP
def dump_context(self, context, filename):
# type: (Dict, str) -> None
if self.implementation_dumps_unicode:
with open(filename, 'w', encoding='utf-8') as ft:
self.implementation.dump(context, ft, *self.additional_dump_args)
else:
with open(filename, 'wb') as fb:
self.implementation.dump(context, fb, *self.additional_dump_args)
def handle_page(self, pagename, ctx, templatename='page.html',
outfilename=None, event_arg=None):
# type: (str, Dict, str, str, Any) -> None
ctx['current_page_name'] = pagename
self.add_sidebars(pagename, ctx)
if not outfilename:
outfilename = path.join(self.outdir,
os_path(pagename) + self.out_suffix)
# we're not taking the return value here, since no template is
# actually rendered
self.app.emit('html-page-context', pagename, templatename, ctx, event_arg)
# make context object serializable
for key in list(ctx):
if isinstance(ctx[key], types.FunctionType):
del ctx[key]
ensuredir(path.dirname(outfilename))
self.dump_context(ctx, outfilename)
# if there is a source file, copy the source file for the
# "show source" link
if ctx.get('sourcename'):
source_name = path.join(self.outdir, '_sources',
os_path(ctx['sourcename']))
ensuredir(path.dirname(source_name))
copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self):
# type: () -> None
# dump the global context
outfilename = path.join(self.outdir, self.globalcontext_filename)
self.dump_context(self.globalcontext, outfilename)
# super here to dump the search index
super().handle_finish()
# copy the environment file from the doctree dir to the output dir
# as needed by the web app
copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME),
path.join(self.outdir, ENV_PICKLE_FILENAME))
# touch 'last build' file, used by the web application to determine
# when to reload its environment and clear the cache
open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close()
class PickleHTMLBuilder(SerializingHTMLBuilder):
"""
A Builder that dumps the generated HTML into pickle files.
"""
name = 'pickle'
epilog = __('You can now process the pickle files in %(outdir)s.')
implementation = pickle
implementation_dumps_unicode = False
additional_dump_args = (pickle.HIGHEST_PROTOCOL,)
indexer_format = pickle
indexer_dumps_unicode = False
out_suffix = '.fpickle'
globalcontext_filename = 'globalcontext.pickle'
searchindex_filename = 'searchindex.pickle'
# compatibility alias
WebHTMLBuilder = PickleHTMLBuilder
class JSONHTMLBuilder(SerializingHTMLBuilder):
"""
A builder that dumps the generated HTML into JSON files.
"""
name = 'json'
epilog = __('You can now process the JSON files in %(outdir)s.')
implementation = jsonimpl
implementation_dumps_unicode = True
indexer_format = jsonimpl
indexer_dumps_unicode = True
out_suffix = '.fjson'
globalcontext_filename = 'globalcontext.json'
searchindex_filename = 'searchindex.json'
def convert_html_css_files(app, config): def convert_html_css_files(app, config):
# type: (Sphinx, Config) -> None # type: (Sphinx, Config) -> None
"""This converts string styled html_css_files to tuple styled one.""" """This converts string styled html_css_files to tuple styled one."""
@ -1386,11 +1234,19 @@ def validate_math_renderer(app):
# for compatibility # for compatibility
from sphinx.builders.dirhtml import DirectoryHTMLBuilder # NOQA from sphinx.builders.dirhtml import DirectoryHTMLBuilder # NOQA
from sphinx.builders.singlehtml import SingleFileHTMLBuilder # NOQA from sphinx.builders.singlehtml import SingleFileHTMLBuilder # NOQA
from sphinxcontrib.serializinghtml import ( # NOQA
LAST_BUILD_FILENAME, JSONHTMLBuilder, PickleHTMLBuilder, SerializingHTMLBuilder
)
deprecated_alias('sphinx.builders.html', deprecated_alias('sphinx.builders.html',
{ {
'LAST_BUILD_FILENAME': LAST_BUILD_FILENAME,
'DirectoryHTMLBuilder': DirectoryHTMLBuilder, 'DirectoryHTMLBuilder': DirectoryHTMLBuilder,
'JSONHTMLBuilder': JSONHTMLBuilder,
'PickleHTMLBuilder': PickleHTMLBuilder,
'SerializingHTMLBuilder': SerializingHTMLBuilder,
'SingleFileHTMLBuilder': SingleFileHTMLBuilder, 'SingleFileHTMLBuilder': SingleFileHTMLBuilder,
'WebHTMLBuilder': PickleHTMLBuilder,
}, },
RemovedInSphinx40Warning) RemovedInSphinx40Warning)
@ -1399,8 +1255,6 @@ def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
# builders # builders
app.add_builder(StandaloneHTMLBuilder) app.add_builder(StandaloneHTMLBuilder)
app.add_builder(PickleHTMLBuilder)
app.add_builder(JSONHTMLBuilder)
# config values # config values
app.add_config_value('html_theme', 'alabaster', 'html') app.add_config_value('html_theme', 'alabaster', 'html')
@ -1440,9 +1294,9 @@ def setup(app):
app.add_config_value('html_search_options', {}, 'html') app.add_config_value('html_search_options', {}, 'html')
app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_search_scorer', '', None)
app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_scaled_image_link', True, 'html')
app.add_config_value('html_experimental_html5_writer', None, 'html')
app.add_config_value('html_baseurl', '', 'html') app.add_config_value('html_baseurl', '', 'html')
app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html_math_renderer', None, 'env')
app.add_config_value('html4_writer', False, 'html')
# event handlers # event handlers
app.connect('config-inited', convert_html_css_files) app.connect('config-inited', convert_html_css_files)

View File

@ -9,364 +9,36 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import html
import os
import warnings import warnings
from os import path
from docutils import nodes from sphinxcontrib.htmlhelp import (
chm_locales, chm_htmlescape, HTMLHelpBuilder, default_htmlhelp_basename
)
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import NodeMatcher
from sphinx.util.osutil import make_filename_from_project
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict, IO, List, Match, Tuple # NOQA from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
logger = logging.getLogger(__name__) deprecated_alias('sphinx.builders.devhelp',
{
'chm_locales': chm_locales,
# Project file (*.hhp) template. 'outname' is the file basename (like 'chm_htmlescape': chm_htmlescape,
# the pythlp in pythlp.hhp); 'version' is the doc version number (like 'HTMLHelpBuilder': HTMLHelpBuilder,
# the 2.2 in Python 2.2). 'default_htmlhelp_basename': default_htmlhelp_basename,
# The magical numbers in the long line under [WINDOWS] set most of the },
# user-visible features (visible buttons, tabs, etc).
# About 0x10384e: This defines the buttons in the help viewer. The
# following defns are taken from htmlhelp.h. Not all possibilities
# actually work, and not all those that work are available from the Help
# Workshop GUI. In particular, the Zoom/Font button works and is not
# available from the GUI. The ones we're using are marked with 'x':
#
# 0x000002 Hide/Show x
# 0x000004 Back x
# 0x000008 Forward x
# 0x000010 Stop
# 0x000020 Refresh
# 0x000040 Home x
# 0x000080 Forward
# 0x000100 Back
# 0x000200 Notes
# 0x000400 Contents
# 0x000800 Locate x
# 0x001000 Options x
# 0x002000 Print x
# 0x004000 Index
# 0x008000 Search
# 0x010000 History
# 0x020000 Favorites
# 0x040000 Jump 1
# 0x080000 Jump 2
# 0x100000 Zoom/Font x
# 0x200000 TOC Next
# 0x400000 TOC Prev
project_template = '''\
[OPTIONS]
Binary TOC=No
Binary Index=No
Compiled file=%(outname)s.chm
Contents file=%(outname)s.hhc
Default Window=%(outname)s
Default topic=%(master_doc)s
Display compile progress=No
Full text search stop list file=%(outname)s.stp
Full-text search=Yes
Index file=%(outname)s.hhk
Language=%(lcid)#x
Title=%(title)s
[WINDOWS]
%(outname)s="%(title)s","%(outname)s.hhc","%(outname)s.hhk",\
"%(master_doc)s","%(master_doc)s",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0
[FILES]
'''
contents_header = '''\
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD><BODY>
<OBJECT type="text/site properties">
<param name="Window Styles" value="0x801227">
<param name="ImageType" value="Folder">
</OBJECT>
<UL>
'''
contents_footer = '''\
</UL></BODY></HTML>
'''
object_sitemap = '''\
<OBJECT type="text/sitemap">
<param name="Name" value="%s">
<param name="Local" value="%s">
</OBJECT>
'''
# List of words the full text search facility shouldn't index. This
# becomes file outname.stp. Note that this list must be pretty small!
# Different versions of the MS docs claim the file has a maximum size of
# 256 or 512 bytes (including \r\n at the end of each line).
# Note that "and", "or", "not" and "near" are operators in the search
# language, so no point indexing them even if we wanted to.
stopwords = """
a and are as at
be but by
for
if in into is it
near no not
of on or
such
that the their then there these they this to
was will with
""".split()
# The following list includes only languages supported by Sphinx. See
# https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms930130(v=msdn.10)
# for more.
chm_locales = {
# lang: LCID, encoding
'ca': (0x403, 'cp1252'),
'cs': (0x405, 'cp1250'),
'da': (0x406, 'cp1252'),
'de': (0x407, 'cp1252'),
'en': (0x409, 'cp1252'),
'es': (0x40a, 'cp1252'),
'et': (0x425, 'cp1257'),
'fa': (0x429, 'cp1256'),
'fi': (0x40b, 'cp1252'),
'fr': (0x40c, 'cp1252'),
'hr': (0x41a, 'cp1250'),
'hu': (0x40e, 'cp1250'),
'it': (0x410, 'cp1252'),
'ja': (0x411, 'cp932'),
'ko': (0x412, 'cp949'),
'lt': (0x427, 'cp1257'),
'lv': (0x426, 'cp1257'),
'nl': (0x413, 'cp1252'),
'no_NB': (0x414, 'cp1252'),
'pl': (0x415, 'cp1250'),
'pt_BR': (0x416, 'cp1252'),
'ru': (0x419, 'cp1251'),
'sk': (0x41b, 'cp1250'),
'sl': (0x424, 'cp1250'),
'sv': (0x41d, 'cp1252'),
'tr': (0x41f, 'cp1254'),
'uk_UA': (0x422, 'cp1251'),
'zh_CN': (0x804, 'cp936'),
'zh_TW': (0x404, 'cp950'),
}
def chm_htmlescape(s, quote=True):
# type: (str, bool) -> str
"""
chm_htmlescape() is a wrapper of html.escape().
.hhc/.hhk files don't recognize hex escaping, we need convert
hex escaping to decimal escaping. for example: ``&#x27;`` -> ``&#39;``
html.escape() may generates a hex escaping ``&#x27;`` for single
quote ``'``, this wrapper fixes this.
"""
s = html.escape(s, quote)
s = s.replace('&#x27;', '&#39;') # re-escape as decimal
return s
class HTMLHelpBuilder(StandaloneHTMLBuilder):
"""
Builder that also outputs Windows HTML help project, contents and
index files. Adapted from the original Doc/tools/prechm.py.
"""
name = 'htmlhelp'
epilog = __('You can now run HTML Help Workshop with the .htp file in '
'%(outdir)s.')
# don't copy the reST source
copysource = False
supported_image_types = ['image/png', 'image/gif', 'image/jpeg']
# don't add links
add_permalinks = False
# don't add sidebar etc.
embedded = True
# don't generate search index or include search page
search = False
lcid = 0x409
encoding = 'cp1252'
def init(self):
# type: () -> None
# the output files for HTML help is .html by default
self.out_suffix = '.html'
self.link_suffix = '.html'
super().init()
# determine the correct locale setting
locale = chm_locales.get(self.config.language)
if locale is not None:
self.lcid, self.encoding = locale
def open_file(self, outdir, basename, mode='w'):
# type: (str, str, str) -> IO
# open a file with the correct encoding for the selected language
warnings.warn('HTMLHelpBuilder.open_file() is deprecated.',
RemovedInSphinx40Warning) RemovedInSphinx40Warning)
return open(path.join(outdir, basename), mode, encoding=self.encoding,
errors='xmlcharrefreplace')
def update_page_context(self, pagename, templatename, ctx, event_arg):
# type: (str, str, Dict, str) -> None
ctx['encoding'] = self.encoding
def handle_finish(self):
# type: () -> None
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
def write_doc(self, docname, doctree):
# type: (str, nodes.document) -> None
for node in doctree.traverse(nodes.reference):
# add ``target=_blank`` attributes to external links
if node.get('internal') is None and 'refuri' in node:
node['target'] = '_blank'
super().write_doc(docname, doctree)
def build_hhx(self, outdir, outname):
# type: (str, str) -> None
logger.info(__('dumping stopword list...'))
filename = path.join(outdir, outname + '.stp')
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
for word in sorted(stopwords):
print(word, file=f)
logger.info(__('writing project file...'))
filename = path.join(outdir, outname + '.hhp')
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
f.write(project_template % {
'outname': outname,
'title': self.config.html_title,
'version': self.config.version,
'project': self.config.project,
'lcid': self.lcid,
'master_doc': self.config.master_doc + self.out_suffix
})
if not outdir.endswith(os.sep):
outdir += os.sep
olen = len(outdir)
for root, dirs, files in os.walk(outdir):
dirs.sort()
files.sort()
staticdir = root.startswith(path.join(outdir, '_static'))
for fn in sorted(files):
if (staticdir and not fn.endswith('.js')) or \
fn.endswith('.html'):
print(path.join(root, fn)[olen:].replace(os.sep, '\\'),
file=f)
logger.info(__('writing TOC file...'))
filename = path.join(outdir, outname + '.hhc')
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
f.write(contents_header)
# special books
f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
self.config.master_doc + self.out_suffix))
for indexname, indexcls, content, collapse in self.domain_indices:
f.write('<LI> ' + object_sitemap % (indexcls.localname,
'%s.html' % indexname))
# the TOC
tocdoc = self.env.get_and_resolve_doctree(
self.config.master_doc, self, prune_toctrees=False)
def write_toc(node, ullevel=0):
# type: (nodes.Node, int) -> None
if isinstance(node, nodes.list_item):
f.write('<LI> ')
for subnode in node:
write_toc(subnode, ullevel)
elif isinstance(node, nodes.reference):
link = node['refuri']
title = chm_htmlescape(node.astext(), True)
f.write(object_sitemap % (title, link))
elif isinstance(node, nodes.bullet_list):
if ullevel != 0:
f.write('<UL>\n')
for subnode in node:
write_toc(subnode, ullevel + 1)
if ullevel != 0:
f.write('</UL>\n')
elif isinstance(node, addnodes.compact_paragraph):
for subnode in node:
write_toc(subnode, ullevel)
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True)
for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph
write_toc(node)
f.write(contents_footer)
logger.info(__('writing index file...'))
index = IndexEntries(self.env).create_index(self)
filename = path.join(outdir, outname + '.hhk')
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
f.write('<UL>\n')
def write_index(title, refs, subitems):
# type: (str, List[Tuple[str, str]], List[Tuple[str, List[Tuple[str, str]]]]) -> None # NOQA
def write_param(name, value):
# type: (str, str) -> None
item = ' <param name="%s" value="%s">\n' % (name, value)
f.write(item)
title = chm_htmlescape(title, True)
f.write('<LI> <OBJECT type="text/sitemap">\n')
write_param('Keyword', title)
if len(refs) == 0:
write_param('See Also', title)
elif len(refs) == 1:
write_param('Local', refs[0][1])
else:
for i, ref in enumerate(refs):
# XXX: better title?
write_param('Name', '[%d] %s' % (i, ref[1]))
write_param('Local', ref[1])
f.write('</OBJECT>\n')
if subitems:
f.write('<UL> ')
for subitem in subitems:
write_index(subitem[0], subitem[1], [])
f.write('</UL>')
for (key, group) in index:
for title, (refs, subitems, key_) in group:
write_index(title, refs, subitems)
f.write('</UL>\n')
def default_htmlhelp_basename(config):
# type: (Config) -> str
"""Better default htmlhelp_basename setting."""
return make_filename_from_project(config.project) + 'doc'
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
app.setup_extension('sphinx.builders.html') warnings.warn('sphinx.builders.htmlhelp has been moved to sphinxcontrib-htmlhelp.',
app.add_builder(HTMLHelpBuilder) RemovedInSphinx40Warning)
app.setup_extension('sphinxcontrib.htmlhelp')
app.add_config_value('htmlhelp_basename', default_htmlhelp_basename, None)
app.add_config_value('htmlhelp_file_suffix', None, 'html', [str])
app.add_config_value('htmlhelp_link_suffix', None, 'html', [str])
return { return {
'version': 'builtin', 'version': 'builtin',

View File

@ -28,7 +28,7 @@ from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.transforms import SphinxTransformer from sphinx.transforms import SphinxTransformer
from sphinx.util import texescape, logging, status_iterator from sphinx.util import texescape, logging, progress_message, status_iterator
from sphinx.util.console import bold, darkgreen # type: ignore from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.util.docutils import SphinxFileOutput, new_document from sphinx.util.docutils import SphinxFileOutput, new_document
from sphinx.util.fileutil import copy_asset_file from sphinx.util.fileutil import copy_asset_file
@ -250,7 +250,7 @@ class LaTeXBuilder(Builder):
toctree_only = entry[5] toctree_only = entry[5]
destination = SphinxFileOutput(destination_path=path.join(self.outdir, targetname), destination = SphinxFileOutput(destination_path=path.join(self.outdir, targetname),
encoding='utf-8', overwrite_if_changed=True) encoding='utf-8', overwrite_if_changed=True)
logger.info(__("processing %s..."), targetname, nonl=True) with progress_message(__("processing %s") % targetname):
toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree) toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree)
if toctrees: if toctrees:
if toctrees[0].get('maxdepth') > 0: if toctrees[0].get('maxdepth') > 0:
@ -267,7 +267,7 @@ class LaTeXBuilder(Builder):
self.post_process_images(doctree) self.post_process_images(doctree)
self.update_doc_context(title, author) self.update_doc_context(title, author)
logger.info(__("writing... "), nonl=True) with progress_message(__("writing")):
docsettings.author = author docsettings.author = author
docsettings.title = title docsettings.title = title
docsettings.contentsname = self.get_contentsname(docname) docsettings.contentsname = self.get_contentsname(docname)
@ -276,7 +276,6 @@ class LaTeXBuilder(Builder):
doctree.settings = docsettings doctree.settings = docsettings
docwriter.write(doctree, destination) docwriter.write(doctree, destination)
logger.info(__("done"))
def get_contentsname(self, indexfile): def get_contentsname(self, indexfile):
# type: (str) -> str # type: (str) -> str
@ -354,8 +353,15 @@ class LaTeXBuilder(Builder):
# type: () -> None # type: () -> None
self.copy_image_files() self.copy_image_files()
self.write_message_catalog() self.write_message_catalog()
self.copy_support_files()
# copy TeX support files from texinputs if self.config.latex_additional_files:
self.copy_latex_additional_files()
@progress_message(__('copying TeX support files'))
def copy_support_files(self):
# type: () -> None
"""copy TeX support files from texinputs."""
# configure usage of xindy (impacts Makefile and latexmkrc) # configure usage of xindy (impacts Makefile and latexmkrc)
# FIXME: convert this rather to a confval with suitable default # FIXME: convert this rather to a confval with suitable default
# according to language ? but would require extra documentation # according to language ? but would require extra documentation
@ -386,21 +392,19 @@ class LaTeXBuilder(Builder):
copy_asset_file(path.join(staticdirname, 'Makefile_t'), copy_asset_file(path.join(staticdirname, 'Makefile_t'),
self.outdir, context=context) self.outdir, context=context)
# copy additional files
if self.config.latex_additional_files:
logger.info(bold(__('copying additional files...')), nonl=True)
for filename in self.config.latex_additional_files:
logger.info(' ' + filename, nonl=True)
copy_asset_file(path.join(self.confdir, filename), self.outdir)
logger.info('')
# the logo is handled differently # the logo is handled differently
if self.config.latex_logo: if self.config.latex_logo:
if not path.isfile(path.join(self.confdir, self.config.latex_logo)): if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo) raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo)
else: else:
copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir) copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
logger.info(__('done'))
@progress_message(__('copying additional files'))
def copy_latex_additional_files(self):
# type: () -> None
for filename in self.config.latex_additional_files:
logger.info(' ' + filename, nonl=True)
copy_asset_file(path.join(self.confdir, filename), self.outdir)
def copy_image_files(self): def copy_image_files(self):
# type: () -> None # type: () -> None
@ -439,7 +443,7 @@ def validate_config_values(app, config):
for key in list(config.latex_elements): for key in list(config.latex_elements):
if key not in DEFAULT_SETTINGS: if key not in DEFAULT_SETTINGS:
msg = __("Unknown configure key: latex_elements[%r]. ignored.") msg = __("Unknown configure key: latex_elements[%r]. ignored.")
logger.warning(msg % key) logger.warning(msg % (key,))
config.latex_elements.pop(key) config.latex_elements.pop(key)

View File

@ -25,7 +25,7 @@ from sphinx.util import encode_uri, requests, logging
from sphinx.util.console import ( # type: ignore from sphinx.util.console import ( # type: ignore
purple, red, darkgreen, darkgray, darkred, turquoise purple, red, darkgreen, darkgray, darkred, turquoise
) )
from sphinx.util.nodes import traverse_parent from sphinx.util.nodes import get_node_line
from sphinx.util.requests import is_ssl_error from sphinx.util.requests import is_ssl_error
if False: if False:
@ -271,17 +271,24 @@ class CheckExternalLinksBuilder(Builder):
# type: (str, nodes.Node) -> None # type: (str, nodes.Node) -> None
logger.info('') logger.info('')
n = 0 n = 0
for node in doctree.traverse(nodes.reference):
if 'refuri' not in node: # reference nodes
for refnode in doctree.traverse(nodes.reference):
if 'refuri' not in refnode:
continue continue
uri = node['refuri'] uri = refnode['refuri']
lineno = None lineno = get_node_line(refnode)
for parent in traverse_parent(node):
if parent.line:
lineno = parent.line
break
self.wqueue.put((uri, docname, lineno), False) self.wqueue.put((uri, docname, lineno), False)
n += 1 n += 1
# image nodes
for imgnode in doctree.traverse(nodes.image):
uri = imgnode['candidates'].get('?')
if uri and '://' in uri:
lineno = get_node_line(imgnode)
self.wqueue.put((uri, docname, lineno), False)
n += 1
done = 0 done = 0
while done < n: while done < n:
self.process_result(self.rqueue.get()) self.process_result(self.rqueue.get())

View File

@ -18,7 +18,8 @@ from sphinx.builders import Builder
from sphinx.environment import NoUri from sphinx.environment import NoUri
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.console import bold, darkgreen # type: ignore from sphinx.util import progress_message
from sphinx.util.console import darkgreen # type: ignore
from sphinx.util.nodes import inline_all_toctrees from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import make_filename_from_project from sphinx.util.osutil import make_filename_from_project
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator
@ -60,6 +61,7 @@ class ManualPageBuilder(Builder):
return '' return ''
raise NoUri raise NoUri
@progress_message(__('writing'))
def write(self, *ignored): def write(self, *ignored):
# type: (Any) -> None # type: (Any) -> None
docwriter = ManualPageWriter(self) docwriter = ManualPageWriter(self)
@ -68,8 +70,6 @@ class ManualPageBuilder(Builder):
components=(docwriter,), components=(docwriter,),
read_config_files=True).get_default_values() # type: Any read_config_files=True).get_default_values() # type: Any
logger.info(bold(__('writing... ')), nonl=True)
for info in self.config.man_pages: for info in self.config.man_pages:
docname, name, description, authors, section = info docname, name, description, authors, section = info
if docname not in self.env.all_docs: if docname not in self.env.all_docs:
@ -105,7 +105,6 @@ class ManualPageBuilder(Builder):
pendingnode.replace_self(pendingnode.children) pendingnode.replace_self(pendingnode.children)
docwriter.write(largetree, destination) docwriter.write(largetree, destination)
logger.info('')
def finish(self): def finish(self):
# type: () -> None # type: () -> None

View File

@ -16,7 +16,8 @@ from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.console import bold, darkgreen # type: ignore from sphinx.util import progress_message
from sphinx.util.console import darkgreen # type: ignore
from sphinx.util.nodes import inline_all_toctrees from sphinx.util.nodes import inline_all_toctrees
if False: if False:
@ -162,24 +163,32 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
# type: (Any) -> None # type: (Any) -> None
docnames = self.env.all_docs docnames = self.env.all_docs
logger.info(bold(__('preparing documents... ')), nonl=True) with progress_message(__('preparing documents')):
self.prepare_writing(docnames) # type: ignore self.prepare_writing(docnames) # type: ignore
logger.info(__('done'))
logger.info(bold(__('assembling single document... ')), nonl=True) with progress_message(__('assembling single document')):
doctree = self.assemble_doctree() doctree = self.assemble_doctree()
self.env.toc_secnumbers = self.assemble_toc_secnumbers() self.env.toc_secnumbers = self.assemble_toc_secnumbers()
self.env.toc_fignumbers = self.assemble_toc_fignumbers() self.env.toc_fignumbers = self.assemble_toc_fignumbers()
logger.info('')
logger.info(bold(__('writing... ')), nonl=True) with progress_message(__('writing')):
self.write_doc_serialized(self.config.master_doc, doctree) self.write_doc_serialized(self.config.master_doc, doctree)
self.write_doc(self.config.master_doc, doctree) self.write_doc(self.config.master_doc, doctree)
logger.info(__('done'))
def finish(self): def finish(self):
# type: () -> None
self.write_additional_files()
self.copy_image_files()
self.copy_download_files()
self.copy_static_files()
self.copy_extra_files()
self.write_buildinfo()
self.dump_inventory()
@progress_message(__('writing additional files'))
def write_additional_files(self):
# type: () -> None # type: () -> None
# no indices or search pages are supported # no indices or search pages are supported
logger.info(bold(__('writing additional files...')), nonl=True)
# additional pages from conf.py # additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items(): for pagename, template in self.config.html_additional_pages.items():
@ -191,15 +200,6 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
fn = path.join(self.outdir, '_static', 'opensearch.xml') fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn) self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
logger.info('')
self.copy_image_files()
self.copy_download_files()
self.copy_static_files()
self.copy_extra_files()
self.write_buildinfo()
self.dump_inventory()
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]

View File

@ -22,8 +22,8 @@ from sphinx.environment import NoUri
from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import status_iterator from sphinx.util import progress_message, status_iterator
from sphinx.util.console import bold, darkgreen # type: ignore from sphinx.util.console import darkgreen # type: ignore
from sphinx.util.docutils import new_document from sphinx.util.docutils import new_document
from sphinx.util.fileutil import copy_asset_file from sphinx.util.fileutil import copy_asset_file
from sphinx.util.nodes import inline_all_toctrees from sphinx.util.nodes import inline_all_toctrees
@ -113,11 +113,11 @@ class TexinfoBuilder(Builder):
destination = FileOutput( destination = FileOutput(
destination_path=path.join(self.outdir, targetname), destination_path=path.join(self.outdir, targetname),
encoding='utf-8') encoding='utf-8')
logger.info(__("processing %s..."), targetname, nonl=True) with progress_message(__("processing %s") % targetname):
doctree = self.assemble_doctree( appendices = self.config.texinfo_appendices or []
docname, toctree_only, doctree = self.assemble_doctree(docname, toctree_only, appendices=appendices)
appendices=(self.config.texinfo_appendices or []))
logger.info(__("writing... "), nonl=True) with progress_message(__("writing")):
self.post_process_images(doctree) self.post_process_images(doctree)
docwriter = TexinfoWriter(self) docwriter = TexinfoWriter(self)
settings = OptionParser( settings = OptionParser(
@ -134,7 +134,6 @@ class TexinfoBuilder(Builder):
settings.docname = docname settings.docname = docname
doctree.settings = settings doctree.settings = settings
docwriter.write(doctree, destination) docwriter.write(doctree, destination)
logger.info(__("done"))
def assemble_doctree(self, indexfile, toctree_only, appendices): def assemble_doctree(self, indexfile, toctree_only, appendices):
# type: (str, bool, List[str]) -> nodes.document # type: (str, bool, List[str]) -> nodes.document
@ -182,16 +181,7 @@ class TexinfoBuilder(Builder):
def finish(self): def finish(self):
# type: () -> None # type: () -> None
self.copy_image_files() self.copy_image_files()
self.copy_support_files()
logger.info(bold(__('copying Texinfo support files... ')), nonl=True)
# copy Makefile
fn = path.join(self.outdir, 'Makefile')
logger.info(fn, nonl=True)
try:
copy_asset_file(os.path.join(template_dir, 'Makefile'), fn)
except OSError as err:
logger.warning(__("error writing file %s: %s"), fn, err)
logger.info(__(' done'))
def copy_image_files(self): def copy_image_files(self):
# type: () -> None # type: () -> None
@ -208,6 +198,15 @@ class TexinfoBuilder(Builder):
logger.warning(__('cannot copy image file %r: %s'), logger.warning(__('cannot copy image file %r: %s'),
path.join(self.srcdir, src), err) path.join(self.srcdir, src), err)
def copy_support_files(self):
# type: () -> None
try:
with progress_message(__('copying Texinfo support files')):
logger.info('Makefile ', nonl=True)
copy_asset_file(os.path.join(template_dir, 'Makefile'), self.outdir)
except OSError as err:
logger.warning(__("error writing file Makefile: %s"), err)
def default_texinfo_documents(config): def default_texinfo_documents(config):
# type: (Config) -> List[Tuple[str, str, str, str, str, str, str]] # type: (Config) -> List[Tuple[str, str, str, str, str, str, str]]

View File

@ -23,9 +23,8 @@ from sphinx.application import Sphinx
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import Tee, format_exception_cut_frames, save_traceback from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import red, nocolor, color_terminal # type: ignore from sphinx.util.console import red, nocolor, color_terminal, terminal_safe # type: ignore
from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.docutils import docutils_namespace, patch_docutils
from sphinx.util.pycompat import terminal_safe
if False: if False:
# For type annotation # For type annotation

View File

@ -17,7 +17,6 @@ import time
import warnings import warnings
from collections import OrderedDict from collections import OrderedDict
from os import path from os import path
from urllib.parse import quote
# try to import readline, unix specific enhancement # try to import readline, unix specific enhancement
try: try:
@ -37,11 +36,10 @@ import sphinx.locale
from sphinx import __display_version__, package_dir from sphinx import __display_version__, package_dir
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import texescape
from sphinx.util.console import ( # type: ignore from sphinx.util.console import ( # type: ignore
colorize, bold, red, turquoise, nocolor, color_terminal colorize, bold, red, turquoise, nocolor, color_terminal
) )
from sphinx.util.osutil import ensuredir, make_filename from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxRenderer from sphinx.util.template import SphinxRenderer
if False: if False:
@ -375,25 +373,15 @@ def generate(d, overwrite=True, silent=False, templatedir=None):
"""Generate project based on values in *d*.""" """Generate project based on values in *d*."""
template = QuickstartRenderer(templatedir=templatedir) template = QuickstartRenderer(templatedir=templatedir)
texescape.init()
if 'mastertoctree' not in d: if 'mastertoctree' not in d:
d['mastertoctree'] = '' d['mastertoctree'] = ''
if 'mastertocmaxdepth' not in d: if 'mastertocmaxdepth' not in d:
d['mastertocmaxdepth'] = 2 d['mastertocmaxdepth'] = 2
d['PY3'] = True
d['project_fn'] = make_filename(d['project'])
d['project_url'] = quote(d['project'].encode('idna'))
d['project_manpage'] = d['project_fn'].lower()
d['now'] = time.asctime() d['now'] = time.asctime()
d['project_underline'] = column_width(d['project']) * '=' d['project_underline'] = column_width(d['project']) * '='
d.setdefault('extensions', []) d.setdefault('extensions', [])
d['copyright'] = time.strftime('%Y') + ', ' + d['author'] d['copyright'] = time.strftime('%Y') + ', ' + d['author']
d['author_texescaped'] = d['author'].translate(texescape.tex_escape_map)
d['project_doc'] = d['project'] + ' Documentation'
d['project_doc_texescaped'] = (d['project'] + ' Documentation').\
translate(texescape.tex_escape_map)
ensuredir(d['path']) ensuredir(d['path'])
@ -528,7 +516,7 @@ Makefile to be used with sphinx-build.
group = parser.add_argument_group(__('Structure options')) group = parser.add_argument_group(__('Structure options'))
group.add_argument('--sep', action='store_true', default=None, group.add_argument('--sep', action='store_true', default=None,
help=__('if specified, separate source and build dirs')) help=__('if specified, separate source and build dirs'))
group.add_argument('--dot', metavar='DOT', group.add_argument('--dot', metavar='DOT', default='_',
help=__('replacement for dot in _templates etc.')) help=__('replacement for dot in _templates etc.'))
group = parser.add_argument_group(__('Project basic options')) group = parser.add_argument_group(__('Project basic options'))
@ -542,9 +530,9 @@ Makefile to be used with sphinx-build.
help=__('release of project')) help=__('release of project'))
group.add_argument('-l', '--language', metavar='LANGUAGE', dest='language', group.add_argument('-l', '--language', metavar='LANGUAGE', dest='language',
help=__('document language')) help=__('document language'))
group.add_argument('--suffix', metavar='SUFFIX', group.add_argument('--suffix', metavar='SUFFIX', default='.rst',
help=__('source file suffix')) help=__('source file suffix'))
group.add_argument('--master', metavar='MASTER', group.add_argument('--master', metavar='MASTER', default='index',
help=__('master document name')) help=__('master document name'))
group.add_argument('--epub', action='store_true', default=False, group.add_argument('--epub', action='store_true', default=False,
help=__('use epub')) help=__('use epub'))
@ -558,11 +546,11 @@ Makefile to be used with sphinx-build.
action='append', help=__('enable arbitrary extensions')) action='append', help=__('enable arbitrary extensions'))
group = parser.add_argument_group(__('Makefile and Batchfile creation')) group = parser.add_argument_group(__('Makefile and Batchfile creation'))
group.add_argument('--makefile', action='store_true', dest='makefile', default=None, group.add_argument('--makefile', action='store_true', dest='makefile', default=True,
help=__('create makefile')) help=__('create makefile'))
group.add_argument('--no-makefile', action='store_false', dest='makefile', group.add_argument('--no-makefile', action='store_false', dest='makefile',
help=__('do not create makefile')) help=__('do not create makefile'))
group.add_argument('--batchfile', action='store_true', dest='batchfile', default=None, group.add_argument('--batchfile', action='store_true', dest='batchfile', default=True,
help=__('create batchfile')) help=__('create batchfile'))
group.add_argument('--no-batchfile', action='store_false', group.add_argument('--no-batchfile', action='store_false',
dest='batchfile', dest='batchfile',
@ -604,6 +592,13 @@ def main(argv=sys.argv[1:]):
# delete None or False value # delete None or False value
d = dict((k, v) for k, v in d.items() if v is not None) d = dict((k, v) for k, v in d.items() if v is not None)
# handle use of CSV-style extension values
d.setdefault('extensions', [])
for ext in d['extensions'][:]:
if ',' in ext:
d['extensions'].remove(ext)
d['extensions'].extend(ext.split(','))
try: try:
if 'quiet' in d: if 'quiet' in d:
if not set(['project', 'author']).issubset(d): if not set(['project', 'author']).issubset(d):
@ -633,13 +628,6 @@ def main(argv=sys.argv[1:]):
print('[Interrupted.]') print('[Interrupted.]')
return 130 # 128 + SIGINT return 130 # 128 + SIGINT
# handle use of CSV-style extension values
d.setdefault('extensions', [])
for ext in d['extensions'][:]:
if ',' in ext:
d['extensions'].remove(ext)
d['extensions'].extend(ext.split(','))
for variable in d.get('variables', []): for variable in d.get('variables', []):
try: try:
name, value = variable.split('=') name, value = variable.split('=')

View File

@ -22,7 +22,8 @@ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.util.osutil import cd from sphinx.util.osutil import cd
from sphinx.util.pycompat import execfile_, NoneType from sphinx.util.pycompat import execfile_
from sphinx.util.typing import NoneType
if False: if False:
# For type annotation # For type annotation

View File

@ -7158,7 +7158,7 @@ class CPPDomain(Domain):
# the non-identifier refs are cross-references, which should be processed: # the non-identifier refs are cross-references, which should be processed:
# - fix parenthesis due to operator() and add_function_parentheses # - fix parenthesis due to operator() and add_function_parentheses
if typ != "identifier": if typ != "identifier":
title = contnode.pop(0).astext() # type: ignore title = contnode.pop(0).astext()
# If it's operator(), we need to add '()' if explicit function parens # If it's operator(), we need to add '()' if explicit function parens
# are requested. Then the Sphinx machinery will add another pair. # are requested. Then the Sphinx machinery will add another pair.
# Also, if it's an 'any' ref that resolves to a function, we need to add # Also, if it's an 'any' ref that resolves to a function, we need to add

View File

@ -151,7 +151,7 @@ class PyXrefMixin:
delims_re = re.compile(delims) delims_re = re.compile(delims)
sub_targets = re.split(delims, target) sub_targets = re.split(delims, target)
split_contnode = bool(contnode and contnode.astext() == target) # type: ignore split_contnode = bool(contnode and contnode.astext() == target)
results = [] results = []
for sub_target in filter(None, sub_targets): for sub_target in filter(None, sub_targets):

View File

@ -72,7 +72,7 @@ INSTANCEATTR = object()
def members_option(arg): def members_option(arg):
# type: (Any) -> Union[object, List[str]] # type: (Any) -> Union[object, List[str]]
"""Used to convert the :members: option to auto directives.""" """Used to convert the :members: option to auto directives."""
if arg is None: if arg is None or arg is True:
return ALL return ALL
return [x.strip() for x in arg.split(',')] return [x.strip() for x in arg.split(',')]
@ -1002,6 +1002,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
return None return None
try: try:
if (not isfunction(self.object) and if (not isfunction(self.object) and
not inspect.ismethod(self.object) and
not isbuiltin(self.object) and not isbuiltin(self.object) and
not inspect.isclass(self.object) and not inspect.isclass(self.object) and
hasattr(self.object, '__call__')): hasattr(self.object, '__call__')):
@ -1032,6 +1033,23 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
pass pass
class DecoratorDocumenter(FunctionDocumenter):
"""
Specialized Documenter subclass for decorator functions.
"""
objtype = 'decorator'
# must be lower than FunctionDocumenter
priority = -1
def format_args(self):
args = super().format_args()
if ',' in args:
return args
else:
return None
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
""" """
Specialized Documenter subclass for classes. Specialized Documenter subclass for classes.
@ -1461,6 +1479,7 @@ def setup(app):
app.add_autodocumenter(ExceptionDocumenter) app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDocumenter)
app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(DecoratorDocumenter)
app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(MethodDocumenter)
app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter)

View File

@ -32,12 +32,17 @@ logger = logging.getLogger(__name__)
class _MockObject: class _MockObject:
"""Used by autodoc_mock_imports.""" """Used by autodoc_mock_imports."""
__display_name__ = '_MockObject'
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
# type: (Any, Any) -> Any # type: (Any, Any) -> Any
if len(args) == 3 and isinstance(args[1], tuple) and args[1][-1].__class__ is cls: if len(args) == 3 and isinstance(args[1], tuple):
superclass = args[1][-1].__class__
if superclass is cls:
# subclassing MockObject # subclassing MockObject
return type(args[0], (_MockObject,), args[2], **kwargs) # type: ignore return _make_subclass(args[0], superclass.__display_name__,
else: superclass=superclass, attributes=args[2])
return super(_MockObject, cls).__new__(cls) return super(_MockObject, cls).__new__(cls)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -62,11 +67,11 @@ class _MockObject:
def __getitem__(self, key): def __getitem__(self, key):
# type: (str) -> _MockObject # type: (str) -> _MockObject
return self return _make_subclass(key, self.__display_name__, self.__class__)()
def __getattr__(self, key): def __getattr__(self, key):
# type: (str) -> _MockObject # type: (str) -> _MockObject
return self return _make_subclass(key, self.__display_name__, self.__class__)()
def __call__(self, *args, **kw): def __call__(self, *args, **kw):
# type: (Any, Any) -> Any # type: (Any, Any) -> Any
@ -75,6 +80,18 @@ class _MockObject:
return args[0] return args[0]
return self return self
def __repr__(self):
# type: () -> str
return self.__display_name__
def _make_subclass(name, module, superclass=_MockObject, attributes=None):
# type: (str, str, Any, dict) -> Any
attrs = {'__module__': module, '__display_name__': module + '.' + name}
attrs.update(attributes or {})
return type(name, (superclass,), attrs)
class _MockModule(ModuleType): class _MockModule(ModuleType):
"""Used by autodoc_mock_imports.""" """Used by autodoc_mock_imports."""
@ -92,9 +109,11 @@ class _MockModule(ModuleType):
def __getattr__(self, name): def __getattr__(self, name):
# type: (str) -> _MockObject # type: (str) -> _MockObject
o = _MockObject() return _make_subclass(name, self.__name__)()
o.__module__ = self.__name__
return o def __repr__(self):
# type: () -> str
return self.__name__
class _MockImporter(MetaPathFinder): class _MockImporter(MetaPathFinder):

View File

@ -30,11 +30,23 @@ if False:
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
def get_node_depth(node):
i = 0
cur_node = node
while cur_node.parent != node.document:
cur_node = cur_node.parent
i += 1
return i
def register_sections_as_label(app, document): def register_sections_as_label(app, document):
# type: (Sphinx, nodes.Node) -> None # type: (Sphinx, nodes.Node) -> None
labels = app.env.domaindata['std']['labels'] labels = app.env.domaindata['std']['labels']
anonlabels = app.env.domaindata['std']['anonlabels'] anonlabels = app.env.domaindata['std']['anonlabels']
for node in document.traverse(nodes.section): for node in document.traverse(nodes.section):
if (app.config.autosectionlabel_maxdepth and
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
continue
labelid = node['ids'][0] labelid = node['ids'][0]
docname = app.env.docname docname = app.env.docname
title = cast(nodes.title, node[0]) title = cast(nodes.title, node[0])
@ -57,6 +69,7 @@ def register_sections_as_label(app, document):
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
app.add_config_value('autosectionlabel_prefix_document', False, 'env') app.add_config_value('autosectionlabel_prefix_document', False, 'env')
app.add_config_value('autosectionlabel_maxdepth', None, 'env')
app.connect('doctree-read', register_sections_as_label) app.connect('doctree-read', register_sections_as_label)
return { return {

View File

@ -72,7 +72,7 @@ from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.ext.autodoc import get_documenters from sphinx.ext.autodoc import get_documenters
from sphinx.ext.autodoc.directive import DocumenterBridge, Options from sphinx.ext.autodoc.directive import DocumenterBridge, Options
from sphinx.ext.autodoc.importer import import_module from sphinx.ext.autodoc.importer import import_module, mock
from sphinx.locale import __ from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import import_object, rst, logging from sphinx.util import import_object, rst, logging
@ -286,6 +286,7 @@ class Autosummary(SphinxDirective):
display_name = name.split('.')[-1] display_name = name.split('.')[-1]
try: try:
with mock(self.config.autosummary_mock_imports):
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes) real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
except ImportError: except ImportError:
logger.warning(__('failed to import %s'), name) logger.warning(__('failed to import %s'), name)
@ -703,6 +704,8 @@ def process_generate_options(app):
return return
recursion_limit = app.config.autosummary_recursion_limit recursion_limit = app.config.autosummary_recursion_limit
with mock(app.config.autosummary_mock_imports):
generate_autosummary_docs(genfiles, builder=app.builder, generate_autosummary_docs(genfiles, builder=app.builder,
warn=logger.warning, info=logger.info, warn=logger.warning, info=logger.info,
suffix=suffix, base_path=app.srcdir, suffix=suffix, base_path=app.srcdir,
@ -732,4 +735,6 @@ def setup(app):
app.connect('builder-inited', process_generate_options) app.connect('builder-inited', process_generate_options)
app.add_config_value('autosummary_generate', [], True, [bool]) app.add_config_value('autosummary_generate', [], True, [bool])
app.add_config_value('autosummary_recursion_limit', 0, 0) app.add_config_value('autosummary_recursion_limit', 0, 0)
app.add_config_value('autosummary_mock_imports',
lambda config: config.autodoc_mock_imports, 'env')
return {'version': sphinx.__display_version__, 'parallel_read_safe': True} return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -9,6 +9,7 @@
""" """
import os import os
import urllib
import sphinx import sphinx
@ -19,14 +20,22 @@ if False:
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
def create_nojekyll(app, env): def create_nojekyll_and_cname(app, env):
# type: (Sphinx, BuildEnvironment) -> None # type: (Sphinx, BuildEnvironment) -> None
if app.builder.format == 'html': if app.builder.format == 'html':
path = os.path.join(app.builder.outdir, '.nojekyll') open(os.path.join(app.builder.outdir, '.nojekyll'), 'wt').close()
open(path, 'wt').close()
html_baseurl = app.config.html_baseurl
if html_baseurl:
domain = urllib.parse.urlparse(html_baseurl).hostname
if domain and not domain.endswith(".github.io"):
with open(os.path.join(app.builder.outdir, 'CNAME'), 'wt') as f:
# NOTE: don't write a trailing newline. The `CNAME` file that's
# auto-generated by the Github UI doesn't have one.
f.write(domain)
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
app.connect('env-updated', create_nojekyll) app.connect('env-updated', create_nojekyll_and_cname)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True} return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -54,8 +54,8 @@ class ImagemagickConverter(ImageConverter):
# type: (str, str) -> bool # type: (str, str) -> bool
"""Converts the image to expected one.""" """Converts the image to expected one."""
try: try:
if _from.lower().endswith('.gif'): # append an index 0 to source filename to pick up the first frame
# when target is GIF format, pick the first frame # (or first page) of image (ex. Animation GIF, PDF)
_from += '[0]' _from += '[0]'
args = ([self.config.image_converter] + args = ([self.config.image_converter] +

View File

@ -12,6 +12,7 @@ import posixpath
import re import re
import shutil import shutil
import subprocess import subprocess
import sys
import tempfile import tempfile
from hashlib import sha1 from hashlib import sha1
from os import path from os import path
@ -26,7 +27,6 @@ from sphinx.util import logging
from sphinx.util.math import get_node_equation_number, wrap_displaymath from sphinx.util.math import get_node_equation_number, wrap_displaymath
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.png import read_png_depth, write_png_depth from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.pycompat import sys_encoding
if False: if False:
# For type annotation # For type annotation
@ -46,9 +46,9 @@ class MathExtError(SphinxError):
def __init__(self, msg, stderr=None, stdout=None): def __init__(self, msg, stderr=None, stdout=None):
# type: (str, bytes, bytes) -> None # type: (str, bytes, bytes) -> None
if stderr: if stderr:
msg += '\n[stderr]\n' + stderr.decode(sys_encoding, 'replace') msg += '\n[stderr]\n' + stderr.decode(sys.getdefaultencoding(), 'replace')
if stdout: if stdout:
msg += '\n[stdout]\n' + stdout.decode(sys_encoding, 'replace') msg += '\n[stdout]\n' + stdout.decode(sys.getdefaultencoding(), 'replace')
super().__init__(msg) super().__init__(msg)

View File

@ -21,7 +21,7 @@ from typing import Any, Union # NOQA
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.transforms import ( from sphinx.transforms import (
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences, ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds, FigureAligner,
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages, AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
UnreferencedFootnotesDetector, SphinxSmartQuotes, DoctreeReadEvent, ManpageLink UnreferencedFootnotesDetector, SphinxSmartQuotes, DoctreeReadEvent, ManpageLink
) )
@ -96,7 +96,7 @@ class SphinxStandaloneReader(SphinxBaseReader):
""" """
transforms = [ApplySourceWorkaround, ExtraTranslatableNodes, PreserveTranslatableMessages, transforms = [ApplySourceWorkaround, ExtraTranslatableNodes, PreserveTranslatableMessages,
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets, Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, FigureAligner,
RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform, RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform,
UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink, UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink,
SphinxDomains, SubstitutionDefinitionsRemover, DoctreeReadEvent, SphinxDomains, SubstitutionDefinitionsRemover, DoctreeReadEvent,

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,15 @@
""" """
import re import re
import warnings
from docutils import nodes, utils from docutils import nodes, utils
from sphinx import addnodes from sphinx import addnodes
from sphinx.errors import SphinxError from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import _ from sphinx.locale import _
from sphinx.util import ws_re from sphinx.util import ws_re
from sphinx.util.docutils import ReferenceRole, SphinxRole
from sphinx.util.nodes import split_explicit_title, process_index_entry, \ from sphinx.util.nodes import split_explicit_title, process_index_entry, \
set_role_source_info set_role_source_info
@ -44,7 +46,7 @@ generic_docroles = {
# -- generic cross-reference role ---------------------------------------------- # -- generic cross-reference role ----------------------------------------------
class XRefRole: class XRefRole(ReferenceRole):
""" """
A generic cross-referencing role. To create a callable that can be used as A generic cross-referencing role. To create a callable that can be used as
a role function, create an instance of this class. a role function, create an instance of this class.
@ -82,8 +84,12 @@ class XRefRole:
if innernodeclass is not None: if innernodeclass is not None:
self.innernodeclass = innernodeclass self.innernodeclass = innernodeclass
super().__init__()
def _fix_parens(self, env, has_explicit_title, title, target): def _fix_parens(self, env, has_explicit_title, title, target):
# type: (BuildEnvironment, bool, str, str) -> Tuple[str, str] # type: (BuildEnvironment, bool, str, str) -> Tuple[str, str]
warnings.warn('XRefRole._fix_parens() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
if not has_explicit_title: if not has_explicit_title:
if title.endswith('()'): if title.endswith('()'):
# remove parentheses # remove parentheses
@ -96,55 +102,70 @@ class XRefRole:
target = target[:-2] target = target[:-2]
return title, target return title, target
def __call__(self, typ, rawtext, text, lineno, inliner, def update_title_and_target(self, title, target):
options={}, content=[]): # type: (str, str) -> Tuple[str, str]
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA if not self.has_explicit_title:
env = inliner.document.settings.env if title.endswith('()'):
if not typ: # remove parentheses
typ = env.temp_data.get('default_role') title = title[:-2]
if not typ: if self.config.add_function_parentheses:
typ = env.config.default_role # add them back to all occurrences if configured
if not typ: title += '()'
raise SphinxError('cannot determine default role!') # remove parentheses from the target too
if target.endswith('()'):
target = target[:-2]
return title, target
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
if ':' not in self.name:
self.refdomain, self.reftype = '', self.name
self.classes = ['xref', self.reftype]
else: else:
typ = typ.lower() self.refdomain, self.reftype = self.name.split(':', 1)
if ':' not in typ: self.classes = ['xref', self.refdomain, '%s-%s' % (self.refdomain, self.reftype)]
domain, role = '', typ
classes = ['xref', role] if self.text.startswith('!'):
else:
domain, role = typ.split(':', 1)
classes = ['xref', domain, '%s-%s' % (domain, role)]
# if the first character is a bang, don't cross-reference at all # if the first character is a bang, don't cross-reference at all
if text[0:1] == '!': return self.create_non_xref_node()
text = utils.unescape(text)[1:] else:
return self.create_xref_node()
def create_non_xref_node(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
text = utils.unescape(self.text[1:])
if self.fix_parens: if self.fix_parens:
text, tgt = self._fix_parens(env, False, text, "") self.has_explicit_title = False # treat as implicit
innernode = self.innernodeclass(rawtext, text, classes=classes) text, target = self.update_title_and_target(text, "")
return self.result_nodes(inliner.document, env, innernode, is_ref=False)
# split title and target in role content node = self.innernodeclass(self.rawtext, text, classes=self.classes)
has_explicit_title, title, target = split_explicit_title(text) return self.result_nodes(self.inliner.document, self.env, node, is_ref=False)
title = utils.unescape(title)
target = utils.unescape(target) def create_xref_node(self):
# fix-up title and target # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
target = self.target
title = self.title
if self.lowercase: if self.lowercase:
target = target.lower() target = target.lower()
if self.fix_parens: if self.fix_parens:
title, target = self._fix_parens( title, target = self.update_title_and_target(title, target)
env, has_explicit_title, title, target)
# create the reference node # create the reference node
refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, options = {'refdoc': self.env.docname,
refexplicit=has_explicit_title) 'refdomain': self.refdomain,
# we may need the line number for warnings 'reftype': self.reftype,
set_role_source_info(inliner, lineno, refnode) 'refexplicit': self.has_explicit_title,
title, target = self.process_link(env, refnode, has_explicit_title, title, target) 'refwarn': self.warn_dangling}
# now that the target and title are finally determined, set them refnode = self.nodeclass(self.rawtext, **options)
self.set_source_info(refnode)
# determine the target and title for the class
title, target = self.process_link(self.env, refnode, self.has_explicit_title,
title, target)
refnode['reftarget'] = target refnode['reftarget'] = target
refnode += self.innernodeclass(rawtext, title, classes=classes) refnode += self.innernodeclass(self.rawtext, title, classes=self.classes)
# we also need the source document
refnode['refdoc'] = env.docname return self.result_nodes(self.inliner.document, self.env, refnode, is_ref=True)
refnode['refwarn'] = self.warn_dangling
# result_nodes allow further modification of return values
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
# methods that can be overwritten # methods that can be overwritten
@ -179,6 +200,8 @@ class AnyXRefRole(XRefRole):
def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
"""Role for PEP/RFC references that generate an index entry.""" """Role for PEP/RFC references that generate an index entry."""
warnings.warn('indexmarkup_role() is deprecated. Please use PEP or RFC class instead.',
RemovedInSphinx40Warning, stacklevel=2)
env = inliner.document.settings.env env = inliner.document.settings.env
if not typ: if not typ:
assert env.temp_data['default_role'] assert env.temp_data['default_role']
@ -242,11 +265,87 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]
raise ValueError('unknown role type: %s' % typ) raise ValueError('unknown role type: %s' % typ)
class PEP(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
target_id = 'index-%s' % self.env.new_serialno('index')
entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
target_id, '', None)]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)
try:
refuri = self.build_uri()
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['pep'])
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = "PEP " + self.title
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
line=self.lineno)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
return [index, target, reference], []
def build_uri(self):
# type: () -> str
base_url = self.inliner.document.settings.pep_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return base_url + 'pep-%04d#%s' % (int(ret[0]), ret[1])
else:
return base_url + 'pep-%04d' % int(ret[0])
class RFC(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
target_id = 'index-%s' % self.env.new_serialno('index')
entries = [('single', 'RFC; RFC %s' % self.target, target_id, '', None)]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)
try:
refuri = self.build_uri()
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['rfc'])
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = "RFC " + self.title
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error('invalid RFC number %s' % self.target,
line=self.lineno)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
return [index, target, reference], []
def build_uri(self):
# type: () -> str
base_url = self.inliner.document.settings.rfc_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return base_url + self.inliner.rfc_url % int(ret[0]) + '#' + ret[1]
else:
return base_url + self.inliner.rfc_url % int(ret[0])
_amp_re = re.compile(r'(?<!&)&(?![&\s])') _amp_re = re.compile(r'(?<!&)&(?![&\s])')
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
warnings.warn('menusel_role() is deprecated. '
'Please use MenuSelection or GUILabel class instead.',
RemovedInSphinx40Warning, stacklevel=2)
env = inliner.document.settings.env env = inliner.document.settings.env
if not typ: if not typ:
assert env.temp_data['default_role'] assert env.temp_data['default_role']
@ -279,6 +378,32 @@ def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
return [node], [] return [node], []
class GUILabel(SphinxRole):
amp_re = re.compile(r'(?<!&)&(?![&\s])')
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
node = nodes.inline(rawtext=self.rawtext, classes=[self.name])
spans = self.amp_re.split(self.text)
node += nodes.Text(spans.pop(0))
for span in spans:
span = span.replace('&&', '&')
letter = nodes.Text(span[0])
accelerator = nodes.inline('', '', letter, classes=['accelerator'])
node += accelerator
node += nodes.Text(span[1:])
return [node], []
class MenuSelection(GUILabel):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
self.text = self.text.replace('-->', '\N{TRIANGULAR BULLET}') # type: ignore
return super().run()
_litvar_re = re.compile('{([^}]+)}') _litvar_re = re.compile('{([^}]+)}')
parens_re = re.compile(r'(\\*{|\\*})') parens_re = re.compile(r'(\\*{|\\*})')
@ -286,6 +411,9 @@ parens_re = re.compile(r'(\\*{|\\*})')
def emph_literal_role(typ, rawtext, text, lineno, inliner, def emph_literal_role(typ, rawtext, text, lineno, inliner,
options={}, content=[]): options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
warnings.warn('emph_literal_role() is deprecated. '
'Please use EmphasizedLiteral class instead.',
RemovedInSphinx40Warning, stacklevel=2)
env = inliner.document.settings.env env = inliner.document.settings.env
if not typ: if not typ:
assert env.temp_data['default_role'] assert env.temp_data['default_role']
@ -333,11 +461,65 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
return [retnode], [] return [retnode], []
class EmphasizedLiteral(SphinxRole):
parens_re = re.compile(r'(\\\\|\\{|\\}|{|})')
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
children = self.parse(self.text)
node = nodes.literal(self.rawtext, '', *children,
role=self.name.lower(), classes=[self.name])
return [node], []
def parse(self, text):
# type: (str) -> List[nodes.Node]
result = [] # type: List[nodes.Node]
stack = ['']
for part in self.parens_re.split(text):
if part == '\\\\': # escaped backslash
stack[-1] += '\\'
elif part == '{':
if len(stack) >= 2 and stack[-2] == "{": # nested
stack[-1] += "{"
else:
# start emphasis
stack.append('{')
stack.append('')
elif part == '}':
if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
# emphasized word found
if stack[0]:
result.append(nodes.Text(stack[0], stack[0]))
result.append(nodes.emphasis(stack[2], stack[2]))
stack = ['']
else:
# emphasized word not found; the rparen is not a special symbol
stack.append('}')
stack = [''.join(stack)]
elif part == '\\{': # escaped left-brace
stack[-1] += '{'
elif part == '\\}': # escaped right-brace
stack[-1] += '}'
else: # others (containing escaped braces)
stack[-1] += part
if ''.join(stack):
# remaining is treated as Text
text = ''.join(stack)
result.append(nodes.Text(text, text))
return result
_abbr_re = re.compile(r'\((.*)\)$', re.S) _abbr_re = re.compile(r'\((.*)\)$', re.S)
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
warnings.warn('abbr_role() is deprecated. Please use Abbrevation class instead.',
RemovedInSphinx40Warning, stacklevel=2)
text = utils.unescape(text) text = utils.unescape(text)
m = _abbr_re.search(text) m = _abbr_re.search(text)
if m is None: if m is None:
@ -349,8 +531,25 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
return [nodes.abbreviation(abbr, abbr, **options)], [] return [nodes.abbreviation(abbr, abbr, **options)], []
class Abbreviation(SphinxRole):
abbr_re = re.compile(r'\((.*)\)$', re.S)
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
matched = self.abbr_re.search(self.text)
if matched:
text = self.text[:matched.start()].strip()
self.options['explanation'] = matched.group(1)
else:
text = self.text
return [nodes.abbreviation(self.rawtext, text, **self.options)], []
def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
warnings.warn('index_role() is deprecated. Please use Index class instead.',
RemovedInSphinx40Warning, stacklevel=2)
# create new reference target # create new reference target
env = inliner.document.settings.env env = inliner.document.settings.env
targetid = 'index-%s' % env.new_serialno('index') targetid = 'index-%s' % env.new_serialno('index')
@ -378,20 +577,44 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
return [indexnode, targetnode, textnode], [] return [indexnode, targetnode, textnode], []
class Index(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
target_id = 'index-%s' % self.env.new_serialno('index')
if self.has_explicit_title:
# if an explicit target is given, process it as a full entry
title = self.title
entries = process_index_entry(self.target, target_id)
else:
# otherwise we just create a single entry
if self.target.startswith('!'):
title = self.title[1:]
entries = [('single', self.target[1:], target_id, 'main', None)]
else:
title = self.title
entries = [('single', self.target, target_id, '', None)]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
text = nodes.Text(title, title)
self.set_source_info(index)
return [index, target, text], []
specific_docroles = { specific_docroles = {
# links to download references # links to download references
'download': XRefRole(nodeclass=addnodes.download_reference), 'download': XRefRole(nodeclass=addnodes.download_reference),
# links to anything # links to anything
'any': AnyXRefRole(warn_dangling=True), 'any': AnyXRefRole(warn_dangling=True),
'pep': indexmarkup_role, 'pep': PEP(),
'rfc': indexmarkup_role, 'rfc': RFC(),
'guilabel': menusel_role, 'guilabel': GUILabel(),
'menuselection': menusel_role, 'menuselection': MenuSelection(),
'file': emph_literal_role, 'file': EmphasizedLiteral(),
'samp': emph_literal_role, 'samp': EmphasizedLiteral(),
'abbr': abbr_role, 'abbr': Abbreviation(),
'index': index_role, 'index': Index(),
} # type: Dict[str, RoleFunction] } # type: Dict[str, RoleFunction]

View File

@ -18,8 +18,8 @@ from docutils import nodes
from sphinx import addnodes from sphinx import addnodes
from sphinx import package_dir from sphinx import package_dir
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.util import jsdump, rpartition
from sphinx.search.jssplitter import splitter_code from sphinx.search.jssplitter import splitter_code
from sphinx.util import jsdump, rpartition
if False: if False:
# For type annotation # For type annotation

View File

@ -0,0 +1,31 @@
{%- macro sitemap(name, docname) -%}
<OBJECT type="text/sitemap">
<PARAM name="Name" value="{{ name|e }}" />
<PARAM name="Local" value="{{ docname|e }}{{ suffix }}" />
</OBJECT>
{%- endmacro -%}
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<META name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1" />
<!-- Sitemap 1.0 -->
</HEAD>
<BODY>
<OBJECT type="text/site properties">
<PARAM name="Window Styles" value="0x801227" />
<PARAM name="ImageType" value="Folder" />
</OBJECT>
<UL>
<LI>
{{ sitemap(short_title, master_doc)|indent(8) }}
</LI>
{%- for indexname, indexcls, content, collapse in domain_indices %}
<LI>
{{ sitemap(indexcls.localname, indexname)|indent(8) }}
</LI>
{%- endfor %}
{{ body|indent(6) }}
</UL>
</BODY>
</HTML>

View File

@ -0,0 +1,21 @@
[OPTIONS]
Binary TOC=No
Binary Index=No
Compiled file={{ outname }}.chm
Contents file={{ outname }}.hhc
Default Window={{ outname }}
Default topic={{ master_doc }}
Display compile progress=No
Full text search stop list file={{ outname }}.stp
Full-text search=Yes
Index file={{ outname }}.hhk
Language={{ "%#x"|format(lcid) }}
Title={{ title }}
[WINDOWS]
{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ master_doc }}","{{ master_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0
[FILES]
{%- for filename in files %}
{{ filename }}
{%- endfor %}

View File

@ -0,0 +1,33 @@
a
and
are
as
at
be
but
by
for
if
in
into
is
it
near
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with

View File

@ -30,10 +30,16 @@ project = {{ project | repr }}
copyright = {{ copyright | repr }} copyright = {{ copyright | repr }}
author = {{ author | repr }} author = {{ author | repr }}
{%- if version %}
# The short X.Y version # The short X.Y version
version = {{ version | repr }} version = {{ version | repr }}
{%- endif %}
{%- if release %}
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = {{ release | repr }} release = {{ release | repr }}
{%- endif %}
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -50,17 +56,20 @@ extensions = [
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['{{ dot }}templates'] templates_path = ['{{ dot }}templates']
{% if suffix != '.rst' -%}
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
# #
# source_suffix = ['.rst', '.md'] # source_suffix = ['.rst', '.md']
source_suffix = {{ suffix | repr }} source_suffix = {{ suffix | repr }}
{% if master_doc != 'index' -%} {% endif -%}
{% if master != 'index' -%}
# The master toctree document. # The master toctree document.
master_doc = {{ master | repr }} master_doc = {{ master | repr }}
{% endif -%} {% endif -%}
{% if language -%}
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
# #
@ -68,6 +77,7 @@ master_doc = {{ master | repr }}
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = {{ language | repr }} language = {{ language | repr }}
{% endif -%}
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path. # This pattern also affects html_static_path and html_extra_path.
@ -81,44 +91,10 @@ exclude_patterns = [{{ exclude_patterns }}]
# #
html_theme = 'alabaster' html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['{{ dot }}static'] html_static_path = ['{{ dot }}static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
{%- if extensions %} {%- if extensions %}

View File

@ -231,6 +231,16 @@ a.headerlink {
visibility: hidden; visibility: hidden;
} }
a.brackets:before,
span.brackets > a:before{
content: "[";
}
a.brackets:after,
span.brackets > a:after {
content: "]";
}
h1:hover > a.headerlink, h1:hover > a.headerlink,
h2:hover > a.headerlink, h2:hover > a.headerlink,
h3:hover > a.headerlink, h3:hover > a.headerlink,
@ -391,6 +401,14 @@ table.citation td {
border-bottom: none; border-bottom: none;
} }
td > p:first-child {
margin-top: 0px;
}
td > p:only-child {
margin-bottom: 0px;
}
/* -- figures --------------------------------------------------------------- */ /* -- figures --------------------------------------------------------------- */
div.figure { div.figure {
@ -460,11 +478,57 @@ ol.upperroman {
list-style: upper-roman; list-style: upper-roman;
} }
li > p:first-child {
margin-top: 0px;
}
li > p:only-child {
margin-bottom: 0px;
}
dl.footnote > dt,
dl.citation > dt {
float: left;
}
dl.footnote > dd,
dl.citation > dd {
margin-bottom: 0em;
}
dl.footnote > dd:after,
dl.citation > dd:after {
content: "";
clear: both;
}
dl.field-list {
display: flex;
flex-wrap: wrap;
}
dl.field-list > dt {
flex-basis: 20%;
font-weight: bold;
word-break: break-word;
}
dl.field-list > dt:after {
content: ":";
}
dl.field-list > dd {
flex-basis: 70%;
padding-left: 1em;
margin-left: 0em;
margin-bottom: 0em;
}
dl { dl {
margin-bottom: 15px; margin-bottom: 15px;
} }
dd p { dd > p:first-child {
margin-top: 0px; margin-top: 0px;
} }

View File

@ -23,7 +23,7 @@ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import new_document from sphinx.util.docutils import new_document
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.util.nodes import apply_source_workaround, is_smartquotable from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquotable
if False: if False:
# For type annotation # For type annotation
@ -309,6 +309,19 @@ class UnreferencedFootnotesDetector(SphinxTransform):
location=node) location=node)
class FigureAligner(SphinxTransform):
"""
Align figures to center by default.
"""
default_priority = 700
def apply(self, **kwargs):
# type: (Any) -> None
matcher = NodeMatcher(nodes.table, nodes.figure)
for node in self.document.traverse(matcher): # type: nodes.Element
node.setdefault('align', 'center')
class FilterSystemMessages(SphinxTransform): class FilterSystemMessages(SphinxTransform):
"""Filter system messages from a doctree.""" """Filter system messages from a doctree."""
default_priority = 999 default_priority = 999

View File

@ -27,6 +27,12 @@ _ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm')
codes = {} # type: Dict[str, str] codes = {} # type: Dict[str, str]
def terminal_safe(s):
# type: (str) -> str
"""safely encode a string for printing to the terminal."""
return s.encode('ascii', 'backslashreplace').decode('ascii')
def get_terminal_width(): def get_terminal_width():
# type: () -> int # type: () -> int
"""Borrowed from the py lib.""" """Borrowed from the py lib."""

View File

@ -23,10 +23,10 @@ from docutils import nodes
from docutils.io import FileOutput from docutils.io import FileOutput
from docutils.parsers.rst import Directive, directives, roles, convert_directive_function from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
from docutils.statemachine import StateMachine from docutils.statemachine import StateMachine
from docutils.utils import Reporter from docutils.utils import Reporter, unescape
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import ExtensionError from sphinx.errors import ExtensionError, SphinxError
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
@ -36,7 +36,8 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
if False: if False:
# For type annotation # For type annotation
from types import ModuleType # NOQA from types import ModuleType # NOQA
from typing import Any, Callable, Generator, List, Set, Tuple, Type # NOQA from typing import Any, Callable, Dict, Generator, List, Set, Tuple, Type # NOQA
from docutils.parsers.rst.states import Inliner # NOQA
from docutils.statemachine import State, StringList # NOQA from docutils.statemachine import State, StringList # NOQA
from sphinx.builders import Builder # NOQA from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA from sphinx.config import Config # NOQA
@ -383,6 +384,99 @@ class SphinxDirective(Directive):
return self.env.config return self.env.config
class SphinxRole:
"""A base class for Sphinx roles.
This class provides helper methods for Sphinx roles.
.. note:: The subclasses of this class might not work with docutils.
This class is strongly coupled with Sphinx.
"""
name = None #: The role name actually used in the document.
rawtext = None #: A string containing the entire interpreted text input.
text = None #: The interpreted text content.
lineno = None #: The line number where the interpreted text begins.
inliner = None #: The ``docutils.parsers.rst.states.Inliner`` object.
options = None #: A dictionary of directive options for customization
#: (from the "role" directive).
content = None #: A list of strings, the directive content for customization
#: (from the "role" directive).
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
self.rawtext = rawtext
self.text = unescape(text)
self.lineno = lineno
self.inliner = inliner
self.options = options
self.content = content
# guess role type
if name:
self.name = name.lower()
else:
self.name = self.env.temp_data.get('default_role')
if not self.name:
self.name = self.env.config.default_role
if not self.name:
raise SphinxError('cannot determine default role!')
return self.run()
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
raise NotImplementedError
@property
def env(self):
# type: () -> BuildEnvironment
"""Reference to the :class:`.BuildEnvironment` object."""
return self.inliner.document.settings.env
@property
def config(self):
# type: () -> Config
"""Reference to the :class:`.Config` object."""
return self.env.config
def set_source_info(self, node, lineno=None):
# type: (nodes.Node, int) -> None
if lineno is None:
lineno = self.lineno
source_info = self.inliner.reporter.get_source_and_line(lineno) # type: ignore
node.source, node.line = source_info
class ReferenceRole(SphinxRole):
"""A base class for reference roles.
The reference roles can accpet ``link title <target>`` style as a text for
the role. The parsed result; link title and target will be stored to
``self.title`` and ``self.target``.
"""
has_explicit_title = None #: A boolean indicates the role has explicit title or not.
title = None #: The link title for the interpreted text.
target = None #: The link target for the interpreted text.
# \x00 means the "<" was backslash-escaped
explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
matched = self.explicit_title_re.match(text)
if matched:
self.has_explicit_title = True
self.title = unescape(matched.group(1))
self.target = unescape(matched.group(2))
else:
self.has_explicit_title = False
self.title = unescape(text)
self.target = unescape(text)
return super().__call__(name, rawtext, text, lineno, inliner, options, content)
class SphinxTranslator(nodes.NodeVisitor): class SphinxTranslator(nodes.NodeVisitor):
"""A base class for Sphinx translators. """A base class for Sphinx translators.

View File

@ -20,7 +20,7 @@ from io import StringIO
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.pycompat import NoneType from sphinx.util.typing import NoneType
if False: if False:
# For type annotation # For type annotation

View File

@ -9,13 +9,20 @@
""" """
import json import json
import warnings
from collections import UserString from collections import UserString
from sphinx.deprecation import RemovedInSphinx40Warning
if False: if False:
# For type annotation # For type annotation
from typing import Any, IO # NOQA from typing import Any, IO # NOQA
warnings.warn('sphinx.util.jsonimpl is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
class SphinxJSONEncoder(json.JSONEncoder): class SphinxJSONEncoder(json.JSONEncoder):
"""JSONEncoder subclass that forces translation proxies.""" """JSONEncoder subclass that forces translation proxies."""
def default(self, obj): def default(self, obj):

View File

@ -285,6 +285,14 @@ def find_source_node(node):
return None return None
def get_node_line(node):
# type: (nodes.Element) -> int
for pnode in traverse_parent(node):
if pnode.line:
return pnode.line
return None
def traverse_parent(node, cls=None): def traverse_parent(node, cls=None):
# type: (nodes.Element, Any) -> Iterable[nodes.Element] # type: (nodes.Element, Any) -> Iterable[nodes.Element]
while node: while node:
@ -293,6 +301,15 @@ def traverse_parent(node, cls=None):
node = node.parent node = node.parent
def get_prev_node(node):
# type: (nodes.Node) -> nodes.Node
pos = node.parent.index(node)
if pos > 0:
return node.parent[pos - 1]
else:
return None
def traverse_translatable_index(doctree): def traverse_translatable_index(doctree):
# type: (nodes.Element) -> Iterable[Tuple[nodes.Element, List[str]]] # type: (nodes.Element) -> Iterable[Tuple[nodes.Element, List[str]]]
"""Traverse translatable index node from a document tree.""" """Traverse translatable index node from a document tree."""

View File

@ -17,6 +17,8 @@ import warnings
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.console import terminal_safe
from sphinx.util.typing import NoneType
if False: if False:
# For type annotation # For type annotation
@ -26,22 +28,9 @@ if False:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
NoneType = type(None)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Python 2/3 compatibility # Python 2/3 compatibility
# sys_encoding: some kind of default system encoding; should be used with
# a lenient error handler
sys_encoding = sys.getdefaultencoding()
# terminal_safe(): safely encode a string for printing to the terminal
def terminal_safe(s):
# type: (str) -> str
return s.encode('ascii', 'backslashreplace').decode('ascii')
# convert_with_2to3(): # convert_with_2to3():
# support for running 2to3 over config files # support for running 2to3 over config files
def convert_with_2to3(filepath): def convert_with_2to3(filepath):
@ -99,9 +88,12 @@ def execfile_(filepath, _globals, open=open):
deprecated_alias('sphinx.util.pycompat', deprecated_alias('sphinx.util.pycompat',
{ {
'NoneType': NoneType, # type: ignore
'TextIOWrapper': io.TextIOWrapper, 'TextIOWrapper': io.TextIOWrapper,
'htmlescape': html.escape, 'htmlescape': html.escape,
'indent': textwrap.indent, 'indent': textwrap.indent,
'terminal_safe': terminal_safe,
'sys_encoding': sys.getdefaultencoding(),
'u': '', 'u': '',
}, },
RemovedInSphinx40Warning) RemovedInSphinx40Warning)

View File

@ -20,6 +20,9 @@ DirectiveOption = Callable[[str], Any]
# Text like nodes which are initialized with text and rawsource # Text like nodes which are initialized with text and rawsource
TextlikeNode = Union[nodes.Text, nodes.TextElement] TextlikeNode = Union[nodes.Text, nodes.TextElement]
# type of None
NoneType = type(None)
# common role functions # common role functions
RoleFunction = Callable[[str, str, str, int, Inliner, Dict, List[str]], RoleFunction = Callable[[str, str, str, int, Inliner, Dict, List[str]],
Tuple[List[nodes.Node], List[nodes.system_message]]] Tuple[List[nodes.Node], List[nodes.system_message]]]

View File

@ -30,7 +30,7 @@ from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _, __ from sphinx.locale import admonitionlabels, _, __
from sphinx.util import split_into, logging from sphinx.util import split_into, logging
from sphinx.util.docutils import SphinxTranslator from sphinx.util.docutils import SphinxTranslator
from sphinx.util.nodes import clean_astext from sphinx.util.nodes import clean_astext, get_prev_node
from sphinx.util.template import LaTeXRenderer from sphinx.util.template import LaTeXRenderer
from sphinx.util.texescape import tex_escape_map, tex_replace_map from sphinx.util.texescape import tex_escape_map, tex_replace_map
@ -1752,9 +1752,15 @@ class LaTeXTranslator(SphinxTranslator):
elif domain.get_enumerable_node_type(next_node) and domain.get_numfig_title(next_node): elif domain.get_enumerable_node_type(next_node) and domain.get_numfig_title(next_node):
return return
if 'refuri' in node or 'refid' in node or 'refname' in node: if 'refuri' in node:
# skip indirect targets (external hyperlink and internal links)
return return
if node.get('refid'):
prev_node = get_prev_node(node)
if isinstance(prev_node, nodes.reference) and node['refid'] == prev_node['refid']:
# a target for a hyperlink reference having alias
pass
else:
add_target(node['refid'])
for id in node['ids']: for id in node['ids']:
add_target(id) add_target(id)

View File

@ -247,7 +247,7 @@ class TexinfoTranslator(SphinxTranslator):
title = self.settings.title # type: str title = self.settings.title # type: str
if not title: if not title:
title_node = self.document.next_node(nodes.title) title_node = self.document.next_node(nodes.title)
title = (title and title_node.astext()) or '<untitled>' title = (title_node and title_node.astext()) or '<untitled>'
elements['title'] = self.escape_id(title) or '<untitled>' elements['title'] = self.escape_id(title) or '<untitled>'
# filename # filename
if not elements['filename']: if not elements['filename']:

View File

@ -1 +0,0 @@
project = 'test'

View File

@ -1,19 +0,0 @@
Index markup
------------
.. index::
single: entry
pair: entry; pair
double: entry; double
triple: index; entry; triple
keyword: with
see: from; to
seealso: fromalso; toalso
.. index::
!Main, !Other
!single: entry; pair
.. index:: triple-quoted string, Unicode Consortium, raw string
single: """; string literal
single: '''; string literal

View File

@ -1,64 +0,0 @@
@echo off
setlocal
pushd %~dp0
set this=%~n0
if not defined PYTHON set PYTHON=py
if not defined SPHINXBUILD (
%PYTHON% -c "import sphinx" > nul 2> nul
if errorlevel 1 (
echo Installing sphinx with %PYTHON%
%PYTHON% -m pip install sphinx
if errorlevel 1 exit /B
)
set SPHINXBUILD=%PYTHON% -c "import sphinx.cmd.build, sys; sys.exit(sphinx.cmd.build.main())"
)
rem Search for HHC in likely places
set HTMLHELP=
where hhc /q && set HTMLHELP=hhc && goto :skiphhcsearch
where /R ..\externals hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc"
if not exist "%HTMLHELP%" where /R "%ProgramFiles(x86)%" hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc"
if not exist "%HTMLHELP%" where /R "%ProgramFiles%" hhc > "%TEMP%\hhc.loc" 2> nul && set /P HTMLHELP= < "%TEMP%\hhc.loc" & del "%TEMP%\hhc.loc"
if not exist "%HTMLHELP%" (
echo.
echo.The HTML Help Workshop was not found. Set the HTMLHELP variable
echo.to the path to hhc.exe or download and install it from
echo.http://msdn.microsoft.com/en-us/library/ms669985
exit /B 1
)
echo hhc.exe path: %HTMLHELP%
if "%BUILDDIR%" EQU "" set BUILDDIR=build
%SPHINXBUILD% >nul 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
popd
exit /B 1
)
set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS%
cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -bhtmlhelp -dbuild\doctrees . "%BUILDDIR%\htmlhelp"
"%HTMLHELP%" "%BUILDDIR%\htmlhelp\test.hhp"
rem hhc.exe seems to always exit with code 1, reset to 0 for less than 2
if not errorlevel 2 cmd /C exit /b 0
echo.
if errorlevel 1 (
echo.Build failed (exit code %ERRORLEVEL%^), check for error messages
echo.above. Any output will be found in %BUILDDIR%\%1
) else (
echo.Build succeeded. All output should be in %BUILDDIR%\%1
)
popd

View File

@ -1,4 +0,0 @@
project = 'Sphinx docutils conf <Tests>'
source_suffix = '.txt'
keep_warnings = True
exclude_patterns = ['_build']

View File

@ -0,0 +1,6 @@
test-docutilsconf
==================
Sphinx [1]_
.. [1] Python Documentation Generator

View File

@ -1,15 +0,0 @@
docutils conf
=============
field-name-limit
----------------
:short: desc
:long long long long: long title
option-limit
------------
--short short desc
--long-long-long-long long desc

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -4,5 +4,10 @@ class Callable():
def __call__(self, arg1, arg2, **kwargs): def __call__(self, arg1, arg2, **kwargs):
pass pass
def method(self, arg1, arg2):
"""docstring of Callable.method()."""
pass
function = Callable() function = Callable()
method = function.method

View File

@ -0,0 +1,16 @@
def deco1(func):
"""docstring for deco1"""
def wrapper():
return func()
return wrapper
def deco2(condition, message):
"""docstring for deco2"""
def decorator(func):
def wrapper():
return func()
return wrapper
return decorator

View File

@ -15,6 +15,11 @@ def decoratedFunction():
return None return None
def func(arg: missing_module.Class):
"""a function takes mocked object as an argument"""
pass
class TestAutodoc(object): class TestAutodoc(object):
"""TestAutodoc docstring.""" """TestAutodoc docstring."""
@missing_name @missing_name

View File

@ -15,6 +15,12 @@ For Windows users
For UNIX users For UNIX users
-------------- --------------
Linux
^^^^^
FreeBSD
^^^^^^^
This one's got an apostrophe This one's got an apostrophe
---------------------------- ----------------------------
@ -26,4 +32,6 @@ References
* :ref:`index:Installation` * :ref:`index:Installation`
* :ref:`index:For Windows users` * :ref:`index:For Windows users`
* :ref:`index:For UNIX users` * :ref:`index:For UNIX users`
* :ref:`index:Linux`
* :ref:`index:FreeBSD`
* :ref:`index:This one's got an apostrophe` * :ref:`index:This one's got an apostrophe`

View File

@ -15,15 +15,23 @@ For Windows users
For UNIX users For UNIX users
-------------- --------------
Linux
^^^^^
FreeBSD
^^^^^^^
This one's got an apostrophe This one's got an apostrophe
---------------------------- ----------------------------
References References
========== ==========
* :ref:`test-ext-autosectionlabel`
* :ref:`Introduce of Sphinx` * :ref:`Introduce of Sphinx`
* :ref:`Installation` * :ref:`Installation`
* :ref:`For Windows users` * :ref:`For Windows users`
* :ref:`For UNIX users` * :ref:`For UNIX users`
* :ref:`Linux`
* :ref:`FreeBSD`
* :ref:`This one's got an apostrophe` * :ref:`This one's got an apostrophe`

View File

@ -0,0 +1,7 @@
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
extensions = ['sphinx.ext.autosummary']
autosummary_generate = True
autosummary_mock_imports = ['unknown']

View File

@ -0,0 +1,6 @@
import unknown
class Foo(unknown.Class):
"""Foo class"""
pass

View File

@ -0,0 +1,7 @@
test-ext-autosummary-mock_imports
=================================
.. autosummary::
:toctree: generated
foo

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -7,7 +7,7 @@ Empty cell
---------- ----------
.. list-table:: .. list-table::
:header-rows: 1
- * un - * un
* *
* trois * trois

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable}} \label{\detokenize{longtable:longtable}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|l|l|} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|l|l|}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-caption}} \label{\detokenize{longtable:longtable-having-caption}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|l|l|} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|l|l|}
\sphinxthelongtablecaptionisattop \sphinxthelongtablecaptionisattop
\caption{caption for longtable\strut}\label{\detokenize{longtable:id1}}\\*[\sphinxlongtablecapskipadjust] \caption{caption for longtable\strut}\label{\detokenize{longtable:id1}}\\*[\sphinxlongtablecapskipadjust]
\hline \hline

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-problematic-cell}} \label{\detokenize{longtable:longtable-having-problematic-cell}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|*{2}{\X{1}{2}|}} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{2}{\X{1}{2}|}}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-both-stub-columns-and-problematic-cell}} \label{\detokenize{longtable:longtable-having-both-stub-columns-and-problematic-cell}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|*{3}{\X{1}{3}|}} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{3}{\X{1}{3}|}}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-verbatim}} \label{\detokenize{longtable:longtable-having-verbatim}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|*{2}{\X{1}{2}|}} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|*{2}{\X{1}{2}|}}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-widths-option}} \label{\detokenize{longtable:longtable-having-widths-option}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|\X{30}{100}|\X{70}{100}|} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|\X{30}{100}|\X{70}{100}|}
\hline\noalign{\phantomsection\label{\detokenize{longtable:namedlongtable}}\label{\detokenize{longtable:mylongtable}}}% \hline\noalign{\phantomsection\label{\detokenize{longtable:namedlongtable}}\label{\detokenize{longtable:mylongtable}}}%
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-having-both-widths-and-problematic-cell}} \label{\detokenize{longtable:longtable-having-both-widths-and-problematic-cell}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|\X{30}{100}|\X{70}{100}|} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|\X{30}{100}|\X{70}{100}|}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -1,6 +1,6 @@
\label{\detokenize{longtable:longtable-with-tabularcolumn}} \label{\detokenize{longtable:longtable-with-tabularcolumn}}
\begin{savenotes}\sphinxatlongtablestart\begin{longtable}{|c|c|} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}[c]{|c|c|}
\hline \hline
\sphinxstyletheadfamily \sphinxstyletheadfamily
header1 header1

View File

@ -11,3 +11,6 @@ Some additional anchors to exercise ignore code
* `Example Bar invalid <http://example.com/#top>`_ * `Example Bar invalid <http://example.com/#top>`_
* `Example anchor invalid <http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist>`_ * `Example anchor invalid <http://www.sphinx-doc.org/en/1.7/intro.html#does-not-exist>`_
* `Complete nonsense <https://localhost:7777/doesnotexist>`_ * `Complete nonsense <https://localhost:7777/doesnotexist>`_
.. image:: http://example.com/image.png
.. figure:: http://example.com/image2.png

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -1,4 +1,3 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']
latex_elements = { latex_elements = {

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -3,8 +3,6 @@ Autodoc tests
Just testing a few autodoc possibilities... Just testing a few autodoc possibilities...
.. automodule:: util
.. automodule:: autodoc_target .. automodule:: autodoc_target
:members: :members:
@ -28,12 +26,6 @@ Just testing a few autodoc possibilities...
:members: :members:
.. automodule:: autodoc_fodder
:noindex:
.. autoclass:: MarkupError
.. currentmodule:: autodoc_target .. currentmodule:: autodoc_target
.. autoclass:: InstAttCls .. autoclass:: InstAttCls

View File

@ -1,7 +0,0 @@
class MarkupError(object):
"""
.. note:: This is a docstring with a
small markup error which should have
correct location information.
"""

View File

@ -4,9 +4,6 @@ Sphinx image handling
.. first, a simple test with direct filename .. first, a simple test with direct filename
.. image:: img.png .. image:: img.png
.. a non-existing image with direct filename
.. image:: foo.png
.. an image with path name (relative to this directory!) .. an image with path name (relative to this directory!)
.. image:: subdir/img.png .. image:: subdir/img.png
:height: 100 :height: 100

View File

@ -3,7 +3,6 @@ Testing downloadable files
Download :download:`img.png` here. Download :download:`img.png` here.
Download :download:`this <subdir/img.png>` there. Download :download:`this <subdir/img.png>` there.
Don't download :download:`this <nonexisting.png>`.
Test file and literal inclusion Test file and literal inclusion
=============================== ===============================

View File

@ -2,4 +2,4 @@
.. Paths in included files are relative to the file that .. Paths in included files are relative to the file that
includes them includes them
.. image:: ../root/img.png .. image:: subdir/img.png

View File

@ -1,3 +1,2 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']
templates_path = ['_templates'] templates_path = ['_templates']

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -1,2 +1 @@
html_theme = 'classic'
exclude_patterns = ['_build'] exclude_patterns = ['_build']

View File

@ -12,6 +12,8 @@ import sys
import pytest import pytest
from sphinx.util import docutils
@pytest.fixture(scope='module', autouse=True) @pytest.fixture(scope='module', autouse=True)
def setup_module(rootdir): def setup_module(rootdir):
@ -26,7 +28,10 @@ def test_html_translator(app, status, warning):
# no set_translator() # no set_translator()
translator_class = app.builder.get_translator_class() translator_class = app.builder.get_translator_class()
assert translator_class assert translator_class
if docutils.__version_info__ < (0, 13):
assert translator_class.__name__ == 'HTMLTranslator' assert translator_class.__name__ == 'HTMLTranslator'
else:
assert translator_class.__name__ == 'HTML5Translator'
@pytest.mark.sphinx('html', testroot='api-set-translator') @pytest.mark.sphinx('html', testroot='api-set-translator')

View File

@ -602,6 +602,29 @@ def test_generate():
'Class.meth', more_content=add_content) 'Class.meth', more_content=add_content)
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_decorator(app):
actual = do_autodoc(app, 'decorator', 'target.decorator.deco1')
assert list(actual) == [
'',
'.. py:decorator:: deco1',
' :module: target.decorator',
'',
' docstring for deco1',
' '
]
actual = do_autodoc(app, 'decorator', 'target.decorator.deco2')
assert list(actual) == [
'',
'.. py:decorator:: deco2(condition, message)',
' :module: target.decorator',
'',
' docstring for deco2',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_exception(app): def test_autodoc_exception(app):
actual = do_autodoc(app, 'exception', 'target.CustomEx') actual = do_autodoc(app, 'exception', 'target.CustomEx')
@ -1342,10 +1365,23 @@ def test_autofunction_for_callable(app):
] ]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_method(app):
actual = do_autodoc(app, 'function', 'target.callable.method')
assert list(actual) == [
'',
'.. py:function:: method(arg1, arg2)',
' :module: target.callable',
'',
' docstring of Callable.method().',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_mocked_module_imports(app, warning): def test_mocked_module_imports(app, warning):
# no autodoc_mock_imports # no autodoc_mock_imports
options = {"members": 'TestAutodoc,decoratedFunction'} options = {"members": 'TestAutodoc,decoratedFunction,func'}
actual = do_autodoc(app, 'module', 'target.need_mocks', options) actual = do_autodoc(app, 'module', 'target.need_mocks', options)
assert list(actual) == [] assert list(actual) == []
assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue() assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue()
@ -1382,6 +1418,12 @@ def test_mocked_module_imports(app, warning):
' :module: target.need_mocks', ' :module: target.need_mocks',
'', '',
' decoratedFunction docstring', ' decoratedFunction docstring',
' ',
'',
'.. py:function:: func(arg: missing_module.Class)',
' :module: target.need_mocks',
'',
' a function takes mocked object as an argument',
' ' ' '
] ]
assert warning.getvalue() == '' assert warning.getvalue() == ''
@ -1511,6 +1553,12 @@ def test_autodoc_default_options(app):
assert ' .. py:attribute:: EnumCls.val1' in actual assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual assert ' .. py:attribute:: EnumCls.val4' not in actual
# with :members: = True
app.config.autodoc_default_options = {'members': True}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
# with :members: and :undoc-members: # with :members: and :undoc-members:
app.config.autodoc_default_options = { app.config.autodoc_default_options = {
'members': None, 'members': None,

View File

@ -8,7 +8,6 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import pickle
import sys import sys
from textwrap import dedent from textwrap import dedent
@ -58,11 +57,7 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
# (html, epub, latex, texinfo and manpage) # (html, epub, latex, texinfo and manpage)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"buildername", "buildername",
[ ['dirhtml', 'singlehtml', 'text', 'changes', 'xml', 'pseudoxml', 'linkcheck'],
# note: no 'html' - if it's ok with dirhtml it's ok with html
'dirhtml', 'singlehtml', 'pickle', 'json', 'text', 'htmlhelp',
'changes', 'xml', 'pseudoxml', 'linkcheck',
],
) )
@mock.patch('sphinx.builders.linkcheck.requests.head', @mock.patch('sphinx.builders.linkcheck.requests.head',
side_effect=request_session_head) side_effect=request_session_head)
@ -110,7 +105,7 @@ def test_image_glob(app, status, warning):
app.builder.build_all() app.builder.build_all()
# index.rst # index.rst
doctree = pickle.loads((app.doctreedir / 'index.doctree').bytes()) doctree = app.env.get_doctree('index')
assert isinstance(doctree[0][1], nodes.image) assert isinstance(doctree[0][1], nodes.image)
assert doctree[0][1]['candidates'] == {'*': 'rimg.png'} assert doctree[0][1]['candidates'] == {'*': 'rimg.png'}
@ -135,7 +130,7 @@ def test_image_glob(app, status, warning):
assert doctree[0][4][0]['uri'] == 'img.*' assert doctree[0][4][0]['uri'] == 'img.*'
# subdir/index.rst # subdir/index.rst
doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes()) doctree = app.env.get_doctree('subdir/index')
assert isinstance(doctree[0][1], nodes.image) assert isinstance(doctree[0][1], nodes.image)
sub = path('subdir') sub = path('subdir')

View File

@ -15,6 +15,8 @@ from xml.etree import ElementTree
import pytest import pytest
from sphinx.util import docutils
# check given command is runnable # check given command is runnable
def runnable(command): def runnable(command):
@ -70,7 +72,7 @@ def test_build_epub(app):
# toc.ncx # toc.ncx
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text()) toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').text())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python documentation' assert toc.find("./ncx:docTitle/ncx:text").text == 'Python'
# toc.ncx / head # toc.ncx / head
meta = list(toc.find("./ncx:head")) meta = list(toc.find("./ncx:head"))
@ -94,7 +96,7 @@ def test_build_epub(app):
# content.opf / metadata # content.opf / metadata
metadata = opf.find("./idpf:metadata") metadata = opf.find("./idpf:metadata")
assert metadata.find("./dc:language").text == 'en' assert metadata.find("./dc:language").text == 'en'
assert metadata.find("./dc:title").text == 'Python documentation' assert metadata.find("./dc:title").text == 'Python'
assert metadata.find("./dc:description").text == 'unknown' assert metadata.find("./dc:description").text == 'unknown'
assert metadata.find("./dc:creator").text == 'unknown' assert metadata.find("./dc:creator").text == 'unknown'
assert metadata.find("./dc:contributor").text == 'unknown' assert metadata.find("./dc:contributor").text == 'unknown'
@ -174,7 +176,7 @@ def test_nested_toc(app):
# toc.ncx # toc.ncx
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes()) toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes())
assert toc.find("./ncx:docTitle/ncx:text").text == 'Python documentation' assert toc.find("./ncx:docTitle/ncx:text").text == 'Python'
# toc.ncx / navPoint # toc.ncx / navPoint
def navinfo(elem): def navinfo(elem):
@ -229,8 +231,7 @@ def test_escaped_toc(app):
# toc.ncx # toc.ncx
toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes()) toc = EPUBElementTree.fromstring((app.outdir / 'toc.ncx').bytes())
assert toc.find("./ncx:docTitle/ncx:text").text == ('need <b>"escaped"</b> ' assert toc.find("./ncx:docTitle/ncx:text").text == 'need <b>"escaped"</b> project'
'project documentation')
# toc.ncx / navPoint # toc.ncx / navPoint
def navinfo(elem): def navinfo(elem):
@ -351,6 +352,8 @@ def test_epub_css_files(app):
'href="https://example.com/custom.css" />' not in content) 'href="https://example.com/custom.css" />' not in content)
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('epub', testroot='roles-download') @pytest.mark.sphinx('epub', testroot='roles-download')
def test_html_download_role(app, status, warning): def test_html_download_role(app, status, warning):
app.build() app.build()

View File

@ -10,6 +10,7 @@
import os import os
import re import re
from hashlib import md5
from itertools import cycle, chain from itertools import cycle, chain
import pytest import pytest
@ -17,6 +18,7 @@ from html5lib import HTMLParser
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq from sphinx.testing.util import strip_escseq
from sphinx.util import docutils
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile
@ -128,6 +130,11 @@ def test_html_warnings(app, warning):
'--- Got:\n' + html_warnings '--- Got:\n' + html_warnings
@pytest.mark.sphinx('html', confoverrides={'html4_writer': True})
def test_html4_output(app, status, warning):
app.build()
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'images.html': [ 'images.html': [
(".//img[@src='_images/img.png']", ''), (".//img[@src='_images/img.png']", ''),
@ -192,18 +199,18 @@ def test_html_warnings(app, warning):
# an option list # an option list
(".//span[@class='option']", '--help'), (".//span[@class='option']", '--help'),
# admonitions # admonitions
(".//p[@class='first admonition-title']", 'My Admonition'), (".//p[@class='admonition-title']", 'My Admonition'),
(".//p[@class='last']", 'Note text.'), (".//div[@class='admonition note']/p", 'Note text.'),
(".//p[@class='last']", 'Warning text.'), (".//div[@class='admonition warning']/p", 'Warning text.'),
# inline markup # inline markup
(".//li/strong", r'^command\\n$'), (".//li/p/strong", r'^command\\n$'),
(".//li/strong", r'^program\\n$'), (".//li/p/strong", r'^program\\n$'),
(".//li/em", r'^dfn\\n$'), (".//li/p/em", r'^dfn\\n$'),
(".//li/kbd", r'^kbd\\n$'), (".//li/p/kbd", r'^kbd\\n$'),
(".//li/span", 'File \N{TRIANGULAR BULLET} Close'), (".//li/p/span", 'File \N{TRIANGULAR BULLET} Close'),
(".//li/code/span[@class='pre']", '^a/$'), (".//li/p/code/span[@class='pre']", '^a/$'),
(".//li/code/em/span[@class='pre']", '^varpart$'), (".//li/p/code/em/span[@class='pre']", '^varpart$'),
(".//li/code/em/span[@class='pre']", '^i$'), (".//li/p/code/em/span[@class='pre']", '^i$'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']" (".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong", 'PEP 8'), "[@class='pep reference external']/strong", 'PEP 8'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']" (".//a[@href='https://www.python.org/dev/peps/pep-0008']"
@ -236,13 +243,13 @@ def test_html_warnings(app, warning):
(".//div[@class='versionchanged']/p", (".//div[@class='versionchanged']/p",
'Second paragraph of versionchanged'), 'Second paragraph of versionchanged'),
# footnote reference # footnote reference
(".//a[@class='footnote-reference']", r'\[1\]'), (".//a[@class='footnote-reference brackets']", r'1'),
# created by reference lookup # created by reference lookup
(".//a[@href='index.html#ref1']", ''), (".//a[@href='index.html#ref1']", ''),
# ``seealso`` directive # ``seealso`` directive
(".//div/p[@class='first admonition-title']", 'See also'), (".//div/p[@class='admonition-title']", 'See also'),
# a ``hlist`` directive # a ``hlist`` directive
(".//table[@class='hlist']/tbody/tr/td/ul/li", '^This$'), (".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'),
# a ``centered`` directive # a ``centered`` directive
(".//p[@class='centered']/strong", 'LICENSE'), (".//p[@class='centered']/strong", 'LICENSE'),
# a glossary # a glossary
@ -261,10 +268,10 @@ def test_html_warnings(app, warning):
# tests for numeric labels # tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'), (".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
# tests for smartypants # tests for smartypants
(".//li", 'Smart “quotes” in English text.'), (".//li/p", 'Smart “quotes” in English text.'),
(".//li", 'Smart — long and short dashes.'), (".//li/p", 'Smart — long and short dashes.'),
(".//li", 'Ellipsis…'), (".//li/p", 'Ellipsis…'),
(".//li//code//span[@class='pre']", 'foo--"bar"...'), (".//li/p/code/span[@class='pre']", 'foo--"bar"...'),
(".//p", 'Этот «абзац» должен использовать „русские“ кавычки.'), (".//p", 'Этот «абзац» должен использовать „русские“ кавычки.'),
(".//p", 'Il dit : « Cest “super” ! »'), (".//p", 'Il dit : « Cest “super” ! »'),
], ],
@ -294,24 +301,24 @@ def test_html_warnings(app, warning):
(".//li[@class='toctree-l1']/a[@href='markup.html']", (".//li[@class='toctree-l1']/a[@href='markup.html']",
'Testing various markup'), 'Testing various markup'),
# test unknown field names # test unknown field names
(".//th[@class='field-name']", 'Field_name:'), (".//dt[@class='field-odd']", 'Field_name'),
(".//th[@class='field-name']", 'Field_name all lower:'), (".//dt[@class='field-even']", 'Field_name all lower'),
(".//th[@class='field-name']", 'FIELD_NAME:'), (".//dt[@class='field-odd']", 'FIELD_NAME'),
(".//th[@class='field-name']", 'FIELD_NAME ALL CAPS:'), (".//dt[@class='field-even']", 'FIELD_NAME ALL CAPS'),
(".//th[@class='field-name']", 'Field_Name:'), (".//dt[@class='field-odd']", 'Field_Name'),
(".//th[@class='field-name']", 'Field_Name All Word Caps:'), (".//dt[@class='field-even']", 'Field_Name All Word Caps'),
(".//th[@class='field-name']", 'Field_name:'), (".//dt[@class='field-odd']", 'Field_name'),
(".//th[@class='field-name']", 'Field_name First word cap:'), (".//dt[@class='field-even']", 'Field_name First word cap'),
(".//th[@class='field-name']", 'FIELd_name:'), (".//dt[@class='field-odd']", 'FIELd_name'),
(".//th[@class='field-name']", 'FIELd_name PARTial caps:'), (".//dt[@class='field-even']", 'FIELd_name PARTial caps'),
# custom sidebar # custom sidebar
(".//h4", 'Custom sidebar'), (".//h4", 'Custom sidebar'),
# docfields # docfields
(".//td[@class='field-body']/strong", '^moo$'), (".//dd[@class='field-odd']/p/strong", '^moo$'),
(".//td[@class='field-body']/strong", tail_check(r'\(Moo\) .* Moo')), (".//dd[@class='field-odd']/p/strong", tail_check(r'\(Moo\) .* Moo')),
(".//td[@class='field-body']/ul/li/strong", '^hour$'), (".//dd[@class='field-odd']/ul/li/p/strong", '^hour$'),
(".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), (".//dd[@class='field-odd']/ul/li/p/em", '^DuplicateType$'),
(".//td[@class='field-body']/ul/li/em", tail_check(r'.* Some parameter')), (".//dd[@class='field-odd']/ul/li/p/em", tail_check(r'.* Some parameter')),
# others # others
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span", (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'perl'), 'perl'),
@ -340,17 +347,17 @@ def test_html_warnings(app, warning):
'index.html': [ 'index.html': [
(".//meta[@name='hc'][@content='hcval']", ''), (".//meta[@name='hc'][@content='hcval']", ''),
(".//meta[@name='hc_co'][@content='hcval_co']", ''), (".//meta[@name='hc_co'][@content='hcval_co']", ''),
(".//td[@class='label']", r'\[Ref1\]'), (".//dt[@class='label']/span[@class='brackets']", r'Ref1'),
(".//td[@class='label']", ''), (".//dt[@class='label']", ''),
(".//li[@class='toctree-l1']/a", 'Testing various markup'), (".//li[@class='toctree-l1']/a", 'Testing various markup'),
(".//li[@class='toctree-l2']/a", 'Inline markup'), (".//li[@class='toctree-l2']/a", 'Inline markup'),
(".//title", 'Sphinx <Tests>'), (".//title", 'Sphinx <Tests>'),
(".//div[@class='footer']", 'Georg Brandl & Team'), (".//div[@class='footer']", 'Georg Brandl & Team'),
(".//a[@href='http://python.org/']" (".//a[@href='http://python.org/']"
"[@class='reference external']", ''), "[@class='reference external']", ''),
(".//li/a[@href='genindex.html']/span", 'Index'), (".//li/p/a[@href='genindex.html']/span", 'Index'),
(".//li/a[@href='py-modindex.html']/span", 'Module Index'), (".//li/p/a[@href='py-modindex.html']/span", 'Module Index'),
(".//li/a[@href='search.html']/span", 'Search Page'), (".//li/p/a[@href='search.html']/span", 'Search Page'),
# custom sidebar only for contents # custom sidebar only for contents
(".//h4", 'Contents sidebar'), (".//h4", 'Contents sidebar'),
# custom JavaScript # custom JavaScript
@ -381,37 +388,41 @@ def test_html_warnings(app, warning):
(".//li/a", "double"), (".//li/a", "double"),
], ],
'footnote.html': [ 'footnote.html': [
(".//a[@class='footnote-reference'][@href='#id9'][@id='id1']", r"\[1\]"), (".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"),
(".//a[@class='footnote-reference'][@href='#id10'][@id='id2']", r"\[2\]"), (".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"),
(".//a[@class='footnote-reference'][@href='#foo'][@id='id3']", r"\[3\]"), (".//a[@class='footnote-reference brackets'][@href='#foo'][@id='id3']", r"3"),
(".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"), (".//a[@class='reference internal'][@href='#bar'][@id='id4']", r"\[bar\]"),
(".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']", r"\[baz_qux\]"), (".//a[@class='reference internal'][@href='#baz-qux'][@id='id5']", r"\[baz_qux\]"),
(".//a[@class='footnote-reference'][@href='#id11'][@id='id6']", r"\[4\]"), (".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"),
(".//a[@class='footnote-reference'][@href='#id12'][@id='id7']", r"\[5\]"), (".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id1']", r"\[1\]"), (".//a[@class='fn-backref'][@href='#id1']", r"1"),
(".//a[@class='fn-backref'][@href='#id2']", r"\[2\]"), (".//a[@class='fn-backref'][@href='#id2']", r"2"),
(".//a[@class='fn-backref'][@href='#id3']", r"\[3\]"), (".//a[@class='fn-backref'][@href='#id3']", r"3"),
(".//a[@class='fn-backref'][@href='#id4']", r"\[bar\]"), (".//a[@class='fn-backref'][@href='#id4']", r"bar"),
(".//a[@class='fn-backref'][@href='#id5']", r"\[baz_qux\]"), (".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"),
(".//a[@class='fn-backref'][@href='#id6']", r"\[4\]"), (".//a[@class='fn-backref'][@href='#id6']", r"4"),
(".//a[@class='fn-backref'][@href='#id7']", r"\[5\]"), (".//a[@class='fn-backref'][@href='#id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id8']", r"\[6\]"), (".//a[@class='fn-backref'][@href='#id8']", r"6"),
], ],
'otherext.html': [ 'otherext.html': [
(".//h1", "Generated section"), (".//h1", "Generated section"),
(".//a[@href='_sources/otherext.foo.txt']", ''), (".//a[@href='_sources/otherext.foo.txt']", ''),
] ]
})) }))
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html_context.hckey_co': 'hcval_co'}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', tags=['testtag'],
confoverrides={'html_context.hckey_co': 'hcval_co'})
@pytest.mark.test_params(shared_result='test_build_html_output') @pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_output(app, cached_etree_parse, fname, expect): def test_html5_output(app, cached_etree_parse, fname, expect):
app.build() app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html_context.hckey_co': 'hcval_co'}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html')
@pytest.mark.test_params(shared_result='test_build_html_output') @pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_download(app): def test_html_download(app):
app.build() app.build()
@ -435,6 +446,29 @@ def test_html_download(app):
assert matched.group(1) == filename assert matched.group(1) == filename
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='roles-download')
def test_html_download_role(app, status, warning):
app.build()
digest = md5((app.srcdir / 'dummy.dat').encode()).hexdigest()
assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists()
content = (app.outdir / 'index.html').text()
assert (('<li><p><a class="reference download internal" download="" '
'href="_downloads/%s/dummy.dat">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">dummy.dat</span></code></a></p></li>' % digest)
in content)
assert ('<li><p><code class="xref download docutils literal notranslate">'
'<span class="pre">not_found.dat</span></code></p></li>' in content)
assert ('<li><p><a class="reference download external" download="" '
'href="http://www.sphinx-doc.org/en/master/_static/sphinxheader.png">'
'<code class="xref download docutils literal notranslate">'
'<span class="pre">Sphinx</span> <span class="pre">logo</span>'
'</code></a></p></li>' in content)
@pytest.mark.sphinx('html', testroot='build-html-translator') @pytest.mark.sphinx('html', testroot='build-html-translator')
def test_html_translator(app): def test_html_translator(app):
app.build() app.build()
@ -473,6 +507,8 @@ def test_html_translator(app):
(".//h1", '2.1.1. Baz A', True), (".//h1", '2.1.1. Baz A', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='tocdepth') @pytest.mark.sphinx('html', testroot='tocdepth')
@pytest.mark.test_params(shared_result='test_build_html_tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth')
def test_tocdepth(app, cached_etree_parse, fname, expect): def test_tocdepth(app, cached_etree_parse, fname, expect):
@ -508,6 +544,8 @@ def test_tocdepth(app, cached_etree_parse, fname, expect):
(".//h4", '2.1.1. Baz A', True), (".//h4", '2.1.1. Baz A', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('singlehtml', testroot='tocdepth') @pytest.mark.sphinx('singlehtml', testroot='tocdepth')
@pytest.mark.test_params(shared_result='test_build_html_tocdepth') @pytest.mark.test_params(shared_result='test_build_html_tocdepth')
def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect): def test_tocdepth_singlehtml(app, cached_etree_parse, fname, expect):
@ -527,44 +565,46 @@ def test_numfig_disabled_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True), (".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//li/code/span", '^fig1$', True), (".//li/p/code/span", '^fig1$', True),
(".//li/code/span", '^Figure%s$', True), (".//li/p/code/span", '^Figure%s$', True),
(".//li/code/span", '^table-1$', True), (".//li/p/code/span", '^table-1$', True),
(".//li/code/span", '^Table:%s$', True), (".//li/p/code/span", '^Table:%s$', True),
(".//li/code/span", '^CODE_1$', True), (".//li/p/code/span", '^CODE_1$', True),
(".//li/code/span", '^Code-%s$', True), (".//li/p/code/span", '^Code-%s$', True),
(".//li/a/span", '^Section 1$', True), (".//li/p/a/span", '^Section 1$', True),
(".//li/a/span", '^Section 2.1$', True), (".//li/p/a/span", '^Section 2.1$', True),
(".//li/code/span", '^Fig.{number}$', True), (".//li/p/code/span", '^Fig.{number}$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True), (".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
], ],
'bar.html': [ 'bar.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True), (".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
], ],
'baz.html': [ 'baz.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True), (".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True), "span[@class='caption-number']", None, True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig') @pytest.mark.sphinx('html', testroot='numfig')
@pytest.mark.test_params(shared_result='test_build_html_numfig') @pytest.mark.test_params(shared_result='test_build_html_numfig')
def test_numfig_disabled(app, cached_etree_parse, fname, expect): def test_numfig_disabled(app, cached_etree_parse, fname, expect):
@ -593,9 +633,9 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 9 $', True), "span[@class='caption-number']", '^Fig. 9 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 10 $', True), "span[@class='caption-number']", '^Fig. 10 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 9 $', True), '^Table 9 $', True),
@ -605,25 +645,25 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 9 $', True), "span[@class='caption-number']", '^Listing 9 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 10 $', True), "span[@class='caption-number']", '^Listing 10 $', True),
(".//li/a/span", '^Fig. 9$', True), (".//li/p/a/span", '^Fig. 9$', True),
(".//li/a/span", '^Figure6$', True), (".//li/p/a/span", '^Figure6$', True),
(".//li/a/span", '^Table 9$', True), (".//li/p/a/span", '^Table 9$', True),
(".//li/a/span", '^Table:6$', True), (".//li/p/a/span", '^Table:6$', True),
(".//li/a/span", '^Listing 9$', True), (".//li/p/a/span", '^Listing 9$', True),
(".//li/a/span", '^Code-6$', True), (".//li/p/a/span", '^Code-6$', True),
(".//li/code/span", '^foo$', True), (".//li/p/code/span", '^foo$', True),
(".//li/code/span", '^bar_a$', True), (".//li/p/code/span", '^bar_a$', True),
(".//li/a/span", '^Fig.9 should be Fig.1$', True), (".//li/p/a/span", '^Fig.9 should be Fig.1$', True),
(".//li/code/span", '^Sect.{number}$', True), (".//li/p/code/span", '^Sect.{number}$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True), "span[@class='caption-number']", '^Fig. 2 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 3 $', True), "span[@class='caption-number']", '^Fig. 3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 4 $', True), "span[@class='caption-number']", '^Fig. 4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1 $', True), '^Table 1 $', True),
@ -643,11 +683,11 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 4 $', True), "span[@class='caption-number']", '^Listing 4 $', True),
], ],
'bar.html': [ 'bar.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 5 $', True), "span[@class='caption-number']", '^Fig. 5 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 7 $', True), "span[@class='caption-number']", '^Fig. 7 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 8 $', True), "span[@class='caption-number']", '^Fig. 8 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 5 $', True), '^Table 5 $', True),
@ -663,7 +703,7 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 8 $', True), "span[@class='caption-number']", '^Listing 8 $', True),
], ],
'baz.html': [ 'baz.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 6 $', True), "span[@class='caption-number']", '^Fig. 6 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 6 $', True), '^Table 6 $', True),
@ -671,6 +711,8 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 6 $', True), "span[@class='caption-number']", '^Listing 6 $', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx( @pytest.mark.sphinx(
'html', testroot='numfig', 'html', testroot='numfig',
srcdir='test_numfig_without_numbered_toctree', srcdir='test_numfig_without_numbered_toctree',
@ -699,9 +741,9 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True), "span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1 $', True), '^Table 1 $', True),
@ -711,25 +753,25 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True), "span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True), "span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True), "span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True), '^Table 1.1 $', True),
@ -749,11 +791,11 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 1.4 $', True), "span[@class='caption-number']", '^Listing 1.4 $', True),
], ],
'bar.html': [ 'bar.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True), "span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True), "span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.4 $', True), "span[@class='caption-number']", '^Fig. 2.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.1 $', True), '^Table 2.1 $', True),
@ -769,7 +811,7 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.4 $', True), "span[@class='caption-number']", '^Listing 2.4 $', True),
], ],
'baz.html': [ 'baz.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2 $', True), "span[@class='caption-number']", '^Fig. 2.2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.2 $', True), '^Table 2.2 $', True),
@ -777,6 +819,8 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.2 $', True), "span[@class='caption-number']", '^Listing 2.2 $', True),
], ],
})) }))
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True}) @pytest.mark.sphinx('html', testroot='numfig', confoverrides={'numfig': True})
@pytest.mark.test_params(shared_result='test_build_html_numfig_on') @pytest.mark.test_params(shared_result='test_build_html_numfig_on')
def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect): def test_numfig_with_numbered_toctree(app, cached_etree_parse, fname, expect):
@ -802,9 +846,9 @@ def test_numfig_with_prefix_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1 $', True), "span[@class='caption-number']", '^Figure:1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2 $', True), "span[@class='caption-number']", '^Figure:2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Tab_1 $', True), '^Tab_1 $', True),
@ -814,25 +858,25 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-1 $', True), "span[@class='caption-number']", '^Code-1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Code-2 $', True), "span[@class='caption-number']", '^Code-2 $', True),
(".//li/a/span", '^Figure:1$', True), (".//li/p/a/span", '^Figure:1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Tab_1$', True), (".//li/p/a/span", '^Tab_1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Code-1$', True), (".//li/p/a/span", '^Code-1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^SECTION-1$', True), (".//li/p/a/span", '^SECTION-1$', True),
(".//li/a/span", '^SECTION-2.1$', True), (".//li/p/a/span", '^SECTION-2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.1 $', True), "span[@class='caption-number']", '^Figure:1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.2 $', True), "span[@class='caption-number']", '^Figure:1.2 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.3 $', True), "span[@class='caption-number']", '^Figure:1.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.4 $', True), "span[@class='caption-number']", '^Figure:1.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Tab_1.1 $', True), '^Tab_1.1 $', True),
@ -852,11 +896,11 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-1.4 $', True), "span[@class='caption-number']", '^Code-1.4 $', True),
], ],
'bar.html': [ 'bar.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.1 $', True), "span[@class='caption-number']", '^Figure:2.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.3 $', True), "span[@class='caption-number']", '^Figure:2.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.4 $', True), "span[@class='caption-number']", '^Figure:2.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Tab_2.1 $', True), '^Tab_2.1 $', True),
@ -872,7 +916,7 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-2.4 $', True), "span[@class='caption-number']", '^Code-2.4 $', True),
], ],
'baz.html': [ 'baz.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.2 $', True), "span[@class='caption-number']", '^Figure:2.2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Tab_2.2 $', True), '^Tab_2.2 $', True),
@ -880,8 +924,10 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-2.2 $', True), "span[@class='caption-number']", '^Code-2.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True, reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig',
confoverrides={'numfig': True,
'numfig_format': {'figure': 'Figure:%s', 'numfig_format': {'figure': 'Figure:%s',
'table': 'Tab_%s', 'table': 'Tab_%s',
'code-block': 'Code-%s', 'code-block': 'Code-%s',
@ -892,8 +938,8 @@ def test_numfig_with_prefix(app, cached_etree_parse, fname, expect):
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.sphinx('html', testroot='numfig',
'numfig': True, 'numfig_secnum_depth': 2}) confoverrides={'numfig': True, 'numfig_secnum_depth': 2})
@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') @pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2')
def test_numfig_with_secnum_depth_warn(app, warning): def test_numfig_with_secnum_depth_warn(app, warning):
app.build() app.build()
@ -906,9 +952,9 @@ def test_numfig_with_secnum_depth_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True), "span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1 $', True), '^Table 1 $', True),
@ -918,25 +964,25 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.1.2$', True), (".//li/p/a/span", '^Figure2.1.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.1.2$', True), (".//li/p/a/span", '^Table:2.1.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.1.2$', True), (".//li/p/a/span", '^Code-2.1.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.2 $', True), "span[@class='caption-number']", '^Fig. 1.1.2 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2.1 $', True), "span[@class='caption-number']", '^Fig. 1.2.1 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True), '^Table 1.1 $', True),
@ -956,11 +1002,11 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 1.2.1 $', True), "span[@class='caption-number']", '^Listing 1.2.1 $', True),
], ],
'bar.html': [ 'bar.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.1 $', True), "span[@class='caption-number']", '^Fig. 2.1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.3 $', True), "span[@class='caption-number']", '^Fig. 2.1.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2.1 $', True), "span[@class='caption-number']", '^Fig. 2.2.1 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.1.1 $', True), '^Table 2.1.1 $', True),
@ -976,7 +1022,7 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.2.1 $', True), "span[@class='caption-number']", '^Listing 2.2.1 $', True),
], ],
'baz.html': [ 'baz.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.2 $', True), "span[@class='caption-number']", '^Fig. 2.1.2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.1.2 $', True), '^Table 2.1.2 $', True),
@ -984,8 +1030,11 @@ def test_numfig_with_secnum_depth_warn(app, warning):
"span[@class='caption-number']", '^Listing 2.1.2 $', True), "span[@class='caption-number']", '^Listing 2.1.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('html', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True, 'numfig_secnum_depth': 2}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='numfig',
confoverrides={'numfig': True,
'numfig_secnum_depth': 2})
@pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2') @pytest.mark.test_params(shared_result='test_build_html_numfig_depth_2')
def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect): def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
app.build() app.build()
@ -994,9 +1043,9 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True), "span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1 $', True), '^Table 1 $', True),
@ -1006,23 +1055,23 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 1 $', True), "span[@class='caption-number']", '^Listing 1 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2 $', True), "span[@class='caption-number']", '^Listing 2 $', True),
(".//li/a/span", '^Fig. 1$', True), (".//li/p/a/span", '^Fig. 1$', True),
(".//li/a/span", '^Figure2.2$', True), (".//li/p/a/span", '^Figure2.2$', True),
(".//li/a/span", '^Table 1$', True), (".//li/p/a/span", '^Table 1$', True),
(".//li/a/span", '^Table:2.2$', True), (".//li/p/a/span", '^Table:2.2$', True),
(".//li/a/span", '^Listing 1$', True), (".//li/p/a/span", '^Listing 1$', True),
(".//li/a/span", '^Code-2.2$', True), (".//li/p/a/span", '^Code-2.2$', True),
(".//li/a/span", '^Section.1$', True), (".//li/p/a/span", '^Section.1$', True),
(".//li/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True), "span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True), "span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True), "span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 1.1 $', True), '^Table 1.1 $', True),
@ -1040,11 +1089,11 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 1.3 $', True), "span[@class='caption-number']", '^Listing 1.3 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 1.4 $', True), "span[@class='caption-number']", '^Listing 1.4 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True), "span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True), "span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.4 $', True), "span[@class='caption-number']", '^Fig. 2.4 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.1 $', True), '^Table 2.1 $', True),
@ -1058,7 +1107,7 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 2.3 $', True), "span[@class='caption-number']", '^Listing 2.3 $', True),
(".//div[@class='code-block-caption']/" (".//div[@class='code-block-caption']/"
"span[@class='caption-number']", '^Listing 2.4 $', True), "span[@class='caption-number']", '^Listing 2.4 $', True),
(".//div[@class='figure']/p[@class='caption']/" (".//div[@class='figure align-center']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.2 $', True), "span[@class='caption-number']", '^Fig. 2.2 $', True),
(".//table/caption/span[@class='caption-number']", (".//table/caption/span[@class='caption-number']",
'^Table 2.2 $', True), '^Table 2.2 $', True),
@ -1066,8 +1115,9 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
"span[@class='caption-number']", '^Listing 2.2 $', True), "span[@class='caption-number']", '^Listing 2.2 $', True),
], ],
})) }))
@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'numfig': True}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('singlehtml', testroot='numfig', confoverrides={'numfig': True})
@pytest.mark.test_params(shared_result='test_build_html_numfig_on') @pytest.mark.test_params(shared_result='test_build_html_numfig_on')
def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect): def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect):
app.build() app.build()
@ -1076,25 +1126,25 @@ def test_numfig_with_singlehtml(app, cached_etree_parse, fname, expect):
@pytest.mark.parametrize("fname,expect", flat_dict({ @pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [ 'index.html': [
(".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", (".//div[@class='figure align-center']/p[@class='caption']"
"Fig. 1", True), "/span[@class='caption-number']", "Fig. 1", True),
(".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", (".//div[@class='figure align-center']/p[@class='caption']"
"Fig. 2", True), "/span[@class='caption-number']", "Fig. 2", True),
(".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", (".//div[@class='figure align-center']/p[@class='caption']"
"Fig. 3", True), "/span[@class='caption-number']", "Fig. 3", True),
(".//div//span[@class='caption-number']", "No.1 ", True), (".//div//span[@class='caption-number']", "No.1 ", True),
(".//div//span[@class='caption-number']", "No.2 ", True), (".//div//span[@class='caption-number']", "No.2 ", True),
(".//li/a/span", 'Fig. 1', True), (".//li/p/a/span", 'Fig. 1', True),
(".//li/a/span", 'Fig. 2', True), (".//li/p/a/span", 'Fig. 2', True),
(".//li/a/span", 'Fig. 3', True), (".//li/p/a/span", 'Fig. 3', True),
(".//li/a/span", 'No.1', True), (".//li/p/a/span", 'No.1', True),
(".//li/a/span", 'No.2', True), (".//li/p/a/span", 'No.2', True),
], ],
})) }))
@pytest.mark.sphinx( @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'html', testroot='add_enumerable_node', reason='docutils-0.13 or above is required')
srcdir='test_enumerable_node', @pytest.mark.sphinx('html', testroot='add_enumerable_node',
) srcdir='test_enumerable_node')
def test_enumerable_node(app, cached_etree_parse, fname, expect): def test_enumerable_node(app, cached_etree_parse, fname, expect):
app.build() app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)

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