Merge branch '4.0.x' into 4.x

This commit is contained in:
Takeshi KOMIYA 2021-04-19 01:08:54 +09:00
commit 668bc9eec9
32 changed files with 1016 additions and 505 deletions

11
CHANGES
View File

@ -30,15 +30,26 @@ Dependencies
Incompatible changes
--------------------
* #9023: Change the CSS classes on :rst:role:`cpp:expr` and
:rst:role:`cpp:texpr`.
Deprecated
----------
Features added
--------------
* #8818: autodoc: Super class having ``Any`` arguments causes nit-picky warning
* #9103: LaTeX: imgconverter: conversion runs even if not needed
* #8127: py domain: Ellipsis in info-field-list causes nit-picky warning
* #9023: More CSS classes on domain descriptions, see :ref:`nodes` for details.
Bugs fixed
----------
* C, C++, fix ``KeyError`` when an ``alias`` directive is the first C/C++
directive in a file with another C/C++ directive later.
Testing
--------

View File

@ -171,10 +171,26 @@ as metadata of the extension. Metadata keys currently recognized are:
source files can be used when the extension is loaded. It defaults to
``False``, i.e. you have to explicitly specify your extension to be
parallel-read-safe after checking that it is.
.. note:: The *parallel-read-safe* extension must satisfy the following
conditions:
* The core logic of the extension is parallely executable during
the reading phase.
* It has event handlers for :event:`env-merge-info` and
:event:`env-purge-doc` events if it stores dataa to the build
environment object (env) during the reading phase.
* ``'parallel_write_safe'``: a boolean that specifies if parallel writing of
output files can be used when the extension is loaded. Since extensions
usually don't negatively influence the process, this defaults to ``True``.
.. note:: The *parallel-write-safe* extension must satisfy the following
conditions:
* The core logic of the extension is parallely executable during
the writing phase.
APIs used for writing extensions
--------------------------------

View File

@ -8,18 +8,32 @@ Doctree node classes added by Sphinx
Nodes for domain-specific object descriptions
---------------------------------------------
Top-level nodes
...............
These nodes form the top-most levels of object descriptions.
.. autoclass:: desc
.. autoclass:: desc_signature
.. autoclass:: desc_signature_line
.. autoclass:: desc_content
.. autoclass:: desc_inline
Nodes for high-level structure in signatures
............................................
These nodes occur in in non-multiline :py:class:`desc_signature` nodes
and in :py:class:`desc_signature_line` nodes.
.. autoclass:: desc_name
.. autoclass:: desc_addname
.. autoclass:: desc_type
.. autoclass:: desc_returns
.. autoclass:: desc_name
.. autoclass:: desc_parameterlist
.. autoclass:: desc_parameter
.. autoclass:: desc_optional
.. autoclass:: desc_annotation
.. autoclass:: desc_content
New admonition-like constructs
------------------------------

View File

@ -51,6 +51,10 @@ The extension adds a config value:
that generate links, i.e. ``:issue:`this issue <123>```. In this case, the
*caption* is not relevant.
.. versionchanged:: 4.0
Support to substitute by '%s' in the caption.
.. note::
Since links are generated from the role in the reading stage, they appear as

View File

@ -301,6 +301,7 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = False
napoleon_type_aliases = None
napoleon_attr_annotations = True
@ -510,6 +511,16 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
:returns: *bool* -- True if successful, False otherwise
.. confval:: napoleon_preprocess_types
True to convert the type definitions in the docstrings as references.
Defaults to *True*.
.. versionadded:: 3.2.1
.. versionchanged:: 3.5
Do preprocess the Google style docstrings also.
.. confval:: napoleon_type_aliases
A mapping to translate type names to other names or references. Works
@ -570,4 +581,4 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
.. versionadded:: 1.8
.. versionchanged:: 3.5
Support ``params_style`` and ``returns_style``
Support ``params_style`` and ``returns_style``

View File

