Coverage for structured_tutorials / sphinx / directives.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-21 19:08 +0100
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-21 19:08 +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 command_text_width: int = self.config.structured_tutorial_command_text_width
47 tutorial_path = get_tutorial_path(self.config.tutorial_root, tutorial_arg)
48 self.current_document["tutorial"] = TutorialWrapper.from_file(
49 tutorial_path, command_text_width=command_text_width
50 )
52 # NOTE: `highlighting` directive returns a custom Element for unknown reasons
53 return []
56class PartDirective(CurrentDocumentMixin, SphinxDirective):
57 """Directive to show a tutorial part."""
59 required_arguments = 0
60 optional_arguments = 1
62 def run(self) -> list[paragraph]:
63 # Get the named ID if set.
64 part_id = None
65 if self.arguments:
66 part_id = self.arguments[0].strip()
68 # Render text
69 tutorial_wrapper: TutorialWrapper = self.current_document["tutorial"]
70 text = tutorial_wrapper.render_part(part_id=part_id)
72 source, _lineno = self.get_source_info()
74 # Create sphinx node
75 node = paragraph()
76 paragraph.source = source
77 state: RSTState = self.state
78 state.nested_parse(StringList(text.splitlines(), source=source), 0, node)
79 return [node]