From 3f7085edb4c49e729d6994dfd76835ec2160288a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 4 Jan 2020 23:48:37 +0900 Subject: [PATCH] py domain: Allow to make a style for arguments of functions and methods (refs: #6417) --- CHANGES | 3 ++ sphinx/domains/python.py | 22 ++++++++------ tests/test_build_html.py | 3 +- tests/test_domain_py.py | 66 ++++++++++++++++++++++++---------------- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/CHANGES b/CHANGES index a16ce6b8cd5..1ec9322a1ff 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Incompatible changes when ``:inherited-members:`` and ``:special-members:`` are given. * #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They 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 * 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 @@ -47,6 +49,7 @@ Features added * #6558: std domain: emit a warning for duplicated generic objects * #6830: py domain: Add new event: :event:`object-description-transform` * 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 :py:meth:`.Sphinx.connect()` * #3077: Implement the scoping for :rst:dir:`productionlist` as indicated diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 5403f499a2c..51c609f7360 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -72,35 +72,39 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: for param in sig.parameters.values(): if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY: # PEP-570: Separator for Positional Only Parameter: / - params += nodes.Text('/') + params += nodes.inline('', '/', classes=['operator']) if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY, None): # PEP-3102: Separator for Keyword Only Parameter: * - params += nodes.Text('*') + params += nodes.inline('', '*', classes=['operator']) node = addnodes.desc_parameter() if param.kind == param.VAR_POSITIONAL: - node += nodes.Text('*' + param.name) + node += nodes.inline('', '*', classes=['operator']) + node += nodes.inline('', param.name, classes=['name']) elif param.kind == param.VAR_KEYWORD: - node += nodes.Text('**' + param.name) + node += nodes.inline('', '**', classes=['operator']) + node += nodes.inline('', param.name, classes=['name']) else: - node += nodes.Text(param.name) + node += nodes.inline('', param.name, classes=['name']) if param.annotation is not param.empty: - node += nodes.Text(': ' + param.annotation) + node += nodes.inline('', ': ', classes=['operator']) + node += nodes.inline('', param.annotation, classes=['annotation']) if param.default is not param.empty: if param.annotation is not param.empty: - node += nodes.Text(' = ' + str(param.default)) + node += nodes.inline('', ' = ', classes=['operator']) else: - node += nodes.Text('=' + str(param.default)) + node += nodes.inline('', '=', classes=['operator']) + node += nodes.inline('', param.default, classes=['default_value']) params += node last_kind = param.kind if last_kind == Parameter.POSITIONAL_ONLY: # PEP-570: Separator for Positional Only Parameter: / - params += nodes.Text('/') + params += nodes.inline('', '/', classes=['operator']) return params diff --git a/tests/test_build_html.py b/tests/test_build_html.py index ac44ca7e494..735cc84bdd0 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -177,7 +177,8 @@ def test_html4_output(app, status, warning): ], 'autodoc.html': [ (".//dt[@id='autodoc_target.Class']", ''), - (".//dt[@id='autodoc_target.function']/em", r'\*\*kwds'), + (".//dt[@id='autodoc_target.function']/em/span", r'\*\*'), + (".//dt[@id='autodoc_target.function']/em/span", r'kwds'), (".//dd/p", r'Return spam\.'), ], 'extapi.html': [ diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 8e92285375c..ea91f79c11d 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -243,8 +243,9 @@ def test_pyfunction_signature(app): assert_node(doctree[1], addnodes.desc, desctype="function", domain="py", objtype="function", noindex=False) assert_node(doctree[1][0][1], - [desc_parameterlist, desc_parameter, ("name", - ": str")]) + [desc_parameterlist, desc_parameter, ([nodes.inline, "name"], + [nodes.inline, ": "], + [nodes.inline, "str"])]) def test_pyfunction_signature_full(app): @@ -259,17 +260,25 @@ def test_pyfunction_signature_full(app): assert_node(doctree[1], addnodes.desc, desctype="function", domain="py", objtype="function", noindex=False) assert_node(doctree[1][0][1], - [desc_parameterlist, ([desc_parameter, ("a", - ": str")], - [desc_parameter, ("b", - "=1")], - [desc_parameter, ("*args", - ": str")], - [desc_parameter, ("c", - ": bool", - " = True")], - [desc_parameter, ("**kwargs", - ": str")])]) + [desc_parameterlist, ([desc_parameter, ([nodes.inline, "a"], + [nodes.inline, ": "], + [nodes.inline, "str"])], + [desc_parameter, ([nodes.inline, "b"], + [nodes.inline, "="], + [nodes.inline, "1"])], + [desc_parameter, ([nodes.inline, "*"], + [nodes.inline, "args"], + [nodes.inline, ": "], + [nodes.inline, "str"])], + [desc_parameter, ([nodes.inline, "c"], + [nodes.inline, ": "], + [nodes.inline, "bool"], + [nodes.inline, " = "], + [nodes.inline, "True"])], + [desc_parameter, ([nodes.inline, "**"], + [nodes.inline, "kwargs"], + [nodes.inline, ": "], + [nodes.inline, "str"])])]) @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @@ -278,37 +287,40 @@ def test_pyfunction_signature_full_py38(app): text = ".. py:function:: hello(*, a)" doctree = restructuredtext.parse(app, text) assert_node(doctree[1][0][1], - [desc_parameterlist, ("*", - [desc_parameter, ("a", - "=None")])]) + [desc_parameterlist, ([nodes.inline, "*"], + [desc_parameter, ([nodes.inline, "a"], + [nodes.inline, "="], + [nodes.inline, "None"])])]) # case: separator in the middle text = ".. py:function:: hello(a, /, b, *, c)" doctree = restructuredtext.parse(app, text) assert_node(doctree[1][0][1], - [desc_parameterlist, ([desc_parameter, "a"], - "/", - [desc_parameter, "b"], - "*", - [desc_parameter, ("c", - "=None")])]) + [desc_parameterlist, ([desc_parameter, nodes.inline, "a"], + [nodes.inline, "/"], + [desc_parameter, nodes.inline, "b"], + [nodes.inline, "*"], + [desc_parameter, ([nodes.inline, "c"], + [nodes.inline, "="], + [nodes.inline, "None"])])]) # case: separator in the middle (2) text = ".. py:function:: hello(a, /, *, b)" doctree = restructuredtext.parse(app, text) assert_node(doctree[1][0][1], - [desc_parameterlist, ([desc_parameter, "a"], + [desc_parameterlist, ([desc_parameter, nodes.inline, "a"], "/", "*", - [desc_parameter, ("b", - "=None")])]) + [desc_parameter, ([nodes.inline, "b"], + [nodes.inline, "="], + [nodes.inline, "None"])])]) # case: separator at tail text = ".. py:function:: hello(a, /)" doctree = restructuredtext.parse(app, text) assert_node(doctree[1][0][1], - [desc_parameterlist, ([desc_parameter, "a"], - "/")]) + [desc_parameterlist, ([desc_parameter, nodes.inline, "a"], + [nodes.inline, "/"])]) def test_optional_pyfunction_signature(app):