Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/sphinxnotes/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
Template,
Host,
pending_node,
rendered_node,
BaseDataDefineRole,
BaseDataDefineDirective,
ExtraContextRegistry,
Expand Down Expand Up @@ -56,8 +55,6 @@
'Template',
'Host',
'pending_node',
'rendered_node',
'rendered_node',
'BaseDataDefineRole',
'BaseDataDefineDirective',
'StrictDataDefineDirective',
Expand Down
4 changes: 1 addition & 3 deletions src/sphinxnotes/data/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import TYPE_CHECKING

from .render import Phase, Template, Host
from .datanodes import pending_node, rendered_node
from .datanodes import pending_node
from .pipeline import (
BaseDataDefineRole,
BaseDataDefineDirective,
Expand All @@ -18,8 +18,6 @@
'Template',
'Host',
'pending_node',
'rendered_node',
'rendered_node',
'BaseDataDefineRole',
'BaseDataDefineDirective',
'ExtraContextRegistry',
Expand Down
118 changes: 58 additions & 60 deletions src/sphinxnotes/data/render/datanodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
Unpicklable,
Report,
Reporter,
find_current_document,
find_nearest_block_element,
)
from ..config import Config
Expand All @@ -35,6 +34,8 @@ class pending_node(Base, Unpicklable):
template: Template
#: Whether rendering to inline nodes.
inline: bool
#: Whether the rendering pipeline is finished (failed is also finished).
rendered: bool

