Merge branch 'master' into simplify_conf.py

This commit is contained in:
Takeshi KOMIYA
2019-02-16 21:25:21 +09:00
committed by GitHub
103 changed files with 1412 additions and 1414 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'

29
CHANGES
View File

@@ -23,6 +23,7 @@ Dependencies
- sphinxcontrib.applehelp - sphinxcontrib.applehelp
- sphinxcontrib.devhelp - sphinxcontrib.devhelp
- sphinxcontrib.htmlhelp
- sphinxcontrib.jsmath - sphinxcontrib.jsmath
- sphinxcontrib.qthelp - sphinxcontrib.qthelp
@@ -66,6 +67,8 @@ Incompatible changes
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 * 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
---------- ----------
@@ -76,6 +79,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
@@ -109,6 +116,11 @@ 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()``
@@ -120,10 +132,13 @@ Deprecated
* ``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()``
@@ -155,6 +170,10 @@ 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
* #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)
@@ -174,10 +193,17 @@ 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
---------- ----------
@@ -226,6 +252,7 @@ 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)
* #6046: LaTeX: ``TypeError`` is raised when invalid latex_elements given
Testing Testing
-------- --------

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

@@ -295,6 +295,11 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinx.builders.singlehtml.SingleFileHTMLBuilder`` - ``sphinx.builders.singlehtml.SingleFileHTMLBuilder``
* - ``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 +375,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
@@ -420,6 +450,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 +475,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.

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

View File

@@ -376,12 +376,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 +390,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

@@ -143,6 +143,11 @@ also use this new config value:
The new files will be placed in the directories specified in the The new files will be placed in the directories specified in the
``:toctree:`` options of the directives. ``:toctree:`` options of the directives.
.. 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,7 @@ install_requires = [
'sphinxcontrib-applehelp', 'sphinxcontrib-applehelp',
'sphinxcontrib-devhelp', 'sphinxcontrib-devhelp',
'sphinxcontrib-jsmath', 'sphinxcontrib-jsmath',
'sphinxcontrib-htmlhelp',
'sphinxcontrib-qthelp', 'sphinxcontrib-qthelp',
'Jinja2>=2.3', 'Jinja2>=2.3',
'Pygments>=2.0', 'Pygments>=2.0',

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,7 @@ builtin_extensions = (
# 1st party extensions # 1st party extensions
'sphinxcontrib.applehelp', 'sphinxcontrib.applehelp',
'sphinxcontrib.devhelp', 'sphinxcontrib.devhelp',
'sphinxcontrib.htmlhelp',
'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

@@ -18,7 +18,6 @@ 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
@@ -58,7 +57,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
@@ -242,8 +241,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 +282,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 +366,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 +550,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(
@@ -1440,9 +1428,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). RemovedInSphinx40Warning)
# 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)
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,33 +250,32 @@ 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:
tocdepth = toctrees[0].get('maxdepth') tocdepth = toctrees[0].get('maxdepth')
else:
tocdepth = None
else: else:
tocdepth = None tocdepth = None
else: doctree = self.assemble_doctree(
tocdepth = None docname, toctree_only,
doctree = self.assemble_doctree( appendices=((docclass != 'howto') and self.config.latex_appendices or []))
docname, toctree_only, doctree['tocdepth'] = tocdepth
appendices=((docclass != 'howto') and self.config.latex_appendices or [])) self.apply_transforms(doctree)
doctree['tocdepth'] = tocdepth self.post_process_images(doctree)
self.apply_transforms(doctree) self.update_doc_context(title, author)
self.post_process_images(doctree)
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)
docsettings.docname = docname docsettings.docname = docname
docsettings.docclass = docclass docsettings.docclass = docclass
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,28 +113,27 @@ 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(
defaults=self.env.settings, defaults=self.env.settings,
components=(docwriter,), components=(docwriter,),
read_config_files=True).get_default_values() # type: Any read_config_files=True).get_default_values() # type: Any
settings.author = author settings.author = author
settings.title = title settings.title = title
settings.texinfo_filename = targetname[:-5] + '.info' settings.texinfo_filename = targetname[:-5] + '.info'
settings.texinfo_elements = self.config.texinfo_elements settings.texinfo_elements = self.config.texinfo_elements
settings.texinfo_dir_entry = direntry or '' settings.texinfo_dir_entry = direntry or ''
settings.texinfo_dir_category = category or '' settings.texinfo_dir_category = category or ''
settings.texinfo_dir_description = description or '' settings.texinfo_dir_description = description or ''
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'])

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

@@ -48,7 +48,7 @@ class _ModuleWrapper(object):
def __getattr__(self, name): def __getattr__(self, name):
# type: (str) -> Any # type: (str) -> Any
if name in self._objects: if name in self._objects:
warnings.warn("%s.%s is now deprecated. Please refer CHANGES to grasp" warnings.warn("%s.%s is now deprecated. Please refer CHANGES to grasp "
"the changes of Sphinx API." % (self._modname, name), "the changes of Sphinx API." % (self._modname, name),
self._warning, stacklevel=3) self._warning, stacklevel=3)
return self._objects[name] return self._objects[name]

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(',')]

