Source code for sphinx_toolbox.collapse

#!/usr/bin/env python3
#
#  collapse.py
r"""
Adds a collapsible section to an HTML page using a details_ element.

.. _details: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details

.. versionadded:: 2.5.0
.. extensions:: sphinx_toolbox.collapse


Usage
------

.. rst:directive:: .. collapse:: [label]

	Adds a collapsible section to an HTML page using a details_ element.

	.. _details: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details

	With non-HTML builders, the content will be added as-is.

	.. rest-example::

		.. collapse:: Details

			Something small enough to escape casual notice.


		.. collapse:: A Different Label
			:class: custom-summary
			:name: summary0

			Something else that might escape notice.

		.. collapse:: A long code block

			.. code-block:: python

				print("Not really")


	.. rst:directive:option:: open
		:type: flag

		The ``:open:`` option can be used to have the section open by default.

		.. versionadded:: 3.0.0

		.. rest-example::

			.. collapse:: Open
				:open:

				This section is open by default.


API Reference
----------------

"""
#
#  Copyright © 2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#

# stdlib
from typing import Optional, Sequence

# 3rd party
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst.roles import set_classes
from domdf_python_tools.stringlist import DelimitedList
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx.writers.html5 import HTML5Translator

# this package
from sphinx_toolbox.utils import SphinxExtMetadata, flag, metadata_add_version

__all__ = ("CollapseDirective", "CollapseNode", "visit_collapse_node", "depart_collapse_node", "setup")


[docs]class CollapseDirective(SphinxDirective): r""" A Sphinx directive to add a collapsible section to an HTML page using a details_ element. .. _details: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details """ final_argument_whitespace: bool = True has_content: bool = True # The label required_arguments: int = 1 option_spec = { "class": directives.class_option, "name": directives.unchanged, "open": flag, }
[docs] def run(self) -> Sequence[nodes.Node]: # type: ignore[override] """ Process the content of the directive. """ set_classes(self.options) self.assert_has_content() text = '\n'.join(self.content) label = self.arguments[0] collapse_node = CollapseNode(text, label, **self.options) self.add_name(collapse_node) collapse_node["classes"].append(f"summary-{nodes.make_id(label)}") self.state.nested_parse(self.content, self.content_offset, collapse_node) return [collapse_node]
[docs]class CollapseNode(nodes.Body, nodes.Element): """ Node that represents a collapsible section. :param rawsource: :param label: """ def __init__(self, rawsource: str = '', label: Optional[str] = None, *children, **attributes): super().__init__(rawsource, *children, **attributes) self["label"] = label
[docs]def visit_collapse_node(translator: HTML5Translator, node: CollapseNode) -> None: """ Visit a :class:`~.CollapseNode`. :param translator: :param node: The node being visited. """ tag_parts = DelimitedList(["details"]) if node.get("names", None): names = DelimitedList(node["names"]) tag_parts.append(f'name="{names: }"') if node.get("classes", None): classes = DelimitedList(node["classes"]) tag_parts.append(f'class="{classes: }"') if node.attributes.get("open", False): tag_parts.append("open") translator.body.append(f"<{tag_parts: }>\n<summary>{node['label']}</summary>") translator.context.append("</details>")
[docs]def depart_collapse_node(translator: HTML5Translator, node: CollapseNode) -> None: """ Depart a :class:`~.CollapseNode`. :param translator: :param node: The node being visited. """ translator.body.append(translator.context.pop())
[docs]@metadata_add_version def setup(app: Sphinx) -> SphinxExtMetadata: """ Setup :mod:`sphinx_toolbox.collapse`. :param app: The Sphinx application. """ app.add_directive("collapse", CollapseDirective) app.add_node( CollapseNode, html=(visit_collapse_node, depart_collapse_node), latex=(lambda *args, **kwargs: None, lambda *args, **kwargs: None) ) return { "parallel_read_safe": True, }