Skip to content

Commit 75f4544

Browse files
committed
feat: Phase.Resolving works now
1 parent a51d3a0 commit 75f4544

4 files changed

Lines changed: 78 additions & 56 deletions

File tree

src/sphinxnotes/data/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ def by_option_store_value_error(opt: ByOption) -> ValueError:
451451

452452

453453
@dataclass(frozen=True)
454-
class Schema(object):
454+
class Schema(Unpicklable):
455455
name: Field | None
456456
attrs: dict[str, Field] | Field
457457
content: Field | None

src/sphinxnotes/data/render/datanodes.py

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from .template import TemplateRenderer
1111
from ..data import RawData, PendingData, ParsedData
1212
from ..utils import (
13-
Unpicklable,
1413
Report,
1514
Reporter,
1615
find_nearest_block_element,
@@ -24,7 +23,7 @@
2423
class Base(nodes.Element): ...
2524

2625

27-
class pending_node(Base, Unpicklable):
26+
class pending_node(Base):
2827
# The data to be rendered by Jinja template.
2928
data: PendingData | ParsedData | dict[str, Any]
3029
# The extra context for Jina template.
@@ -35,6 +34,8 @@ class pending_node(Base, Unpicklable):
3534
inline: bool
3635
#: Whether the rendering pipeline is finished (failed is also finished).
3736
rendered: bool
37+
#: The report of render pipepine.
38+
report: Report
3839

3940
def __init__(
4041
self,
@@ -51,18 +52,58 @@ def __init__(
5152
self.template = tmpl
5253
self.inline = inline
5354
self.rendered = False
55+
self.report = Report(
56+
'Render Report', 'DEBUG', source=self.source, line=self.line
57+
)
5458

5559
# Init hook lists.
5660
self._raw_data_hooks = []
5761
self._parsed_data_hooks = []
5862
self._markup_text_hooks = []
5963
self._rendered_nodes_hooks = []
6064

65+
def get_error_report(self) -> Report:
66+
if self.template.debug:
67+
# Reuse the render report as possible.
68+
self.report['type'] = 'ERROR'
69+
return self.report
70+
return Report('Render Report', 'ERROR', source=self.source, line=self.line)
71+
72+
def ensure_data_parsed(self) -> ParsedData | dict[str, Any] | None:
73+
"""
74+
Ensure self.data is parsed (instance of ParsedData | dict[str, Any]).
75+
if no, parse it.
76+
"""
77+
if not isinstance(self.data, PendingData):
78+
return self.data
79+
80+
self.report.text('Raw data:')
81+
self.report.code(pformat(self.data.raw), lang='python')
82+
self.report.text('Schema:')
83+
self.report.code(pformat(self.data.schema), lang='python')
84+
85+
for hook in self._raw_data_hooks:
86+
hook(self, self.data.raw)
87+
88+
try:
89+
data = self.data = self.data.parse()
90+
except ValueError as e:
91+
report = self.get_error_report()
92+
report.text('Failed to parse raw data:')
93+
report.exception(e)
94+
self += report
95+
return None
96+
97+
for hook in self._parsed_data_hooks:
98+
hook(self, data)
99+
100+
return data
101+
61102
def render(self, host: Host) -> None:
62103
"""
63104
The core function for rendering data to docutils nodes.
64105
65-
1. Schema.parse(RawData) -> ParsedData
106+
1. Schema.parse(RawData) -> ParsedData (self.parse_data)
66107
2. TemplateRenderer.render(ParsedData) -> Markup Text (``str``)
67108
3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
68109
"""
@@ -74,51 +115,22 @@ def render(self, host: Host) -> None:
74115
# Clear empty reports.
75116
Reporter(self).clear_empty()
76117

77-
dbg = Report('Render Report', 'DEBUG', source=self.source, line=self.line)
78-
79-
def get_error_report() -> Report:
80-
if self.template.debug:
81-
# Reuse the debug report as possible.
82-
dbg['type'] = 'ERROR'
83-
return dbg
84-
return Report('Render Report', 'ERROR', source=self.source, line=self.line)
85-
86118
# 1. Prepare context for Jinja template.
87-
if isinstance(self.data, PendingData):
88-
dbg.text('Raw data:')
89-
dbg.code(pformat(self.data.raw), lang='python')
90-
dbg.text('Schema:')
91-
dbg.code(pformat(self.data.schema), lang='python')
92-
93-
for hook in self._raw_data_hooks:
94-
hook(self, self.data.raw)
95-
96-
try:
97-
data = self.data = self.data.parse()
98-
except ValueError as e:
99-
report = get_error_report()
100-
report.text('Failed to parse raw data:')
101-
report.exception(e)
102-
self += report
103-
return
104-
else:
105-
data = self.data
119+
if (data := self.ensure_data_parsed()) is None:
120+
return # parse failure
106121

107-
for hook in self._parsed_data_hooks:
108-
hook(self, data)
109-
110-
dbg.text(f'Parsed data (type: {type(data)}):')
111-
dbg.code(pformat(data), lang='python')
112-
dbg.text('Extra context (only keys):')
113-
dbg.code(pformat(list(self.extra.keys())), lang='python')
114-
dbg.text(f'Template (phase: {self.template.phase}):')
115-
dbg.code(self.template.text, lang='jinja')
122+
self.report.text(f'Parsed data (type: {type(data)}):')
123+
self.report.code(pformat(data), lang='python')
124+
self.report.text('Extra context (only keys):')
125+
self.report.code(pformat(list(self.extra.keys())), lang='python')
126+
self.report.text(f'Template (phase: {self.template.phase}):')
127+
self.report.code(self.template.text, lang='jinja')
116128

117129
# 2. Render the template and data to markup text.
118130
try:
119131
markup = TemplateRenderer(self.template.text).render(data, extra=self.extra)
120132
except Exception as e:
121-
report = get_error_report()
133+
report = self.get_error_report()
122134
report.text('Failed to render Jinja template:')
123135
report.exception(e)
124136
self += report
@@ -127,14 +139,14 @@ def get_error_report() -> Report:
127139
for hook in self._markup_text_hooks:
128140
markup = hook(self, markup)
129141

130-
dbg.text('Rendered markup text:')
131-
dbg.code(markup, lang='rst')
142+
self.report.text('Rendered markup text:')
143+
self.report.code(markup, lang='rst')
132144

133145
# 3. Render the markup text to doctree nodes.
134146
try:
135147
ns, msgs = MarkupRenderer(host).render(markup, inline=self.inline)
136148
except Exception as e:
137-
report = get_error_report()
149+
report = self.get_error_report()
138150
report.text(
139151
'Failed to render markup text '
140152
f'to {"inline " if self.inline else ""}nodes:'
@@ -143,20 +155,21 @@ def get_error_report() -> Report:
143155
self += report
144156
return
145157

146-
dbg.text(f'Rendered nodes (inline: {self.inline}):')
147-
dbg.code('\n\n'.join([n.pformat() for n in ns]), lang='xml')
158+
self.report.text(f'Rendered nodes (inline: {self.inline}):')
159+
self.report.code('\n\n'.join([n.pformat() for n in ns]), lang='xml')
148160
if msgs:
149-
dbg.text('Systemd messages:')
150-
[dbg.node(msg) for msg in msgs]
161+
self.report.text('Systemd messages:')
162+
[self.report.node(msg) for msg in msgs]
151163

152164
# 4. Add rendered nodes to container.
153165
for hook in self._rendered_nodes_hooks:
154166
hook(self, ns)
167+
155168
# TODO: set_source_info?
156169
self += ns
157170

158171
if self.template.debug:
159-
self += dbg
172+
self += self.report
160173

161174
return
162175

src/sphinxnotes/data/render/pipeline.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def process_pending_node(self, n: pending_node) -> bool:
7373
"""
7474
You can add hooks to pending node here.
7575
76-
Return ``true`` if you want to render the pending node *immediately*,
76+
Return ``true`` if you want to render the pending node *now*,
7777
otherwise it will be inserted to doctree directly andwaiting to later
7878
rendering
7979
"""
@@ -123,7 +123,8 @@ def render_queue(self) -> list[pending_node]:
123123
while self._q:
124124
pending = self._q.pop()
125125

126-
if not self.process_pending_node(pending):
126+
render_now = self.process_pending_node(pending)
127+
if not render_now:
127128
ns.append(pending)
128129
continue
129130

@@ -246,12 +247,18 @@ def process_pending_node(self, n: pending_node) -> bool:
246247
def apply(self, **kwargs):
247248
for pending in self.document.findall(pending_node):
248249
self.queue_pending_node(pending)
249-
self.render_queue()
250+
251+
for n in self.render_queue():
252+
# NOTE: In the next Phase, doctrees will be pickled to disk.
253+
# As :cls:`data.Schema` is **Unpicklable**, we should ensure
254+
# ``pending_node.data`` is parsed, which means pending_node dropped
255+
# the reference to Schema.
256+
n.ensure_data_parsed()
250257

251258

252259
class _ResolvingHook(SphinxPostTransform, Pipeline):
253260
# After resolving pending_xref
254-
default_priority = (ReferencesResolver.default_priority or 10) + 5
261+
default_priority = (ReferencesResolver.default_priority or 10) + 5
255262

256263
@override
257264
def process_pending_node(self, n: pending_node) -> bool:
@@ -263,6 +270,8 @@ def apply(self, **kwargs):
263270
for pending in self.document.findall(pending_node):
264271
self.queue_pending_node(pending)
265272
ns = self.render_queue()
273+
274+
# NOTE: Should no node left.
266275
assert len(ns) == 0
267276

268277

src/sphinxnotes/data/utils/ctxproxy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from sphinx.config import Config as SphinxConfig
99

1010
from ..utils import find_first_child
11-
from ..utils import Unpicklable
1211

1312

1413
logger = logging.getLogger(__name__)
@@ -22,8 +21,9 @@ def wrapped(self: Proxy) -> Any:
2221
return property(wrapped)
2322

2423

24+
# FIXME: Unpicklable?
2525
@dataclass(frozen=True)
26-
class Proxy(Unpicklable):
26+
class Proxy:
2727
"""
2828
Proxy complex objects into context for convenient and secure access within
2929
Jinja templates.

0 commit comments

Comments
 (0)