-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add sphinx.ext.collapse
#10532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Add sphinx.ext.collapse
#10532
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
af2e1ad
Add collapsible node
AA-Turner f1a3432
Add `.. collapsible::` directive
AA-Turner e89f4d7
Add `process_collapsible_nodes` helper function
AA-Turner b1ac2b7
Add `CollapsibleNodeTransform`
AA-Turner 9940bde
Add support for collapsible nodes in HTML writers
AA-Turner 7b40e4c
Add tests
AA-Turner f70e5e1
Add documentation
AA-Turner 069b54b
Update CHANGES
AA-Turner 2c8e75b
Add `collapsible_summary` to the documentation
AA-Turner f5ed45a
Update parsing
AA-Turner 26ab27a
Include summary line
AA-Turner 209c09b
Move to ``sphinx.ext.collapse``
AA-Turner 3dbff0c
Refactorings
AA-Turner 2b02d07
Document :open:
AA-Turner ee814e5
Documentation
AA-Turner bceaca1
Add a default class
AA-Turner 1611c89
Use a ``nodes.container``
AA-Turner 02a3d55
Rearrange tests
AA-Turner 7f720eb
Improve example
AA-Turner fee0d0f
Use ``parse_content_to_nodes``
AA-Turner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
.. _collapsible: | ||
|
||
:mod:`sphinx.ext.collapse` -- HTML collapsible content | ||
====================================================== | ||
|
||
.. module:: sphinx.ext.collapse | ||
:synopsis: Support for collapsible content in HTML output. | ||
|
||
.. versionadded:: 7.4 | ||
|
||
.. index:: single: collapse | ||
single: collapsible | ||
single: details | ||
single: summary | ||
pair: collapse; directive | ||
pair: details; directive | ||
pair: summary; directive | ||
|
||
This extension provides a :rst:dir:`collapse` directive to provide support for | ||
`collapsible content`_ in HTML output. | ||
|
||
.. _collapsible content: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details | ||
|
||
This extension is quite simple, and features only one directive: | ||
|
||
.. rst:directive:: .. collapse:: <summary description> | ||
|
||
For HTML builders, this directive places the content of the directive | ||
into an HTML `details disclosure`_ element, | ||
with the *summary description* text included as the summary for the element. | ||
The *summary description* text is parsed as reStructuredText, | ||
and can be broken over multiple lines if required. | ||
|
||
Only the HTML 5 output format supports collapsible content. | ||
For other builders, the *summary description* text | ||
and the body of the directive are rendered in the document. | ||
|
||
.. _details disclosure: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details | ||
|
||
An example and the equivalent output are shown below: | ||
|
||
.. code-block:: reStructuredText | ||
|
||
.. collapse:: ``literal`` and **bold** content, | ||
split over multiple lines. | ||
:open: | ||
|
||
This is the body of the directive. | ||
|
||
It is open by default as the ``:open:`` option was used. | ||
|
||
Markup Demonstration | ||
-------------------- | ||
|
||
The body can also contain *markup*, including sections. | ||
|
||
.. collapse:: ``literal`` and **bold** content, | ||
split over multiple lines. | ||
:open: | ||
|
||
This is the body of the directive. | ||
|
||
It is open by default as the ``:open:`` option was used. | ||
|
||
Markup Demonstration | ||
-------------------- | ||
|
||
The body can also contain *markup*, including sections. | ||
|
||
.. versionadded:: 7.4 | ||
|
||
.. rst:directive:option:: open | ||
|
||
Expand the collapsible content by default. | ||
|
||
Internal node classes | ||
--------------------- | ||
|
||
.. note:: These classes are only relevant to extension and theme developers. | ||
|
||
.. autoclass:: collapsible | ||
.. autoclass:: summary |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
"""Support for collapsible content in HTML.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Any, ClassVar | ||
|
||
from docutils import nodes | ||
from docutils.parsers.rst import directives | ||
|
||
import sphinx | ||
from sphinx.transforms.post_transforms import SphinxPostTransform | ||
from sphinx.util.docutils import SphinxDirective | ||
|
||
if TYPE_CHECKING: | ||
from sphinx.application import Sphinx | ||
from sphinx.util.typing import ExtensionMetadata, OptionSpec | ||
from sphinx.writers.html5 import HTML5Translator | ||
|
||
|
||
class collapsible(nodes.Structural, nodes.Element): | ||
"""Node for collapsible content. | ||
|
||
This is used by the :rst:dir:`collapse` directive. | ||
""" | ||
|
||
|
||
class summary(nodes.General, nodes.TextElement): | ||
"""Node for the description for collapsible content. | ||
|
||
This is used by the :rst:dir:`collapse` directive. | ||
""" | ||
|
||
|
||
def visit_collapsible(translator: HTML5Translator, node: nodes.Element) -> None: | ||
if node.get('open'): | ||
translator.body.append(translator.starttag(node, 'details', open='open')) | ||
else: | ||
translator.body.append(translator.starttag(node, 'details')) | ||
|
||
|
||
def depart_collapsible(translator: HTML5Translator, node: nodes.Element) -> None: | ||
translator.body.append('</details>\n') | ||
|
||
|
||
def visit_summary(translator: HTML5Translator, node: nodes.Element) -> None: | ||
translator.body.append(translator.starttag(node, 'summary')) | ||
|
||
|
||
def depart_summary(translator: HTML5Translator, node: nodes.Element) -> None: | ||
translator.body.append('</summary>\n') | ||
|
||
|
||
class Collapsible(SphinxDirective): | ||
""" | ||
Directive to mark collapsible content, with an optional summary line. | ||
""" | ||
|
||
has_content = True | ||
optional_arguments = 1 | ||
final_argument_whitespace = True | ||
option_spec: ClassVar[OptionSpec] = { | ||
'class': directives.class_option, | ||
'name': directives.unchanged, | ||
'open': directives.flag, | ||
} | ||
|
||
def run(self) -> list[nodes.Node]: | ||
node = collapsible(classes=['collapsible'], open='open' in self.options) | ||
if 'class' in self.options: | ||
node['classes'] += self.options['class'] | ||
self.add_name(node) | ||
node.document = self.state.document | ||
self.set_source_info(node) | ||
|
||
if self.arguments: | ||
# parse the argument as reST | ||
trimmed_summary = self._dedent_string(self.arguments[0].strip()) | ||
textnodes, messages = self.parse_inline(trimmed_summary, lineno=self.lineno) | ||
node.append(summary(trimmed_summary, '', *textnodes)) | ||
node += messages | ||
else: | ||
label = 'Collapsed Content:' | ||
node.append(summary(label, label)) | ||
|
||
return self.parse_content_to_nodes(allow_section_headings=True) | ||
|
||
@staticmethod | ||
def _dedent_string(s: str) -> str: | ||
"""Remove common leading indentation.""" | ||
lines = s.expandtabs(4).splitlines() | ||
|
||
# Find minimum indentation of any non-blank lines after the first. | ||
# If your indent is larger than a million spaces, there's a problem… | ||
margin = 10**6 | ||
for line in lines[1:]: | ||
content = len(line.lstrip()) | ||
if content: | ||
indent = len(line) - content | ||
margin = min(margin, indent) | ||
|
||
if margin == 10**6: | ||
return s | ||
|
||
return '\n'.join(lines[:1] + [line[margin:] for line in lines[1:]]) | ||
|
||
|
||
#: This constant can be modified by programmers that create their own | ||
#: HTML builders outside the Sphinx core. | ||
HTML_5_BUILDERS = frozenset({'html', 'dirhtml'}) | ||
|
||
|
||
class CollapsibleNodeTransform(SphinxPostTransform): | ||
default_priority = 55 | ||
|
||
def run(self, **kwargs: Any) -> None: | ||
"""Filter collapsible and collapsible_summary nodes based on HTML 5 support.""" | ||
if self.app.builder.name in HTML_5_BUILDERS: | ||
return | ||
|
||
for summary_node in self.document.findall(summary): | ||
summary_para = nodes.paragraph('', '', *summary_node) | ||
summary_node.replace_self(summary_para) | ||
|
||
for collapsible_node in self.document.findall(collapsible): | ||
container = nodes.container('', *collapsible_node.children) | ||
collapsible_node.replace_self(container) | ||
|
||
|
||
def setup(app: Sphinx) -> ExtensionMetadata: | ||
app.add_node(collapsible, html=(visit_collapsible, depart_collapsible)) | ||
app.add_node(summary, html=(visit_summary, depart_summary)) | ||
app.add_directive('collapse', Collapsible) | ||
app.add_post_transform(CollapsibleNodeTransform) | ||
|
||
return { | ||
'version': sphinx.__display_version__, | ||
'parallel_read_safe': True, | ||
'parallel_write_safe': True, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
project = 'test-directive-only' | ||
exclude_patterns = ['_build'] | ||
extensions = ['sphinx.ext.collapse'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
Collapsible directive tests | ||
=========================== | ||
|
||
.. collapse:: | ||
|
||
Default section summary line | ||
|
||
.. collapse:: Custom summary line for the collapsible content: | ||
|
||
Collapsible sections can also have custom summary lines | ||
|
||
.. collapse:: Summary text here with **bold** and *em* and a :rfc:`2324` | ||
reference! That was a newline in the reST source! We can also | ||
have links_ and `more links <https://link2.example/>`__. | ||
|
||
This is some body text! | ||
|
||
.. collapse:: Collapsible section with no content. | ||
:name: collapse-no-content | ||
:class: spam | ||
|
||
.. collapse:: Collapsible section with reStructuredText content: | ||
|
||
Collapsible sections can have normal reST content such as **bold** and | ||
*emphasised* text, and also links_! | ||
|
||
.. _links: https://link.example/ | ||
|
||
.. collapse:: Collapsible section with titles: | ||
|
||
Collapsible sections can have sections: | ||
|
||
A Section | ||
--------- | ||
|
||
Some words within a section, as opposed to outwith the section. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
"""Test the collapsible directive with the test root.""" | ||
|
||
import pytest | ||
from docutils import nodes | ||
|
||
from sphinx.ext.collapse import collapsible, summary | ||
|
||
|
||
@pytest.mark.sphinx('text', testroot='ext-collapse') | ||
def test_non_html(app): | ||
app.build(force_all=True) | ||
|
||
# The content is inlined into the document: | ||
assert (app.outdir / 'index.txt').read_text(encoding='utf8') == """\ | ||
Collapsible directive tests | ||
*************************** | ||
|
||
Collapsed Content: | ||
|
||
Default section summary line | ||
|
||
Custom summary line for the collapsible content: | ||
|
||
Collapsible sections can also have custom summary lines | ||
|
||
Summary text here with **bold** and *em* and a **RFC 2324** reference! | ||
That was a newline in the reST source! We can also have links and more | ||
links. | ||
|
||
This is some body text! | ||
|
||
Collapsible section with no content. | ||
|
||
Collapsible section with reStructuredText content: | ||
|
||
Collapsible sections can have normal reST content such as **bold** and | ||
*emphasised* text, and also links! | ||
|
||
Collapsible section with titles: | ||
|
||
Collapsible sections can have sections: | ||
|
||
|
||
A Section | ||
========= | ||
|
||
Some words within a section, as opposed to outwith the section. | ||
""" | ||
|
||
|
||
@pytest.mark.sphinx('text', testroot='ext-collapse') | ||
def test_non_html_post_transform(app): | ||
app.build(force_all=True) | ||
doctree = app.env.get_doctree('index') | ||
app.env.apply_post_transforms(doctree, 'index') | ||
assert list(doctree.findall(collapsible)) == [] | ||
|
||
collapsible_nodes = list(doctree.findall(nodes.container)) | ||
no_content = collapsible_nodes[3] | ||
assert len(no_content) == 1 | ||
assert no_content[0].astext() == 'Collapsible section with no content.' | ||
|
||
|
||
@pytest.mark.sphinx('html', testroot='ext-collapse') | ||
def test_html(app): | ||
app.build(force_all=True) | ||
doctree = app.env.get_doctree('index') | ||
app.env.apply_post_transforms(doctree, 'index') | ||
collapsible_nodes = list(doctree.findall(collapsible)) | ||
assert len(collapsible_nodes) == 6 | ||
|
||
default_summary = collapsible_nodes[0] | ||
assert isinstance(default_summary[0], summary) | ||
assert collapsible_nodes[0][0].astext() == 'Collapsed Content:' | ||
|
||
custom_summary = collapsible_nodes[1] | ||
assert isinstance(custom_summary[0], summary) | ||
assert custom_summary[0].astext() == 'Custom summary line for the collapsible content:' | ||
assert custom_summary[1].astext() == 'Collapsible sections can also have custom summary lines' | ||
|
||
rst_summary = collapsible_nodes[2] | ||
assert isinstance(rst_summary[0], summary) | ||
assert 'RFC 2324' in rst_summary[0].astext() | ||
assert 'We can also\nhave ' in rst_summary[0][8] # type: ignore[operator] | ||
|
||
no_content = collapsible_nodes[3] | ||
assert isinstance(no_content[0], summary) | ||
assert no_content[0].astext() == 'Collapsible section with no content.' | ||
assert len(no_content) == 1 | ||
|
||
rst_content = collapsible_nodes[4] | ||
assert isinstance(rst_content[0], summary) | ||
|
||
nested_titles = collapsible_nodes[5] | ||
assert isinstance(nested_titles[0], summary) | ||
assert isinstance(nested_titles[2], nodes.section) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.