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
20 changes: 16 additions & 4 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Base Directive Classes

.. autoclass:: sphinxnotes.render.BaseContextDirective
:show-inheritance:
:members: process_pending_node, queue_pending_node, current_raw_data, current_context, current_template
:members: process_pending_node, queue_pending_node, current_context, current_template

.. autoclass:: sphinxnotes.render.BaseDataDefineDirective
:show-inheritance:
Expand Down Expand Up @@ -104,11 +104,21 @@ See :doc:`tmpl` for built-in extra-context names such as ``doc`` and
:members:
:undoc-members:

.. autoclass:: sphinxnotes.render.ExtraContextRegistry
:members:
:inherited-members:
:undoc-members:
:class-doc-from: both

Filters
=======

.. autodecorator:: sphinxnotes.render.filter

.. autoclass:: sphinxnotes.render.JinjaRegistry
:members:
:undoc-members:

Data, Field and Schema
======================

Expand All @@ -131,11 +141,9 @@ Data, Field and Schema
:members: name, attrs, content
:undoc-members:

.. autoclass:: sphinxnotes.render.data.Registry
.. autoclass:: sphinxnotes.render.DataRegistry
:members:

.. autotype:: sphinxnotes.render.data.ByOptionStore

Registry
========

Expand All @@ -148,3 +156,7 @@ or add new extra context) by adding new items to
.. autoclass:: sphinxnotes.render.Registry

.. autoproperty:: data

.. autoproperty:: extra_context

.. autoproperty:: jinja
19 changes: 17 additions & 2 deletions src/sphinxnotes/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from . import meta
from .data import (
Registry as DataRegistry,
DataRegistry,
REGISTRY as DATA_REGISTRY,
PlainValue,
Value,
Expand All @@ -28,6 +28,8 @@
extra_context,
ExtraContext,
ExtraContextRequest,
ExtraContextRegistry,
REGISTRY as EXTRA_CONTEXT_REGISTRY,
)
from .pipeline import BaseContextRole, BaseContextDirective
from .sources import (
Expand All @@ -36,7 +38,7 @@
BaseDataDefineDirective,
StrictDataDefineDirective,
)
from .jinja import filter
from .jinja import filter, JinjaRegistry, REGISTRY as JINJA_REGISTRY

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand All @@ -45,6 +47,7 @@
"""Python API for other Sphinx extensions."""
__all__ = [
'Registry',
'DataRegistry',
'PlainValue',
'Value',
'ValueWrapper',
Expand All @@ -58,6 +61,8 @@
'ResolvedContext',
'ExtraContext',
'ExtraContextRequest',
'ExtraContextRegistry',
'EXTRA_CONTEXT_REGISTRY',
'extra_context',
'pending_node',
'BaseContextRole',
Expand All @@ -67,6 +72,8 @@
'BaseDataDefineDirective',
'StrictDataDefineDirective',
'filter',
'JinjaRegistry',
'JINJA_REGISTRY',
]


Expand All @@ -77,6 +84,14 @@ class Registry:
def data(self) -> DataRegistry:
return DATA_REGISTRY

@property
def extra_context(self) -> ExtraContextRegistry:
return EXTRA_CONTEXT_REGISTRY

@property
def jinja(self) -> JinjaRegistry:
return JINJA_REGISTRY


REGISTRY = Registry()

Expand Down
39 changes: 21 additions & 18 deletions src/sphinxnotes/render/ctxnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,30 @@
class pending_node(nodes.Element):
"""A docutils node to be rendered."""

# The context to be rendered by Jinja template.
#: The context to be rendered by Jinja template.
ctx: UnresolvedContext | ResolvedContext
#: Jinja template for rendering the context.
template: Template
#: Whether rendering to inline nodes.
inline: bool
#: Whether the rendering pipeline is finished (failed is also finished).
rendered: bool
#: Stored pickling error for later-phase unresolved context.

# Stored pickling error for later-phase unresolved context.
_ctx_pickle_error: Exception | None

# Types for hook functions.
type UnresolvedContextHook = Callable[[pending_node, UnresolvedContext], None]
type ResolvedContextHook = Callable[[pending_node, ResolvedContext], None]
type MarkupTextHook = Callable[[pending_node, str], str]
type RenderedNodesHook = Callable[[pending_node, list[nodes.Node]], None]

# Hooks for processing render intermediate products.
_unresolved_context_hooks: list[UnresolvedContextHook]
_resolved_context_hooks: list[ResolvedContextHook]
_markup_text_hooks: list[MarkupTextHook]
_rendered_nodes_hooks: list[RenderedNodesHook]

