Coverage for sphinx_ifelse/directives.py: 100%
100 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-26 16:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-26 16:18 +0000
1import copy
3from docutils import nodes
5from sphinx.util.docutils import SphinxDirective
7from sphinx.util import logging
9from sphinx_ifelse.utils import directive2location, remove_all_childs_of_types
12logger = logging.getLogger(__name__)
15class IfElseNode(nodes.General, nodes.Element):
17 def __init__(self,
18 condition: str | None,
19 evaluatedto: bool,
20 previously_evaluatedtoTrue: bool,
21 location):
22 self.condition = condition
23 self.evaluatedto = evaluatedto
24 self.previously_evaluatedtoTrue = previously_evaluatedtoTrue
25 self.location = location
26 super().__init__()
28 def already_evaluatedtoTrue(self):
29 return self.evaluatedto or self.previously_evaluatedtoTrue
32class IfNode(IfElseNode):
33 def __init__(self,
34 condition: str = '',
35 evaluatedto: bool = False,
36 location = None):
37 super().__init__(condition = condition,
38 evaluatedto = evaluatedto,
39 previously_evaluatedtoTrue = False,
40 location = location)
43class ElIfNode(IfElseNode):
44 def __init__(self,
45 condition: str = '',
46 evaluatedto: bool = False,
47 previously_evaluatedtoTrue:bool = False,
48 location = None):
49 super().__init__(condition = condition,
50 evaluatedto = evaluatedto,
51 previously_evaluatedtoTrue = previously_evaluatedtoTrue,
52 location = location)
55class ElseNode(IfElseNode):
56 def __init__(self,
57 previously_evaluatedtoTrue: bool = False,
58 location = None):
59 super().__init__(condition = None,
60 evaluatedto = not previously_evaluatedtoTrue,
61 previously_evaluatedtoTrue = previously_evaluatedtoTrue,
62 location = location)
65def process_ifelse_nodes(app, doctree, fromdocname):
66 nodetypes = [IfNode, ElIfNode, ElseNode]
67 remove_all_childs_of_types(doctree, nodetypes)
68 return
71class AbstractIfElseDirective(SphinxDirective):
72 """
73 Abstract class for common if/else logic.
74 """
76 def evaluate_condition(self, condition:str)->bool:
77 """
78 Determines if a previous sibling directive (if or elif)
79 has already evaluated to True.
81 Returns:
82 bool: True if a previous sibling directive evaluated to True,
83 otherwise False.
84 """
86 env = self.state.document.settings.env
87 app = env.app
89 class_name = self.__class__.__name__
91 variants = app.config.ifelse_variants
93 # eval will change the globals variable, we have to avoid this,
94 # so we create a deep copy
95 variants_deep_copy = copy.deepcopy(variants)
97 try:
98 proceed = eval(condition, variants_deep_copy)
99 except Exception as err:
100 logger.warning(
101 f"{class_name}: exception while evaluating expression: {err}",
102 type="ifelse",
103 subtype=class_name,
104 location=directive2location(self)
105 )
106 proceed = True
108 return proceed
110 def fetch_already_evaluatedtoTrue(self)->bool:
111 """
112 Fetches the result of a already evaluated condition.
114 Returns:
115 bool: `True` if the condition was already evaluated to `True`,
116 otherwise `False`.
117 """
119 class_name = self.__class__.__name__
121 parent = self.state.parent
122 previously_evaluatedtoTrue:bool = False
124 last_sibling = None
126 # find last none 'nodes.comment'
127 for last_sibling in reversed(parent):
128 if not isinstance(last_sibling, nodes.comment):
129 break
131 if isinstance(last_sibling, IfNode) or isinstance(last_sibling, ElIfNode):
132 previously_evaluatedtoTrue = last_sibling.already_evaluatedtoTrue()
133 else:
134 logger.warning(
135 f"{class_name}: without a preceding IfDirective or ElIfDirective. "+ \
136 f"Maybe there is something wrong with the intendition.",
137 type="ifelse",
138 subtype=class_name,
139 location=directive2location(self)
140 )
141 previously_evaluatedtoTrue = False
143 return previously_evaluatedtoTrue
146class IfDirective(AbstractIfElseDirective):
147 """Directive to switch between alternative content in the documentation.
148 """
150 required_arguments = 1
151 optional_arguments = 0
152 final_argument_whitespace = True
153 has_content = True
155 def run(self):
156 condition = self.arguments[0]
157 condition_evaluated_to = self.evaluate_condition(condition=condition)
159 selfnode = IfNode(
160 condition=condition,
161 evaluatedto=condition_evaluated_to,
162 location=directive2location(self)
163 )
165 if condition_evaluated_to:
166 parsed = self.parse_content_to_nodes(allow_section_headings=True)
167 parsed.append(selfnode)
168 return parsed
169 else:
170 return [selfnode]
173class ElIfDirective(AbstractIfElseDirective):
174 """Directive to switch between alternative content in the documentation.
175 """
177 required_arguments = 1
178 optional_arguments = 0
179 final_argument_whitespace = True
180 has_content = True
182 def run(self):
183 parent = self.state.parent
184 env = self.state.document.settings.env
185 app = env.app
187 condition = self.arguments[0]
188 condition_evaluated_to = self.evaluate_condition(condition=condition)
190 previously_evaluatedtoTrue = self.fetch_already_evaluatedtoTrue()
192 selfnode = ElIfNode(
193 condition=condition,
194 evaluatedto=condition_evaluated_to,
195 previously_evaluatedtoTrue = previously_evaluatedtoTrue,
196 location=directive2location(self)
197 )
199 process_content = condition_evaluated_to and not previously_evaluatedtoTrue
201 if process_content:
202 parsed = self.parse_content_to_nodes(allow_section_headings=True)
203 parsed.append(selfnode)
204 return parsed
205 else:
206 return [selfnode]
210class ElseDirective(AbstractIfElseDirective):
211 """Directive to switch between alternative content in the documentation.
212 """
214 required_arguments = 0
215 optional_arguments = 0
216 final_argument_whitespace = False
217 has_content = True
219 def run(self):
220 previously_evaluatedtoTrue = self.fetch_already_evaluatedtoTrue()
222 selfnode = ElseNode(
223 previously_evaluatedtoTrue = previously_evaluatedtoTrue,
224 location=directive2location(self)
225 )
227 if not previously_evaluatedtoTrue:
228 parsed = self.parse_content_to_nodes(allow_section_headings=True)
229 parsed.append(selfnode)
230 return parsed
231 else:
232 return [selfnode]