1616from jinja2 import StrictUndefined , DebugUndefined
1717
1818from .data import ParsedData
19- from .utils import Report
2019
2120if TYPE_CHECKING :
2221 from typing import Any
2524 from .ctx import ResolvedContext
2625
2726
27+ class JinjaRegistry :
28+ """Registry for customizing the Jinja2 environment.
29+
30+ Provides methods to add custom filters and extensions to the Jinja2
31+ rendering environment used by this extension.
32+
33+ Usage::
34+
35+ from sphinxnotes.render.jinja import REGISTRY
36+
37+ def my_filter_factory(env):
38+ def _filter(value):
39+ return value.upper()
40+ return _filter
41+
42+ REGISTRY.add_filter('my_filter', my_filter_factory)
43+ REGISTRY.add_extension('jinja2.ext.i18n')
44+ """
45+
46+ _filters : dict [str , Callable [[BuildEnvironment ], Callable ]]
47+ _extensions : list [str ]
48+
49+ def __init__ (self ) -> None :
50+ self ._filters = {}
51+ self ._extensions = []
52+
53+ def add_filter (
54+ self , name : str , factory : Callable [[BuildEnvironment ], Callable ]
55+ ) -> None :
56+ """Register a filter factory.
57+
58+ :param name: The filter name, used in Jinja templates as ``{{ value|name }}``
59+ :param factory: A callable that takes a :py:class:`~sphinx.environment.BuildEnvironment`
60+ and returns a filter callable
61+
62+ .. note:: Using the :py:deco:`filter` decorator is recommended for most cases.
63+
64+ """
65+ self ._filters [name ] = factory
66+
67+ def add_extension (self , extension : str ) -> None :
68+ """Add a Jinja2 extension.
69+
70+ See `Jinja2 Extensions <https://jinja.palletsprojects.com/en/stable/extensions/>`_
71+ for available bulitin extensions.
72+
73+ :param extension: The extension module path, e.g. ``'jinja2.ext.i18n'``
74+ """
75+ self ._extensions .append (extension )
76+
77+
78+ REGISTRY = JinjaRegistry ()
79+ """The global registry for Jinja2 filter factories."""
80+
81+
2882@dataclass
2983class TemplateRenderer :
3084 text : str
@@ -48,10 +102,7 @@ def render(
48102 return self ._render (ctx , debug = debug )
49103
50104 def _render (self , ctx : dict [str , Any ], debug : bool = False ) -> str :
51- extensions = [
52- 'jinja2.ext.loopcontrols' , # enable {% break %}, {% continue %}
53- 'jinja2.ext.do' , # enable {% do ... %}
54- ]
105+ extensions = list (REGISTRY ._extensions )
55106 if debug :
56107 extensions .append ('jinja2.ext.debug' )
57108
@@ -63,27 +114,19 @@ def _render(self, ctx: dict[str, Any], debug: bool = False) -> str:
63114
64115 return env .from_string (self .text ).render (ctx )
65116
66- def _report_self (self , reporter : Report ) -> None :
67- reporter .code (self .text , lang = 'jinja' , caption = 'Template:' )
68-
69117
70118class _JinjaEnv (SandboxedEnvironment ):
71119 _env : ClassVar [BuildEnvironment ]
72- _filter_factories : ClassVar [dict [str , Callable [[BuildEnvironment ], Callable ]]] = {}
73120
74121 def __init__ (self , * args , ** kwargs ):
75122 super ().__init__ (* args , ** kwargs )
76- for name , factory in self . _filter_factories .items ():
123+ for name , factory in REGISTRY . _filters .items ():
77124 self .filters [name ] = factory (self ._env )
78125
79126 @classmethod
80127 def on_builder_inited (cls , app : Sphinx ):
81128 cls ._env = app .env
82129
83- @classmethod
84- def add_filter (cls , name : str , factory : Callable [[BuildEnvironment ], Callable ]):
85- cls ._filter_factories [name ] = factory
86-
87130 @override
88131 def is_safe_attribute (self , obj , attr , value = None ):
89132 """
@@ -111,11 +154,14 @@ def _filter(value):
111154 """
112155
113156 def decorator (ff ):
114- _JinjaEnv .add_filter (name , ff )
157+ REGISTRY .add_filter (name , ff )
115158 return ff
116159
117160 return decorator
118161
119162
120163def setup (app : Sphinx ):
121164 app .connect ('builder-inited' , _JinjaEnv .on_builder_inited )
165+
166+ REGISTRY .add_extension ('jinja2.ext.loopcontrols' )
167+ REGISTRY .add_extension ('jinja2.ext.do' )
0 commit comments