def __init__(
self,
ctx: UnresolvedContext | ResolvedContext,
Expand All @@ -50,16 +63,18 @@ def __init__(
**attributes,
) -> None:
super().__init__(rawsource, *children, **attributes)
self.ctx = ctx
self.template = tmpl
self.inline = inline
self.rendered = False

# Test whehter ctx pickle-able.
self._ctx_pickle_error = None
if isinstance(ctx, UnresolvedContext) and tmpl.phase != Phase.Parsing:
try:
pickle.dumps(ctx)
except Exception as exc:
self._ctx_pickle_error = exc
self.ctx = ctx
self.template = tmpl
self.inline = inline
self.rendered = False

# Init hook lists.
self._unresolved_context_hooks = []
Expand Down Expand Up @@ -239,18 +254,6 @@ def unwrap_and_replace_self_inline(self, inliner: Report.Inliner) -> None:
# Replace self with inline nodes.
self.replace_self(ns)

"""Hooks for processing render intermediate products."""

type UnresolvedContextHook = Callable[[pending_node, UnresolvedContext], None]
type ResolvedContextHook = Callable[[pending_node, ResolvedContext], None]
type MarkupTextHook = Callable[[pending_node, str], str]
type RenderedNodesHook = Callable[[pending_node, list[nodes.Node]], None]

_unresolved_context_hooks: list[UnresolvedContextHook]
_resolved_context_hooks: list[ResolvedContextHook]
_markup_text_hooks: list[MarkupTextHook]
_rendered_nodes_hooks: list[RenderedNodesHook]

def hook_unresolved_context(self, hook: UnresolvedContextHook) -> None:
self._unresolved_context_hooks.append(hook)

Expand Down
6 changes: 3 additions & 3 deletions src/sphinxnotes/render/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def _str_conv(v: str) -> str:
return vv if isinstance(vv, str) else v


class Registry:
class DataRegistry:
"""Stores supported element types and element forms (containers)."""

etypes: dict[str, type]
Expand Down Expand Up @@ -227,7 +227,7 @@ def add_by_option(
in the DSL
:param etype: The value type for this option
:param default: The default value for this option
:param store: How to store multiple values
:param store: How to store multiple values, can be ``'assign'`` or ``'append'``
:param aliases: Alternative names for this option

.. seealso:: :ref:`add-custom-by-options`
Expand All @@ -239,7 +239,7 @@ def add_by_option(
self.byopts[alias] = opt


REGISTRY = Registry()
REGISTRY = DataRegistry()

# ======================
# Data, Field and Schema
Expand Down
41 changes: 24 additions & 17 deletions src/sphinxnotes/render/extractx.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,34 @@ def generate(self, req: ExtraContextRequest, *args, **kwargs) -> Any: ...
# ==========================


class _ExtraContextRegistry:
ctxs: dict[str, ExtraContext]
class ExtraContextRegistry:
"""Registry for extra contexts."""

_ctxs: dict[str, ExtraContext]

def __init__(self) -> None:
self.ctxs = {}
self._ctxs = {}

def register(self, name: str, ctx: ExtraContext) -> None:
if name in self.ctxs:
raise ValueError(f'Extra context "{name}" already registered')
self.ctxs[name] = ctx
def add(self, name: str, ctx: ExtraContext) -> None:
"""Register an extra context.

def get(self, name: str) -> ExtraContext | None:
if name not in self.ctxs:
return None
return self.ctxs[name]
:param name: The context name, used in templates via ``load_extra('name')``
:param ctx: An :py:class:`ExtraContext` instance

.. note:: Using the :py:deco:`extra_context` decorator is recommended for most cases.
"""
if name in self._ctxs:
raise ValueError(f'Extra context "{name}" already registered')
self._ctxs[name] = ctx

def get_names(self) -> set[str]:
return set(self.ctxs.keys())

REGISTRY = ExtraContextRegistry()
"""The global registry for extra contexts.

_REGISTRY = _ExtraContextRegistry()
This is the underlying registry used by the :py:func:`extra_context` decorator.
Using the decorator is recommended for most cases, but you can also register
extra contexts directly via this registry.
"""


def extra_context(name: str):
Expand All @@ -71,19 +78,19 @@ def extra_context(name: str):
"""

def decorator(cls):
_REGISTRY.register(name, cls())
REGISTRY.add(name, cls())
return cls

return decorator


def extra_context_names() -> set[str]:
return _REGISTRY.get_names()
return set(REGISTRY._ctxs.keys())


def extra_context_loader(request: ExtraContextRequest):
def load_extra(name: str, *args, **kwargs) -> Any:
ctx = _REGISTRY.get(name)
ctx = REGISTRY._ctxs.get(name)
if ctx is None:
raise ValueError(
f'Extra context "{name}" is not registered. '
Expand Down
Loading