diff --git a/CHANGES b/CHANGES index ef3de8895..6f969d642 100644 --- a/CHANGES +++ b/CHANGES @@ -90,6 +90,9 @@ Features added - ``:classmethod:`` - ``:property:`` - ``:staticmethod:`` + +* rst domain: Add :rst:dir:`directive:option` directive to describe the option + for directive * #6306: html: Add a label to search form for accessability purposes Bugs fixed diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 7d616c8c7..0f93c89d4 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -1423,6 +1423,43 @@ The reStructuredText domain (name **rst**) provides the following directives: Bar description. +.. rst:directive:: .. rst:directive:option:: name + + Describes an option for reST directive. The *name* can be a single option + name or option name with arguments which separated with colon (``:``). + For example:: + + .. rst:directive:: toctree + + .. rst:directive:option:: caption: caption of ToC + + .. rst:directive:option:: glob + + will be rendered as: + + .. rst:directive:: toctree + :noindex: + + .. rst:directive:option:: caption: caption of ToC + + .. rst:directive:option:: glob + + .. rubric:: options + + .. rst:directive:option:: type + :type: description for the option of directive + + Describe the type of option value. + + For example:: + + .. rst:directive:: toctree + + .. rst:directive:option:: maxdepth + :type: integer or no value + + .. versionadded:: 2.1 + .. rst:directive:: .. rst:role:: name Describes a reST role. For example:: diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index bccad0628..f054abf28 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -11,6 +11,8 @@ import re from typing import cast +from docutils.parsers.rst import directives + from sphinx import addnodes from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType @@ -98,6 +100,74 @@ class ReSTDirective(ReSTMarkup): # type: (str, str) -> str return _('%s (directive)') % name + def before_content(self): + # type: () -> None + if self.names: + directives = self.env.ref_context.setdefault('rst:directives', []) + directives.append(self.names[0]) + + def after_content(self): + # type: () -> None + directives = self.env.ref_context.setdefault('rst:directives', []) + if directives: + directives.pop() + + +class ReSTDirectiveOption(ReSTMarkup): + """ + Description of an option for reST directive. + """ + option_spec = ReSTMarkup.option_spec.copy() + option_spec.update({ + 'type': directives.unchanged, + }) + + def handle_signature(self, sig, signode): + # type: (str, addnodes.desc_signature) -> str + try: + name, argument = re.split(r'\s*:\s+', sig.strip(), 1) + except ValueError: + name, argument = sig, None + + signode += addnodes.desc_name(':%s:' % name, ':%s:' % name) + if argument: + signode += addnodes.desc_annotation(' ' + argument, ' ' + argument) + if self.options.get('type'): + text = ' (%s)' % self.options['type'] + signode += addnodes.desc_annotation(text, text) + return name + + def add_target_and_index(self, name, sig, signode): + # type: (str, str, addnodes.desc_signature) -> None + targetname = '-'.join([self.objtype, self.current_directive, name]) + if targetname not in self.state.document.ids: + signode['names'].append(targetname) + signode['ids'].append(targetname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + + domain = cast(ReSTDomain, self.env.get_domain('rst')) + domain.note_object(self.objtype, name, location=(self.env.docname, self.lineno)) + + if self.current_directive: + key = name[0].upper() + pair = [_('%s (directive)') % self.current_directive, + _(':%s: (directive option)') % name] + self.indexnode['entries'].append(('pair', '; '.join(pair), targetname, '', key)) + else: + key = name[0].upper() + text = _(':%s: (directive option)') % name + self.indexnode['entries'].append(('single', text, targetname, '', key)) + + @property + def current_directive(self): + # type: () -> str + directives = self.env.ref_context.get('rst:directives') + if directives: + return directives[-1] + else: + return '' + class ReSTRole(ReSTMarkup): """ @@ -119,11 +189,13 @@ class ReSTDomain(Domain): label = 'reStructuredText' object_types = { - 'directive': ObjType(_('directive'), 'dir'), - 'role': ObjType(_('role'), 'role'), + 'directive': ObjType(_('directive'), 'dir'), + 'directive:option': ObjType(_('directive-option'), 'dir'), + 'role': ObjType(_('role'), 'role'), } directives = { 'directive': ReSTDirective, + 'directive:option': ReSTDirectiveOption, 'role': ReSTRole, } roles = { diff --git a/tests/test_domain_rst.py b/tests/test_domain_rst.py index 3310b5752..207ff1ff3 100644 --- a/tests/test_domain_rst.py +++ b/tests/test_domain_rst.py @@ -10,8 +10,7 @@ from sphinx import addnodes from sphinx.addnodes import ( - desc, desc_addname, desc_content, desc_name, desc_optional, desc_parameter, - desc_parameterlist, desc_returns, desc_signature + desc, desc_addname, desc_annotation, desc_content, desc_name, desc_signature ) from sphinx.domains.rst import parse_directive from sphinx.testing import restructuredtext @@ -69,6 +68,66 @@ def test_rst_directive_with_argument(app): domain="rst", objtype="directive", noindex=False) +def test_rst_directive_option(app): + text = ".. rst:directive:option:: foo" + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, desc_name, ":foo:"], + [desc_content, ()])])) + assert_node(doctree[0], + entries=[("single", ":foo: (directive option)", + "directive:option--foo", "", "F")]) + assert_node(doctree[1], addnodes.desc, desctype="directive:option", + domain="rst", objtype="directive:option", noindex=False) + + +def test_rst_directive_option_with_argument(app): + text = ".. rst:directive:option:: foo: bar baz" + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_name, ":foo:"], + [desc_annotation, " bar baz"])], + [desc_content, ()])])) + assert_node(doctree[0], + entries=[("single", ":foo: (directive option)", + "directive:option--foo", "", "F")]) + assert_node(doctree[1], addnodes.desc, desctype="directive:option", + domain="rst", objtype="directive:option", noindex=False) + + +def test_rst_directive_option_type(app): + text = (".. rst:directive:option:: foo\n" + " :type: directives.flags\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_name, ":foo:"], + [desc_annotation, " (directives.flags)"])], + [desc_content, ()])])) + assert_node(doctree[0], + entries=[("single", ":foo: (directive option)", + "directive:option--foo", "", "F")]) + assert_node(doctree[1], addnodes.desc, desctype="directive:option", + domain="rst", objtype="directive:option", noindex=False) + + +def test_rst_directive_and_directive_option(app): + text = (".. rst:directive:: foo\n" + "\n" + " .. rst:directive:option:: bar\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, desc_name, ".. foo::"], + [desc_content, (addnodes.index, + desc)])])) + assert_node(doctree[1][1][0], + entries=[("pair", "foo (directive); :bar: (directive option)", + "directive:option-foo-bar", "", "B")]) + assert_node(doctree[1][1][1], ([desc_signature, desc_name, ":bar:"], + [desc_content, ()])) + assert_node(doctree[1][1][1], addnodes.desc, desctype="directive:option", + domain="rst", objtype="directive:option", noindex=False) + + def test_rst_role(app): text = ".. rst:role:: ref" doctree = restructuredtext.parse(app, text)