[rst] Add level option to rubric directive (#12506)

This commit adds a `level` option to the `rubric` directive, which propagates a `level` attribute to the `rubric` node,
and allows renderers to select a specific heading level.

Logic for this attribute has been added to the HTML5 and LaTeX builder.
This commit is contained in:
Chris Sewell
2024-07-14 05:52:58 +02:00
committed by GitHub
parent 35e7bfc347
commit 41b363dbff
10 changed files with 129 additions and 3 deletions

View File

@@ -28,6 +28,8 @@ Features added
.. rst-class:: compact
* Add ``level`` option to :rst:dir:`rubric` directive.
Patch by Chris Sewell.
* Add optional ``description`` argument to
:meth:`~sphinx.application.Sphinx.add_config_value`.
Patch by Chris Sewell.

View File

@@ -373,8 +373,18 @@ units as well as normal text.
.. rst:directive:: .. rubric:: title
This directive creates a paragraph heading that is not used to create a
table of contents node.
A rubric is like an informal heading that doesn't correspond to the document's structure,
i.e. it does not create a table of contents node.
.. rst:directive:option:: level: n
:type: number from 1 to 6
.. versionadded:: 7.4
Use this option to specify the heading level of the rubric.
In this case the rubric will be rendered as ``<h1>`` to ``<h6>`` for HTML output,
or as the corresponding non-numbered sectioning command for LaTeX
(see :confval:`latex_toplevel_sectioning`).
.. note::

View File

@@ -176,12 +176,38 @@ class MathDirective(SphinxDirective):
ret.insert(0, target)
class Rubric(SphinxDirective):
"""A patch of the docutils' :rst:dir:`rubric` directive,
which adds a level option to specify the heading level of the rubric.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {
'class': directives.class_option,
'name': directives.unchanged,
'level': lambda c: directives.choice(c, ('1', '2', '3', '4', '5', '6')),
}
def run(self) -> list[Node]:
set_classes(self.options)
rubric_text = self.arguments[0]
textnodes, messages = self.parse_inline(rubric_text, lineno=self.lineno)
rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options)
self.add_name(rubric)
if 'level' in self.options:
rubric['level'] = int(self.options['level'])
return [rubric, *messages]
def setup(app: Sphinx) -> ExtensionMetadata:
directives.register_directive('figure', Figure)
directives.register_directive('meta', Meta)
directives.register_directive('csv-table', CSVTable)
directives.register_directive('code', Code)
directives.register_directive('math', MathDirective)
directives.register_directive('rubric', Rubric)
return {
'version': 'builtin',

View File

@@ -509,6 +509,30 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
super().depart_title(node)
# overwritten
def visit_rubric(self, node: Element) -> None:
if "level" in node:
level = node["level"]
if level in (1, 2, 3, 4, 5, 6):
self.body.append(self.starttag(node, f'h{level}', '', CLASS='rubric'))
else:
logger.warning(
__('unsupported rubric heading level: %s'),
level,
type='html',
location=node
)
super().visit_rubric(node)
else:
super().visit_rubric(node)
# overwritten
def depart_rubric(self, node: Element) -> None:
if level := node.get("level"):
self.body.append(f'</h{level}>\n')
else:
super().depart_rubric(node)
# overwritten
def visit_literal_block(self, node: Element) -> None:
if node.rawsource != node.astext():

View File

@@ -967,7 +967,20 @@ class LaTeXTranslator(SphinxTranslator):
def visit_rubric(self, node: Element) -> None:
if len(node) == 1 and node.astext() in ('Footnotes', _('Footnotes')):
raise nodes.SkipNode
self.body.append(r'\subsubsection*{')
tag = 'subsubsection'
if "level" in node:
level = node["level"]
try:
tag = self.sectionnames[self.top_sectionlevel - 1 + level]
except Exception:
logger.warning(
__('unsupported rubric heading level: %s'),
level,
type='latex',
location=node
)
self.body.append(rf'\{tag}*{{')
self.context.append('}' + CR)
self.in_title = 1

View File

@@ -1,3 +1,4 @@
latex_documents = [
('index', 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
]
latex_toplevel_sectioning = 'section'

View File

@@ -5,3 +5,35 @@ test-markup-rubric
.. rubric:: This is
a multiline rubric
.. rubric:: A rubric with a class
:class: myclass
.. rubric:: A rubric with a heading level 1
:level: 1
:class: myclass
.. rubric:: A rubric with a heading level 2
:level: 2
:class: myclass
.. rubric:: A rubric with a heading level 3
:level: 3
:class: myclass
.. rubric:: A rubric with a heading level 4
:level: 4
:class: myclass
.. rubric:: A rubric with a heading level 5
:level: 5
:class: myclass
.. rubric:: A rubric with a heading level 6
:level: 6
:class: myclass
.. rubric:: A rubric with a heading level 7
:level: 7
:class: myclass

View File

@@ -278,3 +278,12 @@ def tail_check(check):
def test_html5_output(app, cached_etree_parse, fname, path, check):
app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, path, check)
@pytest.mark.sphinx('html', testroot='markup-rubric')
def test_html5_rubric(app):
app.build()
assert '"7" unknown' in app.warning.getvalue()
content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert '<p class="rubric">This is a rubric</p>' in content
assert '<h2 class="myclass rubric">A rubric with a heading level 2</h2>' in content

View File

@@ -1759,3 +1759,11 @@ def test_one_parameter_per_line(app, status, warning):
assert ('\\pysiglinewithargsret{\\sphinxbfcode{\\sphinxupquote{hello}}}' in result)
assert ('\\pysigwithonelineperarg{\\sphinxbfcode{\\sphinxupquote{foo}}}' in result)
@pytest.mark.sphinx('latex', testroot='markup-rubric')
def test_latex_rubric(app):
app.build()
content = (app.outdir / 'test.tex').read_text(encoding='utf8')
assert r'\subsubsection*{This is a rubric}' in content
assert r'\subsection*{A rubric with a heading level 2}' in content

View File

@@ -43,6 +43,7 @@ def test_texinfo_rubric(app, status, warning):
output = (app.outdir / 'projectnamenotset.texi').read_text(encoding='utf8')
assert '@heading This is a rubric' in output
assert '@heading This is a multiline rubric' in output
assert '@heading A rubric with a heading level' in output
@pytest.mark.sphinx('texinfo', testroot='markup-citation')