Add `allow_section_headings` to SphinxDirective parsing methods (#12503)

This commit is contained in:
Adam Turner 2024-07-02 23:53:58 +01:00 committed by GitHub
parent 9276639fa6
commit 24a0385997
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 56 additions and 16 deletions

View File

@ -278,7 +278,8 @@ class ObjectDescription(SphinxDirective, Generic[ObjDescT]):
# needed for association of version{added,changed} directives # needed for association of version{added,changed} directives
self.env.temp_data['object'] = self.names[0] self.env.temp_data['object'] = self.names[0]
self.before_content() self.before_content()
content_node = addnodes.desc_content('', *self.parse_content_to_nodes()) content_children = self.parse_content_to_nodes(allow_section_headings=True)
content_node = addnodes.desc_content('', *content_children)
node.append(content_node) node.append(content_node)
self.transform_content(content_node) self.transform_content(content_node)
self.env.app.emit('object-description-transform', self.env.app.emit('object-description-transform',

View File

@ -311,7 +311,7 @@ class JSModule(SphinxDirective):
self.env.ref_context['js:module'] = mod_name self.env.ref_context['js:module'] = mod_name
no_index = 'no-index' in self.options or 'noindex' in self.options no_index = 'no-index' in self.options or 'noindex' in self.options
content_nodes = self.parse_content_to_nodes() content_nodes = self.parse_content_to_nodes(allow_section_headings=True)
ret: list[Node] = [] ret: list[Node] = []
if not no_index: if not no_index:

View File

@ -416,7 +416,7 @@ class PyModule(SphinxDirective):
no_index = 'no-index' in self.options or 'noindex' in self.options no_index = 'no-index' in self.options or 'noindex' in self.options
self.env.ref_context['py:module'] = modname self.env.ref_context['py:module'] = modname
content_nodes = self.parse_content_to_nodes() content_nodes = self.parse_content_to_nodes(allow_section_headings=True)
ret: list[Node] = [] ret: list[Node] = []
if not no_index: if not no_index:

View File

@ -405,7 +405,9 @@ class Glossary(SphinxDirective):
if definition: if definition:
offset = definition.items[0][1] offset = definition.items[0][1]
definition_nodes = nested_parse_to_nodes(self.state, definition, offset=offset) definition_nodes = nested_parse_to_nodes(
self.state, definition, offset=offset, allow_section_headings=False,
)
else: else:
definition_nodes = [] definition_nodes = []
termnodes.append(nodes.definition('', *definition_nodes)) termnodes.append(nodes.definition('', *definition_nodes))

View File

@ -409,7 +409,8 @@ class Autosummary(SphinxDirective):
for text in column_texts: for text in column_texts:
vl = StringList([text], f'{source}:{line}:<autosummary>') vl = StringList([text], f'{source}:{line}:<autosummary>')
with switch_source_input(self.state, vl): with switch_source_input(self.state, vl):
col_nodes = nested_parse_to_nodes(self.state, vl) col_nodes = nested_parse_to_nodes(self.state, vl,
allow_section_headings=False)
if col_nodes and isinstance(col_nodes[0], nodes.paragraph): if col_nodes and isinstance(col_nodes[0], nodes.paragraph):
node = col_nodes[0] node = col_nodes[0]
else: else:

View File

@ -47,7 +47,7 @@ class IfConfig(SphinxDirective):
node.document = self.state.document node.document = self.state.document
self.set_source_info(node) self.set_source_info(node)
node['expr'] = self.arguments[0] node['expr'] = self.arguments[0]
node += self.parse_content_to_nodes() node += self.parse_content_to_nodes(allow_section_headings=True)
return [node] return [node]

View File

@ -434,21 +434,49 @@ class SphinxDirective(Directive):
return f'<unknown>:{line}' return f'<unknown>:{line}'
return '' return ''
def parse_content_to_nodes(self) -> list[Node]: def parse_content_to_nodes(self, allow_section_headings: bool = False) -> list[Node]:
"""Parse the directive's content into nodes.""" """Parse the directive's content into nodes.
return nested_parse_to_nodes(self.state, self.content, offset=self.content_offset)
def parse_text_to_nodes(self, text: str = '', /, *, offset: int = -1) -> list[Node]: :param allow_section_headings:
Are titles (sections) allowed in the directive's content?
Note that this option bypasses Docutils' usual checks on
doctree structure, and misuse of this option can lead to
an incoherent doctree. In Docutils, section nodes should
only be children of ``Structural`` nodes, which includes
``document``, ``section``, and ``sidebar`` nodes.
"""
return nested_parse_to_nodes(
self.state,
self.content,
offset=self.content_offset,
allow_section_headings=allow_section_headings,
)
def parse_text_to_nodes(
self, text: str = '', /, *, offset: int = -1, allow_section_headings: bool = False,
) -> list[Node]:
"""Parse *text* into nodes. """Parse *text* into nodes.
:param text: :param text:
Text, in string form. ``StringList`` is also accepted. Text, in string form. ``StringList`` is also accepted.
:param allow_section_headings:
Are titles (sections) allowed in *text*?
Note that this option bypasses Docutils' usual checks on
doctree structure, and misuse of this option can lead to
an incoherent doctree. In Docutils, section nodes should
only be children of ``Structural`` nodes, which includes
``document``, ``section``, and ``sidebar`` nodes.
:param offset: :param offset:
The offset of the content. The offset of the content.
""" """
if offset == -1: if offset == -1:
offset = self.content_offset offset = self.content_offset
return nested_parse_to_nodes(self.state, text, offset=offset) return nested_parse_to_nodes(
self.state,
text,
offset=offset,
allow_section_headings=allow_section_headings,
)
def parse_inline( def parse_inline(
self, text: str, *, lineno: int = -1, self, text: str, *, lineno: int = -1,

View File

@ -334,7 +334,7 @@ def nested_parse_with_titles(state: RSTState, content: StringList, node: Node,
context, such as docstrings. context, such as docstrings.
This function is retained for compatability and will be deprecated in This function is retained for compatability and will be deprecated in
Sphinx 8. Prefer ``parse_block_text()``. Sphinx 8. Prefer ``nested_parse_to_nodes()``.
""" """
with _fresh_title_style_context(state): with _fresh_title_style_context(state):
ret = state.nested_parse(content, content_offset, node, match_titles=True) ret = state.nested_parse(content, content_offset, node, match_titles=True)

View File

@ -20,6 +20,7 @@ def nested_parse_to_nodes(
*, *,
source: str = '<generated text>', source: str = '<generated text>',
offset: int = 0, offset: int = 0,
allow_section_headings: bool = True,
keep_title_context: bool = False, keep_title_context: bool = False,
) -> list[nodes.Node]: # Element | nodes.Text ) -> list[nodes.Node]: # Element | nodes.Text
"""Parse *text* into nodes. """Parse *text* into nodes.
@ -32,6 +33,13 @@ def nested_parse_to_nodes(
The text's source, used when creating a new ``StringList``. The text's source, used when creating a new ``StringList``.
:param offset: :param offset:
The offset of the content. The offset of the content.
:param allow_section_headings:
Are titles (sections) allowed in *text*?
Note that this option bypasses Docutils' usual checks on
doctree structure, and misuse of this option can lead to
an incoherent doctree. In Docutils, section nodes should
only be children of ``Structural`` nodes, which includes
``document``, ``section``, and ``sidebar`` nodes.
:param keep_title_context: :param keep_title_context:
If this is False (the default), then *content* is parsed as if it were If this is False (the default), then *content* is parsed as if it were
an independent document, meaning that title decorations (e.g. underlines) an independent document, meaning that title decorations (e.g. underlines)
@ -49,10 +57,10 @@ def nested_parse_to_nodes(
node.document = document node.document = document
if keep_title_context: if keep_title_context:
state.nested_parse(content, offset, node, match_titles=True) state.nested_parse(content, offset, node, match_titles=allow_section_headings)
else: else:
with _fresh_title_style_context(state): with _fresh_title_style_context(state):
state.nested_parse(content, offset, node, match_titles=True) state.nested_parse(content, offset, node, match_titles=allow_section_headings)
return node.children return node.children

View File

@ -100,7 +100,7 @@ def test_sphinx_directive_parse_content_to_nodes():
content = 'spam\n====\n\nEggs! *Lobster thermidor.*' content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
directive.content = StringList(content.split('\n'), source='<source>') directive.content = StringList(content.split('\n'), source='<source>')
parsed = directive.parse_content_to_nodes() parsed = directive.parse_content_to_nodes(allow_section_headings=True)
assert len(parsed) == 1 assert len(parsed) == 1
node = parsed[0] node = parsed[0]
assert isinstance(node, nodes.section) assert isinstance(node, nodes.section)
@ -115,7 +115,7 @@ def test_sphinx_directive_parse_text_to_nodes():
directive = make_directive(env=SimpleNamespace()) directive = make_directive(env=SimpleNamespace())
content = 'spam\n====\n\nEggs! *Lobster thermidor.*' content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
parsed = directive.parse_text_to_nodes(content) parsed = directive.parse_text_to_nodes(content, allow_section_headings=True)
assert len(parsed) == 1 assert len(parsed) == 1
node = parsed[0] node = parsed[0]
assert isinstance(node, nodes.section) assert isinstance(node, nodes.section)