@ -115,25 +115,53 @@ class toctree(nodes.General, nodes.Element, translatable):
return messages
# domain-specific object descriptions (class, function etc.)
#############################################################
# Domain-specific object descriptions (class, function etc.)
#############################################################
class _desc_classes_injector(nodes.Element, not_smartquotable):
"""Helper base class for injecting a fixes list of classes.
Use as the first base class.
"""
classes: List[str] = []
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self['classes'].extend(self.classes)
# Top-level nodes
#################
class desc(nodes.Admonition, nodes.Element):
"""Node for object descriptions.
"""Node for a list of object signatures and a common description of them.
This node is similar to a "definition list" with one definition. It
contains one or more ``desc_signature`` and a ``desc_content``.
Contains one or more :py:class:`desc_signature` nodes
and then a single :py:class:`desc_content` node.
This node always has two classes:
- The name of the domain it belongs to, e.g., ``py`` or ``cpp``.
- The name of the object type in the domain, e.g., ``function``.
"""
# TODO: can we introduce a constructor
# that forces the specification of the domain and objtyp?
class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement):
"""Node for object signatures.
The "term" part of the custom Sphinx definition list.
class desc_signature(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.TextElement):
"""Node for a single object signature.
As default the signature is a single line signature,
but set ``is_multiline = True`` to describe a multi-line signature.
In that case all child nodes must be ``desc_signature_line`` nodes.
As default the signature is a single-line signature.
Set ``is_multiline = True`` to describe a multi-line signature.
In that case all child nodes must be :py:class:`desc_signature_line` nodes.
This node always has the classes ``sig``, ``sig-object``, and the domain it belongs to.
"""
# Note: the domain name is being added through a post-transform DescSigAddDomainAsClass
classes = ['sig', 'sig-object']
@property
def child_text_separator(self):
@ -144,18 +172,63 @@ class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement):
class desc_signature_line(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a line in a multi-line object signatures.
"""Node for a line in a multi-line object signature.
It should only be used in a ``desc_signature`` with ``is_multiline`` set.
It should only be used as a child of a :py:class:`desc_signature`
with ``is_multiline`` set to ``True``.
Set ``add_permalink = True`` for the line that should get the permalink.
"""
sphinx_line_type = ''
class desc_content(nodes.General, nodes.Element):
"""Node for object description content.
Must be the last child node in a :py:class:`desc` node.
"""
class desc_inline(_desc_classes_injector, nodes.Inline, nodes.TextElement):
"""Node for a signature fragment in inline text.
This is for example used for roles like :rst:role:`cpp:expr`.
This node always has the classes ``sig``, ``sig-inline``,
and the name of the domain it belongs to.
"""
classes = ['sig', 'sig-inline']
def __init__(self, domain: str, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self['classes'].append(domain)
# Nodes for high-level structure in signatures
##############################################
# nodes to use within a desc_signature or desc_signature_line
class desc_addname(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for additional name parts (module name, class name)."""
class desc_name(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for the main object name.
For example, in the declaration of a Python class ``MyModule.MyClass``,
the main name is ``MyClass``.
This node always has the class ``sig-name``.
"""
classes = ['sig-name', 'descname'] # 'descname' is for backwards compatibility
class desc_addname(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for additional name parts for an object.
For example, in the declaration of a Python class ``MyModule.MyClass``,
the additional name part is ``MyModule.``.
This node always has the class ``sig-prename``.
"""
# 'descclassname' is for backwards compatibility
classes = ['sig-prename', 'descclassname']
# compatibility alias
@ -168,14 +241,11 @@ class desc_type(nodes.Part, nodes.Inline, nodes.FixedTextElement):
class desc_returns(desc_type):
"""Node for a "returns" annotation (a la -> in Python)."""
def astext(self) -> str:
return ' -> ' + super().astext()
class desc_name(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for the main object name."""
class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a general parameter list."""
child_text_separator = ', '
@ -200,14 +270,14 @@ class desc_annotation(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for signature annotations (not Python 3-style annotations)."""
class desc_content(nodes.General, nodes.Element):
"""Node for object description content.
# Leaf nodes for markup of text fragments
#########################################
This is the "definition" part of the custom Sphinx definition list.
"""
# Signature text elements, generally translated to node.inline
# in SigElementFallbackTransform.
# When adding a new one, add it to SIG_ELEMENTS.
class desc_sig_element(nodes.inline):
class desc_sig_element(nodes.inline, _desc_classes_injector):
"""Common parent class of nodes for inline text of a signature."""
classes: List[str] = []
@ -217,8 +287,20 @@ class desc_sig_element(nodes.inline):
self['classes'].extend(self.classes)
# to not reinvent the wheel, the classes in the following desc_sig classes
# are based on those used in Pygments
class desc_sig_space(desc_sig_element):
"""Node for a space in a signature."""
classes = ["w"]
def __init__(self, rawsource: str = '', text: str = ' ',
*children: Element, **attributes: Any) -> None:
super().__init__(rawsource, text, *children, **attributes)
class desc_sig_name(desc_sig_element):
"""Node for a name in a signature."""
"""Node for an identifier in a signature."""
classes = ["n"]
@ -228,10 +310,44 @@ class desc_sig_operator(desc_sig_element):
class desc_sig_punctuation(desc_sig_element):
"""Node for a punctuation in a signature."""
"""Node for punctuation in a signature."""
classes = ["p"]
class desc_sig_keyword(desc_sig_element):
"""Node for a general keyword in a signature."""
classes = ["k"]
class desc_sig_keyword_type(desc_sig_element):
"""Node for a keyword which is a built-in type in a signature."""
classes = ["kt"]
class desc_sig_literal_number(desc_sig_element):
"""Node for a numeric literal in a signature."""
classes = ["m"]
class desc_sig_literal_string(desc_sig_element):
"""Node for a string literal in a signature."""
classes = ["s"]
class desc_sig_literal_char(desc_sig_element):
"""Node for a character literal in a signature."""
classes = ["sc"]
SIG_ELEMENTS = [desc_sig_space,
desc_sig_name,
desc_sig_operator,
desc_sig_punctuation,
desc_sig_keyword, desc_sig_keyword_type,
desc_sig_literal_number, desc_sig_literal_string, desc_sig_literal_char]
###############################################################
# new admonition-like constructs
class versionmodified(nodes.Admonition, nodes.TextElement):
@ -336,6 +452,7 @@ class pending_xref(nodes.Inline, nodes.Element):
These nodes are resolved before writing output, in
BuildEnvironment.resolve_references.
"""
child_text_separator = ''
class pending_xref_condition(nodes.Inline, nodes.TextElement):
@ -412,21 +529,25 @@ class manpage(nodes.Inline, nodes.FixedTextElement):
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_node(toctree)
app.add_node(desc)
app.add_node(desc_signature)
app.add_node(desc_signature_line)
app.add_node(desc_content)
app.add_node(desc_inline)
app.add_node(desc_name)
app.add_node(desc_addname)
app.add_node(desc_type)
app.add_node(desc_returns)
app.add_node(desc_name)
app.add_node(desc_parameterlist)
app.add_node(desc_parameter)
app.add_node(desc_optional)
app.add_node(desc_annotation)
app.add_node(desc_content)
app.add_node(desc_sig_name)
app.add_node(desc_sig_operator)
app.add_node(desc_sig_punctuation)
for n in SIG_ELEMENTS:
app.add_node(n)
app.add_node(versionmodified)
app.add_node(seealso)
app.add_node(productionlist)

View File

@ -172,6 +172,7 @@ class ObjectDescription(SphinxDirective, Generic[T]):
node['noindex'] = noindex = ('noindex' in self.options)
if self.domain:
node['classes'].append(self.domain)
node['classes'].append(node['objtype'])
self.names: List[T] = []
signatures = self.get_signatures()

View File

@ -9,8 +9,7 @@
"""
import re
from typing import (Any, Callable, Dict, Generator, Iterator, List, Tuple, Type, TypeVar,
Union, cast)
from typing import Any, Callable, Dict, Generator, Iterator, List, Tuple, TypeVar, Union, cast
from docutils import nodes
from docutils.nodes import Element, Node, TextElement, system_message
@ -132,6 +131,10 @@ class ASTIdentifier(ASTBaseBase):
prefix: str, symbol: "Symbol") -> None:
# note: slightly different signature of describe_signature due to the prefix
verify_description_mode(mode)
if self.is_anon():
node = addnodes.desc_sig_name(text="[anonymous]")
else:
node = addnodes.desc_sig_name(self.identifier, self.identifier)
if mode == 'markType':
targetText = prefix + self.identifier
pnode = addnodes.pending_xref('', refdomain='c',
@ -139,21 +142,14 @@ class ASTIdentifier(ASTBaseBase):
reftarget=targetText, modname=None,
classname=None)
pnode['c:parent_key'] = symbol.get_lookup_key()
if self.is_anon():
pnode += nodes.strong(text="[anonymous]")
else:
pnode += nodes.Text(self.identifier)
pnode += node
signode += pnode
elif mode == 'lastIsName':
if self.is_anon():
signode += nodes.strong(text="[anonymous]")
else:
signode += addnodes.desc_name(self.identifier, self.identifier)
nameNode = addnodes.desc_name()
nameNode += node
signode += nameNode
elif mode == 'noneIsName':
if self.is_anon():
signode += nodes.strong(text="[anonymous]")
else:
signode += nodes.Text(self.identifier)
signode += node
else:
raise Exception('Unknown description mode: %s' % mode)
@ -184,18 +180,18 @@ class ASTNestedName(ASTBase):
# just print the name part, with template args, not template params
if mode == 'noneIsName':
if self.rooted:
assert False, "Can this happen?" # TODO
signode += nodes.Text('.')
for i in range(len(self.names)):
if i != 0:
assert False, "Can this happen?" # TODO
signode += nodes.Text('.')
n = self.names[i]
n.describe_signature(signode, mode, env, '', symbol)
elif mode == 'param':
assert not self.rooted, str(self)
assert len(self.names) == 1
node = nodes.emphasis()
self.names[0].describe_signature(node, 'noneIsName', env, '', symbol)
signode += node
self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol)
elif mode == 'markType' or mode == 'lastIsName' or mode == 'markName':
# Each element should be a pending xref targeting the complete
# prefix.
@ -213,13 +209,13 @@ class ASTNestedName(ASTBase):
if self.rooted:
prefix += '.'
if mode == 'lastIsName' and len(names) == 0:
signode += nodes.Text('.')
signode += addnodes.desc_sig_punctuation('.', '.')
else:
dest += nodes.Text('.')
dest += addnodes.desc_sig_punctuation('.', '.')
for i in range(len(names)):
ident = names[i]
if not first:
dest += nodes.Text('.')
dest += addnodes.desc_sig_punctuation('.', '.')
prefix += '.'
first = False
txt_ident = str(ident)
@ -228,7 +224,7 @@ class ASTNestedName(ASTBase):
prefix += txt_ident
if mode == 'lastIsName':
if len(self.names) > 1:
dest += addnodes.desc_addname('.', '.')
dest += addnodes.desc_sig_punctuation('.', '.')
signode += dest
self.names[-1].describe_signature(signode, mode, env, '', symbol)
else:
@ -262,7 +258,8 @@ class ASTBooleanLiteral(ASTLiteral):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(str(self)))
txt = str(self)
signode += addnodes.desc_sig_keyword(txt, txt)
class ASTNumberLiteral(ASTLiteral):
@ -275,7 +272,7 @@ class ASTNumberLiteral(ASTLiteral):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
signode += addnodes.desc_sig_literal_number(txt, txt)
class ASTCharLiteral(ASTLiteral):
@ -297,7 +294,7 @@ class ASTCharLiteral(ASTLiteral):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
signode += addnodes.desc_sig_literal_char(txt, txt)
class ASTStringLiteral(ASTLiteral):
@ -310,7 +307,7 @@ class ASTStringLiteral(ASTLiteral):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
signode += addnodes.desc_sig_literal_string(txt, txt)
class ASTIdExpression(ASTExpression):
@ -341,9 +338,9 @@ class ASTParenExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('(', '('))
signode += addnodes.desc_sig_punctuation('(', '(')
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')', ')'))
signode += addnodes.desc_sig_punctuation(')', ')')
# Postfix expressions
@ -374,9 +371,9 @@ class ASTPostfixArray(ASTPostfixOp):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('['))
signode += addnodes.desc_sig_punctuation('[', '[')
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(']'))
signode += addnodes.desc_sig_punctuation(']', ']')
class ASTPostfixInc(ASTPostfixOp):
@ -385,7 +382,7 @@ class ASTPostfixInc(ASTPostfixOp):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('++'))
signode += addnodes.desc_sig_operator('++', '++')
class ASTPostfixDec(ASTPostfixOp):
@ -394,7 +391,7 @@ class ASTPostfixDec(ASTPostfixOp):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('--'))
signode += addnodes.desc_sig_operator('--', '--')
class ASTPostfixMemberOfPointer(ASTPostfixOp):
@ -406,7 +403,7 @@ class ASTPostfixMemberOfPointer(ASTPostfixOp):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('->'))
signode += addnodes.desc_sig_operator('->', '->')
self.name.describe_signature(signode, 'noneIsName', env, symbol)
@ -444,9 +441,11 @@ class ASTUnaryOpExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.op))
if self.op[0] in 'cn':
signode.append(nodes.Text(" "))
signode += addnodes.desc_sig_keyword(self.op, self.op)
signode += addnodes.desc_sig_space()
else:
signode += addnodes.desc_sig_operator(self.op, self.op)
self.expr.describe_signature(signode, mode, env, symbol)
@ -459,9 +458,10 @@ class ASTSizeofType(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof('))
signode += addnodes.desc_sig_keyword('sizeof', 'sizeof')
signode += addnodes.desc_sig_punctuation('(', '(')
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
signode += addnodes.desc_sig_punctuation(')', ')')
class ASTSizeofExpr(ASTExpression):
@ -473,7 +473,8 @@ class ASTSizeofExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof '))
signode += addnodes.desc_sig_keyword('sizeof', 'sizeof')
signode += addnodes.desc_sig_space()
self.expr.describe_signature(signode, mode, env, symbol)
@ -486,9 +487,10 @@ class ASTAlignofExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('alignof('))
signode += addnodes.desc_sig_keyword('alignof', 'alignof')
signode += addnodes.desc_sig_punctuation('(', '(')
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
signode += addnodes.desc_sig_punctuation(')', ')')
# Other expressions
@ -508,9 +510,9 @@ class ASTCastExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('('))
signode += addnodes.desc_sig_punctuation('(', '(')
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
signode += addnodes.desc_sig_punctuation(')', ')')
self.expr.describe_signature(signode, mode, env, symbol)
@ -535,9 +537,13 @@ class ASTBinOpExpr(ASTBase):
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
signode.append(nodes.Text(self.ops[i - 1]))
signode.append(nodes.Text(' '))
signode += addnodes.desc_sig_space()
op = self.ops[i - 1]
if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'):
signode += addnodes.desc_sig_keyword(op, op)
else:
signode += addnodes.desc_sig_operator(op, op)
signode += addnodes.desc_sig_space()
self.exprs[i].describe_signature(signode, mode, env, symbol)
@ -562,9 +568,13 @@ class ASTAssignmentExpr(ASTExpression):
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
signode.append(nodes.Text(self.ops[i - 1]))
signode.append(nodes.Text(' '))
signode += addnodes.desc_sig_space()
op = self.ops[i - 1]
if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'):
signode += addnodes.desc_sig_keyword(op, op)
else:
signode += addnodes.desc_sig_operator(op, op)
signode += addnodes.desc_sig_space()
self.exprs[i].describe_signature(signode, mode, env, symbol)
@ -580,7 +590,7 @@ class ASTFallbackExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += nodes.Text(self.expr)
signode += nodes.literal(self.expr, self.expr)
################################################################################
@ -600,7 +610,7 @@ class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += nodes.Text(str(self.name))
signode += addnodes.desc_sig_keyword_type(self.name, self.name)
class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
@ -623,8 +633,8 @@ class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
if self.prefix:
signode += addnodes.desc_annotation(self.prefix, self.prefix)
signode += nodes.Text(' ')
signode += addnodes.desc_sig_keyword(self.prefix, self.prefix)
signode += addnodes.desc_sig_space()
self.nestedName.describe_signature(signode, mode, env, symbol=symbol)
@ -647,7 +657,7 @@ class ASTFunctionParameter(ASTBase):
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
if self.ellipsis:
signode += nodes.Text('...')
signode += addnodes.desc_sig_punctuation('...', '...')
else:
self.arg.describe_signature(signode, mode, env, symbol=symbol)
@ -688,17 +698,18 @@ class ASTParameters(ASTBase):
paramlist += param
signode += paramlist
else:
signode += nodes.Text('(', '(')
signode += addnodes.desc_sig_punctuation('(', '(')
first = True
for arg in self.args:
if not first:
signode += nodes.Text(', ', ', ')
signode += addnodes.desc_sig_punctuation(',', ',')
signode += addnodes.desc_sig_space()
first = False
arg.describe_signature(signode, 'markType', env, symbol=symbol)
signode += nodes.Text(')', ')')
signode += addnodes.desc_sig_punctuation(')', ')')
for attr in self.attrs:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
attr.describe_signature(signode)
@ -744,12 +755,12 @@ class ASTDeclSpecsSimple(ASTBaseBase):
def describe_signature(self, modifiers: List[Node]) -> None:
def _add(modifiers: List[Node], text: str) -> None:
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_annotation(text, text))
modifiers.append(addnodes.desc_sig_space())
modifiers.append(addnodes.desc_sig_keyword(text, text))
for attr in self.attrs:
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_sig_space())
modifiers.append(attr.describe_signature(modifiers))
if self.storage:
_add(modifiers, self.storage)
@ -799,24 +810,19 @@ class ASTDeclSpecs(ASTBase):
verify_description_mode(mode)
modifiers: List[Node] = []
def _add(modifiers: List[Node], text: str) -> None:
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_annotation(text, text))
self.leftSpecs.describe_signature(modifiers)
for m in modifiers:
signode += m
if self.trailingTypeSpec:
if len(modifiers) > 0:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
self.trailingTypeSpec.describe_signature(signode, mode, env,
symbol=symbol)
modifiers = []
self.rightSpecs.describe_signature(modifiers)
if len(modifiers) > 0:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
for m in modifiers:
signode += m
@ -857,13 +863,13 @@ class ASTArray(ASTBase):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode.append(nodes.Text("["))
signode += addnodes.desc_sig_punctuation('[', '[')
addSpace = False
def _add(signode: TextElement, text: str) -> bool:
if addSpace:
signode += nodes.Text(' ')
signode += addnodes.desc_annotation(text, text)
signode += addnodes.desc_sig_space()
signode += addnodes.desc_sig_keyword(text, text)
return True
if self.static:
@ -875,12 +881,12 @@ class ASTArray(ASTBase):
if self.const:
addSpace = _add(signode, 'const')
if self.vla:
signode.append(nodes.Text('*'))
signode += addnodes.desc_sig_punctuation('*', '*')
elif self.size:
if addSpace:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
self.size.describe_signature(signode, 'markType', env, symbol)
signode.append(nodes.Text("]"))
signode += addnodes.desc_sig_punctuation(']', ']')
class ASTDeclarator(ASTBase):
@ -964,7 +970,9 @@ class ASTDeclaratorNameBitField(ASTDeclarator):
verify_description_mode(mode)
if self.declId:
self.declId.describe_signature(signode, mode, env, symbol)
signode += nodes.Text(' : ', ' : ')
signode += addnodes.desc_sig_space()
signode += addnodes.desc_sig_punctuation(':', ':')
signode += addnodes.desc_sig_space()
self.size.describe_signature(signode, mode, env, symbol)
@ -1016,28 +1024,28 @@ class ASTDeclaratorPtr(ASTDeclarator):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode += nodes.Text("*")
signode += addnodes.desc_sig_punctuation('*', '*')
for a in self.attrs:
a.describe_signature(signode)
if len(self.attrs) > 0 and (self.restrict or self.volatile or self.const):
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
def _add_anno(signode: TextElement, text: str) -> None:
signode += addnodes.desc_annotation(text, text)
signode += addnodes.desc_sig_keyword(text, text)
if self.restrict:
_add_anno(signode, 'restrict')
if self.volatile:
if self.restrict:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
_add_anno(signode, 'volatile')
if self.const:
if self.restrict or self.volatile:
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
_add_anno(signode, 'const')
if self.const or self.volatile or self.restrict or len(self.attrs) > 0:
if self.next.require_space_after_declSpecs():
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
self.next.describe_signature(signode, mode, env, symbol)
@ -1070,9 +1078,9 @@ class ASTDeclaratorParen(ASTDeclarator):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode += nodes.Text('(')
signode += addnodes.desc_sig_punctuation('(', '(')
self.inner.describe_signature(signode, mode, env, symbol)
signode += nodes.Text(')')
signode += addnodes.desc_sig_punctuation(')', ')')
self.next.describe_signature(signode, "noneIsName", env, symbol)
@ -1090,15 +1098,16 @@ class ASTParenExprList(ASTBaseParenExprList):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode.append(nodes.Text('('))
signode += addnodes.desc_sig_punctuation('(', '(')
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
signode += addnodes.desc_sig_punctuation(',', ',')
signode += addnodes.desc_sig_space()
else:
first = False
e.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
signode += addnodes.desc_sig_punctuation(')', ')')
class ASTBracedInitList(ASTBase):
@ -1114,17 +1123,18 @@ class ASTBracedInitList(ASTBase):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode.append(nodes.Text('{'))
signode += addnodes.desc_sig_punctuation('{', '{')
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
signode += addnodes.desc_sig_punctuation(',', ',')
signode += addnodes.desc_sig_space()
else:
first = False
e.describe_signature(signode, mode, env, symbol)
if self.trailingComma:
signode.append(nodes.Text(','))
signode.append(nodes.Text('}'))
signode += addnodes.desc_sig_punctuation(',', ',')
signode += addnodes.desc_sig_punctuation('}', '}')
class ASTInitializer(ASTBase):
@ -1144,7 +1154,9 @@ class ASTInitializer(ASTBase):
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
if self.hasAssign:
signode.append(nodes.Text(' = '))
signode += addnodes.desc_sig_space()
signode += addnodes.desc_sig_punctuation('=', '=')
signode += addnodes.desc_sig_space()
self.value.describe_signature(signode, 'markType', env, symbol)
@ -1187,7 +1199,7 @@ class ASTType(ASTBase):
self.declSpecs.describe_signature(signode, 'markType', env, symbol)
if (self.decl.require_space_after_declSpecs() and
len(str(self.declSpecs)) > 0):
signode += nodes.Text(' ')
signode += addnodes.desc_sig_space()
# for parameters that don't really declare new names we get 'markType',
# this should not be propagated, but be 'noneIsName'.
if mode == 'markType':
@ -1241,10 +1253,10 @@ class ASTMacroParameter(ASTBase):
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
if self.ellipsis:
signode += nodes.Text('...')
signode += addnodes.desc_sig_punctuation('...', '...')
elif self.variadic:
name = str(self)
signode += nodes.emphasis(name, name)
signode += addnodes.desc_sig_name(name, name)
else:
self.arg.describe_signature(signode, mode, env, symbol=symbol)
@ -1427,23 +1439,27 @@ class ASTDeclaration(ASTBaseBase):
elif self.objectType == 'macro':
pass
elif self.objectType == 'struct':
mainDeclNode += addnodes.desc_annotation('struct ', 'struct ')
mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct')
mainDeclNode += addnodes.desc_sig_space()
elif self.objectType == 'union':
mainDeclNode += addnodes.desc_annotation('union ', 'union ')
mainDeclNode += addnodes.desc_sig_keyword('union', 'union')
mainDeclNode += addnodes.desc_sig_space()
elif self.objectType == 'enum':
mainDeclNode += addnodes.desc_annotation('enum ', 'enum ')
mainDeclNode += addnodes.desc_sig_keyword('enum', 'enum')
mainDeclNode += addnodes.desc_sig_space()
elif self.objectType == 'enumerator':
mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ')
mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator')
mainDeclNode += addnodes.desc_sig_space()
elif self.objectType == 'type':
decl = cast(ASTType, self.declaration)
prefix = decl.get_type_declaration_prefix()
prefix += ' '
mainDeclNode += addnodes.desc_annotation(prefix, prefix)
mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix)
mainDeclNode += addnodes.desc_sig_space()
else:
assert False
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
if self.semicolon:
mainDeclNode += nodes.Text(';')
mainDeclNode += addnodes.desc_sig_punctuation(';', ';')
class SymbolLookupResult:
@ -3438,7 +3454,8 @@ class AliasNode(nodes.Element):
if 'c:parent_symbol' not in env.temp_data:
root = env.domaindata['c']['root_symbol']
env.temp_data['c:parent_symbol'] = root
self.parentKey = env.temp_data['c:parent_symbol'].get_lookup_key()
env.ref_context['c:parent_key'] = root.get_lookup_key()
self.parentKey = env.ref_context['c:parent_key']
else:
assert parentKey is not None
self.parentKey = parentKey
@ -3661,31 +3678,28 @@ class CExprRole(SphinxRole):
if asCode:
# render the expression as inline code
self.class_type = 'c-expr'
self.node_type: Type[TextElement] = nodes.literal
else:
# render the expression as inline text
self.class_type = 'c-texpr'
self.node_type = nodes.inline
def run(self) -> Tuple[List[Node], List[system_message]]:
text = self.text.replace('\n', ' ')
parser = DefinitionParser(text, location=self.get_source_info(),
config=self.env.config)
# attempt to mimic XRefRole classes, except that...
classes = ['xref', 'c', self.class_type]
try:
ast = parser.parse_expression()
except DefinitionError as ex:
logger.warning('Unparseable C expression: %r\n%s', text, ex,
location=self.get_source_info())
# see below
return [self.node_type(text, text, classes=classes)], []
return [addnodes.desc_inline('c', text, text, classes=[self.class_type])], []
parentSymbol = self.env.temp_data.get('c:parent_symbol', None)
if parentSymbol is None:
parentSymbol = self.env.domaindata['c']['root_symbol']
# ...most if not all of these classes should really apply to the individual references,
# not the container node
signode = self.node_type(classes=classes)
signode = addnodes.desc_inline('c', classes=[self.class_type])
ast.describe_signature(signode, 'markType', self.env, parentSymbol)
return [signode], []

