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

1# Copyright (c) 2025 Mathias Ertl 

2# Licensed under the MIT License. See LICENSE file for details. 

3 

4"""Directives for Sphinx.""" 

5 

6from typing import TYPE_CHECKING 

7 

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 

13 

14from structured_tutorials.sphinx.utils import TutorialWrapper, get_tutorial_path 

15 

16if TYPE_CHECKING: 

17 from sphinx.environment import _CurrentDocument 

18 

19 

20class CurrentDocumentMixin: 

21 """Mixin adding the current document property and context.""" 

22 

23 if TYPE_CHECKING: 

24 env: BuildEnvironment 

25 

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 

34 

35 

36class TutorialDirective(CurrentDocumentMixin, SphinxDirective): 

37 """Directive to specify the currently rendered tutorial.""" 

38 

39 has_content = False 

40 required_arguments = 1 

41 optional_arguments = 0 

42 

43 def run(self) -> list[Node]: 

44 tutorial_arg = self.arguments[0].strip() 

45 

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 ) 

52 

53 # NOTE: `highlighting` directive returns a custom Element for unknown reasons 

54 return [] 

55 

56 

57class PartDirective(CurrentDocumentMixin, SphinxDirective): 

58 """Directive to show a tutorial part.""" 

59 

60 required_arguments = 0 

61 optional_arguments = 1 

62 

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() 

68 

69 # Render text 

70 tutorial_wrapper: TutorialWrapper = self.current_document["tutorial"] 

71 text = tutorial_wrapper.render_part(part_id=part_id) 

72 

73 source, _lineno = self.get_source_info() 

74 

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]