View File

@@ -32,13 +32,18 @@ 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):
# subclassing MockObject superclass = args[1][-1].__class__
return type(args[0], (_MockObject,), args[2], **kwargs) # type: ignore if superclass is cls:
else: # subclassing MockObject
return super(_MockObject, cls).__new__(cls) return _make_subclass(args[0], superclass.__display_name__,
superclass=superclass, attributes=args[2])
return super(_MockObject, cls).__new__(cls)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None # type: (Any, Any) -> None
@@ -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,7 +286,8 @@ class Autosummary(SphinxDirective):
display_name = name.split('.')[-1] display_name = name.split('.')[-1]
try: try:
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes) with mock(self.config.autosummary_mock_imports):
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)
items.append((name, '', '', name)) items.append((name, '', '', name))
@@ -702,10 +703,11 @@ def process_generate_options(app):
'But your source_suffix does not contain .rst. Skipped.')) 'But your source_suffix does not contain .rst. Skipped.'))
return return
generate_autosummary_docs(genfiles, builder=app.builder, with mock(app.config.autosummary_mock_imports):
warn=logger.warning, info=logger.info, generate_autosummary_docs(genfiles, builder=app.builder,
suffix=suffix, base_path=app.srcdir, warn=logger.warning, info=logger.info,
app=app) suffix=suffix, base_path=app.srcdir,
app=app)
def setup(app): def setup(app):
@@ -729,4 +731,7 @@ def setup(app):
app.connect('doctree-read', process_autosummary_toc) app.connect('doctree-read', process_autosummary_toc)
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_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

@@ -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,

View File

@@ -9,13 +9,16 @@
""" """
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.deprecation import RemovedInSphinx40Warning
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
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
@@ -179,6 +182,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 +247,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 +360,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 +393,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 +443,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 +513,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 +559,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

@@ -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

@@ -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:

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

@@ -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,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

@@ -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
assert translator_class.__name__ == 'HTMLTranslator' if docutils.__version_info__ < (0, 13):
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

@@ -1345,7 +1345,7 @@ def test_autofunction_for_callable(app):
@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 +1382,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 +1517,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

@@ -60,7 +60,7 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
"buildername", "buildername",
[ [
# note: no 'html' - if it's ok with dirhtml it's ok with html # note: no 'html' - if it's ok with dirhtml it's ok with html
'dirhtml', 'singlehtml', 'pickle', 'json', 'text', 'htmlhelp', 'dirhtml', 'singlehtml', 'pickle', 'json', 'text',
'changes', 'xml', 'pseudoxml', 'linkcheck', 'changes', 'xml', 'pseudoxml', 'linkcheck',
], ],
) )

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):
@@ -350,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,20 +924,22 @@ 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')
'numfig_format': {'figure': 'Figure:%s', @pytest.mark.sphinx('html', testroot='numfig',
'table': 'Tab_%s', confoverrides={'numfig': True,
'code-block': 'Code-%s', 'numfig_format': {'figure': 'Figure:%s',
'section': 'SECTION-%s'}}) 'table': 'Tab_%s',
'code-block': 'Code-%s',
'section': 'SECTION-%s'}})
@pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn') @pytest.mark.test_params(shared_result='test_build_html_numfig_format_warn')
def test_numfig_with_prefix(app, cached_etree_parse, fname, expect): def test_numfig_with_prefix(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)
@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)

View File

