From 1b82384e27ab443af4e0ab7aca693ed685eeba33 Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Tue, 21 Dec 2021 11:21:13 +0100 Subject: [PATCH] Add option_emphasise_placeholders config value. Support parsing of "variable part" for option directives similarly to samp roles. Fixes: #9965. Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- doc/usage/configuration.rst | 9 +++++++ doc/usage/restructuredtext/domains.rst | 3 +++ sphinx/config.py | 1 + sphinx/domains/std.py | 36 +++++++++++++++++++++++--- tests/roots/test-root/objects.txt | 9 +++++++ tests/test_build_html.py | 22 ++++++++++++++++ 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 677eb39271a..e66af3a0eee 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -706,6 +706,15 @@ General configuration .. versionadded:: 3.0 +.. confval:: option_emphasise_placeholders + + Default is ``False``. + When enabled, emphasise placeholders in ``.. option:`` directives. + To display literal braces, escape with a backslash (``\{``). For example, + ``option_emphasise_placeholders=True`` and ``.. option:: -foption={TYPE}`` would + render with ``TYPE`` emphasised. + + .. versionadded:: 5.0 .. _intl-options: diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 30bde8ea138..54e32540037 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -1750,6 +1750,9 @@ There is a set of directives allowing documenting command-line programs: referenceable by :rst:role:`option` (in the example case, you'd use something like ``:option:`dest_dir```, ``:option:`-m```, or ``:option:`--module```). + Use :confval:`option_emphasise_placeholders` for parsing of + "variable part" of a literal text (similarly to the ``samp`` role). + ``cmdoption`` directive is a deprecated alias for the ``option`` directive. .. rst:directive:: .. envvar:: name diff --git a/sphinx/config.py b/sphinx/config.py index 5f92479d382..831f265bc74 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -140,6 +140,7 @@ class Config: 'smartquotes_excludes': ({'languages': ['ja'], 'builders': ['man', 'text']}, 'env', []), + 'option_emphasise_placeholders': (False, 'env', []), } def __init__(self, config: Dict[str, Any] = {}, overrides: Dict[str, Any] = {}) -> None: diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index d5c962dc827..8ff199ade50 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -15,7 +15,7 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import _, __ -from sphinx.roles import XRefRole +from sphinx.roles import EmphasizedLiteral, XRefRole from sphinx.util import docname_join, logging, ws_re from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import clean_astext, make_id, make_refnode @@ -34,6 +34,8 @@ # RE for grammar tokens token_re = re.compile(r'`((~?\w*:)?\w+)`', re.U) +samp_role = EmphasizedLiteral() + class GenericObject(ObjectDescription[str]): """ @@ -170,15 +172,41 @@ def handle_signature(self, sig: str, signode: desc_signature) -> str: location=signode) continue optname, args = m.groups() - if optname.endswith('[') and args.endswith(']'): + if optname[-1] == '[' and args[-1] == ']': # optional value surrounded by brackets (ex. foo[=bar]) optname = optname[:-1] args = '[' + args if count: - signode += addnodes.desc_addname(', ', ', ') + if self.env.config.option_emphasise_placeholders: + signode += addnodes.desc_sig_punctuation(',', ',') + signode += addnodes.desc_sig_space() + else: + signode += addnodes.desc_addname(', ', ', ') signode += addnodes.desc_name(optname, optname) - signode += addnodes.desc_addname(args, args) + if self.env.config.option_emphasise_placeholders: + add_end_bracket = False + if not args: + continue + if args[0] == '[' and args[-1] == ']': + add_end_bracket = True + signode += addnodes.desc_sig_punctuation('[', '[') + args = args[1:-1] + if args[0] == ' ': + signode += addnodes.desc_sig_space() + args = args.strip() + if args[0] == '=': + signode += addnodes.desc_sig_punctuation('=', '=') + args = args[1:] + for part in samp_role.parse(args): + if isinstance(part, nodes.Text): + signode += nodes.Text(part.astext()) + else: + signode += part + if add_end_bracket: + signode += addnodes.desc_sig_punctuation(']', ']') + else: + signode += addnodes.desc_addname(args, args) if not count: firstname = optname signode['allnames'] = [optname] diff --git a/tests/roots/test-root/objects.txt b/tests/roots/test-root/objects.txt index 7e2db1bb872..a4a5c667c9f 100644 --- a/tests/roots/test-root/objects.txt +++ b/tests/roots/test-root/objects.txt @@ -194,6 +194,15 @@ Link to :option:`perl +p`, :option:`--ObjC++`, :option:`--plugin.option`, :optio Link to :option:`hg commit` and :option:`git commit -p`. +.. option:: --abi={TYPE} + +.. option:: --test={WHERE}-{COUNT} + +.. option:: --wrap=\{\{value\}\} + +.. option:: -allowable_client {client_name} + +Foo bar. User markup =========== diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 75f89a67a0f..3ef0e36550d 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1735,3 +1735,25 @@ def test_html_code_role(app): assert ('
' + '
' +
             common_content) in content
+
+
+@pytest.mark.sphinx('html', testroot='root',
+                    confoverrides={'option_emphasise_placeholders': True})
+def test_option_emphasise_placeholders(app, status, warning):
+    app.build()
+    content = (app.outdir / 'objects.html').read_text()
+    assert 'TYPE' in content
+    assert '{TYPE}' not in content
+    assert ('WHERE'
+            '-'
+            'COUNT' in content)
+    assert '{{value}}' in content
+
+
+@pytest.mark.sphinx('html', testroot='root')
+def test_option_emphasise_placeholders_default(app, status, warning):
+    app.build()
+    content = (app.outdir / 'objects.html').read_text()
+    assert '={TYPE}' in content
+    assert '={WHERE}-{COUNT}' in content
+    assert '{client_name}' in content