mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
[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:
@@ -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.
|
||||
|
||||
@@ -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::
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
latex_documents = [
|
||||
('index', 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
|
||||
]
|
||||
latex_toplevel_sectioning = 'section'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user