@@ -1,367 +0,0 @@
"""
test_build_html5
~~~~~~~~~~~~~~~~
Test the HTML5 writer and check output against XPath.
This code is digest to reduce test running time.
Complete test code is here:
https://github.com/sphinx-doc/sphinx/pull/2805/files
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
from hashlib import md5
import pytest
from html5lib import HTMLParser
from test_build_html import flat_dict, tail_check, check_xpath
from sphinx.util.docutils import is_html5_writer_available
etree_cache = {}
@pytest.mark.skipif(not is_html5_writer_available(), reason='HTML5 writer is not available')
@pytest.fixture(scope='module')
def cached_etree_parse():
def parse(fname):
if fname in etree_cache:
return etree_cache[fname]
with (fname).open('rb') as fp:
etree = HTMLParser(namespaceHTMLElements=False).parse(fp)
etree_cache.clear()
etree_cache[fname] = etree
return etree
yield parse
etree_cache.clear()
@pytest.mark.skipif(not is_html5_writer_available(), reason='HTML5 writer is not available')
@pytest.mark.parametrize("fname,expect", flat_dict({
'images.html': [
(".//img[@src='_images/img.png']", ''),
(".//img[@src='_images/img1.png']", ''),
(".//img[@src='_images/simg.png']", ''),
(".//img[@src='_images/svgimg.svg']", ''),
(".//a[@href='_sources/images.txt']", ''),
],
'subdir/images.html': [
(".//img[@src='../_images/img1.png']", ''),
(".//img[@src='../_images/rimg.png']", ''),
],
'subdir/includes.html': [
(".//a[@class='reference download internal']", ''),
(".//img[@src='../_images/img.png']", ''),
(".//p", 'This is an include file.'),
(".//pre/span", 'line 1'),
(".//pre/span", 'line 2'),
],
'includes.html': [
(".//pre", 'Max Strauß'),
(".//a[@class='reference download internal']", ''),
(".//pre/span", '"quotes"'),
(".//pre/span", "'included'"),
(".//pre/span[@class='s2']", 'üöä'),
(".//div[@class='inc-pyobj1 highlight-text notranslate']//pre",
r'^class Foo:\n pass\n\s*$'),
(".//div[@class='inc-pyobj2 highlight-text notranslate']//pre",
r'^ def baz\(\):\n pass\n\s*$'),
(".//div[@class='inc-lines highlight-text notranslate']//pre",
r'^class Foo:\n pass\nclass Bar:\n$'),
(".//div[@class='inc-startend highlight-text notranslate']//pre",
'^foo = "Including Unicode characters: üöä"\\n$'),
(".//div[@class='inc-preappend highlight-text notranslate']//pre",
r'(?m)^START CODE$'),
(".//div[@class='inc-pyobj-dedent highlight-python notranslate']//span",
r'def'),
(".//div[@class='inc-tab3 highlight-text notranslate']//pre",
r'-| |-'),
(".//div[@class='inc-tab8 highlight-python notranslate']//pre/span",
r'-| |-'),
],
'autodoc.html': [
(".//dt[@id='autodoc_target.Class']", ''),
(".//dt[@id='autodoc_target.function']/em", r'\*\*kwds'),
(".//dd/p", r'Return spam\.'),
],
'extapi.html': [
(".//strong", 'from class: Bar'),
],
'markup.html': [
(".//title", 'set by title directive'),
(".//p/em", 'Section author: Georg Brandl'),
(".//p/em", 'Module author: Georg Brandl'),
# created by the meta directive
(".//meta[@name='author'][@content='Me']", ''),
(".//meta[@name='keywords'][@content='docs, sphinx']", ''),
# a label created by ``.. _label:``
(".//div[@id='label']", ''),
# code with standard code blocks
(".//pre", '^some code$'),
# an option list
(".//span[@class='option']", '--help'),
# admonitions
(".//p[@class='admonition-title']", 'My Admonition'),
(".//div[@class='admonition note']/p", 'Note text.'),
(".//div[@class='admonition warning']/p", 'Warning text.'),
# inline markup
(".//li/p/strong", r'^command\\n$'),
(".//li/p/strong", r'^program\\n$'),
(".//li/p/em", r'^dfn\\n$'),
(".//li/p/kbd", r'^kbd\\n$'),
(".//li/p/span", 'File \N{TRIANGULAR BULLET} Close'),
(".//li/p/code/span[@class='pre']", '^a/$'),
(".//li/p/code/em/span[@class='pre']", '^varpart$'),
(".//li/p/code/em/span[@class='pre']", '^i$'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong", 'PEP 8'),
(".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong",
'Python Enhancement Proposal #8'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'RFC 1'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'Request for Comments #1'),
(".//a[@href='objects.html#envvar-HOME']"
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#with']"
"[@class='reference internal']/code/span[@class='pre']", '^with$'),
(".//a[@href='#grammar-token-try-stmt']"
"[@class='reference internal']/code/span", '^statement$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^there$'),
(".//a[@href='subdir/includes.html']"
"[@class='reference internal']/span", 'Including in subdir'),
(".//a[@href='objects.html#cmdoption-python-c']"
"[@class='reference internal']/code/span[@class='pre']", '-c'),
# abbreviations
(".//abbr[@title='abbreviation']", '^abbr$'),
# version stuff
(".//div[@class='versionadded']/p/span", 'New in version 0.6: '),
(".//div[@class='versionadded']/p/span",
tail_check('First paragraph of versionadded')),
(".//div[@class='versionchanged']/p/span",
tail_check('First paragraph of versionchanged')),
(".//div[@class='versionchanged']/p",
'Second paragraph of versionchanged'),
# footnote reference
(".//a[@class='footnote-reference brackets']", r'1'),
# created by reference lookup
(".//a[@href='index.html#ref1']", ''),
# ``seealso`` directive
(".//div/p[@class='admonition-title']", 'See also'),
# a ``hlist`` directive
(".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'),
# a ``centered`` directive
(".//p[@class='centered']/strong", 'LICENSE'),
# a glossary
(".//dl/dt[@id='term-boson']", 'boson'),
# a production list
(".//pre/strong", 'try_stmt'),
(".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'),
# tests for ``only`` directive
(".//p", 'A global substitution.'),
(".//p", 'In HTML.'),
(".//p", 'In both.'),
(".//p", 'Always present'),
# tests for ``any`` role
(".//a[@href='#with']/span", 'headings'),
(".//a[@href='objects.html#func_without_body']/code/span", 'objects'),
# tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
],
'objects.html': [
(".//dt[@id='mod.Cls.meth1']", ''),
(".//dt[@id='errmod.Error']", ''),
(".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'),
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
(".//dl[@class='userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields
(".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'),
(".//a[@class='reference internal'][@href='#Time']", 'Time'),
(".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'),
# C references
(".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c.Sphinx_DoSomething']", ''),
(".//a[@href='#c.SphinxStruct.member']", ''),
(".//a[@href='#c.SPHINX_USE_PYTHON']", ''),
(".//a[@href='#c.SphinxType']", ''),
(".//a[@href='#c.sphinx_global']", ''),
# test global TOC created by toctree()
(".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']",
'Testing object descriptions'),
(".//li[@class='toctree-l1']/a[@href='markup.html']",
'Testing various markup'),
# test unknown field names
(".//dt[@class='field-odd']", 'Field_name'),
(".//dt[@class='field-even']", 'Field_name all lower'),
(".//dt[@class='field-odd']", 'FIELD_NAME'),
(".//dt[@class='field-even']", 'FIELD_NAME ALL CAPS'),
(".//dt[@class='field-odd']", 'Field_Name'),
(".//dt[@class='field-even']", 'Field_Name All Word Caps'),
(".//dt[@class='field-odd']", 'Field_name'),
(".//dt[@class='field-even']", 'Field_name First word cap'),
(".//dt[@class='field-odd']", 'FIELd_name'),
(".//dt[@class='field-even']", 'FIELd_name PARTial caps'),
# custom sidebar
(".//h4", 'Custom sidebar'),
# docfields
(".//dd[@class='field-odd']/p/strong", '^moo$'),
(".//dd[@class='field-odd']/p/strong", tail_check(r'\(Moo\) .* Moo')),
(".//dd[@class='field-odd']/ul/li/p/strong", '^hour$'),
(".//dd[@class='field-odd']/ul/li/p/em", '^DuplicateType$'),
(".//dd[@class='field-odd']/ul/li/p/em", tail_check(r'.* Some parameter')),
# others
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'perl'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-p']/code/span",
'\\+p'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span",
'--ObjC\\+\\+'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span",
'--plugin.option'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']"
"/code/span",
'create-auth-token'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span",
'arg'),
(".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span",
'hg'),
(".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span",
'commit'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'git'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'commit'),
(".//a[@class='reference internal'][@href='#cmdoption-git-commit-p']/code/span",
'-p'),
],
'index.html': [
(".//meta[@name='hc'][@content='hcval']", ''),
(".//meta[@name='hc_co'][@content='hcval_co']", ''),
(".//dt[@class='label']/span[@class='brackets']", r'Ref1'),
(".//dt[@class='label']", ''),
(".//li[@class='toctree-l1']/a", 'Testing various markup'),
(".//li[@class='toctree-l2']/a", 'Inline markup'),
(".//title", 'Sphinx <Tests>'),
(".//div[@class='footer']", 'Georg Brandl & Team'),
(".//a[@href='http://python.org/']"
"[@class='reference external']", ''),
(".//li/p/a[@href='genindex.html']/span", 'Index'),
(".//li/p/a[@href='py-modindex.html']/span", 'Module Index'),
(".//li/p/a[@href='search.html']/span", 'Search Page'),
# custom sidebar only for contents
(".//h4", 'Contents sidebar'),
# custom JavaScript
(".//script[@src='file://moo.js']", ''),
# URL in contents
(".//a[@class='reference external'][@href='http://sphinx-doc.org/']",
'http://sphinx-doc.org/'),
(".//a[@class='reference external'][@href='http://sphinx-doc.org/latest/']",
'Latest reference'),
# Indirect hyperlink targets across files
(".//a[@href='markup.html#some-label'][@class='reference internal']/span",
'^indirect hyperref$'),
],
'bom.html': [
(".//title", " File with UTF-8 BOM"),
],
'extensions.html': [
(".//a[@href='http://python.org/dev/']", "http://python.org/dev/"),
(".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"),
(".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"),
],
'genindex.html': [
# index entries
(".//a/strong", "Main"),
(".//a/strong", "[1]"),
(".//a/strong", "Other"),
(".//a", "entry"),
(".//li/a", "double"),
],
'footnote.html': [
(".//a[@class='footnote-reference brackets'][@href='#id9'][@id='id1']", r"1"),
(".//a[@class='footnote-reference brackets'][@href='#id10'][@id='id2']", r"2"),
(".//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='#baz-qux'][@id='id5']", r"\[baz_qux\]"),
(".//a[@class='footnote-reference brackets'][@href='#id11'][@id='id6']", r"4"),
(".//a[@class='footnote-reference brackets'][@href='#id12'][@id='id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id1']", r"1"),
(".//a[@class='fn-backref'][@href='#id2']", r"2"),
(".//a[@class='fn-backref'][@href='#id3']", r"3"),
(".//a[@class='fn-backref'][@href='#id4']", r"bar"),
(".//a[@class='fn-backref'][@href='#id5']", r"baz_qux"),
(".//a[@class='fn-backref'][@href='#id6']", r"4"),
(".//a[@class='fn-backref'][@href='#id7']", r"5"),
(".//a[@class='fn-backref'][@href='#id8']", r"6"),
],
'otherext.html': [
(".//h1", "Generated section"),
(".//a[@href='_sources/otherext.foo.txt']", ''),
]
}))
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={
'html_context.hckey_co': 'hcval_co',
'html_experimental_html5_writer': True})
@pytest.mark.test_params(shared_result='test_build_html5_output')
def test_html5_output(app, cached_etree_parse, fname, expect):
app.build()
print(app.outdir / fname)
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
@pytest.mark.sphinx('html', tags=['testtag'], confoverrides={
'html_context.hckey_co': 'hcval_co',
'html_experimental_html5_writer': True})
@pytest.mark.test_params(shared_result='test_build_html_output')
def test_html_download(app):
app.build()
# subdir/includes.html
result = (app.outdir / 'subdir' / 'includes.html').text()
pattern = ('<a class="reference download internal" download="" '
'href="../(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
assert matched
assert (app.outdir / matched.group(1)).exists()
filename = matched.group(1)
# includes.html
result = (app.outdir / 'includes.html').text()
pattern = ('<a class="reference download internal" download="" '
'href="(_downloads/.*/img.png)">')
matched = re.search(pattern, result)
assert matched
assert (app.outdir / matched.group(1)).exists()
assert matched.group(1) == filename
@pytest.mark.sphinx('html', testroot='roles-download',
confoverrides={'html_experimental_html5_writer': True})
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)

View File

@@ -1,63 +0,0 @@
"""
test_build_htmlhelp
~~~~~~~~~~~~~~~~~~~
Test the HTML Help builder and check output against XPath.
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import pytest
from sphinx.builders.htmlhelp import chm_htmlescape
from sphinx.builders.htmlhelp import default_htmlhelp_basename
from sphinx.config import Config
@pytest.mark.sphinx('htmlhelp', testroot='basic')
def test_default_htmlhelp_file_suffix(app, warning):
assert app.builder.out_suffix == '.html'
@pytest.mark.sphinx('htmlhelp', testroot='basic',
confoverrides={'htmlhelp_file_suffix': '.htm'})
def test_htmlhelp_file_suffix(app, warning):
assert app.builder.out_suffix == '.htm'
def test_default_htmlhelp_basename():
config = Config({'project': 'Sphinx Documentation'})
config.init_values()
assert default_htmlhelp_basename(config) == 'sphinxdoc'
@pytest.mark.sphinx('htmlhelp', testroot='build-htmlhelp')
def test_chm(app):
app.build()
# check .hhk file
outname = app.builder.config.htmlhelp_basename
hhk_path = str(app.outdir / outname + '.hhk')
with open(hhk_path, 'rb') as f:
data = f.read()
m = re.search(br'&#[xX][0-9a-fA-F]+;', data)
assert m is None, 'Hex escaping exists in .hhk file: ' + str(m.group(0))
def test_chm_htmlescape():
assert chm_htmlescape('Hello world') == 'Hello world'
assert chm_htmlescape(u'Unicode 文字') == u'Unicode 文字'
assert chm_htmlescape('&#x45') == '&amp;#x45'
assert chm_htmlescape('<Hello> "world"') == '&lt;Hello&gt; &quot;world&quot;'
assert chm_htmlescape('<Hello> "world"', True) == '&lt;Hello&gt; &quot;world&quot;'
assert chm_htmlescape('<Hello> "world"', False) == '&lt;Hello&gt; "world"'
assert chm_htmlescape("Hello 'world'") == "Hello &#39;world&#39;"
assert chm_htmlescape("Hello 'world'", True) == "Hello &#39;world&#39;"
assert chm_htmlescape("Hello 'world'", False) == "Hello 'world'"

