Merge branch '2.0' into 6311_autosummary_confused_by_complex_typehints2

This commit is contained in:
Takeshi KOMIYA 2019-05-13 21:16:43 +09:00 committed by GitHub
commit 59d0f076ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 574 additions and 158 deletions

20
CHANGES
View File

@ -11,6 +11,7 @@ Incompatible changes
API directly API directly
* #6230: The anchor of term in glossary directive is changed if it is consisted * #6230: The anchor of term in glossary directive is changed if it is consisted
by non-ASCII characters by non-ASCII characters
* #4550: html: Centering tables by default using CSS
Deprecated Deprecated
---------- ----------
@ -73,7 +74,8 @@ Features added
* #6180: Support ``--keep-going`` with BuildDoc setup command * #6180: Support ``--keep-going`` with BuildDoc setup command
* ``math`` directive now supports ``:class:`` option * ``math`` directive now supports ``:class:`` option
* todo: ``todo`` directive now supports ``:name:`` option * todo: ``todo`` directive now supports ``:name:`` option
* #6232: Enable CLI override of Makefile variables * Enable override via environment of ``SPHINXOPTS`` and ``SPHINXBUILD`` Makefile
variables (refs: #6232, #6303)
* #6287: autodoc: Unable to document bound instance methods exported as module * #6287: autodoc: Unable to document bound instance methods exported as module
functions functions
* #6289: autodoc: :confval:`autodoc_default_options` now supports * #6289: autodoc: :confval:`autodoc_default_options` now supports
@ -83,8 +85,16 @@ Features added
imported members on autosummary imported members on autosummary
* #6271: ``make clean`` is catastrophically broken if building into '.' * #6271: ``make clean`` is catastrophically broken if building into '.'
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive * #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
* py domain: Add ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options * py domain: Add new options to :rst:dir:`py:method` directive
to :rst:dir:`py:method` directive
- ``:async:``
- ``:classmethod:``
- ``:property:``
- ``:staticmethod:``
* rst domain: Add :rst:dir:`directive:option` directive to describe the option
for directive
* #6306: html: Add a label to search form for accessability purposes
Bugs fixed Bugs fixed
---------- ----------
@ -95,7 +105,11 @@ Bugs fixed
* commented term in glossary directive is wrongly recognized * commented term in glossary directive is wrongly recognized
* #6299: rst domain: rst:directive directive generates waste space * #6299: rst domain: rst:directive directive generates waste space
* #6331: man: invalid output when doctest follows rubric * #6331: man: invalid output when doctest follows rubric
* #6351: "Hyperlink target is not referenced" message is shown even if
referenced
* #6165: autodoc: ``tab_width`` setting of docutils has been ignored
* #6311: autosummary: autosummary table gets confused by complex type hints * #6311: autosummary: autosummary table gets confused by complex type hints
* Generated Makefiles lack a final EOL (refs: #6232)
Testing Testing
-------- --------

View File

@ -31,7 +31,8 @@ This is the current list of contributed extensions in that repository:
- actdiag: embed activity diagrams by using actdiag_ - actdiag: embed activity diagrams by using actdiag_
- adadomain: an extension for Ada support (Sphinx 1.0 needed) - adadomain: an extension for Ada support (Sphinx 1.0 needed)
- ansi: parse ANSI color sequences inside documents - ansi: parse ANSI color sequences inside documents
- argdoc: automatically generate documentation for command-line arguments, descriptions, and help text - argdoc: automatically generate documentation for command-line arguments,
descriptions and help text
- astah: embed diagram by using astah - astah: embed diagram by using astah
- autoanysrc: Gather reST documentation from any source files - autoanysrc: Gather reST documentation from any source files
- autorun: Execute code in a ``runblock`` directive - autorun: Execute code in a ``runblock`` directive
@ -64,7 +65,8 @@ This is the current list of contributed extensions in that repository:
- imgur: embed Imgur images, albums, and metadata in documents - imgur: embed Imgur images, albums, and metadata in documents
- inlinesyntaxhighlight_: inline syntax highlighting - inlinesyntaxhighlight_: inline syntax highlighting
- lassodomain: a domain for documenting Lasso_ source code - lassodomain: a domain for documenting Lasso_ source code
- libreoffice: an extension to include any drawing supported by LibreOffice (e.g. odg, vsd, ...) - libreoffice: an extension to include any drawing supported by LibreOffice
(e.g. odg, vsd, ...)
- lilypond: an extension inserting music scripts from Lilypond_ in PNG format - lilypond: an extension inserting music scripts from Lilypond_ in PNG format
- makedomain_: a domain for `GNU Make`_ - makedomain_: a domain for `GNU Make`_
- matlabdomain: document MATLAB_ code - matlabdomain: document MATLAB_ code
@ -100,8 +102,8 @@ This is the current list of contributed extensions in that repository:
- zopeext: provide an ``autointerface`` directive for using `Zope interfaces`_ - zopeext: provide an ``autointerface`` directive for using `Zope interfaces`_
See the :doc:`extension tutorials <../development/tutorials/index>` on getting started with writing your See the :doc:`extension tutorials <../development/tutorials/index>` on getting
own extensions. started with writing your own extensions.
.. _aafigure: https://launchpad.net/aafigure .. _aafigure: https://launchpad.net/aafigure

View File

@ -97,7 +97,8 @@ extension. These are:
The config is available as ``app.config`` or ``env.config``. The config is available as ``app.config`` or ``env.config``.
To see an example of use of these objects, refer to :doc:`../development/tutorials/index`. To see an example of use of these objects, refer to
:doc:`../development/tutorials/index`.
.. _build-phases: .. _build-phases:

View File

@ -147,5 +147,6 @@ return ``node.children`` from the Directive.
.. seealso:: .. seealso::
`Creating directives <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_ `Creating directives`_ HOWTO of the Docutils documentation
HOWTO of the Docutils documentation
.. _Creating directives: http://docutils.sourceforge.net/docs/howto/rst-directives.html

View File

@ -354,8 +354,8 @@ are in HTML form), these variables are also available:
.. data:: body .. data:: body
A string containing the content of the page in HTML form as produced by the HTML builder, A string containing the content of the page in HTML form as produced by the
before the theme is applied. HTML builder, before the theme is applied.
.. data:: display_toc .. data:: display_toc
@ -382,8 +382,9 @@ are in HTML form), these variables are also available:
.. data:: page_source_suffix .. data:: page_source_suffix
The suffix of the file that was rendered. Since we support a list of :confval:`source_suffix`, The suffix of the file that was rendered. Since we support a list of
this will allow you to properly link to the original source file. :confval:`source_suffix`, this will allow you to properly link to the
original source file.
.. data:: parents .. data:: parents

View File

@ -229,9 +229,13 @@ The following directives are provided for module and class contents:
The ``classmethod`` option and ``staticmethod`` option can be given (with The ``classmethod`` option and ``staticmethod`` option can be given (with
no value) to indicate the method is a class method (or a static method). no value) to indicate the method is a class method (or a static method).
The ``property`` option can be given (with no value) to indicate the method
is a property.
.. versionchanged:: 2.1 .. versionchanged:: 2.1
``:async:``, ``:classmethod:`` and ``:staticmethod:`` options added. ``:async:``, ``:classmethod:``, ``:property:`` and ``:staticmethod:``
options added.
.. rst:directive:: .. py:staticmethod:: name(parameters) .. rst:directive:: .. py:staticmethod:: name(parameters)
@ -1079,15 +1083,16 @@ These roles link to the given declaration types:
.. admonition:: Note on References with Templates Parameters/Arguments .. admonition:: Note on References with Templates Parameters/Arguments
These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must be These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must
taken when referencing a (partial) template specialization, e.g. if the link looks like be taken when referencing a (partial) template specialization, e.g. if the
this: ``:cpp:class:`MyClass<int>```. link looks like this: ``:cpp:class:`MyClass<int>```.
This is interpreted as a link to ``int`` with a title of ``MyClass``. This is interpreted as a link to ``int`` with a title of ``MyClass``.
In this case, escape the opening angle bracket with a backslash, In this case, escape the opening angle bracket with a backslash,
like this: ``:cpp:class:`MyClass\<int>```. like this: ``:cpp:class:`MyClass\<int>```.
When a custom title is not needed it may be useful to use the roles for inline expressions, When a custom title is not needed it may be useful to use the roles for
:rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where angle brackets do not need escaping. inline expressions, :rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where
angle brackets do not need escaping.
Declarations without template parameters and template arguments Declarations without template parameters and template arguments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -1419,6 +1424,43 @@ The reStructuredText domain (name **rst**) provides the following directives:
Bar description. Bar description.
.. rst:directive:: .. rst:directive:option:: name
Describes an option for reST directive. The *name* can be a single option
name or option name with arguments which separated with colon (``:``).
For example::
.. rst:directive:: toctree
.. rst:directive:option:: caption: caption of ToC
.. rst:directive:option:: glob
will be rendered as:
.. rst:directive:: toctree
:noindex:
.. rst:directive:option:: caption: caption of ToC
.. rst:directive:option:: glob
.. rubric:: options
.. rst:directive:option:: type
:type: description for the option of directive
Describe the type of option value.
For example::
.. rst:directive:: toctree
.. rst:directive:option:: maxdepth
:type: integer or no value
.. versionadded:: 2.1
.. rst:directive:: .. rst:role:: name .. rst:directive:: .. rst:role:: name
Describes a reST role. For example:: Describes a reST role. For example::

View File

@ -55,6 +55,11 @@ strict_optional = False
filterwarnings = filterwarnings =
all all
ignore::DeprecationWarning:docutils.io ignore::DeprecationWarning:docutils.io
markers =
sphinx
apidoc
setup_command
test_params
[coverage:run] [coverage:run]
branch = True branch = True

View File

@ -587,22 +587,28 @@ class PyMethod(PyObject):
option_spec.update({ option_spec.update({
'async': directives.flag, 'async': directives.flag,
'classmethod': directives.flag, 'classmethod': directives.flag,
'property': directives.flag,
'staticmethod': directives.flag, 'staticmethod': directives.flag,
}) })
def needs_arglist(self): def needs_arglist(self):
# type: () -> bool # type: () -> bool
return True if 'property' in self.options:
return False
else:
return True
def get_signature_prefix(self, sig): def get_signature_prefix(self, sig):
# type: (str) -> str # type: (str) -> str
prefix = [] prefix = []
if 'async' in self.options: if 'async' in self.options:
prefix.append('async') prefix.append('async')
if 'staticmethod' in self.options:
prefix.append('static')
if 'classmethod' in self.options: if 'classmethod' in self.options:
prefix.append('classmethod') prefix.append('classmethod')
if 'property' in self.options:
prefix.append('property')
if 'staticmethod' in self.options:
prefix.append('static')
if prefix: if prefix:
return ' '.join(prefix) + ' ' return ' '.join(prefix) + ' '
@ -622,10 +628,12 @@ class PyMethod(PyObject):
else: else:
return '%s()' % name return '%s()' % name
if 'staticmethod' in self.options: if 'classmethod' in self.options:
return _('%s() (%s static method)') % (methname, clsname)
elif 'classmethod' in self.options:
return _('%s() (%s class method)') % (methname, clsname) return _('%s() (%s class method)') % (methname, clsname)
elif 'property' in self.options:
return _('%s() (%s property)') % (methname, clsname)
elif 'staticmethod' in self.options:
return _('%s() (%s static method)') % (methname, clsname)
else: else:
return _('%s() (%s method)') % (methname, clsname) return _('%s() (%s method)') % (methname, clsname)

View File

@ -11,6 +11,8 @@
import re import re
from typing import cast from typing import cast
from docutils.parsers.rst import directives
from sphinx import addnodes from sphinx import addnodes
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType from sphinx.domains import Domain, ObjType
@ -98,6 +100,74 @@ class ReSTDirective(ReSTMarkup):
# type: (str, str) -> str # type: (str, str) -> str
return _('%s (directive)') % name return _('%s (directive)') % name
def before_content(self):
# type: () -> None
if self.names:
directives = self.env.ref_context.setdefault('rst:directives', [])
directives.append(self.names[0])
def after_content(self):
# type: () -> None
directives = self.env.ref_context.setdefault('rst:directives', [])
if directives:
directives.pop()
class ReSTDirectiveOption(ReSTMarkup):
"""
Description of an option for reST directive.
"""
option_spec = ReSTMarkup.option_spec.copy()
option_spec.update({
'type': directives.unchanged,
})
def handle_signature(self, sig, signode):
# type: (str, addnodes.desc_signature) -> str
try:
name, argument = re.split(r'\s*:\s+', sig.strip(), 1)
except ValueError:
name, argument = sig, None
signode += addnodes.desc_name(':%s:' % name, ':%s:' % name)
if argument:
signode += addnodes.desc_annotation(' ' + argument, ' ' + argument)
if self.options.get('type'):
text = ' (%s)' % self.options['type']
signode += addnodes.desc_annotation(text, text)
return name
def add_target_and_index(self, name, sig, signode):
# type: (str, str, addnodes.desc_signature) -> None
targetname = '-'.join([self.objtype, self.current_directive, name])
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
domain = cast(ReSTDomain, self.env.get_domain('rst'))
domain.note_object(self.objtype, name, location=(self.env.docname, self.lineno))
if self.current_directive:
key = name[0].upper()
pair = [_('%s (directive)') % self.current_directive,
_(':%s: (directive option)') % name]
self.indexnode['entries'].append(('pair', '; '.join(pair), targetname, '', key))
else:
key = name[0].upper()
text = _(':%s: (directive option)') % name
self.indexnode['entries'].append(('single', text, targetname, '', key))
@property
def current_directive(self):
# type: () -> str
directives = self.env.ref_context.get('rst:directives')
if directives:
return directives[-1]
else:
return ''
class ReSTRole(ReSTMarkup): class ReSTRole(ReSTMarkup):
""" """
@ -119,11 +189,13 @@ class ReSTDomain(Domain):
label = 'reStructuredText' label = 'reStructuredText'
object_types = { object_types = {
'directive': ObjType(_('directive'), 'dir'), 'directive': ObjType(_('directive'), 'dir'),
'role': ObjType(_('role'), 'role'), 'directive:option': ObjType(_('directive-option'), 'dir'),
'role': ObjType(_('role'), 'role'),
} }
directives = { directives = {
'directive': ReSTDirective, 'directive': ReSTDirective,
'directive:option': ReSTDirectiveOption,
'role': ReSTRole, 'role': ReSTRole,
} }
roles = { roles = {

View File

@ -448,7 +448,8 @@ class Documenter:
docstring = getdoc(self.object, self.get_attr, docstring = getdoc(self.object, self.get_attr,
self.env.config.autodoc_inherit_docstrings) self.env.config.autodoc_inherit_docstrings)
if docstring: if docstring:
return [prepare_docstring(docstring, ignore)] tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, ignore, tab_width)]
return [] return []
def process_doc(self, docstrings): def process_doc(self, docstrings):
@ -942,7 +943,9 @@ class DocstringSignatureMixin:
if base not in valid_names: if base not in valid_names:
continue continue
# re-prepare docstring to ignore more leading indentation # re-prepare docstring to ignore more leading indentation
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:])) tab_width = self.directive.state.document.settings.tab_width # type: ignore
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:]),
tabsize=tab_width)
result = args, retann result = args, retann
# don't look any further # don't look any further
break break
@ -1186,7 +1189,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
docstrings = [initdocstring] docstrings = [initdocstring]
else: else:
docstrings.append(initdocstring) docstrings.append(initdocstring)
return [prepare_docstring(docstring, ignore) for docstring in docstrings]
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings]
def add_content(self, more_content, no_docstring=False): def add_content(self, more_content, no_docstring=False):
# type: (Any, bool) -> None # type: (Any, bool) -> None
@ -1422,6 +1427,37 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
super().add_content(more_content, no_docstring) super().add_content(more_content, no_docstring)
class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for properties.
"""
objtype = 'property'
directivetype = 'method'
member_order = 60
# before AttributeDocumenter
priority = AttributeDocumenter.priority + 1
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
return inspect.isproperty(member) and isinstance(parent, ClassDocumenter)
def document_members(self, all_members=False):
# type: (bool) -> None
pass
def get_real_modname(self):
# type: () -> str
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
def add_directive_header(self, sig):
# type: (str) -> None
super().add_directive_header(sig)
self.add_line(' :property:', self.get_sourcename())
class InstanceAttributeDocumenter(AttributeDocumenter): class InstanceAttributeDocumenter(AttributeDocumenter):
""" """
Specialized Documenter subclass for attributes that cannot be imported Specialized Documenter subclass for attributes that cannot be imported
@ -1513,6 +1549,7 @@ def setup(app):
app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(DecoratorDocumenter)
app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(MethodDocumenter)
app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True) app.add_config_value('autoclass_content', 'class', True)

View File

@ -6,10 +6,14 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import warnings
from docutils import nodes from docutils import nodes
from docutils.parsers.rst.states import Struct
from docutils.statemachine import StringList from docutils.statemachine import StringList
from docutils.utils import assemble_option_dict from docutils.utils import assemble_option_dict
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.ext.autodoc import Options, get_documenters from sphinx.ext.autodoc import Options, get_documenters
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective, switch_source_input from sphinx.util.docutils import SphinxDirective, switch_source_input
@ -17,7 +21,7 @@ from sphinx.util.nodes import nested_parse_with_titles
if False: if False:
# For type annotation # For type annotation
from typing import Callable, Dict, List, Set, Type # NOQA from typing import Any, Callable, Dict, List, Set, Type # NOQA
from docutils.parsers.rst.state import RSTState # NOQA from docutils.parsers.rst.state import RSTState # NOQA
from docutils.utils import Reporter # NOQA from docutils.utils import Reporter # NOQA
from sphinx.config import Config # NOQA from sphinx.config import Config # NOQA
@ -50,8 +54,8 @@ class DummyOptionSpec(dict):
class DocumenterBridge: class DocumenterBridge:
"""A parameters container for Documenters.""" """A parameters container for Documenters."""
def __init__(self, env, reporter, options, lineno): def __init__(self, env, reporter, options, lineno, state=None):
# type: (BuildEnvironment, Reporter, Options, int) -> None # type: (BuildEnvironment, Reporter, Options, int, Any) -> None
self.env = env self.env = env
self.reporter = reporter self.reporter = reporter
self.genopt = options self.genopt = options
@ -59,6 +63,16 @@ class DocumenterBridge:
self.filename_set = set() # type: Set[str] self.filename_set = set() # type: Set[str]
self.result = StringList() self.result = StringList()
if state:
self.state = state
else:
# create fake object for self.state.document.settings.tab_width
warnings.warn('DocumenterBridge requires a state object on instantiation.',
RemovedInSphinx40Warning)
settings = Struct(tab_width=8)
document = Struct(settings=settings)
self.state = Struct(document=document)
def warn(self, msg): def warn(self, msg):
# type: (str) -> None # type: (str) -> None
logger.warning(msg, location=(self.env.docname, self.lineno)) logger.warning(msg, location=(self.env.docname, self.lineno))
@ -131,7 +145,7 @@ class AutodocDirective(SphinxDirective):
return [] return []
# generate the output # generate the output
params = DocumenterBridge(self.env, reporter, documenter_options, lineno) params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state)
documenter = doccls(params, self.arguments[0]) documenter = doccls(params, self.arguments[0])
documenter.generate(more_content=self.content) documenter.generate(more_content=self.content)
if not params.result: if not params.result:

View File

@ -175,7 +175,7 @@ _app = None # type: Sphinx
class FakeDirective(DocumenterBridge): class FakeDirective(DocumenterBridge):
def __init__(self): def __init__(self):
# type: () -> None # type: () -> None
super().__init__({}, None, Options(), 0) # type: ignore super().__init__({}, None, Options(), 0, None) # type: ignore
def get_documenter(app, obj, parent): def get_documenter(app, obj, parent):
@ -236,7 +236,7 @@ class Autosummary(SphinxDirective):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
self.bridge = DocumenterBridge(self.env, self.state.document.reporter, self.bridge = DocumenterBridge(self.env, self.state.document.reporter,
Options(), self.lineno) Options(), self.lineno, self.state)
names = [x.strip().split()[0] for x in self.content names = [x.strip().split()[0] for x in self.content
if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])] if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])]

View File

@ -40,7 +40,7 @@ from sphinx.util.rst import escape as rst_escape
if False: if False:
# For type annotation # For type annotation
from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union # NOQA
from sphinx.builders import Builder # NOQA from sphinx.builders import Builder # NOQA
from sphinx.ext.autodoc import Documenter # NOQA from sphinx.ext.autodoc import Documenter # NOQA
@ -169,8 +169,8 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
except TemplateNotFound: except TemplateNotFound:
template = template_env.get_template('autosummary/base.rst') template = template_env.get_template('autosummary/base.rst')
def get_members(obj, typ, include_public=[], imported=True): def get_members(obj, types, include_public=[], imported=True):
# type: (Any, str, List[str], bool) -> Tuple[List[str], List[str]] # type: (Any, Set[str], List[str], bool) -> Tuple[List[str], List[str]] # NOQA
items = [] # type: List[str] items = [] # type: List[str]
for name in dir(obj): for name in dir(obj):
try: try:
@ -178,7 +178,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
except AttributeError: except AttributeError:
continue continue
documenter = get_documenter(app, value, obj) documenter = get_documenter(app, value, obj)
if documenter.objtype == typ: if documenter.objtype in types:
if imported or getattr(value, '__module__', None) == obj.__name__: if imported or getattr(value, '__module__', None) == obj.__name__:
# skip imported members if expected # skip imported members if expected
items.append(name) items.append(name)
@ -191,19 +191,19 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
if doc.objtype == 'module': if doc.objtype == 'module':
ns['members'] = dir(obj) ns['members'] = dir(obj)
ns['functions'], ns['all_functions'] = \ ns['functions'], ns['all_functions'] = \
get_members(obj, 'function', imported=imported_members) get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \ ns['classes'], ns['all_classes'] = \
get_members(obj, 'class', imported=imported_members) get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \ ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, 'exception', imported=imported_members) get_members(obj, {'exception'}, imported=imported_members)
elif doc.objtype == 'class': elif doc.objtype == 'class':
ns['members'] = dir(obj) ns['members'] = dir(obj)
ns['inherited_members'] = \ ns['inherited_members'] = \
set(dir(obj)) - set(obj.__dict__.keys()) set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = \ ns['methods'], ns['all_methods'] = \
get_members(obj, 'method', ['__init__']) get_members(obj, {'method'}, ['__init__'])
ns['attributes'], ns['all_attributes'] = \ ns['attributes'], ns['all_attributes'] = \
get_members(obj, 'attribute') get_members(obj, {'attribute', 'property'})
parts = name.split('.') parts = name.split('.')
if doc.objtype in ('method', 'attribute'): if doc.objtype in ('method', 'attribute'):

View File

@ -32,6 +32,7 @@ if False:
# For type annotation # For type annotation
from typing import Any, Dict # NOQA from typing import Any, Dict # NOQA
from pygments.formatter import Formatter # NOQA from pygments.formatter import Formatter # NOQA
from pygments.style import Style # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -69,16 +70,8 @@ class PygmentsBridge:
def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None): def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None):
# type: (str, str, bool) -> None # type: (str, str, bool) -> None
self.dest = dest self.dest = dest
if stylename is None or stylename == 'sphinx':
style = SphinxStyle style = self.get_style(stylename)
elif stylename == 'none':
style = NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
style = getattr(__import__(module, None, None, ['__name__']),
stylename)
else:
style = get_style_by_name(stylename)
self.formatter_args = {'style': style} # type: Dict[str, Any] self.formatter_args = {'style': style} # type: Dict[str, Any]
if dest == 'html': if dest == 'html':
self.formatter = self.html_formatter self.formatter = self.html_formatter
@ -91,6 +84,18 @@ class PygmentsBridge:
warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.', warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.',
RemovedInSphinx30Warning, stacklevel=2) RemovedInSphinx30Warning, stacklevel=2)
def get_style(self, stylename):
# type: (str) -> Style
if stylename is None or stylename == 'sphinx':
return SphinxStyle
elif stylename == 'none':
return NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
return getattr(__import__(module, None, None, ['__name__']), stylename)
else:
return get_style_by_name(stylename)
def get_formatter(self, **kwargs): def get_formatter(self, **kwargs):
# type: (Any) -> Formatter # type: (Any) -> Formatter
kwargs.update(self.formatter_args) kwargs.update(self.formatter_args)
@ -110,11 +115,8 @@ class PygmentsBridge:
return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \ return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
source + '\\end{Verbatim}\n' source + '\\end{Verbatim}\n'
def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs): def get_lexer(self, source, lang, opts=None, location=None):
# type: (str, str, Any, Any, bool, Any) -> str # type: (str, str, Any, Any) -> Lexer
if not isinstance(source, str):
source = source.decode()
# find out which lexer to use # find out which lexer to use
if lang in ('py', 'python'): if lang in ('py', 'python'):
if source.startswith('>>>'): if source.startswith('>>>'):
@ -145,6 +147,15 @@ class PygmentsBridge:
else: else:
lexer.add_filter('raiseonerror') lexer.add_filter('raiseonerror')
return lexer
def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs):
# type: (str, str, Any, Any, bool, Any) -> str
if not isinstance(source, str):
source = source.decode()
lexer = self.get_lexer(source, lang, opts, location)
# trim doctest options if wanted # trim doctest options if wanted
if isinstance(lexer, PythonConsoleLexer) and self.trim_doctest_flags: if isinstance(lexer, PythonConsoleLexer) and self.trim_doctest_flags:
source = doctest.blankline_re.sub('', source) source = doctest.blankline_re.sub('', source)
@ -165,6 +176,7 @@ class PygmentsBridge:
type='misc', subtype='highlighting_failure', type='misc', subtype='highlighting_failure',
location=location) location=location)
hlsource = highlight(source, lexers['none'], formatter) hlsource = highlight(source, lexers['none'], formatter)
if self.dest == 'html': if self.dest == 'html':
return hlsource return hlsource
else: else:

View File

@ -16,6 +16,7 @@ from docutils.io import FileInput, NullOutput
from docutils.parsers.rst import Parser as RSTParser from docutils.parsers.rst import Parser as RSTParser
from docutils.readers import standalone from docutils.readers import standalone
from docutils.statemachine import StringList, string2lines from docutils.statemachine import StringList, string2lines
from docutils.transforms.references import DanglingReferences
from docutils.writers import UnfilteredWriter from docutils.writers import UnfilteredWriter
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
@ -64,7 +65,15 @@ class SphinxBaseReader(standalone.Reader):
def get_transforms(self): def get_transforms(self):
# type: () -> List[Type[Transform]] # type: () -> List[Type[Transform]]
return super().get_transforms() + self.transforms transforms = super().get_transforms() + self.transforms
# remove transforms which is not needed for Sphinx
unused = [DanglingReferences]
for transform in unused:
if transform in transforms:
transforms.remove(transform)
return transforms
def new_document(self): def new_document(self):
# type: () -> nodes.document # type: () -> nodes.document

View File

@ -1,5 +1,5 @@
\begin{savenotes}\sphinxatlongtablestart\begin{longtable} \begin{savenotes}\sphinxatlongtablestart\begin{longtable}
<%- if table.align == 'center' -%> <%- if table.align in ('center', 'default') -%>
[c] [c]
<%- elif table.align == 'left' -%> <%- elif table.align == 'left' -%>
[l] [l]

View File

@ -1,6 +1,6 @@
\begin{savenotes}\sphinxattablestart \begin{savenotes}\sphinxattablestart
<% if table.align -%> <% if table.align -%>
<%- if table.align == 'center' -%> <%- if table.align in ('center', 'default') -%>
\centering \centering
<%- elif table.align == 'left' -%> <%- elif table.align == 'left' -%>
\raggedright \raggedright

View File

@ -1,6 +1,6 @@
\begin{savenotes}\sphinxattablestart \begin{savenotes}\sphinxattablestart
<% if table.align -%> <% if table.align -%>
<%- if table.align == 'center' -%> <%- if table.align in ('center', 'default') -%>
\centering \centering
<%- elif table.align == 'left' -%> <%- elif table.align == 'left' -%>
\raggedright \raggedright

View File

@ -1,14 +1,12 @@
# Minimal makefile for Sphinx documentation # Minimal makefile for Sphinx documentation
# #
# You can set these variables from the command line. For example: # You can set these variables from the command line, and also
# SPHINXOPTS='-E -W -n' make html # from the environment for the first two.
# will run the html builder in a clean environment (-E), treating warnings
# as errors (-W), in nitpicky mode (-n).
SPHINXOPTS ?= SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build SPHINXBUILD ?= sphinx-build
SOURCEDIR ?= {{ rsrcdir }} SOURCEDIR = {{ rsrcdir }}
BUILDDIR ?= {{ rbuilddir }} BUILDDIR = {{ rbuilddir }}
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:

View File

@ -9,10 +9,10 @@
#} #}
{%- if pagename != "search" and builder != "singlehtml" %} {%- if pagename != "search" and builder != "singlehtml" %}
<div id="searchbox" style="display: none" role="search"> <div id="searchbox" style="display: none" role="search">
<h3>{{ _('Quick search') }}</h3> <h3 id="searchlabel">{{ _('Quick search') }}</h3>
<div class="searchformwrapper"> <div class="searchformwrapper">
<form class="search" action="{{ pathto('search') }}" method="get"> <form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" /> <input type="text" name="q" aria-labelledby="searchlabel" />
<input type="submit" value="{{ _('Go') }}" /> <input type="submit" value="{{ _('Go') }}" />
</form> </form>
</div> </div>

View File

@ -289,6 +289,12 @@ img.align-center, .figure.align-center, object.align-center {
margin-right: auto; margin-right: auto;
} }
img.align-default, .figure.align-default {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left { .align-left {
text-align: left; text-align: left;
} }
@ -297,6 +303,10 @@ img.align-center, .figure.align-center, object.align-center {
text-align: center; text-align: center;
} }
.align-default {
text-align: center;
}
.align-right { .align-right {
text-align: right; text-align: right;
} }
@ -368,6 +378,11 @@ table.align-center {
margin-right: auto; margin-right: auto;
} }
table.align-default {
margin-left: auto;
margin-right: auto;
}
table caption span.caption-number { table caption span.caption-number {
font-style: italic; font-style: italic;
} }

View File

@ -293,7 +293,7 @@ class FigureAligner(SphinxTransform):
# type: (Any) -> None # type: (Any) -> None
matcher = NodeMatcher(nodes.table, nodes.figure) matcher = NodeMatcher(nodes.table, nodes.figure)
for node in self.document.traverse(matcher): # type: nodes.Element for node in self.document.traverse(matcher): # type: nodes.Element
node.setdefault('align', 'center') node.setdefault('align', 'default')
class FilterSystemMessages(SphinxTransform): class FilterSystemMessages(SphinxTransform):

View File

@ -9,7 +9,7 @@
""" """
from docutils import nodes from docutils import nodes
from docutils.transforms.references import Substitutions from docutils.transforms.references import DanglingReferences, Substitutions
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
@ -31,6 +31,22 @@ class SubstitutionDefinitionsRemover(SphinxTransform):
node.parent.remove(node) node.parent.remove(node)
class SphinxDanglingReferences(DanglingReferences):
"""DanglingReferences transform which does not output info messages."""
def apply(self, **kwargs):
# type: (Any) -> None
try:
reporter = self.document.reporter
report_level = reporter.report_level
# suppress INFO level messages for a while
reporter.report_level = max(reporter.WARNING_LEVEL, reporter.report_level)
super().apply()
finally:
reporter.report_level = report_level
class SphinxDomains(SphinxTransform): class SphinxDomains(SphinxTransform):
"""Collect objects to Sphinx domains for cross references.""" """Collect objects to Sphinx domains for cross references."""
default_priority = 850 default_priority = 850
@ -44,6 +60,7 @@ class SphinxDomains(SphinxTransform):
def setup(app): def setup(app):
# type: (Sphinx) -> Dict[str, Any] # type: (Sphinx) -> Dict[str, Any]
app.add_transform(SubstitutionDefinitionsRemover) app.add_transform(SubstitutionDefinitionsRemover)
app.add_transform(SphinxDanglingReferences)
app.add_transform(SphinxDomains) app.add_transform(SphinxDomains)
return { return {

View File

@ -15,8 +15,8 @@ if False:
from typing import List # NOQA from typing import List # NOQA
def prepare_docstring(s, ignore=1): def prepare_docstring(s, ignore=1, tabsize=8):
# type: (str, int) -> List[str] # type: (str, int, int) -> List[str]
"""Convert a docstring into lines of parseable reST. Remove common leading """Convert a docstring into lines of parseable reST. Remove common leading
indentation, where the indentation of a given number of lines (usually just indentation, where the indentation of a given number of lines (usually just
one) is ignored. one) is ignored.
@ -25,7 +25,7 @@ def prepare_docstring(s, ignore=1):
ViewList (used as argument of nested_parse().) An empty line is added to ViewList (used as argument of nested_parse().) An empty line is added to
act as a separator between this docstring and following content. act as a separator between this docstring and following content.
""" """
lines = s.expandtabs().splitlines() lines = s.expandtabs(tabsize).splitlines()
# Find minimum indentation of any non-blank lines after ignored lines. # Find minimum indentation of any non-blank lines after ignored lines.
margin = sys.maxsize margin = sys.maxsize
for line in lines[ignore:]: for line in lines[ignore:]:

View File

@ -224,6 +224,12 @@ def iscoroutinefunction(obj):
return False return False
def isproperty(obj):
# type: (Any) -> bool
"""Check if the object is property."""
return isinstance(obj, property)
def safe_getattr(obj, name, *defargs): def safe_getattr(obj, name, *defargs):
# type: (Any, str, str) -> object # type: (Any, str, str) -> object
"""A getattr() that turns all exceptions into AttributeErrors.""" """A getattr() that turns all exceptions into AttributeErrors."""

View File

@ -1565,6 +1565,7 @@ class LaTeXTranslator(SphinxTranslator):
(1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'), (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
(1, 'bottom'): ('\\raisebox{-\\height}{', '}'), (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
(0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'), (0, 'center'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'),
(0, 'default'): ('{\\hspace*{\\fill}', '\\hspace*{\\fill}}'),
# These 2 don't exactly do the right thing. The image should # These 2 don't exactly do the right thing. The image should
# be floated alongside the paragraph. See # be floated alongside the paragraph. See
# https://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG # https://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG

View File

@ -11,6 +11,7 @@
import platform import platform
import sys import sys
from unittest.mock import Mock
from warnings import catch_warnings from warnings import catch_warnings
import pytest import pytest
@ -36,7 +37,9 @@ def do_autodoc(app, objtype, name, options=None):
app.env.temp_data.setdefault('docname', 'index') # set dummy docname app.env.temp_data.setdefault('docname', 'index') # set dummy docname
doccls = app.registry.documenters[objtype] doccls = app.registry.documenters[objtype]
docoptions = process_documenter_options(doccls, app.config, options) docoptions = process_documenter_options(doccls, app.config, options)
bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1) state = Mock()
state.document.settings.tab_width = 8
bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state)
documenter = doccls(bridge, name) documenter = doccls(bridge, name)
documenter.generate() documenter.generate()
@ -95,7 +98,9 @@ def setup_test():
genopt = options, genopt = options,
result = ViewList(), result = ViewList(),
filename_set = set(), filename_set = set(),
state = Mock(),
) )
directive.state.document.settings.tab_width = 8
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
@ -756,7 +761,7 @@ def test_autodoc_undoc_members(app):
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()', ' .. py:method:: Class.meth()',
' .. py:method:: Class.moore(a, e, f) -> happiness', ' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.skipmeth()',
@ -777,6 +782,7 @@ def test_autodoc_inherited_members(app):
' .. py:method:: Class.inheritedstaticmeth(cls)', ' .. py:method:: Class.inheritedstaticmeth(cls)',
' .. py:method:: Class.meth()', ' .. py:method:: Class.meth()',
' .. py:method:: Class.moore(a, e, f) -> happiness', ' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.prop',
' .. py:method:: Class.skipmeth()' ' .. py:method:: Class.skipmeth()'
] ]
@ -836,7 +842,7 @@ def test_autodoc_special_members(app):
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()', ' .. py:method:: Class.meth()',
' .. py:method:: Class.moore(a, e, f) -> happiness', ' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.skipmeth()',
@ -1028,7 +1034,7 @@ def test_autodoc_member_order(app):
' .. py:method:: Class.excludemeth()', ' .. py:method:: Class.excludemeth()',
' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.skipattr',
' .. py:attribute:: Class.attr', ' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',
@ -1062,7 +1068,7 @@ def test_autodoc_member_order(app):
' .. py:attribute:: Class.inst_attr_inline', ' .. py:attribute:: Class.inst_attr_inline',
' .. py:attribute:: Class.inst_attr_string', ' .. py:attribute:: Class.inst_attr_string',
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.skipattr',
' .. py:attribute:: Class.udocattr' ' .. py:attribute:: Class.udocattr'
] ]
@ -1085,7 +1091,7 @@ def test_autodoc_member_order(app):
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()', ' .. py:method:: Class.meth()',
' .. py:method:: Class.moore(a, e, f) -> happiness', ' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.skipmeth()',
@ -1152,14 +1158,16 @@ def test_autodoc_docstring_signature(app):
' indented line', ' indented line',
' ', ' ',
' ', ' ',
' .. py:attribute:: DocstringSig.prop1', ' .. py:method:: DocstringSig.prop1',
' :module: target', ' :module: target',
' :property:',
' ', ' ',
' First line of docstring', ' First line of docstring',
' ', ' ',
' ', ' ',
' .. py:attribute:: DocstringSig.prop2', ' .. py:method:: DocstringSig.prop2',
' :module: target', ' :module: target',
' :property:',
' ', ' ',
' First line of docstring', ' First line of docstring',
' Second line of docstring', ' Second line of docstring',
@ -1194,15 +1202,17 @@ def test_autodoc_docstring_signature(app):
' indented line', ' indented line',
' ', ' ',
' ', ' ',
' .. py:attribute:: DocstringSig.prop1', ' .. py:method:: DocstringSig.prop1',
' :module: target', ' :module: target',
' :property:',
' ', ' ',
' DocstringSig.prop1(self)', ' DocstringSig.prop1(self)',
' First line of docstring', ' First line of docstring',
' ', ' ',
' ', ' ',
' .. py:attribute:: DocstringSig.prop2', ' .. py:method:: DocstringSig.prop2',
' :module: target', ' :module: target',
' :property:',
' ', ' ',
' First line of docstring', ' First line of docstring',
' Second line of docstring', ' Second line of docstring',
@ -1717,7 +1727,7 @@ def test_autodoc_default_options_with_values(app):
' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.skipmeth()',
' .. py:method:: Class.excludemeth()', ' .. py:method:: Class.excludemeth()',
' .. py:attribute:: Class.attr', ' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.prop', ' .. py:method:: Class.prop',
' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr', ' .. py:attribute:: Class.mdocattr',

View File

@ -565,7 +565,7 @@ 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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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']/"
@ -582,21 +582,21 @@ def test_numfig_disabled_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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']/"
@ -633,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 9 $', True), "span[@class='caption-number']", '^Fig. 9 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -657,13 +657,13 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
(".//li/p/code/span", '^Sect.{number}$', True), (".//li/p/code/span", '^Sect.{number}$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True), "span[@class='caption-number']", '^Fig. 2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 3 $', True), "span[@class='caption-number']", '^Fig. 3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -683,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 5 $', True), "span[@class='caption-number']", '^Fig. 5 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 7 $', True), "span[@class='caption-number']", '^Fig. 7 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -703,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -741,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -765,13 +765,13 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True), "span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True), "span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -791,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True), "span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True), "span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -811,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -846,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1 $', True), "span[@class='caption-number']", '^Figure:1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -870,13 +870,13 @@ def test_numfig_with_prefix_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.1 $', True), "span[@class='caption-number']", '^Figure:1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.2 $', True), "span[@class='caption-number']", '^Figure:1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.3 $', True), "span[@class='caption-number']", '^Figure:1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -896,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.1 $', True), "span[@class='caption-number']", '^Figure:2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2.3 $', True), "span[@class='caption-number']", '^Figure:2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -916,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -952,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -976,13 +976,13 @@ def test_numfig_with_secnum_depth_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
], ],
'foo.html': [ 'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1.2 $', True), "span[@class='caption-number']", '^Fig. 1.1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1002,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.1 $', True), "span[@class='caption-number']", '^Fig. 2.1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1.3 $', True), "span[@class='caption-number']", '^Fig. 2.1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1022,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1043,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1 $', True), "span[@class='caption-number']", '^Fig. 1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1065,13 +1065,13 @@ def test_numfig_with_secnum_depth(app, cached_etree_parse, fname, expect):
(".//li/p/a/span", '^Section.2.1$', True), (".//li/p/a/span", '^Section.2.1$', True),
(".//li/p/a/span", '^Fig.1 should be Fig.1$', True), (".//li/p/a/span", '^Fig.1 should be Fig.1$', True),
(".//li/p/a/span", '^Sect.1 Foo$', True), (".//li/p/a/span", '^Sect.1 Foo$', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.1 $', True), "span[@class='caption-number']", '^Fig. 1.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.2 $', True), "span[@class='caption-number']", '^Fig. 1.2 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.3 $', True), "span[@class='caption-number']", '^Fig. 1.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1089,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.1 $', True), "span[@class='caption-number']", '^Fig. 2.1 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2.3 $', True), "span[@class='caption-number']", '^Fig. 2.3 $', True),
(".//div[@class='figure align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1107,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 align-center']/p[@class='caption']/" (".//div[@class='figure align-default']/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),
@ -1126,11 +1126,11 @@ 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 align-center']/p[@class='caption']" (".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "Fig. 1", True), "/span[@class='caption-number']", "Fig. 1", True),
(".//div[@class='figure align-center']/p[@class='caption']" (".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "Fig. 2", True), "/span[@class='caption-number']", "Fig. 2", True),
(".//div[@class='figure align-center']/p[@class='caption']" (".//div[@class='figure align-default']/p[@class='caption']"
"/span[@class='caption-number']", "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),
@ -1338,7 +1338,7 @@ def test_html_sidebar(app, status, warning):
assert '<h1 class="logo"><a href="#">Python</a></h1>' in result assert '<h1 class="logo"><a href="#">Python</a></h1>' in result
assert '<h3>Navigation</h3>' in result assert '<h3>Navigation</h3>' in result
assert '<h3>Related Topics</h3>' in result assert '<h3>Related Topics</h3>' in result
assert '<h3>Quick search</h3>' in result assert '<h3 id="searchlabel">Quick search</h3>' in result
app.builder.add_sidebars('index', ctx) app.builder.add_sidebars('index', ctx)
assert ctx['sidebars'] == ['about.html', 'navigation.html', 'relations.html', assert ctx['sidebars'] == ['about.html', 'navigation.html', 'relations.html',
@ -1353,7 +1353,7 @@ def test_html_sidebar(app, status, warning):
assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result
assert '<h3>Navigation</h3>' not in result assert '<h3>Navigation</h3>' not in result
assert '<h3>Related Topics</h3>' in result assert '<h3>Related Topics</h3>' in result
assert '<h3>Quick search</h3>' not in result assert '<h3 id="searchlabel">Quick search</h3>' not in result
app.builder.add_sidebars('index', ctx) app.builder.add_sidebars('index', ctx)
assert ctx['sidebars'] == ['relations.html'] assert ctx['sidebars'] == ['relations.html']
@ -1367,7 +1367,7 @@ def test_html_sidebar(app, status, warning):
assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result assert '<h1 class="logo"><a href="#">Python</a></h1>' not in result
assert '<h3>Navigation</h3>' not in result assert '<h3>Navigation</h3>' not in result
assert '<h3>Related Topics</h3>' not in result assert '<h3>Related Topics</h3>' not in result
assert '<h3>Quick search</h3>' not in result assert '<h3 id="searchlabel">Quick search</h3>' not in result
app.builder.add_sidebars('index', ctx) app.builder.add_sidebars('index', ctx)
assert ctx['sidebars'] == [] assert ctx['sidebars'] == []

View File

@ -333,7 +333,9 @@ def test_pymethod_options(app):
" .. py:method:: meth3\n" " .. py:method:: meth3\n"
" :staticmethod:\n" " :staticmethod:\n"
" .. py:method:: meth4\n" " .. py:method:: meth4\n"
" :async:\n") " :async:\n"
" .. py:method:: meth5\n"
" :property:\n")
domain = app.env.get_domain('py') domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index, assert_node(doctree, (addnodes.index,
@ -346,6 +348,8 @@ def test_pymethod_options(app):
addnodes.index, addnodes.index,
desc, desc,
addnodes.index, addnodes.index,
desc,
addnodes.index,
desc)])])) desc)])]))
# method # method
@ -387,6 +391,15 @@ def test_pymethod_options(app):
assert 'Class.meth4' in domain.objects assert 'Class.meth4' in domain.objects
assert domain.objects['Class.meth4'] == ('index', 'method') assert domain.objects['Class.meth4'] == ('index', 'method')
# :property:
assert_node(doctree[1][1][8], addnodes.index,
entries=[('single', 'meth5() (Class property)', 'Class.meth5', '', None)])
assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "],
[desc_name, "meth5"])],
[desc_content, ()]))
assert 'Class.meth5' in domain.objects
assert domain.objects['Class.meth5'] == ('index', 'method')
def test_pyclassmethod(app): def test_pyclassmethod(app):
text = (".. py:class:: Class\n" text = (".. py:class:: Class\n"

View File

@ -10,8 +10,7 @@
from sphinx import addnodes from sphinx import addnodes
from sphinx.addnodes import ( from sphinx.addnodes import (
desc, desc_addname, desc_content, desc_name, desc_optional, desc_parameter, desc, desc_addname, desc_annotation, desc_content, desc_name, desc_signature
desc_parameterlist, desc_returns, desc_signature
) )
from sphinx.domains.rst import parse_directive from sphinx.domains.rst import parse_directive
from sphinx.testing import restructuredtext from sphinx.testing import restructuredtext
@ -69,6 +68,66 @@ def test_rst_directive_with_argument(app):
domain="rst", objtype="directive", noindex=False) domain="rst", objtype="directive", noindex=False)
def test_rst_directive_option(app):
text = ".. rst:directive:option:: foo"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, desc_name, ":foo:"],
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
"directive:option--foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
def test_rst_directive_option_with_argument(app):
text = ".. rst:directive:option:: foo: bar baz"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, ":foo:"],
[desc_annotation, " bar baz"])],
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
"directive:option--foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
def test_rst_directive_option_type(app):
text = (".. rst:directive:option:: foo\n"
" :type: directives.flags\n")
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, ":foo:"],
[desc_annotation, " (directives.flags)"])],
[desc_content, ()])]))
assert_node(doctree[0],
entries=[("single", ":foo: (directive option)",
"directive:option--foo", "", "F")])
assert_node(doctree[1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
def test_rst_directive_and_directive_option(app):
text = (".. rst:directive:: foo\n"
"\n"
" .. rst:directive:option:: bar\n")
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, desc_name, ".. foo::"],
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0],
entries=[("pair", "foo (directive); :bar: (directive option)",
"directive:option-foo-bar", "", "B")])
assert_node(doctree[1][1][1], ([desc_signature, desc_name, ":bar:"],
[desc_content, ()]))
assert_node(doctree[1][1][1], addnodes.desc, desctype="directive:option",
domain="rst", objtype="directive:option", noindex=False)
def test_rst_role(app): def test_rst_role(app):
text = ".. rst:role:: ref" text = ".. rst:role:: ref"
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)

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 align-center" .*?>\s*' html = (r'<div class="figure align-default" .*?>\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 align-center\" .*?>\n' html = (r'<div class=\"figure align-default\" .*?>\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'

View File

@ -140,7 +140,7 @@ def test_inheritance_diagram_png_html(app, status, warning):
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n' pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" ' '<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">' 'class="inheritance graphviz" /></div>\n<p class="caption">'
@ -157,7 +157,7 @@ def test_inheritance_diagram_svg_html(app, status, warning):
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n' pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<object data="_images/inheritance-\\w+.svg" ' '<object data="_images/inheritance-\\w+.svg" '
'type="image/svg\\+xml" class="inheritance graphviz">\n' 'type="image/svg\\+xml" class="inheritance graphviz">\n'
@ -197,7 +197,7 @@ def test_inheritance_diagram_latex_alias(app, status, warning):
content = (app.outdir / 'index.html').text() content = (app.outdir / 'index.html').text()
pattern = ('<div class="figure align-center" id="id1">\n' pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">' '<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" ' '<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'class="inheritance graphviz" /></div>\n<p class="caption">' 'class="inheritance graphviz" /></div>\n<p class="caption">'

View File

@ -126,4 +126,4 @@ def test_theme_sidebars(app, status, warning):
assert '<h3><a href="#">Table of Contents</a></h3>' in result assert '<h3><a href="#">Table of Contents</a></h3>' in result
assert '<h3>Related Topics</h3>' not in result assert '<h3>Related Topics</h3>' not in result
assert '<h3>This Page</h3>' not in result assert '<h3>This Page</h3>' not in result
assert '<h3>Quick search</h3>' in result assert '<h3 id="searchlabel">Quick search</h3>' in result

View File

@ -475,3 +475,14 @@ def test_isattributedescriptor(app):
assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType # NOQA assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType # NOQA
assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType # NOQA assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType # NOQA
assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA
def test_isproperty(app):
from target.functions import func
from target.methods import Base
assert inspect.isproperty(Base.prop) is True # property of class
assert inspect.isproperty(Base().prop) is False # property of instance
assert inspect.isproperty(Base.meth) is False # method of class
assert inspect.isproperty(Base().meth) is False # method of instance
assert inspect.isproperty(func) is False # function

View File

@ -66,6 +66,15 @@ extras =
commands = commands =
python setup.py build_sphinx {posargs} python setup.py build_sphinx {posargs}
[testenv:docslint]
basepython = python3
description =
Lint documentation.
extras =
docs
commands =
python utils/doclinter.py CHANGES CONTRIBUTING.rst README.rst doc/
[testenv:bindep] [testenv:bindep]
description = description =
Install binary dependencies. Install binary dependencies.

59
utils/doclinter.py Normal file
View File

@ -0,0 +1,59 @@
"""
utils.doclinter
~~~~~~~~~~~~~~~
A linter for Sphinx docs
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import re
import sys
from typing import List
MAX_LINE_LENGTH = 100
def lint(path: str) -> int:
with open(path) as f:
document = f.readlines()
errors = 0
for i, line in enumerate(document):
if line.endswith(' '):
print('%s:%d: the line ends with whitespace.' %
(path, i + 1))
errors += 1
if len(line) > MAX_LINE_LENGTH:
if re.match(r'^\s*\.\. ', line):
# ignore directives and hyperlink targets
pass
else:
print('%s:%d: the line is too long (%d > %d).' %
(path, i + 1, len(line), MAX_LINE_LENGTH))
errors += 1
return errors
def main(args: List[str]) -> int:
errors = 0
for directory in args:
for root, dirs, files in os.walk(directory):
for filename in files:
if filename.endswith('.rst'):
path = os.path.join(root, filename)
errors += lint(path)
if errors:
return 1
else:
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))