Coverage for structured_tutorials / sphinx / directives.py: 100%
34 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-26 12:41 +0100
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-26 12:41 +0100
1# Copyright (c) 2025 Mathias Ertl
2# Licensed under the MIT License. See LICENSE file for details.
4"""Directives for Sphinx."""
6from typing import TYPE_CHECKING
8from docutils.nodes import Node, paragraph
9from docutils.parsers.rst.states import RSTState
10from docutils.statemachine import StringList
11from sphinx.environment import BuildEnvironment
12from sphinx.util.docutils import SphinxDirective
14from structured_tutorials.sphinx.utils import TutorialWrapper, get_tutorial_path
16if TYPE_CHECKING:
17 from sphinx.environment import _CurrentDocument
20class CurrentDocumentMixin:
21 """Mixin adding the current document property and context."""
23 if TYPE_CHECKING:
24 env: BuildEnvironment
26 # NOTE: sphinx 8.2.0 introduced "current_document", temp_data is deprecated and kept only for
27 # backwards compatability: https://github.com/sphinx-doc/sphinx/pull/13151
28 @property
29 def current_document(self) -> "_CurrentDocument": # pragma: no cover
30 if hasattr(self.env, "current_document"):
31 return self.env.current_document
32 else:
33 return self.env.temp_data
36class TutorialDirective(CurrentDocumentMixin, SphinxDirective):
37 """Directive to specify the currently rendered tutorial."""
39 has_content = False
40 required_arguments = 1
41 optional_arguments = 0
43 def run(self) -> list[Node]:
44 tutorial_arg = self.arguments[0].strip()
46 context = self.config.structured_tutorials_context.get(tutorial_arg, {})
47 command_text_width: int = self.config.structured_tutorials_command_text_width
48 tutorial_path = get_tutorial_path(self.config.structured_tutorials_root, tutorial_arg)
49 self.current_document["tutorial"] = TutorialWrapper.from_file(
50 tutorial_path, context=context, command_text_width=command_text_width
51 )
53 # NOTE: `highlighting` directive returns a custom Element for unknown reasons
54 return []
57class PartDirective(CurrentDocumentMixin, SphinxDirective):
58 """Directive to show a tutorial part."""
60 required_arguments = 0
61 optional_arguments = 1
63 def run(self) -> list[paragraph]:
64 # Get the named ID if set.
65 part_id = None
66 if self.arguments:
67 part_id = self.arguments[0].strip()
69 # Render text
70 tutorial_wrapper: TutorialWrapper = self.current_document["tutorial"]
71 text = tutorial_wrapper.render_part(part_id=part_id)
73 source, _lineno = self.get_source_info()
75 # Create sphinx node
76 node = paragraph()
77 paragraph.source = source
78 state: RSTState = self.state
79 state.nested_parse(StringList(text.splitlines(), source=source), 0, node)
80 return [node]