View File

@@ -24,7 +24,10 @@ def test_defaults(app, status, warning):
assert "Anchor 'does-not-exist' not found" in content assert "Anchor 'does-not-exist' not found" in content
# looking for non-existent URL should fail # looking for non-existent URL should fail
assert " Max retries exceeded with url: /doesnotexist" in content assert " Max retries exceeded with url: /doesnotexist" in content
assert len(content.splitlines()) == 3 # images should fail
assert "Not Found for url: http://example.com/image.png" in content
assert "Not Found for url: http://example.com/image2.png" in content
assert len(content.splitlines()) == 5
@pytest.mark.sphinx( @pytest.mark.sphinx(
@@ -32,7 +35,9 @@ def test_defaults(app, status, warning):
confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"], confoverrides={'linkcheck_anchors_ignore': ["^!", "^top$"],
'linkcheck_ignore': [ 'linkcheck_ignore': [
'https://localhost:7777/doesnotexist', 'https://localhost:7777/doesnotexist',
'http://www.sphinx-doc.org/en/1.7/intro.html#'] 'http://www.sphinx-doc.org/en/1.7/intro.html#',
'http://example.com/image.png',
'http://example.com/image2.png']
}) })
def test_anchors_ignored(app, status, warning): def test_anchors_ignored(app, status, warning):
app.builder.build_all() app.builder.build_all()

