Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-05-04 00:15:20 +09:00
commit 75203967d8
22 changed files with 195 additions and 52 deletions

View File

@ -8,4 +8,7 @@ jobs:
- checkout - checkout
- run: /python3.6/bin/pip install -U pip setuptools - run: /python3.6/bin/pip install -U pip setuptools
- run: /python3.6/bin/pip install -U .[test] - run: /python3.6/bin/pip install -U .[test]
- run: make test PYTHON=/python3.6/bin/python - run: mkdir -p test-reports/pytest
- run: make test PYTHON=/python3.6/bin/python TEST=--junitxml=test-reports/pytest/results.xml
- store_test_results:
path: test-reports

View File

@ -47,6 +47,7 @@ Deprecated
been changed to Sphinx object been changed to Sphinx object
* ``sphinx.ext.autosummary.generate.AutosummaryRenderer`` takes an object type * ``sphinx.ext.autosummary.generate.AutosummaryRenderer`` takes an object type
as an argument as an argument
* The ``ignore`` argument of ``sphinx.ext.autodoc.Documenter.get_doc()``
* The ``template_dir`` argument of ``sphinx.ext.autosummary.generate. * The ``template_dir`` argument of ``sphinx.ext.autosummary.generate.
AutosummaryRenderer`` AutosummaryRenderer``
* The ``module`` argument of ``sphinx.ext.autosummary.generate. * The ``module`` argument of ``sphinx.ext.autosummary.generate.
@ -55,6 +56,7 @@ Deprecated
generate_autosummary_docs()`` generate_autosummary_docs()``
* The ``template_dir`` argument of ``sphinx.ext.autosummary.generate. * The ``template_dir`` argument of ``sphinx.ext.autosummary.generate.
generate_autosummary_docs()`` generate_autosummary_docs()``
* The ``ignore`` argument of ``sphinx.util.docstring.prepare_docstring()``
* ``sphinx.ext.autosummary.generate.AutosummaryRenderer.exists()`` * ``sphinx.ext.autosummary.generate.AutosummaryRenderer.exists()``
Features added Features added
@ -80,6 +82,7 @@ Features added
to generate stub files recursively to generate stub files recursively
* #4030: autosummary: Add :confval:`autosummary_context` to add template * #4030: autosummary: Add :confval:`autosummary_context` to add template
variables for custom templates variables for custom templates
* #7530: html: Support nested <kbd> elements
* #7481: html theme: Add right margin to footnote/citation labels * #7481: html theme: Add right margin to footnote/citation labels
* #7482: html theme: CSS spacing for code blocks with captions and line numbers * #7482: html theme: CSS spacing for code blocks with captions and line numbers
* #7443: html theme: Add new options :confval:`globaltoc_collapse` and * #7443: html theme: Add new options :confval:`globaltoc_collapse` and
@ -98,6 +101,7 @@ Features added
* C++, parse trailing return types. * C++, parse trailing return types.
* #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`, * #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`,
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
* #7582: napoleon: a type for attribute are represented like type annotation
Bugs fixed Bugs fixed
---------- ----------
@ -108,6 +112,9 @@ Bugs fixed
* #7469: autodoc: The change of autodoc-process-docstring for variables is * #7469: autodoc: The change of autodoc-process-docstring for variables is
cached unexpectedly cached unexpectedly
* #7559: autodoc: misdetects a sync function is async * #7559: autodoc: misdetects a sync function is async
* #6857: autodoc: failed to detect a classmethod on Enum class
* #7562: autodoc: a typehint contains spaces is wrongly rendered under
autodoc_typehints='description' mode
* #7535: sphinx-autogen: crashes when custom template uses inheritance * #7535: sphinx-autogen: crashes when custom template uses inheritance
* #7536: sphinx-autogen: crashes when template uses i18n feature * #7536: sphinx-autogen: crashes when template uses i18n feature
* #2785: html: Bad alignment of equation links * #2785: html: Bad alignment of equation links

View File

