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
* #6230: The anchor of term in glossary directive is changed if it is consisted
by non-ASCII characters
* #4550: html: Centering tables by default using CSS
Deprecated
----------
@ -73,7 +74,8 @@ Features added
* #6180: Support ``--keep-going`` with BuildDoc setup command
* ``math`` directive now supports ``:class:`` 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
functions
* #6289: autodoc: :confval:`autodoc_default_options` now supports
@ -83,8 +85,16 @@ Features added
imported members on autosummary
* #6271: ``make clean`` is catastrophically broken if building into '.'
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
* py domain: Add ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options
to :rst:dir:`py:method` directive
* py domain: Add new options 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
----------
@ -95,7 +105,11 @@ Bugs fixed
* commented term in glossary directive is wrongly recognized
* #6299: rst domain: rst:directive directive generates waste space
* #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
* Generated Makefiles lack a final EOL (refs: #6232)
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_
- adadomain: an extension for Ada support (Sphinx 1.0 needed)
- 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
- autoanysrc: Gather reST documentation from any source files
- 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
- inlinesyntaxhighlight_: inline syntax highlighting
- 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
- makedomain_: a domain for `GNU Make`_
- 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`_
See the :doc:`extension tutorials <../development/tutorials/index>` on getting started with writing your
own extensions.
See the :doc:`extension tutorials <../development/tutorials/index>` on getting
started with writing your own extensions.
.. _aafigure: https://launchpad.net/aafigure

View File

@ -97,7 +97,8 @@ extension. These are:
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:

View File

@ -147,5 +147,6 @@ return ``node.children`` from the Directive.
.. seealso::
`Creating directives <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_
HOWTO of the Docutils documentation
`Creating directives`_ 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
A string containing the content of the page in HTML form as produced by the HTML builder,
before the theme is applied.
A string containing the content of the page in HTML form as produced by the
HTML builder, before the theme is applied.
.. data:: display_toc
@ -382,8 +382,9 @@ are in HTML form), these variables are also available:
.. data:: page_source_suffix
The suffix of the file that was rendered. Since we support a list of :confval:`source_suffix`,
this will allow you to properly link to the original source file.
The suffix of the file that was rendered. Since we support a list of
:confval:`source_suffix`, this will allow you to properly link to the
original source file.
.. 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
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
``:async:``, ``:classmethod:`` and ``:staticmethod:`` options added.
``:async:``, ``:classmethod:``, ``:property:`` and ``:staticmethod:``
options added.
.. 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
These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must be
taken when referencing a (partial) template specialization, e.g. if the link looks like
this: ``:cpp:class:`MyClass<int>```.
These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must
be taken when referencing a (partial) template specialization, e.g. if the
link looks like this: ``:cpp:class:`MyClass<int>```.
This is interpreted as a link to ``int`` with a title of ``MyClass``.
In this case, escape the opening angle bracket with a backslash,
like this: ``:cpp:class:`MyClass\<int>```.
When a custom title is not needed it may be useful to use the roles for inline expressions,
:rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where angle brackets do not need escaping.
When a custom title is not needed it may be useful to use the roles for
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -1419,6 +1424,43 @@ The reStructuredText domain (name **rst**) provides the following directives:
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
Describes a reST role. For example::

View File

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

View File

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

View File

@ -11,6 +11,8 @@
import re
from typing import cast
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
@ -98,6 +100,74 @@ class ReSTDirective(ReSTMarkup):
# type: (str, str) -> str
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):
"""
@ -119,11 +189,13 @@ class ReSTDomain(Domain):
label = 'reStructuredText'
object_types = {
'directive': ObjType(_('directive'), 'dir'),
'role': ObjType(_('role'), 'role'),
'directive': ObjType(_('directive'), 'dir'),
'directive:option': ObjType(_('directive-option'), 'dir'),
'role': ObjType(_('role'), 'role'),
}
directives = {
'directive': ReSTDirective,
'directive:option': ReSTDirectiveOption,
'role': ReSTRole,
}
roles = {

View File

@ -448,7 +448,8 @@ class Documenter:
docstring = getdoc(self.object, self.get_attr,
self.env.config.autodoc_inherit_docstrings)
if docstring:
return [prepare_docstring(docstring, ignore)]
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, ignore, tab_width)]
return []
def process_doc(self, docstrings):
@ -942,7 +943,9 @@ class DocstringSignatureMixin:
if base not in valid_names:
continue
# 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
# don't look any further
break
@ -1186,7 +1189,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
docstrings = [initdocstring]
else:
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):
# type: (Any, bool) -> None
@ -1422,6 +1427,37 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
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):
"""
Specialized Documenter subclass for attributes that cannot be imported
@ -1513,6 +1549,7 @@ def setup(app):
app.add_autodocumenter(DecoratorDocumenter)
app.add_autodocumenter(MethodDocumenter)
app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True)

