mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #6990 from tk0miya/function_signature
py domain: Allow to make a style for arguments of functions and methods
This commit is contained in:
commit
5c0d0438c4
3
CHANGES
3
CHANGES
@ -19,6 +19,8 @@ Incompatible changes
|
|||||||
when ``:inherited-members:`` and ``:special-members:`` are given.
|
when ``:inherited-members:`` and ``:special-members:`` are given.
|
||||||
* #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They
|
* #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They
|
||||||
are not displayed on output document now
|
are not displayed on output document now
|
||||||
|
* #6417: py domain: doctree of desc_parameterlist has been changed. The
|
||||||
|
argument names, annotations and default values are wrapped with inline node
|
||||||
* The structure of ``sphinx.events.EventManager.listeners`` has changed
|
* The structure of ``sphinx.events.EventManager.listeners`` has changed
|
||||||
* Due to the scoping changes for :rst:dir:`productionlist` some uses of
|
* Due to the scoping changes for :rst:dir:`productionlist` some uses of
|
||||||
:rst:role:`token` must be modified to include the scope which was previously
|
:rst:role:`token` must be modified to include the scope which was previously
|
||||||
@ -67,6 +69,7 @@ Features added
|
|||||||
* #6830: py domain: Add new event: :event:`object-description-transform`
|
* #6830: py domain: Add new event: :event:`object-description-transform`
|
||||||
* #6895: py domain: Do not emit nitpicky warnings for built-in types
|
* #6895: py domain: Do not emit nitpicky warnings for built-in types
|
||||||
* py domain: Support lambda functions in function signature
|
* py domain: Support lambda functions in function signature
|
||||||
|
* #6417: py domain: Allow to make a style for arguments of functions and methods
|
||||||
* Support priority of event handlers. For more detail, see
|
* Support priority of event handlers. For more detail, see
|
||||||
:py:meth:`.Sphinx.connect()`
|
:py:meth:`.Sphinx.connect()`
|
||||||
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
|
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
|
||||||
|
@ -12,7 +12,7 @@ import warnings
|
|||||||
from typing import Any, Dict, List, Sequence
|
from typing import Any, Dict, List, Sequence
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.nodes import Node
|
from docutils.nodes import Element, Node
|
||||||
|
|
||||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||||
|
|
||||||
@ -174,6 +174,31 @@ class desc_content(nodes.General, nodes.Element):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class desc_sig_element(nodes.inline):
|
||||||
|
"""Common parent class of nodes for inline text of a signature."""
|
||||||
|
classes = [] # type: List[str]
|
||||||
|
|
||||||
|
def __init__(self, rawsource: str = '', text: str = '',
|
||||||
|
*children: Element, **attributes: Any) -> None:
|
||||||
|
super().__init__(rawsource, text, *children, **attributes)
|
||||||
|
self['classes'].extend(self.classes)
|
||||||
|
|
||||||
|
|
||||||
|
class desc_sig_name(desc_sig_element):
|
||||||
|
"""Node for a name in a signature."""
|
||||||
|
classes = ["n"]
|
||||||
|
|
||||||
|
|
||||||
|
class desc_sig_operator(desc_sig_element):
|
||||||
|
"""Node for an operator in a signature."""
|
||||||
|
classes = ["o"]
|
||||||
|
|
||||||
|
|
||||||
|
class desc_sig_punctuation(desc_sig_element):
|
||||||
|
"""Node for a punctuation in a signature."""
|
||||||
|
classes = ["p"]
|
||||||
|
|
||||||
|
|
||||||
# new admonition-like constructs
|
# new admonition-like constructs
|
||||||
|
|
||||||
class versionmodified(nodes.Admonition, nodes.TextElement):
|
class versionmodified(nodes.Admonition, nodes.TextElement):
|
||||||
@ -332,6 +357,9 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
|
|||||||
app.add_node(desc_optional)
|
app.add_node(desc_optional)
|
||||||
app.add_node(desc_annotation)
|
app.add_node(desc_annotation)
|
||||||
app.add_node(desc_content)
|
app.add_node(desc_content)
|
||||||
|
app.add_node(desc_sig_name)
|
||||||
|
app.add_node(desc_sig_operator)
|
||||||
|
app.add_node(desc_sig_punctuation)
|
||||||
app.add_node(versionmodified)
|
app.add_node(versionmodified)
|
||||||
app.add_node(seealso)
|
app.add_node(seealso)
|
||||||
app.add_node(productionlist)
|
app.add_node(productionlist)
|
||||||
|
@ -75,35 +75,42 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
|
|||||||
for param in sig.parameters.values():
|
for param in sig.parameters.values():
|
||||||
if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
|
if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
|
||||||
# PEP-570: Separator for Positional Only Parameter: /
|
# PEP-570: Separator for Positional Only Parameter: /
|
||||||
params += addnodes.desc_parameter('', nodes.Text('/'))
|
params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
|
||||||
if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
|
if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
|
||||||
param.POSITIONAL_ONLY,
|
param.POSITIONAL_ONLY,
|
||||||
None):
|
None):
|
||||||
# PEP-3102: Separator for Keyword Only Parameter: *
|
# PEP-3102: Separator for Keyword Only Parameter: *
|
||||||
params += addnodes.desc_parameter('', nodes.Text('*'))
|
params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '*'))
|
||||||
|
|
||||||
node = addnodes.desc_parameter()
|
node = addnodes.desc_parameter()
|
||||||
if param.kind == param.VAR_POSITIONAL:
|
if param.kind == param.VAR_POSITIONAL:
|
||||||
node += nodes.Text('*' + param.name)
|
node += addnodes.desc_sig_operator('', '*')
|
||||||
|
node += addnodes.desc_sig_name('', param.name)
|
||||||
elif param.kind == param.VAR_KEYWORD:
|
elif param.kind == param.VAR_KEYWORD:
|
||||||
node += nodes.Text('**' + param.name)
|
node += addnodes.desc_sig_operator('', '**')
|
||||||
|
node += addnodes.desc_sig_name('', param.name)
|
||||||
else:
|
else:
|
||||||
node += nodes.Text(param.name)
|
node += addnodes.desc_sig_name('', param.name)
|
||||||
|
|
||||||
if param.annotation is not param.empty:
|
if param.annotation is not param.empty:
|
||||||
node += nodes.Text(': ' + param.annotation)
|
node += addnodes.desc_sig_punctuation('', ':')
|
||||||
|
node += nodes.Text(' ')
|
||||||
|
node += addnodes.desc_sig_name('', param.annotation)
|
||||||
if param.default is not param.empty:
|
if param.default is not param.empty:
|
||||||
if param.annotation is not param.empty:
|
if param.annotation is not param.empty:
|
||||||
node += nodes.Text(' = ' + str(param.default))
|
node += nodes.Text(' ')
|
||||||
|
node += addnodes.desc_sig_operator('', '=')
|
||||||
|
node += nodes.Text(' ')
|
||||||
else:
|
else:
|
||||||
node += nodes.Text('=' + str(param.default))
|
node += addnodes.desc_sig_operator('', '=')
|
||||||
|
node += nodes.inline('', param.default, classes=['default_value'])
|
||||||
|
|
||||||
params += node
|
params += node
|
||||||
last_kind = param.kind
|
last_kind = param.kind
|
||||||
|
|
||||||
if last_kind == Parameter.POSITIONAL_ONLY:
|
if last_kind == Parameter.POSITIONAL_ONLY:
|
||||||
# PEP-570: Separator for Positional Only Parameter: /
|
# PEP-570: Separator for Positional Only Parameter: /
|
||||||
params += addnodes.desc_parameter('', nodes.Text('/'))
|
params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any, Dict, List, Tuple, Type
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
@ -22,6 +22,7 @@ from sphinx.errors import NoUri
|
|||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.transforms import SphinxTransform
|
from sphinx.transforms import SphinxTransform
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
|
from sphinx.util.docutils import SphinxTranslator
|
||||||
from sphinx.util.nodes import process_only_nodes
|
from sphinx.util.nodes import process_only_nodes
|
||||||
|
|
||||||
|
|
||||||
@ -186,9 +187,41 @@ class OnlyNodeTransform(SphinxPostTransform):
|
|||||||
process_only_nodes(self.document, self.app.builder.tags)
|
process_only_nodes(self.document, self.app.builder.tags)
|
||||||
|
|
||||||
|
|
||||||
|
class SigElementFallbackTransform(SphinxPostTransform):
|
||||||
|
"""Fallback desc_sig_element 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__)
|
||||||
|
|
||||||
|
translator = self.app.builder.get_translator_class()
|
||||||
|
if isinstance(translator, SphinxTranslator):
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
def fallback(self):
|
||||||
|
for node in self.document.traverse(addnodes.desc_sig_element):
|
||||||
|
newnode = nodes.inline()
|
||||||
|
newnode.update_all_atts(node)
|
||||||
|
newnode.extend(node)
|
||||||
|
node.replace_self(newnode)
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||||
app.add_post_transform(ReferencesResolver)
|
app.add_post_transform(ReferencesResolver)
|
||||||
app.add_post_transform(OnlyNodeTransform)
|
app.add_post_transform(OnlyNodeTransform)
|
||||||
|
app.add_post_transform(SigElementFallbackTransform)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'version': 'builtin',
|
'version': 'builtin',
|
||||||
|
@ -177,7 +177,8 @@ def test_html4_output(app, status, warning):
|
|||||||
],
|
],
|
||||||
'autodoc.html': [
|
'autodoc.html': [
|
||||||
(".//dl[@class='py class']/dt[@id='autodoc-target-class']", ''),
|
(".//dl[@class='py class']/dt[@id='autodoc-target-class']", ''),
|
||||||
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em", r'\*\*kwds'),
|
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em/span", r'\*\*'),
|
||||||
|
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em/span", r'kwds'),
|
||||||
(".//dd/p", r'Return spam\.'),
|
(".//dd/p", r'Return spam\.'),
|
||||||
],
|
],
|
||||||
'extapi.html': [
|
'extapi.html': [
|
||||||
|
@ -17,7 +17,8 @@ from docutils import nodes
|
|||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.addnodes import (
|
from sphinx.addnodes import (
|
||||||
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional,
|
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional,
|
||||||
desc_parameter, desc_parameterlist, desc_returns, desc_signature
|
desc_parameter, desc_parameterlist, desc_returns, desc_signature,
|
||||||
|
desc_sig_name, desc_sig_operator, desc_sig_punctuation,
|
||||||
)
|
)
|
||||||
from sphinx.domains import IndexEntry
|
from sphinx.domains import IndexEntry
|
||||||
from sphinx.domains.python import (
|
from sphinx.domains.python import (
|
||||||
@ -246,8 +247,10 @@ def test_pyfunction_signature(app):
|
|||||||
assert_node(doctree[1], addnodes.desc, desctype="function",
|
assert_node(doctree[1], addnodes.desc, desctype="function",
|
||||||
domain="py", objtype="function", noindex=False)
|
domain="py", objtype="function", noindex=False)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, desc_parameter, ("name",
|
[desc_parameterlist, desc_parameter, ([desc_sig_name, "name"],
|
||||||
": str")])
|
[desc_sig_punctuation, ":"],
|
||||||
|
" ",
|
||||||
|
[nodes.inline, "str"])])
|
||||||
|
|
||||||
|
|
||||||
def test_pyfunction_signature_full(app):
|
def test_pyfunction_signature_full(app):
|
||||||
@ -262,17 +265,31 @@ def test_pyfunction_signature_full(app):
|
|||||||
assert_node(doctree[1], addnodes.desc, desctype="function",
|
assert_node(doctree[1], addnodes.desc, desctype="function",
|
||||||
domain="py", objtype="function", noindex=False)
|
domain="py", objtype="function", noindex=False)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, ([desc_parameter, ("a",
|
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"],
|
||||||
": str")],
|
[desc_sig_punctuation, ":"],
|
||||||
[desc_parameter, ("b",
|
" ",
|
||||||
"=1")],
|
[desc_sig_name, "str"])],
|
||||||
[desc_parameter, ("*args",
|
[desc_parameter, ([desc_sig_name, "b"],
|
||||||
": str")],
|
[desc_sig_operator, "="],
|
||||||
[desc_parameter, ("c",
|
[nodes.inline, "1"])],
|
||||||
": bool",
|
[desc_parameter, ([desc_sig_operator, "*"],
|
||||||
" = True")],
|
[desc_sig_name, "args"],
|
||||||
[desc_parameter, ("**kwargs",
|
[desc_sig_punctuation, ":"],
|
||||||
": str")])])
|
" ",
|
||||||
|
[desc_sig_name, "str"])],
|
||||||
|
[desc_parameter, ([desc_sig_name, "c"],
|
||||||
|
[desc_sig_punctuation, ":"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_name, "bool"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_operator, "="],
|
||||||
|
" ",
|
||||||
|
[nodes.inline, "True"])],
|
||||||
|
[desc_parameter, ([desc_sig_operator, "**"],
|
||||||
|
[desc_sig_name, "kwargs"],
|
||||||
|
[desc_sig_punctuation, ":"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_name, "str"])])])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
|
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
|
||||||
@ -281,37 +298,40 @@ def test_pyfunction_signature_full_py38(app):
|
|||||||
text = ".. py:function:: hello(*, a)"
|
text = ".. py:function:: hello(*, a)"
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, ([desc_parameter, "*"],
|
[desc_parameterlist, ([desc_parameter, nodes.inline, "*"],
|
||||||
[desc_parameter, ("a",
|
[desc_parameter, ([desc_sig_name, "a"],
|
||||||
"=None")])])
|
[desc_sig_operator, "="],
|
||||||
|
[nodes.inline, "None"])])])
|
||||||
|
|
||||||
# case: separator in the middle
|
# case: separator in the middle
|
||||||
text = ".. py:function:: hello(a, /, b, *, c)"
|
text = ".. py:function:: hello(a, /, b, *, c)"
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, ([desc_parameter, "a"],
|
[desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
|
||||||
[desc_parameter, "/"],
|
[desc_parameter, desc_sig_operator, "/"],
|
||||||
[desc_parameter, "b"],
|
[desc_parameter, desc_sig_name, "b"],
|
||||||
[desc_parameter, "*"],
|
[desc_parameter, desc_sig_operator, "*"],
|
||||||
[desc_parameter, ("c",
|
[desc_parameter, ([desc_sig_name, "c"],
|
||||||
"=None")])])
|
[desc_sig_operator, "="],
|
||||||
|
[nodes.inline, "None"])])])
|
||||||
|
|
||||||
# case: separator in the middle (2)
|
# case: separator in the middle (2)
|
||||||
text = ".. py:function:: hello(a, /, *, b)"
|
text = ".. py:function:: hello(a, /, *, b)"
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, ([desc_parameter, "a"],
|
[desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
|
||||||
[desc_parameter, "/"],
|
[desc_parameter, desc_sig_operator, "/"],
|
||||||
[desc_parameter, "*"],
|
[desc_parameter, desc_sig_operator, "*"],
|
||||||
[desc_parameter, ("b",
|
[desc_parameter, ([desc_sig_name, "b"],
|
||||||
"=None")])])
|
[desc_sig_operator, "="],
|
||||||
|
[nodes.inline, "None"])])])
|
||||||
|
|
||||||
# case: separator at tail
|
# case: separator at tail
|
||||||
text = ".. py:function:: hello(a, /)"
|
text = ".. py:function:: hello(a, /)"
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree[1][0][1],
|
assert_node(doctree[1][0][1],
|
||||||
[desc_parameterlist, ([desc_parameter, "a"],
|
[desc_parameterlist, ([desc_parameter, desc_sig_name, "a"],
|
||||||
[desc_parameter, "/"])])
|
[desc_parameter, desc_sig_operator, "/"])])
|
||||||
|
|
||||||
|
|
||||||
def test_optional_pyfunction_signature(app):
|
def test_optional_pyfunction_signature(app):
|
||||||
|
Loading…
Reference in New Issue
Block a user