@ -69,6 +69,11 @@ The following is a list of deprecated interfaces.
- 5.0 - 5.0
- N/A - N/A
* - The ``ignore`` argument of ``sphinx.ext.autodoc.Documenter.get_doc()``
- 3.1
- 5.0
- N/A
* - The ``template_dir`` argument of * - The ``template_dir`` argument of
``sphinx.ext.autosummary.generate.AutosummaryRenderer`` ``sphinx.ext.autosummary.generate.AutosummaryRenderer``
- 3.1 - 3.1
@ -98,6 +103,11 @@ The following is a list of deprecated interfaces.
- 5.0 - 5.0
- N/A - N/A
* - The ``ignore`` argument of ``sphinx.util.docstring.prepare_docstring()``
- 3.1
- 5.0
- N/A
* - ``desc_signature['first']`` * - ``desc_signature['first']``
- -
- 3.0 - 3.0

View File

@ -1227,6 +1227,9 @@ def setup(app: Sphinx) -> Dict[str, Any]:
# load default math renderer # load default math renderer
app.setup_extension('sphinx.ext.mathjax') app.setup_extension('sphinx.ext.mathjax')
# load transforms for HTML builder
app.setup_extension('sphinx.builders.html.transforms')
return { return {
'version': 'builtin', 'version': 'builtin',
'parallel_read_safe': True, 'parallel_read_safe': True,

View File

@ -0,0 +1,69 @@
"""
sphinx.builders.html.transforms
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Transforms for HTML builder.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
from typing import Any, Dict
from docutils import nodes
from sphinx.application import Sphinx
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util.nodes import NodeMatcher
class KeyboardTransform(SphinxPostTransform):
"""Transform :kbd: role to more detailed form.
Before::
<literal class="kbd">
Control-x
After::
<literal class="kbd">
<literal class="kbd">
Control
-
<literal class="kbd">
x
"""
default_priority = 400
builders = ('html',)
pattern = re.compile(r'(-|\+|\^|\s+)')
def run(self, **kwargs: Any) -> None:
matcher = NodeMatcher(nodes.literal, classes=["kbd"])
for node in self.document.traverse(matcher): # type: nodes.literal
parts = self.pattern.split(node[-1].astext())
if len(parts) == 1:
continue
node.pop()
while parts:
key = parts.pop(0)
node += nodes.literal('', key, classes=["kbd"])
try:
# key separator (ex. -, +, ^)
sep = parts.pop(0)
node += nodes.Text(sep)
except IndexError:
pass
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_post_transform(KeyboardTransform)
return {
'version': 'builtin',
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -775,10 +775,11 @@ class PyDecoratorMixin:
if cls.__name__ != 'DirectiveAdapter': if cls.__name__ != 'DirectiveAdapter':
warnings.warn('PyDecoratorMixin is deprecated. ' warnings.warn('PyDecoratorMixin is deprecated. '
'Please check the implementation of %s' % cls, 'Please check the implementation of %s' % cls,
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
break break
else: else:
warnings.warn('PyDecoratorMixin is deprecated', RemovedInSphinx50Warning) warnings.warn('PyDecoratorMixin is deprecated',
RemovedInSphinx50Warning, stacklevel=2)
ret = super().handle_signature(sig, signode) # type: ignore ret = super().handle_signature(sig, signode) # type: ignore
signode.insert(0, addnodes.desc_addname('@', '@')) signode.insert(0, addnodes.desc_addname('@', '@'))

View File

@ -645,7 +645,7 @@ class StandardDomain(Domain):
def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None:
warnings.warn('StandardDomain.add_object() is deprecated.', warnings.warn('StandardDomain.add_object() is deprecated.',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
self.objects[objtype, name] = (docname, labelid) self.objects[objtype, name] = (docname, labelid)
@property @property

View File

@ -395,9 +395,9 @@ class Documenter:
except TypeError: except TypeError:
# retry without arguments for old documenters # retry without arguments for old documenters
args = self.format_args() args = self.format_args()
except Exception as err: except Exception:
logger.warning(__('error while formatting arguments for %s: %s') % logger.warning(__('error while formatting arguments for %s:') %
(self.fullname, err), type='autodoc') self.fullname, type='autodoc', exc_info=True)
args = None args = None
retann = self.retann retann = self.retann
@ -428,8 +428,12 @@ class Documenter:
# etc. don't support a prepended module name # etc. don't support a prepended module name
self.add_line(' :module: %s' % self.modname, sourcename) self.add_line(' :module: %s' % self.modname, sourcename)
def get_doc(self, ignore: int = 1) -> List[List[str]]: def get_doc(self, ignore: int = None) -> List[List[str]]:
"""Decode and return lines of the docstring(s) for the object.""" """Decode and return lines of the docstring(s) for the object."""
if ignore is not None:
warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
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,
self.parent, self.object_name) self.parent, self.object_name)
@ -741,8 +745,8 @@ class Documenter:
# parse right now, to get PycodeErrors on parsing (results will # parse right now, to get PycodeErrors on parsing (results will
# be cached anyway) # be cached anyway)
self.analyzer.find_attr_docs() self.analyzer.find_attr_docs()
except PycodeError as err: except PycodeError:
logger.debug('[autodoc] module analyzer failed: %s', err) logger.debug('[autodoc] module analyzer failed:', exc_info=True)
# no source file -- e.g. for builtin and C modules # no source file -- e.g. for builtin and C modules
self.analyzer = None self.analyzer = None
# at least add the module.__file__ as a dependency # at least add the module.__file__ as a dependency
@ -844,7 +848,7 @@ class ModuleDocumenter(Documenter):
if self.options.deprecated: if self.options.deprecated:
self.add_line(' :deprecated:', sourcename) self.add_line(' :deprecated:', sourcename)
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
if want_all: if want_all:
if (self.options.ignore_module_all or not if (self.options.ignore_module_all or not
hasattr(self.object, '__all__')): hasattr(self.object, '__all__')):
@ -970,7 +974,7 @@ class DocstringSignatureMixin:
break break
return result return result
def get_doc(self, ignore: int = 1) -> List[List[str]]: def get_doc(self, ignore: int = None) -> List[List[str]]:
lines = getattr(self, '_new_docstrings', None) lines = getattr(self, '_new_docstrings', None)
if lines is not None: if lines is not None:
return lines return lines
@ -1226,7 +1230,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), self.add_line(' ' + _('Bases: %s') % ', '.join(bases),
sourcename) sourcename)
def get_doc(self, ignore: int = 1) -> List[List[str]]: def get_doc(self, ignore: int = None) -> List[List[str]]:
lines = getattr(self, '_new_docstrings', None) lines = getattr(self, '_new_docstrings', None)
if lines is not None: if lines is not None:
return lines return lines
@ -1719,8 +1723,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
self.env.note_reread() self.env.note_reread()
return False return False
def get_doc(self, ignore: int = 1) -> List[List[str]]: def get_doc(self, ignore: int = None) -> List[List[str]]:
"""Decode and return lines of the docstring(s) for the object.""" """Decode and return lines of the docstring(s) for the object."""
if ignore is not None:
warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
name = self.objpath[-1] name = self.objpath[-1]
__slots__ = safe_getattr(self.parent, '__slots__', []) __slots__ = safe_getattr(self.parent, '__slots__', [])
if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str): if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str):
@ -1732,7 +1740,7 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]: def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]:
"""Returns registered Documenter classes""" """Returns registered Documenter classes"""
warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning) warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2)
return app.registry.documenters return app.registry.documenters

View File

@ -13,6 +13,7 @@ import traceback
import warnings import warnings
from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Tuple from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Tuple
from sphinx.pycode import ModuleAnalyzer
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.inspect import isclass, isenumclass, safe_getattr from sphinx.util.inspect import isclass, isenumclass, safe_getattr
@ -127,7 +128,7 @@ class Attribute(NamedTuple):
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
analyzer: Any = None) -> Dict[str, Attribute]: analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]:
"""Get members and attributes of target object.""" """Get members and attributes of target object."""
from sphinx.ext.autodoc import INSTANCEATTR from sphinx.ext.autodoc import INSTANCEATTR
@ -143,8 +144,9 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
members[name] = Attribute(name, True, value) members[name] = Attribute(name, True, value)
superclass = subject.__mro__[1] superclass = subject.__mro__[1]
for name, value in obj_dict.items(): for name in obj_dict:
if name not in superclass.__dict__: if name not in superclass.__dict__:
value = safe_getattr(subject, name)
members[name] = Attribute(name, True, value) members[name] = Attribute(name, True, value)
# members in __slots__ # members in __slots__

View File

@ -122,7 +122,7 @@ class AutosummaryRenderer:
RemovedInSphinx50Warning, stacklevel=2) RemovedInSphinx50Warning, stacklevel=2)
if template_dir: if template_dir:
warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')]
loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path,
@ -274,11 +274,11 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
overwrite: bool = True) -> None: overwrite: bool = True) -> None:
if builder: if builder:
warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', warnings.warn('builder argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
if template_dir: if template_dir:
warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
showed_sources = list(sorted(sources)) showed_sources = list(sorted(sources))
if len(showed_sources) > 20: if len(showed_sources) > 20:
@ -371,7 +371,7 @@ def find_autosummary_in_docstring(name: str, module: str = None, filename: str =
""" """
if module: if module:
warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
try: try:
real_name, obj, parent, modname = import_by_name(name) real_name, obj, parent, modname = import_by_name(name)

View File

@ -168,11 +168,10 @@ class Config:
**If False**:: **If False**::
.. attribute:: attr1 .. attribute:: attr1
:type: int
Description of `attr1` Description of `attr1`
:type: int
napoleon_use_param : :obj:`bool` (Defaults to True) napoleon_use_param : :obj:`bool` (Defaults to True)
True to use a ``:param:`` role for each function parameter. False to True to use a ``:param:`` role for each function parameter. False to
use a single ``:parameters:`` role for all the parameters. use a single ``:parameters:`` role for all the parameters.

View File

@ -584,13 +584,12 @@ class GoogleDocstring:
lines.append('.. attribute:: ' + _name) lines.append('.. attribute:: ' + _name)
if self._opt and 'noindex' in self._opt: if self._opt and 'noindex' in self._opt:
lines.append(' :noindex:') lines.append(' :noindex:')
if _type:
lines.extend(self._indent([':type: %s' % _type], 3))
lines.append('') lines.append('')
fields = self._format_field('', '', _desc) fields = self._format_field('', '', _desc)
lines.extend(self._indent(fields, 3)) lines.extend(self._indent(fields, 3))
if _type:
lines.append('')
lines.extend(self._indent([':type: %s' % _type], 3))
lines.append('') lines.append('')
if self._config.napoleon_use_ivar: if self._config.napoleon_use_ivar:
lines.append('') lines.append('')

View File

@ -63,7 +63,7 @@ class Parser(docutils.parsers.Parser):
@property @property
def app(self) -> "Sphinx": def app(self) -> "Sphinx":
warnings.warn('parser.app is deprecated.', RemovedInSphinx50Warning) warnings.warn('parser.app is deprecated.', RemovedInSphinx50Warning, stacklevel=2)
return self._app return self._app

View File

@ -10,10 +10,13 @@
import re import re
import sys import sys
import warnings
from typing import Dict, List from typing import Dict, List
from docutils.parsers.rst.states import Body from docutils.parsers.rst.states import Body
from sphinx.deprecation import RemovedInSphinx50Warning
field_list_item_re = re.compile(Body.patterns['field_marker']) field_list_item_re = re.compile(Body.patterns['field_marker'])
@ -42,7 +45,7 @@ def extract_metadata(s: str) -> Dict[str, str]:
return metadata return metadata
def prepare_docstring(s: str, ignore: int = 1, tabsize: int = 8) -> List[str]: def prepare_docstring(s: str, ignore: int = None, tabsize: int = 8) -> 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.
@ -51,6 +54,12 @@ def prepare_docstring(s: str, ignore: int = 1, tabsize: int = 8) -> List[str]:
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.
""" """
if ignore is None:
ignore = 1
else:
warnings.warn("The 'ignore' argument to parepare_docstring() is deprecated.",
RemovedInSphinx50Warning, stacklevel=2)
lines = s.expandtabs(tabsize).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

View File

@ -58,7 +58,7 @@ def getargspec(func: Callable) -> Any:
"""Like inspect.getfullargspec but supports bound methods, and wrapped """Like inspect.getfullargspec but supports bound methods, and wrapped
methods.""" methods."""
warnings.warn('sphinx.ext.inspect.getargspec() is deprecated', warnings.warn('sphinx.ext.inspect.getargspec() is deprecated',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
# On 3.5+, signature(int) or similar raises ValueError. On 3.4, it # On 3.5+, signature(int) or similar raises ValueError. On 3.4, it
# succeeds with a bogus signature. We want a TypeError uniformly, to # succeeds with a bogus signature. We want a TypeError uniformly, to
# match historical behavior. # match historical behavior.

View File

@ -411,9 +411,13 @@ class WarningIsErrorFilter(logging.Filter):
message = record.msg # use record.msg itself message = record.msg # use record.msg itself
if location: if location:
raise SphinxWarning(location + ":" + str(message)) exc = SphinxWarning(location + ":" + str(message))
else: else:
raise SphinxWarning(message) exc = SphinxWarning(message)
if record.exc_info is not None:
raise exc from record.exc_info[1]
else:
raise exc
else: else:
return True return True

View File

@ -58,8 +58,8 @@ class NodeMatcher:
# => [<reference ...>, <reference ...>, ...] # => [<reference ...>, <reference ...>, ...]
""" """
def __init__(self, *classes: "Type[Node]", **attrs: Any) -> None: def __init__(self, *node_classes: "Type[Node]", **attrs: Any) -> None:
self.classes = classes self.classes = node_classes
self.attrs = attrs self.attrs = attrs
def match(self, node: Node) -> bool: def match(self, node: Node) -> bool:

View File

@ -93,7 +93,7 @@ class LaTeXWriter(writers.Writer):
visitor = self.builder.create_translator(self.document, self.builder, self.theme) visitor = self.builder.create_translator(self.document, self.builder, self.theme)
except TypeError: except TypeError:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".', warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
visitor = self.builder.create_translator(self.document, self.builder) visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor) self.document.walkabout(visitor)
@ -289,7 +289,7 @@ class LaTeXTranslator(SphinxTranslator):
if theme is None: if theme is None:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".', warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning) RemovedInSphinx50Warning, stacklevel=2)
# flags # flags
self.in_title = 0 self.in_title = 0

View File

@ -16,3 +16,8 @@ class EnumCls(enum.Enum):
def say_hello(self): def say_hello(self):
"""a method says hello to you.""" """a method says hello to you."""
pass pass
@classmethod
def say_goodbye(cls):
"""a classmethod says good-bye to you."""
pass

View File

@ -1112,8 +1112,7 @@ def test_slots(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app): def test_enum_class(app):
options = {"members": None, options = {"members": None}
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options) actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
assert list(actual) == [ assert list(actual) == [
'', '',
@ -1123,6 +1122,13 @@ def test_enum_class(app):
' this is enum class', ' this is enum class',
'', '',
'', '',
' .. py:method:: EnumCls.say_goodbye()',
' :module: target.enum',
' :classmethod:',
'',
' a classmethod says good-bye to you.',
'',
'',
' .. py:method:: EnumCls.say_hello()', ' .. py:method:: EnumCls.say_hello()',
' :module: target.enum', ' :module: target.enum',
'', '',
@ -1149,11 +1155,6 @@ def test_enum_class(app):
'', '',
' doc for val3', ' doc for val3',
'', '',
'',
' .. py:attribute:: EnumCls.val4',
' :module: target.enum',
' :value: 34',
''
] ]
# checks for an attribute of EnumClass # checks for an attribute of EnumClass

View File

@ -53,22 +53,19 @@ class NamedtupleSubclassTest(BaseDocstringTest):
Sample namedtuple subclass Sample namedtuple subclass
.. attribute:: attr1 .. attribute:: attr1
:type: Arbitrary type
Quick description of attr1 Quick description of attr1
:type: Arbitrary type
.. attribute:: attr2 .. attribute:: attr2
:type: Another arbitrary type
Quick description of attr2 Quick description of attr2
:type: Another arbitrary type
.. attribute:: attr3 .. attribute:: attr3
:type: Type
Adds a newline after the type Adds a newline after the type
:type: Type
""" """
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@ -412,10 +409,9 @@ Attributes:
actual = str(GoogleDocstring(docstring)) actual = str(GoogleDocstring(docstring))
expected = """\ expected = """\
.. attribute:: in_attr .. attribute:: in_attr
:type: :class:`numpy.ndarray`
super-dooper attribute super-dooper attribute
:type: :class:`numpy.ndarray`
""" """
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@ -427,10 +423,9 @@ Attributes:
actual = str(GoogleDocstring(docstring)) actual = str(GoogleDocstring(docstring))
expected = """\ expected = """\
.. attribute:: in_attr .. attribute:: in_attr
:type: numpy.ndarray
super-dooper attribute super-dooper attribute
:type: numpy.ndarray
""" """
self.assertEqual(expected, actual) self.assertEqual(expected, actual)

View File

@ -16,6 +16,7 @@ from docutils.parsers.rst import Parser as RstParser
from docutils.transforms.universal import SmartQuotes from docutils.transforms.universal import SmartQuotes
from sphinx import addnodes from sphinx import addnodes
from sphinx.builders.html.transforms import KeyboardTransform
from sphinx.builders.latex import LaTeXBuilder from sphinx.builders.latex import LaTeXBuilder
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.testing.util import Struct, assert_node from sphinx.testing.util import Struct, assert_node
@ -94,6 +95,7 @@ class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator):
def verify_re_html(app, parse): def verify_re_html(app, parse):
def verify(rst, html_expected): def verify(rst, html_expected):
document = parse(rst) document = parse(rst)
KeyboardTransform(document).apply()
html_translator = ForgivingHTMLTranslator(document, app.builder) html_translator = ForgivingHTMLTranslator(document, app.builder)
document.walkabout(html_translator) document.walkabout(html_translator)
html_translated = ''.join(html_translator.fragment).strip() html_translated = ''.join(html_translator.fragment).strip()
@ -237,6 +239,32 @@ def get_verifier(verify, verify_re):
'<p><kbd class="kbd docutils literal notranslate">space</kbd></p>', '<p><kbd class="kbd docutils literal notranslate">space</kbd></p>',
'\\sphinxkeyboard{\\sphinxupquote{space}}', '\\sphinxkeyboard{\\sphinxupquote{space}}',
), ),
(
# kbd role
'verify',
':kbd:`Control+X`',
('<p><kbd class="kbd docutils literal notranslate">'
'<kbd class="kbd docutils literal notranslate">Control</kbd>'
'+'
'<kbd class="kbd docutils literal notranslate">X</kbd>'
'</kbd></p>'),
'\\sphinxkeyboard{\\sphinxupquote{Control+X}}',
),
(
# kbd role
'verify',
':kbd:`M-x M-s`',
('<p><kbd class="kbd docutils literal notranslate">'
'<kbd class="kbd docutils literal notranslate">M</kbd>'
'-'
'<kbd class="kbd docutils literal notranslate">x</kbd>'
' '
'<kbd class="kbd docutils literal notranslate">M</kbd>'
'-'
'<kbd class="kbd docutils literal notranslate">s</kbd>'
'</kbd></p>'),
'\\sphinxkeyboard{\\sphinxupquote{M\\sphinxhyphen{}x M\\sphinxhyphen{}s}}',
),
( (
# non-interpolation of dashes in option role # non-interpolation of dashes in option role
'verify_re', 'verify_re',