View File

@@ -10,11 +10,12 @@
import pytest import pytest
@pytest.mark.sphinx('dummy', srcdir="test_builder") @pytest.mark.sphinx('dummy', srcdir="test_builder", freshenv=True)
def test_incremental_reading(app): def test_incremental_reading(app):
# first reading # first reading
updated = app.builder.read() updated = app.builder.read()
assert set(updated) == app.env.found_docs == set(app.env.all_docs) assert set(updated) == app.env.found_docs == set(app.env.all_docs)
assert updated == sorted(updated) # sorted by alphanumeric
# test if exclude_patterns works ok # test if exclude_patterns works ok
assert 'subdir/excluded' not in app.env.found_docs assert 'subdir/excluded' not in app.env.found_docs
@@ -27,29 +28,20 @@ def test_incremental_reading(app):
# second reading # second reading
updated = app.builder.read() updated = app.builder.read()
# "includes" and "images" are in there because they contain references assert set(updated) == set(['index', 'new'])
# to nonexisting downloadable or image files, which are given another
# chance to exist
assert set(updated) == set(['index', 'new', 'includes', 'images'])
assert 'autodoc' not in app.env.all_docs assert 'autodoc' not in app.env.all_docs
assert 'autodoc' not in app.env.found_docs assert 'autodoc' not in app.env.found_docs
@pytest.mark.sphinx('dummy') @pytest.mark.sphinx('dummy', testroot='warnings', freshenv=True)
def test_env_read_docs(app): def test_incremental_reading_for_missing_files(app):
"""By default, docnames are read in alphanumeric order""" # first reading
def on_env_read_docs_1(app, env, docnames): updated = app.builder.read()
pass assert set(updated) == app.env.found_docs == set(app.env.all_docs)
app.connect('env-before-read-docs', on_env_read_docs_1) # second reading
updated = app.builder.read()
read_docnames = app.builder.read() # "index" is listed up to updated because it contains references
assert len(read_docnames) > 2 and read_docnames == sorted(read_docnames) # to nonexisting downloadable or image files
assert set(updated) == set(['index'])
def on_env_read_docs_2(app, env, docnames):
docnames.remove('images')
app.connect('env-before-read-docs', on_env_read_docs_2)
read_docnames = app.builder.read()
assert len(read_docnames) == 2