File diff suppressed because it is too large Load Diff

View File

@ -304,7 +304,7 @@ class PyXrefMixin:
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = nodes.emphasis,
contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:
delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'
delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\.\.\.)'
delims_re = re.compile(delims)
sub_targets = re.split(delims, target)

View File

@ -240,7 +240,7 @@ class Config:
:returns: *bool* -- True if successful, False otherwise
napoleon_preprocess_types : :obj:`bool` (Defaults to False)
Enable the type preprocessor for numpy style docstrings.
Enable the type preprocessor.
napoleon_type_aliases : :obj:`dict` (Defaults to None)
Add a mapping of strings to string, translating types in numpy

View File

@ -508,6 +508,63 @@ table.hlist td {
vertical-align: top;
}
/* -- object description styles --------------------------------------------- */
.sig {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
}
.sig-name, code.descname {
background-color: transparent;
font-weight: bold;
}
.sig-name {
font-size: 1.1em;
}
code.descname {
font-size: 1.2em;
}
.sig-prename, code.descclassname {
background-color: transparent;
}
.optional {
font-size: 1.3em;
}
.sig-paren {
font-size: larger;
}
.sig-param.n {
font-style: italic;
}
/* C++ specific styling */
.sig-inline.c-texpr,
.sig-inline.cpp-texpr {
font-family: unset;
}
.sig.c .k, .sig.c .kt,
.sig.cpp .k, .sig.cpp .kt {
color: #0033B3;
}
.sig.c .m,
.sig.cpp .m {
color: #1750EB;
}
.sig.c .s, .sig.c .sc,
.sig.cpp .s, .sig.cpp .sc {
color: #067D17;
}
/* -- other body styles ----------------------------------------------------- */
@ -634,14 +691,6 @@ dl.glossary dt {
font-size: 1.1em;
}
.optional {
font-size: 1.3em;
}
.sig-paren {
font-size: larger;
}
.versionmodified {
font-style: italic;
}
@ -786,16 +835,6 @@ div.literal-block-wrapper {
margin: 1em 0;
}
code.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
code.descclassname {
background-color: transparent;
}
code.xref, a code {
background-color: transparent;
font-weight: bold;

View File

@ -206,13 +206,9 @@ class OnlyNodeTransform(SphinxPostTransform):
class SigElementFallbackTransform(SphinxPostTransform):
"""Fallback desc_sig_element nodes to inline if translator does not supported them."""
"""Fallback various desc_* nodes to inline if translator does not supported them."""
default_priority = 200
SIG_ELEMENTS = [addnodes.desc_sig_name,
addnodes.desc_sig_operator,
addnodes.desc_sig_punctuation]
def run(self, **kwargs: Any) -> None:
def has_visitor(translator: Type[nodes.NodeVisitor], node: Type[Element]) -> bool:
return hasattr(translator, "visit_%s" % node.__name__)
@ -222,24 +218,35 @@ class SigElementFallbackTransform(SphinxPostTransform):
# subclass of SphinxTranslator supports desc_sig_element nodes automatically.
return
if all(has_visitor(translator, node) for node in self.SIG_ELEMENTS):
# the translator supports all desc_sig_element nodes
return
else:
self.fallback()
# for the leaf elements (desc_sig_element), the translator should support _all_
if not all(has_visitor(translator, node) for node in addnodes.SIG_ELEMENTS):
self.fallback(addnodes.desc_sig_element)
def fallback(self) -> None:
for node in self.document.traverse(addnodes.desc_sig_element):
if not has_visitor(translator, addnodes.desc_inline):
self.fallback(addnodes.desc_inline)
def fallback(self, nodeType: Any) -> None:
for node in self.document.traverse(nodeType):
newnode = nodes.inline()
newnode.update_all_atts(node)
newnode.extend(node)
node.replace_self(newnode)
class PropagateDescDomain(SphinxPostTransform):
"""Add the domain name of the parent node as a class in each desc_signature node."""
default_priority = 200
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(addnodes.desc_signature):
node['classes'].append(node.parent['domain'])
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_post_transform(ReferencesResolver)
app.add_post_transform(OnlyNodeTransform)
app.add_post_transform(SigElementFallbackTransform)
app.add_post_transform(PropagateDescDomain)
return {
'version': 'builtin',

View File

@ -197,7 +197,7 @@ class ImageConverter(BaseImageConverter):
def match(self, node: nodes.image) -> bool:
if not self.app.builder.supported_image_types:
return False
elif set(node['candidates']) & set(self.app.builder.supported_image_types):
elif set(self.guess_mimetypes(node)) & set(self.app.builder.supported_image_types):
# builder supports the image; no need to convert
return False
elif self.available is None:

View File

@ -138,16 +138,16 @@ def _restify_py37(cls: Optional[Type]) -> str:
if len(cls.__args__) > 1 and cls.__args__[-1] is NoneType:
if len(cls.__args__) > 2:
args = ', '.join(restify(a) for a in cls.__args__[:-1])
return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % args
return ':obj:`~typing.Optional`\\ [:obj:`~typing.Union`\\ [%s]]' % args
else:
return ':obj:`Optional`\\ [%s]' % restify(cls.__args__[0])
return ':obj:`~typing.Optional`\\ [%s]' % restify(cls.__args__[0])
else:
args = ', '.join(restify(a) for a in cls.__args__)
return ':obj:`Union`\\ [%s]' % args
return ':obj:`~typing.Union`\\ [%s]' % args
elif inspect.isgenericalias(cls):
if getattr(cls, '_name', None):
if cls.__module__ == 'typing':
text = ':class:`%s`' % cls._name
text = ':class:`~%s.%s`' % (cls.__module__, cls._name)
else:
text = ':class:`%s.%s`' % (cls.__module__, cls._name)
else:
@ -167,20 +167,23 @@ def _restify_py37(cls: Optional[Type]) -> str:
return text
elif hasattr(cls, '__qualname__'):
if cls.__module__ == 'typing':
return ':class:`%s`' % cls.__qualname__
return ':class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
else:
return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
elif hasattr(cls, '_name'):
# SpecialForm
if cls.__module__ == 'typing':
return ':obj:`%s`' % cls._name
return ':obj:`~%s.%s`' % (cls.__module__, cls._name)
else:
return ':obj:`%s.%s`' % (cls.__module__, cls._name)
elif isinstance(cls, ForwardRef):
return ':class:`%s`' % cls.__forward_arg__
else:
# not a class (ex. TypeVar)
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
if cls.__module__ == 'typing':
return ':obj:`~%s.%s`' % (cls.__module__, cls.__name__)
else:
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
def _restify_py36(cls: Optional[Type]) -> str:
@ -203,13 +206,23 @@ def _restify_py36(cls: Optional[Type]) -> str:
if (isinstance(cls, typing.TupleMeta) and # type: ignore
not hasattr(cls, '__tuple_params__')):
if module == 'typing':
reftext = ':class:`~typing.%s`' % qualname
else:
reftext = ':class:`%s`' % qualname
params = cls.__args__
if params:
param_str = ', '.join(restify(p) for p in params)
return ':class:`%s`\\ [%s]' % (qualname, param_str)
return reftext + '\\ [%s]' % param_str
else:
return ':class:`%s`' % qualname
return reftext
elif isinstance(cls, typing.GenericMeta):
if module == 'typing':
reftext = ':class:`~typing.%s`' % qualname
else:
reftext = ':class:`%s`' % qualname
if cls.__args__ is None or len(cls.__args__) <= 2: # type: ignore # NOQA
params = cls.__args__ # type: ignore
elif cls.__origin__ == Generator: # type: ignore
@ -217,13 +230,13 @@ def _restify_py36(cls: Optional[Type]) -> str:
else: # typing.Callable
args = ', '.join(restify(arg) for arg in cls.__args__[:-1]) # type: ignore
result = restify(cls.__args__[-1]) # type: ignore
return ':class:`%s`\\ [[%s], %s]' % (qualname, args, result)
return reftext + '\\ [[%s], %s]' % (args, result)
if params:
param_str = ', '.join(restify(p) for p in params)
return ':class:`%s`\\ [%s]' % (qualname, param_str)
return reftext + '\\ [%s]' % (param_str)
else:
return ':class:`%s`' % qualname
return reftext
elif (hasattr(cls, '__origin__') and
cls.__origin__ is typing.Union):
params = cls.__args__
@ -231,32 +244,36 @@ def _restify_py36(cls: Optional[Type]) -> str:
if len(params) > 1 and params[-1] is NoneType:
if len(params) > 2:
param_str = ", ".join(restify(p) for p in params[:-1])
return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % param_str
return (':obj:`~typing.Optional`\\ '
'[:obj:`~typing.Union`\\ [%s]]' % param_str)
else:
return ':obj:`Optional`\\ [%s]' % restify(params[0])
return ':obj:`~typing.Optional`\\ [%s]' % restify(params[0])
else:
param_str = ', '.join(restify(p) for p in params)
return ':obj:`Union`\\ [%s]' % param_str
return ':obj:`~typing.Union`\\ [%s]' % param_str
else:
return ':obj:`Union`'
elif hasattr(cls, '__qualname__'):
if cls.__module__ == 'typing':
return ':class:`%s`' % cls.__qualname__
return ':class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
else:
return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
elif hasattr(cls, '_name'):
# SpecialForm
if cls.__module__ == 'typing':
return ':obj:`%s`' % cls._name
return ':obj:`~%s.%s`' % (cls.__module__, cls._name)
else:
return ':obj:`%s.%s`' % (cls.__module__, cls._name)
elif hasattr(cls, '__name__'):
# not a class (ex. TypeVar)
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
if cls.__module__ == 'typing':
return ':obj:`~%s.%s`' % (cls.__module__, cls.__name__)
else:
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
else:
# others (ex. Any)
if cls.__module__ == 'typing':
return ':obj:`%s`' % qualname
return ':obj:`~%s.%s`' % (cls.__module__, qualname)
else:
return ':obj:`%s.%s`' % (cls.__module__, qualname)

View File

@ -107,8 +107,15 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def depart_start_of_file(self, node: Element) -> None:
self.docnames.pop()
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes for descriptions
##################################
def visit_desc(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dl', CLASS=node['objtype']))
self.body.append(self.starttag(node, 'dl'))
def depart_desc(self, node: Element) -> None:
self.body.append('</dl>\n\n')
@ -133,8 +140,29 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
self.add_permalink_ref(node.parent, _('Permalink to this definition'))
self.body.append('<br />')
def visit_desc_content(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node: Element) -> None:
self.body.append('</dd>')
def visit_desc_inline(self, node: Element) -> None:
self.body.append(self.starttag(node, 'span', ''))
def depart_desc_inline(self, node: Element) -> None:
self.body.append('</span>')
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
self.body.append(self.starttag(node, 'code', ''))
def depart_desc_name(self, node: Element) -> None:
self.body.append('</code>')
def visit_desc_addname(self, node: Element) -> None:
self.body.append(self.starttag(node, 'code', '', CLASS='descclassname'))
self.body.append(self.starttag(node, 'code', ''))
def depart_desc_addname(self, node: Element) -> None:
self.body.append('</code>')
@ -151,12 +179,6 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def depart_desc_returns(self, node: Element) -> None:
pass
def visit_desc_name(self, node: Element) -> None:
self.body.append(self.starttag(node, 'code', '', CLASS='descname'))
def depart_desc_name(self, node: Element) -> None:
self.body.append('</code>')
def visit_desc_parameterlist(self, node: Element) -> None:
self.body.append('<span class="sig-paren">(</span>')
self.first_param = 1
@ -205,11 +227,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
def depart_desc_annotation(self, node: Element) -> None:
self.body.append('</em>')
def visit_desc_content(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node: Element) -> None:
self.body.append('</dd>')
##############################################
def visit_versionmodified(self, node: Element) -> None:
self.body.append(self.starttag(node, 'div', CLASS=node['type']))

View File

@ -78,8 +78,15 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def depart_start_of_file(self, node: Element) -> None:
self.docnames.pop()
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes for descriptions
##################################
def visit_desc(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dl', CLASS=node['objtype']))
self.body.append(self.starttag(node, 'dl'))
def depart_desc(self, node: Element) -> None:
self.body.append('</dl>\n\n')
@ -104,11 +111,32 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
self.add_permalink_ref(node.parent, _('Permalink to this definition'))
self.body.append('<br />')
def visit_desc_content(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node: Element) -> None:
self.body.append('</dd>')
def visit_desc_inline(self, node: Element) -> None:
self.body.append(self.starttag(node, 'span', ''))
def depart_desc_inline(self, node: Element) -> None:
self.body.append('</span>')
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
self.body.append(self.starttag(node, 'span', ''))
def depart_desc_name(self, node: Element) -> None:
self.body.append('</span>')
def visit_desc_addname(self, node: Element) -> None:
self.body.append(self.starttag(node, 'code', '', CLASS='sig-prename descclassname'))
self.body.append(self.starttag(node, 'span', ''))
def depart_desc_addname(self, node: Element) -> None:
self.body.append('</code>')
self.body.append('</span>')
def visit_desc_type(self, node: Element) -> None:
pass
@ -122,12 +150,6 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def depart_desc_returns(self, node: Element) -> None:
pass
def visit_desc_name(self, node: Element) -> None:
self.body.append(self.starttag(node, 'code', '', CLASS='sig-name descname'))
def depart_desc_name(self, node: Element) -> None:
self.body.append('</code>')
def visit_desc_parameterlist(self, node: Element) -> None:
self.body.append('<span class="sig-paren">(</span>')
self.first_param = 1
@ -176,11 +198,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def depart_desc_annotation(self, node: Element) -> None:
self.body.append('</em>')
def visit_desc_content(self, node: Element) -> None:
self.body.append(self.starttag(node, 'dd', ''))
def depart_desc_content(self, node: Element) -> None:
self.body.append('</dd>')
##############################################
def visit_versionmodified(self, node: Element) -> None:
self.body.append(self.starttag(node, 'div', CLASS=node['type']))

View File

@ -698,6 +698,13 @@ class LaTeXTranslator(SphinxTranslator):
def depart_subtitle(self, node: Element) -> None:
self.body.append(self.context.pop())
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes for descriptions
##################################
def visit_desc(self, node: Element) -> None:
if self.config.latex_show_urls == 'footnote':
self.body.append(BLANKLINE)
@ -750,6 +757,31 @@ class LaTeXTranslator(SphinxTranslator):
def depart_desc_signature_line(self, node: Element) -> None:
self._depart_signature_line(node)
def visit_desc_content(self, node: Element) -> None:
if node.children and not isinstance(node.children[0], nodes.paragraph):
# avoid empty desc environment which causes a formatting bug
self.body.append('~')
def depart_desc_content(self, node: Element) -> None:
pass
def visit_desc_inline(self, node: Element) -> None:
self.body.append(r'\sphinxcode{\sphinxupquote{')
def depart_desc_inline(self, node: Element) -> None:
self.body.append('}}')
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
self.body.append(r'\sphinxbfcode{\sphinxupquote{')
self.literal_whitespace += 1
def depart_desc_name(self, node: Element) -> None:
self.body.append('}}')
self.literal_whitespace -= 1
def visit_desc_addname(self, node: Element) -> None:
self.body.append(r'\sphinxcode{\sphinxupquote{')
self.literal_whitespace += 1
@ -770,14 +802,6 @@ class LaTeXTranslator(SphinxTranslator):
def depart_desc_returns(self, node: Element) -> None:
self.body.append(r'}')
def visit_desc_name(self, node: Element) -> None:
self.body.append(r'\sphinxbfcode{\sphinxupquote{')
self.literal_whitespace += 1
def depart_desc_name(self, node: Element) -> None:
self.body.append('}}')
self.literal_whitespace -= 1
def visit_desc_parameterlist(self, node: Element) -> None:
# close name, open parameterlist
self.body.append('}{')
@ -811,13 +835,7 @@ class LaTeXTranslator(SphinxTranslator):
def depart_desc_annotation(self, node: Element) -> None:
self.body.append('}}')
def visit_desc_content(self, node: Element) -> None:
if node.children and not isinstance(node.children[0], nodes.paragraph):
# avoid empty desc environment which causes a formatting bug
self.body.append('~')
def depart_desc_content(self, node: Element) -> None:
pass
##############################################
def visit_seealso(self, node: Element) -> None:
self.body.append(BLANKLINE)

View File

@ -120,6 +120,13 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def depart_start_of_file(self, node: Element) -> None:
pass
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes for descriptions
##################################
def visit_desc(self, node: Element) -> None:
self.visit_definition_list(node)
@ -139,6 +146,27 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def depart_desc_signature_line(self, node: Element) -> None:
self.body.append(' ')
def visit_desc_content(self, node: Element) -> None:
self.visit_definition(node)
def depart_desc_content(self, node: Element) -> None:
self.depart_definition(node)
def visit_desc_inline(self, node: Element) -> None:
pass
def depart_desc_inline(self, node: Element) -> None:
pass
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
pass
def depart_desc_name(self, node: Element) -> None:
pass
def visit_desc_addname(self, node: Element) -> None:
pass
@ -157,12 +185,6 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def depart_desc_returns(self, node: Element) -> None:
pass
def visit_desc_name(self, node: Element) -> None:
pass
def depart_desc_name(self, node: Element) -> None:
pass
def visit_desc_parameterlist(self, node: Element) -> None:
self.body.append('(')
self.first_param = 1
@ -191,11 +213,7 @@ class ManualPageTranslator(SphinxTranslator, BaseTranslator):
def depart_desc_annotation(self, node: Element) -> None:
pass
def visit_desc_content(self, node: Element) -> None:
self.visit_definition(node)
def depart_desc_content(self, node: Element) -> None:
self.depart_definition(node)
##############################################
def visit_versionmodified(self, node: Element) -> None:
self.visit_paragraph(node)

View File

@ -1367,7 +1367,12 @@ class TexinfoTranslator(SphinxTranslator):
self.body.append('\n\n')
raise nodes.SkipNode
# -- Desc
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes for descriptions
##################################
def visit_desc(self, node: addnodes.desc) -> None:
self.descs.append(node)
@ -1408,6 +1413,21 @@ class TexinfoTranslator(SphinxTranslator):
def depart_desc_signature_line(self, node: Element) -> None:
pass
def visit_desc_content(self, node: Element) -> None:
pass
def depart_desc_content(self, node: Element) -> None:
pass
def visit_desc_inline(self, node: Element) -> None:
pass
def depart_desc_inline(self, node: Element) -> None:
pass
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
pass
@ -1470,11 +1490,7 @@ class TexinfoTranslator(SphinxTranslator):
def depart_desc_annotation(self, node: Element) -> None:
pass
def visit_desc_content(self, node: Element) -> None:
pass
def depart_desc_content(self, node: Element) -> None:
pass
##############################################
def visit_inline(self, node: Element) -> None:
pass

View File

@ -536,6 +536,13 @@ class TextTranslator(SphinxTranslator):
def depart_attribution(self, node: Element) -> None:
pass
#############################################################
# Domain-specific object descriptions
#############################################################
# Top-level nodes
#################
def visit_desc(self, node: Element) -> None:
pass
@ -555,6 +562,22 @@ class TextTranslator(SphinxTranslator):
def depart_desc_signature_line(self, node: Element) -> None:
self.add_text('\n')
def visit_desc_content(self, node: Element) -> None:
self.new_state()
self.add_text(self.nl)
def depart_desc_content(self, node: Element) -> None:
self.end_state()
def visit_desc_inline(self, node: Element) -> None:
pass
def depart_desc_inline(self, node: Element) -> None:
pass
# Nodes for high-level structure in signatures
##############################################
def visit_desc_name(self, node: Element) -> None:
pass
@ -606,12 +629,7 @@ class TextTranslator(SphinxTranslator):
def depart_desc_annotation(self, node: Element) -> None:
pass
def visit_desc_content(self, node: Element) -> None:
self.new_state()
self.add_text(self.nl)
def depart_desc_content(self, node: Element) -> None:
self.end_state()
##############################################
def visit_figure(self, node: Element) -> None:
self.new_state()

Binary file not shown.

View File

@ -2,3 +2,4 @@ test-ext-imgconverter
=====================
.. image:: svgimg.svg
.. image:: img.pdf

View File

@ -213,3 +213,9 @@ CPP domain
.. cpp:function:: T& operator[]( unsigned j )
const T& operator[]( unsigned j ) const
.. cpp:function:: template<typename T1, typename T2> \
requires A<T1, T2> \
void f()
- :cpp:expr:`a + b`

View File

@ -285,10 +285,10 @@ def test_html4_output(app, status, warning):
'objects.html': [
(".//dt[@id='mod.Cls.meth1']", ''),
(".//dt[@id='errmod.Error']", ''),
(".//dt/code/span", r'long\(parameter,'),
(".//dt/code/span", r'list\)'),
(".//dt/code/span", 'another'),
(".//dt/code/span", 'one'),
(".//dt/span[@class='sig-name descname']/span[@class='pre']", r'long\(parameter,'),
(".//dt/span[@class='sig-name descname']/span[@class='pre']", r'list\)'),
(".//dt/span[@class='sig-name descname']/span[@class='pre']", 'another'),
(".//dt/span[@class='sig-name descname']/span[@class='pre']", 'one'),
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
(".//dl[@class='std userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),

View File

@ -73,6 +73,7 @@ def _check(name, input, idDict, output, key, asTextOutput):
print("Input: ", input)
print("astext(): ", resAsText)
print("Expected: ", outputAsText)
print("Node:", parentNode)
raise DefinitionError("")
idExpected = [None]
@ -743,6 +744,9 @@ def test_anon_definitions():
check('class', '@1', {3: "Ut1_1"}, asTextOutput='class [anonymous]')
check('class', '@a::A', {3: "NUt1_a1AE"}, asTextOutput='class [anonymous]::A')
check('function', 'int f(int @a)', {1: 'f__i', 2: '1fi'},
asTextOutput='int f(int [anonymous])')
def test_templates():
check('class', "A<T>", {2: "IE1AI1TE"}, output="template<> {key}A<T>")
@ -1206,7 +1210,7 @@ not found in `{test}`
cpp_any_role = RoleClasses('cpp-any', 'a', ['code'])
# NYI: consistent looks
# texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'code'])
expr_role = RoleClasses('cpp-expr', 'code', ['a'])
expr_role = RoleClasses('cpp-expr', 'span', ['a'])
texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'span'])
# XRefRole-style classes
@ -1223,8 +1227,7 @@ not found in `{test}`
for role in (expr_role, texpr_role):
name = role.name
expect = '`{name}` puts the domain and role classes at its root'.format(name=name)
# NYI: xref should go in the references
assert {'xref', 'cpp', name} <= role.classes, expect
assert {'sig', 'sig-inline', 'cpp', name} <= role.classes, expect
# reference classes

View File

@ -876,7 +876,9 @@ def test_info_field_list(app):
"\n"
" :param str name: blah blah\n"
" :param age: blah blah\n"
" :type age: int\n")
" :type age: int\n"
" :param items: blah blah\n"
" :type items: Tuple[str, ...]\n")
doctree = restructuredtext.parse(app, text)
print(doctree)
@ -890,6 +892,7 @@ def test_info_field_list(app):
assert_node(doctree[3][1][0][0],
([nodes.field_name, "Parameters"],
[nodes.field_body, nodes.bullet_list, ([nodes.list_item, nodes.paragraph],
[nodes.list_item, nodes.paragraph],
[nodes.list_item, nodes.paragraph])]))
# :param str name:
@ -916,6 +919,26 @@ def test_info_field_list(app):
refdomain="py", reftype="class", reftarget="int",
**{"py:module": "example", "py:class": "Class"})
# :param items: + :type items:
assert_node(doctree[3][1][0][0][1][0][2][0],
([addnodes.literal_strong, "items"],
" (",
[pending_xref, addnodes.literal_emphasis, "Tuple"],
[addnodes.literal_emphasis, "["],
[pending_xref, addnodes.literal_emphasis, "str"],
[addnodes.literal_emphasis, ", "],
[addnodes.literal_emphasis, "..."],
[addnodes.literal_emphasis, "]"],
")",
" -- ",
"blah blah"))
assert_node(doctree[3][1][0][0][1][0][2][0][2], pending_xref,
refdomain="py", reftype="class", reftarget="Tuple",
**{"py:module": "example", "py:class": "Class"})
assert_node(doctree[3][1][0][0][1][0][2][0][4], pending_xref,
refdomain="py", reftype="class", reftarget="str",
**{"py:module": "example", "py:class": "Class"})
def test_info_field_list_var(app):
text = (".. py:class:: Class\n"

View File

@ -1893,12 +1893,12 @@ def test_autodoc_GenericAlias(app):
' .. py:attribute:: Class.T',
' :module: target.genericalias',
'',
' alias of :class:`List`\\ [:class:`int`]',
' alias of :class:`~typing.List`\\ [:class:`int`]',
'',
'.. py:attribute:: T',
' :module: target.genericalias',
'',
' alias of :class:`List`\\ [:class:`int`]',
' alias of :class:`~typing.List`\\ [:class:`int`]',
]
else:
assert list(actual) == [

View File

@ -256,7 +256,8 @@ def test_show_inheritance_for_subclass_of_generic_type(app):
'.. py:class:: Quux(iterable=(), /)',
' :module: target.classes',
'',
' Bases: :class:`List`\\ [:obj:`Union`\\ [:class:`int`, :class:`float`]]',
' Bases: :class:`~typing.List`\\ '
'[:obj:`~typing.Union`\\ [:class:`int`, :class:`float`]]',
'',
' A subclass of List[Union[int, float]]',
'',

View File

@ -19,6 +19,11 @@ def test_ext_imgconverter(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'python.tex').read_text()
# supported image (not converted)
assert '\\sphinxincludegraphics{{img}.pdf}' in content
# non supported image (converted)
assert '\\sphinxincludegraphics{{svgimg}.png}' in content
assert not (app.outdir / 'svgimg.svg').exists()
assert (app.outdir / 'svgimg.png').exists()

View File

@ -258,10 +258,10 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
'<span class="pre">Bar</span></code></a>' in html)
assert ('<a class="reference external"'
' href="https://docs.python.org/index.html#foons"'
' title="(in foo v2.0)"><span class="pre">foons</span></a>' in html)
' title="(in foo v2.0)"><span class="n"><span class="pre">foons</span></span></a>' in html)
assert ('<a class="reference external"'
' href="https://docs.python.org/index.html#foons_bartype"'
' title="(in foo v2.0)"><span class="pre">bartype</span></a>' in html)
' title="(in foo v2.0)"><span class="n"><span class="pre">bartype</span></span></a>' in html)
def test_missing_reference_jsdomain(tempdir, app, status, warning):

