Skip to content

Commit 2f56f6e

Browse files
authored
refactor: Remove rendered_node (#1)
Left a custom node in doctree usually cause many compatibility issues: - NotImplementedError: <class 'docutils.writers._html_base.SimpleListChecker'> visiting unknown node type: rendered_node - NotImplementedError: <class 'sphinx.writers.html5.HTML5Translator'> departing unknown node type: rendered_node It is impossible to impl visit method for every translator/visitor, so we remove the term of "rendered_node" from project.
1 parent 81cd18a commit 2f56f6e

5 files changed

Lines changed: 82 additions & 85 deletions

File tree

src/sphinxnotes/data/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
Template,
2929
Host,
3030
pending_node,
31-
rendered_node,
3231
BaseDataDefineRole,
3332
BaseDataDefineDirective,
3433
ExtraContextRegistry,
@@ -56,8 +55,6 @@
5655
'Template',
5756
'Host',
5857
'pending_node',
59-
'rendered_node',
60-
'rendered_node',
6158
'BaseDataDefineRole',
6259
'BaseDataDefineDirective',
6360
'StrictDataDefineDirective',

src/sphinxnotes/data/render/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import TYPE_CHECKING
33

44
from .render import Phase, Template, Host
5-
from .datanodes import pending_node, rendered_node
5+
from .datanodes import pending_node
66
from .pipeline import (
77
BaseDataDefineRole,
88
BaseDataDefineDirective,
@@ -18,8 +18,6 @@
1818
'Template',
1919
'Host',
2020
'pending_node',
21-
'rendered_node',
22-
'rendered_node',
2321
'BaseDataDefineRole',
2422
'BaseDataDefineDirective',
2523
'ExtraContextRegistry',

src/sphinxnotes/data/render/datanodes.py

Lines changed: 58 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
Unpicklable,
1414
Report,
1515
Reporter,
16-
find_current_document,
1716
find_nearest_block_element,
1817
)
1918
from ..config import Config
@@ -35,6 +34,8 @@ class pending_node(Base, Unpicklable):
3534
template: Template
3635
#: Whether rendering to inline nodes.
3736
inline: bool
37+
#: Whether the rendering pipeline is finished (failed is also finished).
38+
rendered: bool
3839

3940
def __init__(
4041
self,
@@ -50,31 +51,26 @@ def __init__(
5051
self.extra = {}
5152
self.template = tmpl
5253
self.inline = inline
54+
self.rendered = False
5355

5456
# Init hook lists.
5557
self._raw_data_hooks = []
5658
self._parsed_data_hooks = []
5759
self._markup_text_hooks = []
58-
self._rendered_node_hooks = []
60+
self._rendered_nodes_hooks = []
5961

60-
def render(self, host: Host) -> rendered_node:
62+
def render(self, host: Host) -> None:
6163
"""
6264
The core function for rendering data to docutils nodes.
6365
6466
1. Schema.parse(RawData) -> ParsedData
6567
2. TemplateRenderer.render(ParsedData) -> Markup Text (``str``)
6668
3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
6769
"""
68-
# 0. Create container for rendered nodes.
69-
rendered = rendered_node()
70-
# Copy attributes from pending_node.
71-
rendered.update_all_atts(self)
72-
# Copy source and line (which are not included in update_all_atts).
73-
rendered.source, rendered.line = self.source, self.line
74-
# Copy the pending's children to the rendered.
75-
rendered[:0] = [x.deepcopy() for x in self.children]
76-
# Clear all empty reports.
77-
Reporter(rendered).clear_empty()
70+
71+
# Make sure the function is called once.
72+
assert not self.rendered
73+
self.rendered = True
7874

7975
report = Report(
8076
'Render Debug Report', 'DEBUG', source=self.source, line=self.line
@@ -91,35 +87,33 @@ def render(self, host: Host) -> rendered_node:
9187
hook(self, self.data.raw)
9288

9389
try:
94-
data = self.data.parse()
90+
data = self.data = self.data.parse()
9591
except ValueError:
9692
report.text('Failed to parse raw data:')
9793
report.excption()
98-
rendered += report
99-
return rendered
94+
self += report
95+
return
10096
else:
10197
data = self.data
10298

10399
for hook in self._parsed_data_hooks:
104100
hook(self, data)
105101

106-
rendered.data = data
107-
108-
report.text('Parsed data:')
102+
report.text(f'Parsed data (type: {type(data)}):')
109103
report.code(pformat(data), lang='python')
110-
report.text('Extra context (just key):')
104+
report.text('Extra context (only keys):')
111105
report.code(pformat(list(self.extra.keys())), lang='python')
112-
report.text('Template:')
106+
report.text(f'Template (phase: {self.template.phase}):')
113107
report.code(self.template.text, lang='jinja')
114108

115109
# 2. Render the template and data to markup text.
116110
try:
117111
markup = TemplateRenderer(self.template.text).render(data, extra=self.extra)
118-
except Exception: # TODO: what excetpion?
112+
except Exception:
119113
report.text('Failed to render Jinja template:')
120114
report.excption()
121-
rendered += report
122-
return rendered
115+
self += report
116+
return
123117

124118
for hook in self._markup_text_hooks:
125119
markup = hook(self, markup)
@@ -136,8 +130,8 @@ def render(self, host: Host) -> rendered_node:
136130
f'to {"inline " if self.inline else ""}nodes:'
137131
)
138132
report.excption()
139-
rendered += report
140-
return rendered
133+
self += report
134+
return
141135

142136
report.text(f'Rendered nodes (inline: {self.inline}):')
143137
report.code('\n\n'.join([n.pformat() for n in ns]), lang='xml')
@@ -146,21 +140,45 @@ def render(self, host: Host) -> rendered_node:
146140
[report.node(msg) for msg in msgs]
147141

148142
# 4. Add rendered nodes to container.
149-
rendered += ns
143+
for hook in self._rendered_nodes_hooks:
144+
hook(self, ns)
145+
# TODO: set_source_info?
146+
self += ns
150147

151148
if self.template.debug or Config.render_debug:
152-
rendered += report
149+
self += report
153150

154-
for hook in self._rendered_node_hooks:
155-
hook(self, rendered)
151+
Reporter(self).clear_empty()
156152

157-
return rendered
153+
return
158154

159-
def replace_self_inline(
160-
self, rendered: rendered_node, inliner: Report.Inliner
161-
) -> None:
162-
# Split inline nodes and system_message noeds from rendered_node node.
163-
ns, msgs = rendered.inline(inliner)
155+
def unwrap(self) -> list[nodes.Node]:
156+
children = self.children
157+
self.clear()
158+
return children
159+
160+
def unwrap_inline(
161+
self, inliner: Report.Inliner
162+
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
163+
# Report (nodes.system_message subclass) is not inline node,
164+
# should be removed before inserting to doctree.
165+
reports = Reporter(self).clear()
166+
for report in reports:
167+
self.append(report.problematic(inliner))
168+
169+
children = self.children
170+
self.clear()
171+
172+
return children, [x for x in reports]
173+
174+
def unwrap_and_replace_self(self) -> None:
175+
children = self.unwrap()
176+
# Replace self with children.
177+
self.replace_self(children)
178+
179+
def unwrap_and_replace_self_inline(self, inliner: Report.Inliner) -> None:
180+
# Unwrap inline nodes and system_message noeds from node.
181+
ns, msgs = self.unwrap_inline(inliner)
164182

165183
# Insert reports to nearst block elements (usually nodes.paragraph).
166184
doctree = inliner.document if isinstance(inliner, Inliner) else inliner[1]
@@ -175,12 +193,12 @@ def replace_self_inline(
175193
type RawDataHook = Callable[[pending_node, RawData], None]
176194
type ParsedDataHook = Callable[[pending_node, ParsedData | dict[str, Any]], None]
177195
type MarkupTextHook = Callable[[pending_node, str], str]
178-
type RenderedNodeHook = Callable[[pending_node, rendered_node], None]
196+
type RenderedNodesHook = Callable[[pending_node, list[nodes.Node]], None]
179197

180198
_raw_data_hooks: list[RawDataHook]
181199
_parsed_data_hooks: list[ParsedDataHook]
182200
_markup_text_hooks: list[MarkupTextHook]
183-
_rendered_node_hooks: list[RenderedNodeHook]
201+
_rendered_nodes_hooks: list[RenderedNodesHook]
184202

185203
def hook_raw_data(self, hook: RawDataHook) -> None:
186204
self._raw_data_hooks.append(hook)
@@ -191,25 +209,5 @@ def hook_parsed_data(self, hook: ParsedDataHook) -> None:
191209
def hook_markup_text(self, hook: MarkupTextHook) -> None:
192210
self._markup_text_hooks.append(hook)
193211

194-
def hook_rendered_node(self, hook: RenderedNodeHook) -> None:
195-
self._rendered_node_hooks.append(hook)
196-
197-
198-
class rendered_node(Base, nodes.container):
199-
# The data used when rendering this node.
200-
data: ParsedData | dict[str, Any] | None
201-
202-
def inline(
203-
self,
204-
inliner: Inliner | tuple[nodes.document, nodes.Element],
205-
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
206-
# Report (nodes.system_message subclass) is not inline node,
207-
# should be removed before inserting to doctree.
208-
reports = Reporter(self).clear()
209-
for report in reports:
210-
self.append(report.problematic(inliner))
211-
212-
children = self.children
213-
self.clear()
214-
215-
return children, [x for x in reports]
212+
def hook_rendered_nodes(self, hook: RenderedNodesHook) -> None:
213+
self._rendered_nodes_hooks.append(hook)

src/sphinxnotes/data/render/extractx.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class _SphinxExtraContext(GlobalExtraContxt):
123123
def generate(self) -> Any:
124124
return proxy(self.app)
125125

126+
126127
class _DocutilsExtraContext(GlobalExtraContxt):
127128
@override
128129
def generate(self) -> Any:
@@ -132,6 +133,7 @@ def generate(self) -> Any:
132133
'roles': _roles,
133134
}
134135

136+
135137
# ========================
136138
# Extra Context Management
137139
# ========================

src/sphinxnotes/data/render/pipeline.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from sphinx.transforms.post_transforms import SphinxPostTransform, ReferencesResolver
5252

5353
from .render import HostWrapper, Phase, Template, Host, ParseHost, TransformHost
54-
from .datanodes import pending_node, rendered_node
54+
from .datanodes import pending_node
5555
from .extractx import ExtraContextGenerator
5656
from ..data import RawData, PendingData, ParsedData, Schema
5757

@@ -107,12 +107,12 @@ def queue_any_data(self, data: Any, tmpl: Template) -> pending_node:
107107
return pending
108108

109109
@final
110-
def render_queue(self) -> list[pending_node | rendered_node]:
110+
def render_queue(self) -> list[pending_node]:
111111
"""
112112
Try rendering all pending nodes in queue.
113113
114-
If the timing(Phase) is ok, :cls:`pending_node` will be rendered to a
115-
:cls:`rendered_node`; otherwise, the pending node is unchanged.
114+
If the timing(Phase) is ok, :cls:`pending_node` will be rendered
115+
(pending.rendered = True); otherwise, the pending node is unchanged.
116116
117117
If the pending node is already inserted to document, it will not be return.
118118
And the corrsponding rendered node will replace it too.
@@ -130,18 +130,17 @@ def render_queue(self) -> list[pending_node | rendered_node]:
130130
ExtraContextGenerator(pending).on_anytime()
131131

132132
host = cast(Host, self)
133-
rendered = pending.render(host)
133+
pending.render(host)
134134

135135
if pending.parent is None:
136-
ns.append(rendered)
136+
ns.append(pending)
137137
continue
138138

139139
if pending.inline:
140-
pending.replace_self_inline(
141-
rendered, (HostWrapper(host).doctree, pending.parent)
142-
)
140+
host_ = HostWrapper(host)
141+
pending.unwrap_and_replace_self_inline((host_.doctree, pending.parent))
143142
else:
144-
pending.replace_self(rendered)
143+
pending.unwrap_and_replace_self()
145144

146145
return ns
147146

@@ -195,7 +194,14 @@ def run(self) -> list[nodes.Node]:
195194
self.current_data(), self.current_schema(), self.current_template()
196195
)
197196

198-
return [x for x in self.render_queue()]
197+
ns = []
198+
for x in self.render_queue():
199+
if not x.rendered:
200+
ns.append(x)
201+
continue
202+
ns += x.unwrap()
203+
204+
return ns
199205

200206

201207
class BaseDataDefineRole(BaseDataDefiner, SphinxRole):
@@ -216,12 +222,12 @@ def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]:
216222

217223
ns, msgs = [], []
218224
for n in self.render_queue():
219-
if not isinstance(n, rendered_node):
225+
if not n.rendered:
220226
ns.append(n)
221227
continue
222-
n, msg = n.inline(self.inliner)
223-
ns += n
224-
msgs += msg
228+
ns_, msgs_ = n.unwrap_inline(self.inliner)
229+
ns += ns_
230+
msgs += msgs_
225231

226232
return ns, msgs
227233

@@ -238,8 +244,6 @@ def process_pending_node(self, n: pending_node) -> bool:
238244

239245
@override
240246
def run(self) -> list[nodes.Node]:
241-
logger.warning(f'running parsed hook for doc {self.env.docname}...')
242-
243247
for pending in self.state.document.findall(pending_node):
244248
self.queue_pending_node(pending)
245249
# Hook system_message method to let it report the
@@ -281,8 +285,6 @@ def process_pending_node(self, n: pending_node) -> bool:
281285

282286
@override
283287
def apply(self, **kwargs):
284-
logger.warning(f'running resolving hook for doc {self.env.docname}...')
285-
286288
for pending in self.document.findall(pending_node):
287289
self.queue_pending_node(pending)
288290

0 commit comments

Comments
 (0)