View File

@@ -17,6 +17,7 @@ import sphinx.domains.cpp as cppDomain
from sphinx import addnodes from sphinx import addnodes
from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError
from sphinx.domains.cpp import Symbol, _max_id, _id_prefix from sphinx.domains.cpp import Symbol, _max_id, _id_prefix
from sphinx.util import docutils
def parse(name, string): def parse(name, string):
@@ -734,12 +735,14 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning):
# TODO: properly check for the warnings we expect # TODO: properly check for the warnings we expect
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True})
def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning): def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning):
app.builder.build_all() app.builder.build_all()
def check(spec, text, file): def check(spec, text, file):
pattern = '<li>%s<a .*?><code .*?><span .*?>%s</span></code></a></li>' % spec pattern = '<li><p>%s<a .*?><code .*?><span .*?>%s</span></code></a></p></li>' % spec
res = re.search(pattern, text) res = re.search(pattern, text)
if not res: if not res:
print("Pattern\n\t%s\nnot found in %s" % (pattern, file)) print("Pattern\n\t%s\nnot found in %s" % (pattern, file))
@@ -775,13 +778,14 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war
check(s, t, f) check(s, t, f)
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={ @pytest.mark.skipif(docutils.__version_info__ < (0, 13),
'add_function_parentheses': False}) reason='docutils-0.13 or above is required')
@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': False})
def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning):
app.builder.build_all() app.builder.build_all()
def check(spec, text, file): def check(spec, text, file):
pattern = '<li>%s<a .*?><code .*?><span .*?>%s</span></code></a></li>' % spec pattern = '<li><p>%s<a .*?><code .*?><span .*?>%s</span></code></a></p></li>' % spec
res = re.search(pattern, text) res = re.search(pattern, text)
if not res: if not res:
print("Pattern\n\t%s\nnot found in %s" % (pattern, file)) print("Pattern\n\t%s\nnot found in %s" % (pattern, file))

