"""
Cement mustache extension module.
**Note** This extension has an external dependency on ``pystache``. Cement
explicitly does **not** include external dependencies for optional
extensions.
* In Cement ``>=3.0.8`` you must include ``cement[mustache]`` in your
applications dependencies.
* In Cement ``<3.0.8`` you must include ``pystache`` in your applications
dependencies.
"""
from typing import TYPE_CHECKING, Any
from pystache.renderer import Renderer # type: ignore
from ..core.output import OutputHandler
from ..core.template import TemplateHandler
from ..utils.misc import minimal_logger
if TYPE_CHECKING:
from ..core.foundation import App # pragma: nocover # TYPE_CHECKING import
LOG = minimal_logger(__name__)
class PartialsLoader:
def __init__(self, handler: TemplateHandler) -> None:
self.handler = handler
def get(self, template: str) -> str | bytes | None:
content, _type, _path = self.handler.load(template)
return content
[docs]
class MustacheOutputHandler(OutputHandler):
"""
This class implements the :ref:`Output <cement.core.output>` Handler
interface. It provides text output from template and uses the
`Mustache Templating Language <http://mustache.github.com>`_. Please
see the developer documentation on
:cement:`Output Handling <dev/output>`.
"""
_meta: Meta # type: ignore
def __init__(self, *args: Any, **kw: Any) -> None:
super().__init__(*args, **kw)
self.templater: MustacheTemplateHandler = None # type: ignore
[docs]
def _setup(self, app: "App") -> None:
super()._setup(app)
self.templater = self.app.handler.resolve('template', 'mustache', # type: ignore
setup=True)
[docs]
def render(self, data: dict[str, Any], template: str | None = None, **kw: Any) -> str:
"""
Take a data dictionary and render it using the given template file.
Additional keyword arguments passed to ``stache.render()``.
Args:
data (dict): The data dictionary to render.
Keyword Args:
template (str): The path to the template, after the
``template_module`` or ``template_dirs`` prefix as defined in
the application.
Returns:
str: The rendered template text
"""
LOG.debug(f"rendering content using '{template}' as a template.")
# `template=None` is well-defined at runtime: TemplateHandler.load
# guards `if not template_path:` and raises FrameworkError. The
# arg-type narrowing here documents that we accept the None default
# but rely on the load() guard for the actual rejection.
content, _type, _path = self.templater.load(template) # type: ignore[arg-type]
return self.templater.render(content, data)
[docs]
class MustacheTemplateHandler(TemplateHandler):
"""
This class implements the :ref:`Template <cement.core.template>` Handler
interface. It renderd content as template, and supports copying entire
source template directories using the
`Mustache Templating Language <http://mustache.github.com>`_. Please
see the developer documentation on
:cement:`Template Handling <dev/template>`.
**Note** This extension has an external dependency on ``pystache``. You
must include ``pystache`` in your applications dependencies as Cement
explicitly does **not** include external dependencies for optional
extensions.
"""
_meta: Meta # type: ignore
def __init__(self, *args: Any, **kw: Any) -> None:
super().__init__(*args, **kw)
self._partials_loader = PartialsLoader(self)
[docs]
def render(self, content: str | bytes, data: dict[str, Any]) -> str:
"""
Render the given ``content`` as template with the ``data`` dictionary.
Args:
content (str): The template content to render.
data (dict): The data dictionary to render.
Returns:
str: The rendered template text
"""
stache = Renderer(partials=self._partials_loader)
return stache.render(content, data) # type: ignore
def load(app: "App") -> None:
app.handler.register(MustacheOutputHandler)
app.handler.register(MustacheTemplateHandler)