Skip to content

Commit 24a0385

Browse files
authored
Add allow_section_headings to SphinxDirective parsing methods (#12503)
1 parent 9276639 commit 24a0385

File tree

10 files changed

+57
-17
lines changed

10 files changed

+57
-17
lines changed

sphinx/directives/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ def run(self) -> list[Node]:
278278
# needed for association of version{added,changed} directives
279279
self.env.temp_data['object'] = self.names[0]
280280
self.before_content()
281-
content_node = addnodes.desc_content('', *self.parse_content_to_nodes())
281+
content_children = self.parse_content_to_nodes(allow_section_headings=True)
282+
content_node = addnodes.desc_content('', *content_children)
282283
node.append(content_node)
283284
self.transform_content(content_node)
284285
self.env.app.emit('object-description-transform',

sphinx/domains/javascript.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ def run(self) -> list[Node]:
311311
self.env.ref_context['js:module'] = mod_name
312312
no_index = 'no-index' in self.options or 'noindex' in self.options
313313

314-
content_nodes = self.parse_content_to_nodes()
314+
content_nodes = self.parse_content_to_nodes(allow_section_headings=True)
315315

316316
ret: list[Node] = []
317317
if not no_index:

sphinx/domains/python/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ def run(self) -> list[Node]:
416416
no_index = 'no-index' in self.options or 'noindex' in self.options
417417
self.env.ref_context['py:module'] = modname
418418

419-
content_nodes = self.parse_content_to_nodes()
419+
content_nodes = self.parse_content_to_nodes(allow_section_headings=True)
420420

421421
ret: list[Node] = []
422422
if not no_index:

sphinx/domains/std/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,9 @@ def run(self) -> list[Node]:
405405

406406
if definition:
407407
offset = definition.items[0][1]
408-
definition_nodes = nested_parse_to_nodes(self.state, definition, offset=offset)
408+
definition_nodes = nested_parse_to_nodes(
409+
self.state, definition, offset=offset, allow_section_headings=False,
410+
)
409411
else:
410412
definition_nodes = []
411413
termnodes.append(nodes.definition('', *definition_nodes))

sphinx/ext/autosummary/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,8 @@ def append_row(*column_texts: str) -> None:
409409
for text in column_texts:
410410
vl = StringList([text], f'{source}:{line}:<autosummary>')
411411
with switch_source_input(self.state, vl):
412-
col_nodes = nested_parse_to_nodes(self.state, vl)
412+
col_nodes = nested_parse_to_nodes(self.state, vl,
413+
allow_section_headings=False)
413414
if col_nodes and isinstance(col_nodes[0], nodes.paragraph):
414415
node = col_nodes[0]
415416
else:

sphinx/ext/ifconfig.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def run(self) -> list[Node]:
4747
node.document = self.state.document
4848
self.set_source_info(node)
4949
node['expr'] = self.arguments[0]
50-
node += self.parse_content_to_nodes()
50+
node += self.parse_content_to_nodes(allow_section_headings=True)
5151
return [node]
5252

5353

sphinx/util/docutils.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,21 +434,49 @@ def get_location(self) -> str:
434434
return f'<unknown>:{line}'
435435
return ''
436436

437-
def parse_content_to_nodes(self) -> list[Node]:
438-
"""Parse the directive's content into nodes."""
439-
return nested_parse_to_nodes(self.state, self.content, offset=self.content_offset)
440-
441-
def parse_text_to_nodes(self, text: str = '', /, *, offset: int = -1) -> list[Node]:
437+
def parse_content_to_nodes(self, allow_section_headings: bool = False) -> list[Node]:
438+
"""Parse the directive's content into nodes.
439+
440+
:param allow_section_headings:
441+
Are titles (sections) allowed in the directive's content?
442+
Note that this option bypasses Docutils' usual checks on
443+
doctree structure, and misuse of this option can lead to
444+
an incoherent doctree. In Docutils, section nodes should
445+
only be children of ``Structural`` nodes, which includes
446+
``document``, ``section``, and ``sidebar`` nodes.
447+
"""
448+
return nested_parse_to_nodes(
449+
self.state,
450+
self.content,
451+
offset=self.content_offset,
452+
allow_section_headings=allow_section_headings,
453+
)
454+
455+
def parse_text_to_nodes(
456+
self, text: str = '', /, *, offset: int = -1, allow_section_headings: bool = False,
457+
) -> list[Node]:
442458
"""Parse *text* into nodes.
443459
444460
:param text:
445461
Text, in string form. ``StringList`` is also accepted.
462+
:param allow_section_headings:
463+
Are titles (sections) allowed in *text*?
464+
Note that this option bypasses Docutils' usual checks on
465+
doctree structure, and misuse of this option can lead to
466+
an incoherent doctree. In Docutils, section nodes should
467+
only be children of ``Structural`` nodes, which includes
468+
``document``, ``section``, and ``sidebar`` nodes.
446469
:param offset:
447470
The offset of the content.
448471
"""
449472
if offset == -1:
450473
offset = self.content_offset
451-
return nested_parse_to_nodes(self.state, text, offset=offset)
474+
return nested_parse_to_nodes(
475+
self.state,
476+
text,
477+
offset=offset,
478+
allow_section_headings=allow_section_headings,
479+
)
452480

453481
def parse_inline(
454482
self, text: str, *, lineno: int = -1,

sphinx/util/nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def nested_parse_with_titles(state: RSTState, content: StringList, node: Node,
334334
context, such as docstrings.
335335
336336
This function is retained for compatability and will be deprecated in
337-
Sphinx 8. Prefer ``parse_block_text()``.
337+
Sphinx 8. Prefer ``nested_parse_to_nodes()``.
338338
"""
339339
with _fresh_title_style_context(state):
340340
ret = state.nested_parse(content, content_offset, node, match_titles=True)

sphinx/util/parsing.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def nested_parse_to_nodes(
2020
*,
2121
source: str = '<generated text>',
2222
offset: int = 0,
23+
allow_section_headings: bool = True,
2324
keep_title_context: bool = False,
2425
) -> list[nodes.Node]: # Element | nodes.Text
2526
"""Parse *text* into nodes.
@@ -32,6 +33,13 @@ def nested_parse_to_nodes(
3233
The text's source, used when creating a new ``StringList``.
3334
:param offset:
3435
The offset of the content.
36+
:param allow_section_headings:
37+
Are titles (sections) allowed in *text*?
38+
Note that this option bypasses Docutils' usual checks on
39+
doctree structure, and misuse of this option can lead to
40+
an incoherent doctree. In Docutils, section nodes should
41+
only be children of ``Structural`` nodes, which includes
42+
``document``, ``section``, and ``sidebar`` nodes.
3543
:param keep_title_context:
3644
If this is False (the default), then *content* is parsed as if it were
3745
an independent document, meaning that title decorations (e.g. underlines)
@@ -49,10 +57,10 @@ def nested_parse_to_nodes(
4957
node.document = document
5058

5159
if keep_title_context:
52-
state.nested_parse(content, offset, node, match_titles=True)
60+
state.nested_parse(content, offset, node, match_titles=allow_section_headings)
5361
else:
5462
with _fresh_title_style_context(state):
55-
state.nested_parse(content, offset, node, match_titles=True)
63+
state.nested_parse(content, offset, node, match_titles=allow_section_headings)
5664
return node.children
5765

5866

tests/test_util/test_util_docutils_sphinx_directive.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_sphinx_directive_parse_content_to_nodes():
100100
content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
101101
directive.content = StringList(content.split('\n'), source='<source>')
102102

103-
parsed = directive.parse_content_to_nodes()
103+
parsed = directive.parse_content_to_nodes(allow_section_headings=True)
104104
assert len(parsed) == 1
105105
node = parsed[0]
106106
assert isinstance(node, nodes.section)
@@ -115,7 +115,7 @@ def test_sphinx_directive_parse_text_to_nodes():
115115
directive = make_directive(env=SimpleNamespace())
116116
content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
117117

118-
parsed = directive.parse_text_to_nodes(content)
118+
parsed = directive.parse_text_to_nodes(content, allow_section_headings=True)
119119
assert len(parsed) == 1
120120
node = parsed[0]
121121
assert isinstance(node, nodes.section)

0 commit comments

Comments
 (0)