View File

@ -6,10 +6,14 @@
:license: BSD, see LICENSE for details.
"""
import warnings
from docutils import nodes
from docutils.parsers.rst.states import Struct
from docutils.statemachine import StringList
from docutils.utils import assemble_option_dict
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.ext.autodoc import Options, get_documenters
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective, switch_source_input
@ -17,7 +21,7 @@ from sphinx.util.nodes import nested_parse_with_titles
if False:
# 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.utils import Reporter # NOQA
from sphinx.config import Config # NOQA
@ -50,8 +54,8 @@ class DummyOptionSpec(dict):
class DocumenterBridge:
"""A parameters container for Documenters."""
def __init__(self, env, reporter, options, lineno):
# type: (BuildEnvironment, Reporter, Options, int) -> None
def __init__(self, env, reporter, options, lineno, state=None):
# type: (BuildEnvironment, Reporter, Options, int, Any) -> None
self.env = env
self.reporter = reporter
self.genopt = options
@ -59,6 +63,16 @@ class DocumenterBridge:
self.filename_set = set() # type: Set[str]
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):
# type: (str) -> None
logger.warning(msg, location=(self.env.docname, self.lineno))
@ -131,7 +145,7 @@ class AutodocDirective(SphinxDirective):
return []
# 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.generate(more_content=self.content)
if not params.result:

View File

@ -175,7 +175,7 @@ _app = None # type: Sphinx
class FakeDirective(DocumenterBridge):
def __init__(self):
# type: () -> None
super().__init__({}, None, Options(), 0) # type: ignore
super().__init__({}, None, Options(), 0, None) # type: ignore
def get_documenter(app, obj, parent):
@ -236,7 +236,7 @@ class Autosummary(SphinxDirective):
def run(self):
# type: () -> List[nodes.Node]
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
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:
# 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.ext.autodoc import Documenter # NOQA
@ -169,8 +169,8 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
except TemplateNotFound:
template = template_env.get_template('autosummary/base.rst')
def get_members(obj, typ, include_public=[], imported=True):
# type: (Any, str, List[str], bool) -> Tuple[List[str], List[str]]
def get_members(obj, types, include_public=[], imported=True):
# type: (Any, Set[str], List[str], bool) -> Tuple[List[str], List[str]] # NOQA
items = [] # type: List[str]
for name in dir(obj):
try:
@ -178,7 +178,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
except AttributeError:
continue
documenter = get_documenter(app, value, obj)
if documenter.objtype == typ:
if documenter.objtype in types:
if imported or getattr(value, '__module__', None) == obj.__name__:
# skip imported members if expected
items.append(name)
@ -191,19 +191,19 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
if doc.objtype == 'module':
ns['members'] = dir(obj)
ns['functions'], ns['all_functions'] = \
get_members(obj, 'function', imported=imported_members)
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
get_members(obj, 'class', imported=imported_members)
get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, 'exception', imported=imported_members)
get_members(obj, {'exception'}, imported=imported_members)
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['inherited_members'] = \
set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = \
get_members(obj, 'method', ['__init__'])
get_members(obj, {'method'}, ['__init__'])
ns['attributes'], ns['all_attributes'] = \
get_members(obj, 'attribute')
get_members(obj, {'attribute', 'property'})
parts = name.split('.')
if doc.objtype in ('method', 'attribute'):

View File

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

View File

@ -16,6 +16,7 @@ from docutils.io import FileInput, NullOutput
from docutils.parsers.rst import Parser as RSTParser
from docutils.readers import standalone
from docutils.statemachine import StringList, string2lines
from docutils.transforms.references import DanglingReferences
from docutils.writers import UnfilteredWriter
from sphinx.deprecation import RemovedInSphinx30Warning
@ -64,7 +65,15 @@ class SphinxBaseReader(standalone.Reader):
def get_transforms(self):
# 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):
# type: () -> nodes.document

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,10 @@
#}
{%- if pagename != "search" and builder != "singlehtml" %}
<div id="searchbox" style="display: none" role="search">
<h3>{{ _('Quick search') }}</h3>
<h3 id="searchlabel">{{ _('Quick search') }}</h3>
<div class="searchformwrapper">
<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') }}" />
</form>
</div>

View File

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

View File

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

View File

@ -9,7 +9,7 @@
"""
from docutils import nodes
from docutils.transforms.references import Substitutions
from docutils.transforms.references import DanglingReferences, Substitutions
from sphinx.transforms import SphinxTransform
@ -31,6 +31,22 @@ class SubstitutionDefinitionsRemover(SphinxTransform):
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):
"""Collect objects to Sphinx domains for cross references."""
default_priority = 850
@ -44,6 +60,7 @@ class SphinxDomains(SphinxTransform):
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(SubstitutionDefinitionsRemover)
app.add_transform(SphinxDanglingReferences)
app.add_transform(SphinxDomains)
return {

View File

@ -15,8 +15,8 @@ if False:
from typing import List # NOQA
def prepare_docstring(s, ignore=1):
# type: (str, int) -> List[str]
def prepare_docstring(s, ignore=1, tabsize=8):
# type: (str, int, int) -> List[str]
"""Convert a docstring into lines of parseable reST. Remove common leading
indentation, where the indentation of a given number of lines (usually just
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
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.
margin = sys.maxsize
for line in lines[ignore:]:

View File

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

View File

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

View File

@ -11,6 +11,7 @@
import platform
import sys
from unittest.mock import Mock
from warnings import catch_warnings
import pytest
@ -36,7 +37,9 @@ def do_autodoc(app, objtype, name, options=None):
app.env.temp_data.setdefault('docname', 'index') # set dummy docname
doccls = app.registry.documenters[objtype]
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.generate()
@ -95,7 +98,9 @@ def setup_test():
genopt = options,
result = ViewList(),
filename_set = set(),
state = Mock(),
)
directive.state.document.settings.tab_width = 8
processed_docstrings = []
processed_signatures = []
@ -756,7 +761,7 @@ def test_autodoc_undoc_members(app):
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. 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:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
@ -777,6 +782,7 @@ def test_autodoc_inherited_members(app):
' .. py:method:: Class.inheritedstaticmeth(cls)',
' .. py:method:: Class.meth()',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.prop',
' .. py:method:: Class.skipmeth()'
]
@ -836,7 +842,7 @@ def test_autodoc_special_members(app):
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. 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:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
@ -1028,7 +1034,7 @@ def test_autodoc_member_order(app):
' .. py:method:: Class.excludemeth()',
' .. py:attribute:: Class.skipattr',
' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.prop',
' .. py:method:: Class.prop',
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. 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_string',
' .. py:attribute:: Class.mdocattr',
' .. py:attribute:: Class.prop',
' .. py:method:: Class.prop',
' .. py:attribute:: Class.skipattr',
' .. py:attribute:: Class.udocattr'
]
@ -1085,7 +1091,7 @@ def test_autodoc_member_order(app):
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. 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:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
@ -1152,14 +1158,16 @@ def test_autodoc_docstring_signature(app):
' indented line',
' ',
' ',
' .. py:attribute:: DocstringSig.prop1',
' .. py:method:: DocstringSig.prop1',
' :module: target',
' :property:',
' ',
' First line of docstring',
' ',
' ',
' .. py:attribute:: DocstringSig.prop2',
' .. py:method:: DocstringSig.prop2',
' :module: target',
' :property:',
' ',
' First line of docstring',
' Second line of docstring',
@ -1194,15 +1202,17 @@ def test_autodoc_docstring_signature(app):
' indented line',
' ',
' ',
' .. py:attribute:: DocstringSig.prop1',
' .. py:method:: DocstringSig.prop1',
' :module: target',
' :property:',
' ',
' DocstringSig.prop1(self)',
' First line of docstring',
' ',
' ',
' .. py:attribute:: DocstringSig.prop2',
' .. py:method:: DocstringSig.prop2',
' :module: target',
' :property:',
' ',
' First 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.excludemeth()',
' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.prop',
' .. py:method:: Class.prop',
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr',

View File

@ -565,7 +565,7 @@ def test_numfig_disabled_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'index.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
@ -582,21 +582,21 @@ def test_numfig_disabled_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'foo.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True),
],
'bar.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//div[@class='code-block-caption']/"
"span[@class='caption-number']", None, True),
],
'baz.html': [
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", None, True),
(".//table/caption/span[@class='caption-number']", None, True),
(".//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({
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 10 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 9 $', True),
@ -657,13 +657,13 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
(".//li/p/code/span", '^Sect.{number}$', True),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@ -683,11 +683,11 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 4 $', True),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//table/caption/span[@class='caption-number']",
'^Table 5 $', True),
@ -703,7 +703,7 @@ def test_numfig_without_numbered_toctree_warn(app, warning):
"span[@class='caption-number']", '^Listing 8 $', True),
],
'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),
(".//table/caption/span[@class='caption-number']",
'^Table 6 $', True),
@ -741,9 +741,9 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@ -765,13 +765,13 @@ def test_numfig_with_numbered_toctree_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^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),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//table/caption/span[@class='caption-number']",
'^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),
],
'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),
(".//table/caption/span[@class='caption-number']",
'^Table 2.2 $', True),
@ -846,9 +846,9 @@ def test_numfig_with_prefix_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_1 $', True),
@ -870,13 +870,13 @@ def test_numfig_with_prefix_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Figure:1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^Tab_1.1 $', True),
@ -896,11 +896,11 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-1.4 $', True),
],
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//table/caption/span[@class='caption-number']",
'^Tab_2.1 $', True),
@ -916,7 +916,7 @@ def test_numfig_with_prefix_warn(app, warning):
"span[@class='caption-number']", '^Code-2.4 $', True),
],
'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),
(".//table/caption/span[@class='caption-number']",
'^Tab_2.2 $', True),
@ -952,9 +952,9 @@ def test_numfig_with_secnum_depth_warn(app, warning):
@pytest.mark.parametrize("fname,expect", flat_dict({
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^Table 1 $', True),
@ -976,13 +976,13 @@ def test_numfig_with_secnum_depth_warn(app, warning):
(".//li/p/a/span", '^Sect.1 Foo$', True),
],
'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),
(".//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),
(".//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),
(".//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),
(".//table/caption/span[@class='caption-number']",
'^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),
],
'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),
(".//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),
(".//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),
(".//table/caption/span[@class='caption-number']",
'^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),
],
'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),
(".//table/caption/span[@class='caption-number']",
'^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({
'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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 2 $', True),
(".//table/caption/span[@class='caption-number']",
'^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", '^Fig.1 should be Fig.1$', 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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"span[@class='caption-number']", '^Fig. 1.4 $', True),
(".//table/caption/span[@class='caption-number']",
'^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),
(".//div[@class='code-block-caption']/"
"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),
(".//div[@class='figure align-center']/p[@class='caption']/"
(".//div[@class='figure align-default']/p[@class='caption']/"
"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),
(".//table/caption/span[@class='caption-number']",
'^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),
(".//div[@class='code-block-caption']/"
"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),
(".//table/caption/span[@class='caption-number']",
'^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({
'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),
(".//div[@class='figure align-center']/p[@class='caption']"
(".//div[@class='figure align-default']/p[@class='caption']"
"/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),
(".//div//span[@class='caption-number']", "No.1 ", 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 '<h3>Navigation</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)
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 '<h3>Navigation</h3>' not 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)
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 '<h3>Navigation</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)
assert ctx['sidebars'] == []

View File

@ -333,7 +333,9 @@ def test_pymethod_options(app):
" .. py:method:: meth3\n"
" :staticmethod:\n"
" .. py:method:: meth4\n"
" :async:\n")
" :async:\n"
" .. py:method:: meth5\n"
" :property:\n")
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
@ -346,6 +348,8 @@ def test_pymethod_options(app):
addnodes.index,
desc,
addnodes.index,
desc,
addnodes.index,
desc)])]))
# method
@ -387,6 +391,15 @@ def test_pymethod_options(app):
assert 'Class.meth4' in domain.objects
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):
text = (".. py:class:: Class\n"

View File

@ -10,8 +10,7 @@
from sphinx import addnodes
from sphinx.addnodes import (
desc, desc_addname, desc_content, desc_name, desc_optional, desc_parameter,
desc_parameterlist, desc_returns, desc_signature
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_signature
)
from sphinx.domains.rst import parse_directive
from sphinx.testing import restructuredtext
@ -69,6 +68,66 @@ def test_rst_directive_with_argument(app):
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):
text = ".. rst:role:: ref"
doctree = restructuredtext.parse(app, text)

View File

@ -21,7 +21,7 @@ def test_graphviz_png_html(app, status, warning):
app.builder.build_all()
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'<span class="caption-text">caption of graph</span>.*</p>\s*</div>')
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()
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'\s*<p class=\"warning\">digraph foo {\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()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'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()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<object data="_images/inheritance-\\w+.svg" '
'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()
pattern = ('<div class="figure align-center" id="id1">\n'
pattern = ('<div class="figure align-default" id="id1">\n'
'<div class="graphviz">'
'<img src="_images/inheritance-\\w+.png" alt="Inheritance diagram of test.Foo" '
'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>Related Topics</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(datetime.timedelta.days) is True # MemberDescriptorType # 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 =
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]
description =
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:]))