View File

@@ -17,8 +17,6 @@ from sphinx.testing.comparer import PathComparer
@pytest.mark.sphinx('dummy') @pytest.mark.sphinx('dummy')
def test_images(app): def test_images(app):
app.build() app.build()
assert ('image file not readable: foo.png'
in app._warning.getvalue())
tree = app.env.get_doctree('images') tree = app.env.get_doctree('images')
htmlbuilder = StandaloneHTMLBuilder(app) htmlbuilder = StandaloneHTMLBuilder(app)

View File

@@ -49,7 +49,6 @@ def apidoc_params(request):
def test_simple(make_app, apidoc): def test_simple(make_app, apidoc):
outdir = apidoc.outdir outdir = apidoc.outdir
assert (outdir / 'conf.py').isfile() assert (outdir / 'conf.py').isfile()
assert (outdir / 'autodoc_fodder.rst').isfile()
assert (outdir / 'index.rst').isfile() assert (outdir / 'index.rst').isfile()
app = make_app('text', srcdir=outdir) app = make_app('text', srcdir=outdir)
@@ -273,7 +272,6 @@ def test_excludes_module_should_not_be_skipped(apidoc):
def test_multibyte_parameters(make_app, apidoc): def test_multibyte_parameters(make_app, apidoc):
outdir = apidoc.outdir outdir = apidoc.outdir
assert (outdir / 'conf.py').isfile() assert (outdir / 'conf.py').isfile()
assert (outdir / 'autodoc_fodder.rst').isfile()
assert (outdir / 'index.rst').isfile() assert (outdir / 'index.rst').isfile()
conf_py = (outdir / 'conf.py').text() conf_py = (outdir / 'conf.py').text()

View File

@@ -16,6 +16,21 @@ import pytest
from sphinx.ext.autodoc.importer import _MockModule, _MockObject, mock from sphinx.ext.autodoc.importer import _MockModule, _MockObject, mock
def test_MockModule():
mock = _MockModule('mocked_module', None)
assert isinstance(mock.some_attr, _MockObject)
assert isinstance(mock.some_method, _MockObject)
assert isinstance(mock.attr1.attr2, _MockObject)
assert isinstance(mock.attr1.attr2.meth(), _MockObject)
assert repr(mock.some_attr) == 'mocked_module.some_attr'
assert repr(mock.some_method) == 'mocked_module.some_method'
assert repr(mock.attr1.attr2) == 'mocked_module.attr1.attr2'
assert repr(mock.attr1.attr2.meth) == 'mocked_module.attr1.attr2.meth'
assert repr(mock) == 'mocked_module'
def test_MockObject(): def test_MockObject():
mock = _MockObject() mock = _MockObject()
assert isinstance(mock.some_attr, _MockObject) assert isinstance(mock.some_attr, _MockObject)
@@ -25,6 +40,7 @@ def test_MockObject():
class SubClass(mock.SomeClass): class SubClass(mock.SomeClass):
"""docstring of SubClass""" """docstring of SubClass"""
def method(self): def method(self):
return "string" return "string"

View File

