diff --git a/src/sphinxnotes/render/ctxnodes.py b/src/sphinxnotes/render/ctxnodes.py index e108eb9..c0172d7 100644 --- a/src/sphinxnotes/render/ctxnodes.py +++ b/src/sphinxnotes/render/ctxnodes.py @@ -95,19 +95,17 @@ def err_report() -> Report: if self._ctx_pickle_error is not None: report = err_report() - report.text( + report.exception(self._ctx_pickle_error, caption=( f'UnresolvedContext used by {self.template.phase} phase templates ' 'must be picklable:' - ) - report.exception(self._ctx_pickle_error) + )) self += report return None # 1. Prepare context for Jinja template. if isinstance(self.ctx, UnresolvedContext): pdata = self.ctx - report.text('Unresolved context:') - report.code(pformat(pdata), lang='python') + report.code(pformat(pdata), lang='python', caption='Unresolved context:') for hook in self._unresolved_context_hooks: hook(self, pdata) @@ -116,8 +114,7 @@ def err_report() -> Report: ctx = self.ctx = pdata.resolve(host.env) except Exception as e: report = err_report() - report.text('Failed to resolve unresolved context:') - report.exception(e) + report.exception(e, caption='Failed to resolve unresolved context:') self += report return None else: @@ -126,14 +123,11 @@ def err_report() -> Report: for hook in self._resolved_context_hooks: hook(self, ctx) - report.text(f'Resolved context (type: {type(ctx)}):') - report.code(pformat(ctx), lang='python') - report.text(f'Template (phase: {self.template.phase}):') - report.code(self.template.text, lang='jinja') + report.code(pformat(ctx), lang='python', caption=f'Resolved context (type: {type(ctx)}):') + report.code(self.template.text, lang='jinja', caption=f'Template (phase: {self.template.phase}):') extractx_req = ExtraContextRequest(self.template.phase, self, host.env, host) - report.text('Available extra context names:') - report.code(pformat(sorted(extra_context_names())), lang='python') + report.code(pformat(sorted(extra_context_names())), lang='python', caption='Available extra context names:') # 2. Render the template and context to markup text. try: @@ -144,32 +138,28 @@ def err_report() -> Report: ) except Exception as e: report = err_report() - report.text('Failed to render Jinja template:') - report.exception(e) + report.exception(e, caption='Failed to render Jinja template:') self += report return for hook in self._markup_text_hooks: markup = hook(self, markup) - report.text('Rendered markup text:') - report.code(markup, lang='rst') + report.code(markup, lang='rst', caption='Rendered markup text:') # 3. Render the markup text to doctree nodes. try: ns, msgs = MarkupRenderer(host).render(markup, inline=self.inline) except Exception as e: report = err_report() - report.text( + report.exception(e, caption=( 'Failed to render markup text ' f'to {"inline " if self.inline else ""}nodes:' - ) - report.exception(e) + )) self += report return - report.text(f'Rendered nodes (inline: {self.inline}):') - report.code('\n\n'.join([n.pformat() for n in ns]), lang='xml') + report.code('\n\n'.join([n.pformat() for n in ns]), lang='xml', caption=f'Rendered nodes (inline: {self.inline}):') if msgs: report.text('Systemd messages:') [report.node(msg) for msg in msgs] diff --git a/src/sphinxnotes/render/jinja.py b/src/sphinxnotes/render/jinja.py index b4ad3c4..f5991cd 100644 --- a/src/sphinxnotes/render/jinja.py +++ b/src/sphinxnotes/render/jinja.py @@ -64,8 +64,7 @@ def _render(self, ctx: dict[str, Any], debug: bool = False) -> str: return env.from_string(self.text).render(ctx) def _report_self(self, reporter: Report) -> None: - reporter.text('Template:') - reporter.code(self.text, lang='jinja') + reporter.code(self.text, lang='jinja', caption='Template:') class _JinjaEnv(SandboxedEnvironment): diff --git a/src/sphinxnotes/render/utils/__init__.py b/src/sphinxnotes/render/utils/__init__.py index bad7d78..bd8631c 100644 --- a/src/sphinxnotes/render/utils/__init__.py +++ b/src/sphinxnotes/render/utils/__init__.py @@ -123,11 +123,18 @@ def log(self, msg: str) -> None: def text(self, text: str) -> None: self.node(nodes.paragraph(text, text)) - def code(self, code: str, lang: str | None = None) -> None: + def code(self, code: str, lang: str | None = None, caption: str | None = None) -> None: blk = nodes.literal_block(code, code) if lang: blk['language'] = lang - self.node(blk) + if caption: + # See also: :meth:`sphinx.directives.code.container_wrapper`. + container = nodes.container('', literal_block=True, classes=['literal-block-wrapper']) + container += nodes.caption(caption, '', nodes.Text(caption)) + container += blk + self.node(container) + else: + self.node(blk) def list(self, lines: Iterable[str]) -> None: bullet_list = nodes.bullet_list(bullet='*') @@ -141,13 +148,13 @@ def list(self, lines: Iterable[str]) -> None: self.node(bullet_list) - def traceback(self) -> None: + def traceback(self, caption: str | None = None) -> None: # https://pygments.org/docs/lexers/#pygments.lexers.python.PythonTracebackLexer - self.code(traceback.format_exc(), lang='pytb') + self.code(traceback.format_exc(), lang='pytb', caption=caption) - def exception(self, e: Exception) -> None: + def exception(self, e: Exception, caption: str | None = None) -> None: # https://pygments.org/docs/lexers/#pygments.lexers.python.PythonTracebackLexer - self.code(str(e), lang='pytb') + self.code(str(e), lang='pytb', caption=caption) def is_error(self) -> bool: return self['type'] == 'ERROR'