def __init__(
self,
Expand All @@ -50,31 +51,26 @@ def __init__(
self.extra = {}
self.template = tmpl
self.inline = inline
self.rendered = False

# Init hook lists.
self._raw_data_hooks = []
self._parsed_data_hooks = []
self._markup_text_hooks = []
self._rendered_node_hooks = []
self._rendered_nodes_hooks = []

def render(self, host: Host) -> rendered_node:
def render(self, host: Host) -> None:
"""
The core function for rendering data to docutils nodes.

1. Schema.parse(RawData) -> ParsedData
2. TemplateRenderer.render(ParsedData) -> Markup Text (``str``)
3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
"""
# 0. Create container for rendered nodes.
rendered = rendered_node()
# Copy attributes from pending_node.
rendered.update_all_atts(self)
# Copy source and line (which are not included in update_all_atts).
rendered.source, rendered.line = self.source, self.line
# Copy the pending's children to the rendered.
rendered[:0] = [x.deepcopy() for x in self.children]
# Clear all empty reports.
Reporter(rendered).clear_empty()

# Make sure the function is called once.
assert not self.rendered
self.rendered = True

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

try:
data = self.data.parse()
data = self.data = self.data.parse()
except ValueError:
report.text('Failed to parse raw data:')
report.excption()
rendered += report
return rendered
self += report
return
else:
data = self.data

for hook in self._parsed_data_hooks:
hook(self, data)

rendered.data = data

report.text('Parsed data:')
report.text(f'Parsed data (type: {type(data)}):')
report.code(pformat(data), lang='python')
report.text('Extra context (just key):')
report.text('Extra context (only keys):')
report.code(pformat(list(self.extra.keys())), lang='python')
report.text('Template:')
report.text(f'Template (phase: {self.template.phase}):')
report.code(self.template.text, lang='jinja')

# 2. Render the template and data to markup text.
try:
markup = TemplateRenderer(self.template.text).render(data, extra=self.extra)
except Exception: # TODO: what excetpion?
except Exception:
report.text('Failed to render Jinja template:')
report.excption()
rendered += report
return rendered
self += report
return

for hook in self._markup_text_hooks:
markup = hook(self, markup)
Expand All @@ -136,8 +130,8 @@ def render(self, host: Host) -> rendered_node:
f'to {"inline " if self.inline else ""}nodes:'
)
report.excption()
rendered += report
return rendered
self += report
return

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

# 4. Add rendered nodes to container.
rendered += ns
for hook in self._rendered_nodes_hooks:
hook(self, ns)
# TODO: set_source_info?
self += ns

if self.template.debug or Config.render_debug:
rendered += report
self += report

for hook in self._rendered_node_hooks:
hook(self, rendered)
Reporter(self).clear_empty()

return rendered
return

def replace_self_inline(
self, rendered: rendered_node, inliner: Report.Inliner
) -> None:
# Split inline nodes and system_message noeds from rendered_node node.
ns, msgs = rendered.inline(inliner)
def unwrap(self) -> list[nodes.Node]:
children = self.children
self.clear()
return children

def unwrap_inline(
self, inliner: Report.Inliner
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
# Report (nodes.system_message subclass) is not inline node,
# should be removed before inserting to doctree.
reports = Reporter(self).clear()
for report in reports:
self.append(report.problematic(inliner))

children = self.children
self.clear()

return children, [x for x in reports]

def unwrap_and_replace_self(self) -> None:
children = self.unwrap()
# Replace self with children.
self.replace_self(children)

def unwrap_and_replace_self_inline(self, inliner: Report.Inliner) -> None:
# Unwrap inline nodes and system_message noeds from node.
ns, msgs = self.unwrap_inline(inliner)

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

_raw_data_hooks: list[RawDataHook]
_parsed_data_hooks: list[ParsedDataHook]
_markup_text_hooks: list[MarkupTextHook]
_rendered_node_hooks: list[RenderedNodeHook]
_rendered_nodes_hooks: list[RenderedNodesHook]

def hook_raw_data(self, hook: RawDataHook) -> None:
self._raw_data_hooks.append(hook)
Expand All @@ -191,25 +209,5 @@ def hook_parsed_data(self, hook: ParsedDataHook) -> None:
def hook_markup_text(self, hook: MarkupTextHook) -> None:
self._markup_text_hooks.append(hook)

def hook_rendered_node(self, hook: RenderedNodeHook) -> None:
self._rendered_node_hooks.append(hook)


class rendered_node(Base, nodes.container):
# The data used when rendering this node.
data: ParsedData | dict[str, Any] | None

def inline(
self,
inliner: Inliner | tuple[nodes.document, nodes.Element],
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
# Report (nodes.system_message subclass) is not inline node,
# should be removed before inserting to doctree.
reports = Reporter(self).clear()
for report in reports:
self.append(report.problematic(inliner))

children = self.children
self.clear()

return children, [x for x in reports]
def hook_rendered_nodes(self, hook: RenderedNodesHook) -> None:
self._rendered_nodes_hooks.append(hook)
2 changes: 2 additions & 0 deletions src/sphinxnotes/data/render/extractx.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class _SphinxExtraContext(GlobalExtraContxt):
def generate(self) -> Any:
return proxy(self.app)


class _DocutilsExtraContext(GlobalExtraContxt):
@override
def generate(self) -> Any:
Expand All @@ -132,6 +133,7 @@ def generate(self) -> Any:
'roles': _roles,
}


# ========================
# Extra Context Management
# ========================
Expand Down
40 changes: 21 additions & 19 deletions src/sphinxnotes/data/render/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from sphinx.transforms.post_transforms import SphinxPostTransform, ReferencesResolver

from .render import HostWrapper, Phase, Template, Host, ParseHost, TransformHost
from .datanodes import pending_node, rendered_node
from .datanodes import pending_node
from .extractx import ExtraContextGenerator
from ..data import RawData, PendingData, ParsedData, Schema

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

@final
def render_queue(self) -> list[pending_node | rendered_node]:
def render_queue(self) -> list[pending_node]:
"""
Try rendering all pending nodes in queue.

If the timing(Phase) is ok, :cls:`pending_node` will be rendered to a
:cls:`rendered_node`; otherwise, the pending node is unchanged.
If the timing(Phase) is ok, :cls:`pending_node` will be rendered
(pending.rendered = True); otherwise, the pending node is unchanged.

If the pending node is already inserted to document, it will not be return.
And the corrsponding rendered node will replace it too.
Expand All @@ -130,18 +130,17 @@ def render_queue(self) -> list[pending_node | rendered_node]:
ExtraContextGenerator(pending).on_anytime()

host = cast(Host, self)
rendered = pending.render(host)
pending.render(host)

if pending.parent is None:
ns.append(rendered)
ns.append(pending)
continue

if pending.inline:
pending.replace_self_inline(
rendered, (HostWrapper(host).doctree, pending.parent)
)
host_ = HostWrapper(host)
pending.unwrap_and_replace_self_inline((host_.doctree, pending.parent))
else:
pending.replace_self(rendered)
pending.unwrap_and_replace_self()

return ns

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

return [x for x in self.render_queue()]
ns = []
for x in self.render_queue():
if not x.rendered:
ns.append(x)
continue
ns += x.unwrap()

return ns


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

ns, msgs = [], []
for n in self.render_queue():
if not isinstance(n, rendered_node):
if not n.rendered:
ns.append(n)
continue
n, msg = n.inline(self.inliner)
ns += n
msgs += msg
ns_, msgs_ = n.unwrap_inline(self.inliner)
ns += ns_
msgs += msgs_

return ns, msgs

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

@override
def run(self) -> list[nodes.Node]:
logger.warning(f'running parsed hook for doc {self.env.docname}...')

for pending in self.state.document.findall(pending_node):
self.queue_pending_node(pending)
# Hook system_message method to let it report the
Expand Down Expand Up @@ -281,8 +285,6 @@ def process_pending_node(self, n: pending_node) -> bool:

@override
def apply(self, **kwargs):
logger.warning(f'running resolving hook for doc {self.env.docname}...')

for pending in self.document.findall(pending_node):
self.queue_pending_node(pending)

Expand Down