@@ -12,37 +12,82 @@ import re
import pytest import pytest
from sphinx.util import docutils
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-autosectionlabel') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel')
def test_autosectionlabel_html(app, status, warning): def test_autosectionlabel_html(app, status, warning, skipped_labels=False):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = ('<li><a class="reference internal" href="#introduce-of-sphinx">' html = ('<li><p><a class="reference internal" href="#introduce-of-sphinx">'
'<span class=".*?">Introduce of Sphinx</span></a></li>') '<span class=".*?">Introduce of Sphinx</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#installation">' html = ('<li><p><a class="reference internal" href="#installation">'
'<span class="std std-ref">Installation</span></a></li>') '<span class="std std-ref">Installation</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#for-windows-users">' html = ('<li><p><a class="reference internal" href="#for-windows-users">'
'<span class="std std-ref">For Windows users</span></a></li>') '<span class="std std-ref">For Windows users</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
html = ('<li><a class="reference internal" href="#for-unix-users">' html = ('<li><p><a class="reference internal" href="#for-unix-users">'
'<span class="std std-ref">For UNIX users</span></a></li>') '<span class="std std-ref">For UNIX users</span></a></p></li>')
assert re.search(html, content, re.S)
html = ('<li><p><a class="reference internal" href="#linux">'
'<span class="std std-ref">Linux</span></a></p></li>')
assert re.search(html, content, re.S)
html = ('<li><p><a class="reference internal" href="#freebsd">'
'<span class="std std-ref">FreeBSD</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# for smart_quotes (refs: #4027) # for smart_quotes (refs: #4027)
html = ('<li><a class="reference internal" ' html = ('<li><p><a class="reference internal" '
'href="#this-one-s-got-an-apostrophe">' 'href="#this-one-s-got-an-apostrophe">'
'<span class="std std-ref">This ones got an apostrophe' '<span class="std std-ref">This ones got an apostrophe'
'</span></a></li>') '</span></a></p></li>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
# Re-use test definition from above, just change the test root directory # Re-use test definition from above, just change the test root directory
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document') @pytest.mark.sphinx('html', testroot='ext-autosectionlabel-prefix-document')
def test_autosectionlabel_prefix_document_html(app, status, warning): def test_autosectionlabel_prefix_document_html(app, status, warning):
return test_autosectionlabel_html(app, status, warning) test_autosectionlabel_html(app, status, warning)
@pytest.mark.skipif(docutils.__version_info__ < (0, 13),
reason='docutils-0.13 or above is required')
@pytest.mark.sphinx('html', testroot='ext-autosectionlabel',
confoverrides={'autosectionlabel_maxdepth': 3})
def test_autosectionlabel_maxdepth(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').text()
# depth: 1
html = ('<li><p><a class="reference internal" href="#test-ext-autosectionlabel">'
'<span class=".*?">test-ext-autosectionlabel</span></a></p></li>')
assert re.search(html, content, re.S)
# depth: 2
html = ('<li><p><a class="reference internal" href="#installation">'
'<span class="std std-ref">Installation</span></a></p></li>')
assert re.search(html, content, re.S)
# depth: 3
html = ('<li><p><a class="reference internal" href="#for-windows-users">'
'<span class="std std-ref">For Windows users</span></a></p></li>')
assert re.search(html, content, re.S)
# depth: 4
html = '<li><p><span class="xref std std-ref">Linux</span></p></li>'
assert re.search(html, content, re.S)
assert 'WARNING: undefined label: linux' in warning.getvalue()

View File

@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import sys
from io import StringIO from io import StringIO
import pytest import pytest
@@ -203,7 +204,7 @@ def test_autosummary_latex_table_colspec(app, status, warning):
result = (app.outdir / 'python.tex').text(encoding='utf8') result = (app.outdir / 'python.tex').text(encoding='utf8')
print(status.getvalue()) print(status.getvalue())
print(warning.getvalue()) print(warning.getvalue())
assert r'\begin{longtable}{\X{1}{2}\X{1}{2}}' in result assert r'\begin{longtable}[c]{\X{1}{2}\X{1}{2}}' in result
assert r'p{0.5\linewidth}' not in result assert r'p{0.5\linewidth}' not in result
@@ -229,3 +230,15 @@ def test_import_by_name():
assert obj == sphinx.ext.autosummary.Autosummary.get_items assert obj == sphinx.ext.autosummary.Autosummary.get_items
assert parent is sphinx.ext.autosummary.Autosummary assert parent is sphinx.ext.autosummary.Autosummary
assert modname == 'sphinx.ext.autosummary' assert modname == 'sphinx.ext.autosummary'
@pytest.mark.sphinx('dummy', testroot='ext-autosummary-mock_imports')
def test_autosummary_mock_imports(app, status, warning):
try:
app.build()
assert warning.getvalue() == ''
# generated/foo is generated successfully
assert app.env.get_doctree('generated/foo')
finally:
sys.modules.pop('foo', None) # unload foo module

View File

@@ -15,3 +15,18 @@ import pytest
def test_githubpages(app, status, warning): def test_githubpages(app, status, warning):
app.builder.build_all() app.builder.build_all()
assert (app.outdir / '.nojekyll').exists() assert (app.outdir / '.nojekyll').exists()
assert not (app.outdir / 'CNAME').exists()
@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.github.io'})
def test_no_cname_for_github_io_domain(app, status, warning):
app.builder.build_all()
assert (app.outdir / '.nojekyll').exists()
assert not (app.outdir / 'CNAME').exists()
@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.org'})
def test_cname_for_custom_domain(app, status, warning):
app.builder.build_all()
assert (app.outdir / '.nojekyll').exists()
assert (app.outdir / 'CNAME').text() == 'sphinx-doc.org'

View File

@@ -21,7 +21,7 @@ def test_graphviz_png_html(app, status, warning):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = (r'<div class="figure" .*?>\s*' html = (r'<div class="figure align-center" .*?>\s*'
r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">' r'<div class="graphviz"><img .*?/></div>\s*<p class="caption">'
r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>') r'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
assert re.search(html, content, re.S) assert re.search(html, content, re.S)
@@ -52,7 +52,7 @@ def test_graphviz_svg_html(app, status, warning):
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
html = (r'<div class=\"figure\" .*?>\n' html = (r'<div class=\"figure align-center\" .*?>\n'
r'<div class="graphviz"><object data=\".*\.svg\".*>\n' r'<div class="graphviz"><object data=\".*\.svg\".*>\n'
r'\s*<p class=\"warning\">digraph foo {\n' r'\s*<p class=\"warning\">digraph foo {\n'
r'bar -&gt; baz\n' r'bar -&gt; baz\n'

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