View File

@ -47,42 +47,56 @@ def test_restify():
assert restify(Integral) == ":class:`numbers.Integral`"
assert restify(Struct) == ":class:`struct.Struct`"
assert restify(TracebackType) == ":class:`types.TracebackType`"
assert restify(Any) == ":obj:`Any`"
assert restify(Any) == ":obj:`~typing.Any`"
def test_restify_type_hints_containers():
assert restify(List) == ":class:`List`"
assert restify(Dict) == ":class:`Dict`"
assert restify(List[int]) == ":class:`List`\\ [:class:`int`]"
assert restify(List[str]) == ":class:`List`\\ [:class:`str`]"
assert restify(Dict[str, float]) == ":class:`Dict`\\ [:class:`str`, :class:`float`]"
assert restify(Tuple[str, str, str]) == ":class:`Tuple`\\ [:class:`str`, :class:`str`, :class:`str`]"
assert restify(Tuple[str, ...]) == ":class:`Tuple`\\ [:class:`str`, ...]"
assert restify(List[Dict[str, Tuple]]) == ":class:`List`\\ [:class:`Dict`\\ [:class:`str`, :class:`Tuple`]]"
assert restify(MyList[Tuple[int, int]]) == ":class:`tests.test_util_typing.MyList`\\ [:class:`Tuple`\\ [:class:`int`, :class:`int`]]"
assert restify(Generator[None, None, None]) == ":class:`Generator`\\ [:obj:`None`, :obj:`None`, :obj:`None`]"
assert restify(List) == ":class:`~typing.List`"
assert restify(Dict) == ":class:`~typing.Dict`"
assert restify(List[int]) == ":class:`~typing.List`\\ [:class:`int`]"
assert restify(List[str]) == ":class:`~typing.List`\\ [:class:`str`]"
assert restify(Dict[str, float]) == (":class:`~typing.Dict`\\ "
"[:class:`str`, :class:`float`]")
assert restify(Tuple[str, str, str]) == (":class:`~typing.Tuple`\\ "
"[:class:`str`, :class:`str`, :class:`str`]")
assert restify(Tuple[str, ...]) == ":class:`~typing.Tuple`\\ [:class:`str`, ...]"
assert restify(List[Dict[str, Tuple]]) == (":class:`~typing.List`\\ "
"[:class:`~typing.Dict`\\ "
"[:class:`str`, :class:`~typing.Tuple`]]")
assert restify(MyList[Tuple[int, int]]) == (":class:`tests.test_util_typing.MyList`\\ "
"[:class:`~typing.Tuple`\\ "
"[:class:`int`, :class:`int`]]")
assert restify(Generator[None, None, None]) == (":class:`~typing.Generator`\\ "
"[:obj:`None`, :obj:`None`, :obj:`None`]")
def test_restify_type_hints_Callable():
assert restify(Callable) == ":class:`Callable`"
assert restify(Callable) == ":class:`~typing.Callable`"
if sys.version_info >= (3, 7):
assert restify(Callable[[str], int]) == ":class:`Callable`\\ [[:class:`str`], :class:`int`]"
assert restify(Callable[..., int]) == ":class:`Callable`\\ [[...], :class:`int`]"
assert restify(Callable[[str], int]) == (":class:`~typing.Callable`\\ "
"[[:class:`str`], :class:`int`]")
assert restify(Callable[..., int]) == (":class:`~typing.Callable`\\ "
"[[...], :class:`int`]")
else:
assert restify(Callable[[str], int]) == ":class:`Callable`\\ [:class:`str`, :class:`int`]"
assert restify(Callable[..., int]) == ":class:`Callable`\\ [..., :class:`int`]"
assert restify(Callable[[str], int]) == (":class:`~typing.Callable`\\ "
"[:class:`str`, :class:`int`]")
assert restify(Callable[..., int]) == (":class:`~typing.Callable`\\ "
"[..., :class:`int`]")
def test_restify_type_hints_Union():
assert restify(Optional[int]) == ":obj:`Optional`\\ [:class:`int`]"
assert restify(Union[str, None]) == ":obj:`Optional`\\ [:class:`str`]"
assert restify(Union[int, str]) == ":obj:`Union`\\ [:class:`int`, :class:`str`]"
assert restify(Optional[int]) == ":obj:`~typing.Optional`\\ [:class:`int`]"
assert restify(Union[str, None]) == ":obj:`~typing.Optional`\\ [:class:`str`]"
assert restify(Union[int, str]) == ":obj:`~typing.Union`\\ [:class:`int`, :class:`str`]"
if sys.version_info >= (3, 7):
assert restify(Union[int, Integral]) == ":obj:`Union`\\ [:class:`int`, :class:`numbers.Integral`]"
assert restify(Union[int, Integral]) == (":obj:`~typing.Union`\\ "
"[:class:`int`, :class:`numbers.Integral`]")
assert (restify(Union[MyClass1, MyClass2]) ==
":obj:`Union`\\ [:class:`tests.test_util_typing.MyClass1`, :class:`tests.test_util_typing.<MyClass2>`]")
(":obj:`~typing.Union`\\ "
"[:class:`tests.test_util_typing.MyClass1`, "
":class:`tests.test_util_typing.<MyClass2>`]"))
else:
assert restify(Union[int, Integral]) == ":class:`numbers.Integral`"
assert restify(Union[MyClass1, MyClass2]) == ":class:`tests.test_util_typing.MyClass1`"
@ -97,7 +111,7 @@ def test_restify_type_hints_typevars():
assert restify(T) == ":obj:`tests.test_util_typing.T`"
assert restify(T_co) == ":obj:`tests.test_util_typing.T_co`"
assert restify(T_contra) == ":obj:`tests.test_util_typing.T_contra`"
assert restify(List[T]) == ":class:`List`\\ [:obj:`tests.test_util_typing.T`]"
assert restify(List[T]) == ":class:`~typing.List`\\ [:obj:`tests.test_util_typing.T`]"
assert restify(MyInt) == ":class:`MyInt`"
@ -110,7 +124,7 @@ def test_restify_type_hints_alias():
MyStr = str
MyTuple = Tuple[str, str]
assert restify(MyStr) == ":class:`str`"
assert restify(MyTuple) == ":class:`Tuple`\\ [:class:`str`, :class:`str`]" # type: ignore
assert restify(MyTuple) == ":class:`~typing.Tuple`\\ [:class:`str`, :class:`str`]"
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')