changeset 185:139179dc7abd

render: Add support for a Mustache template engine. Add unit tests for the new class.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 04 Jan 2015 14:59:12 -0800
parents 27d623a241c6
children e61fbae61402
files piecrust/plugins/builtin.py piecrust/templating/pystacheengine.py tests/test_templating_pystacheengine.py
diffstat 3 files changed, 124 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/plugins/builtin.py	Sun Jan 04 14:58:40 2015 -0800
+++ b/piecrust/plugins/builtin.py	Sun Jan 04 14:59:12 2015 -0800
@@ -28,6 +28,7 @@
 from piecrust.sources.autoconfig import AutoConfigSource
 from piecrust.sources.prose import ProseSource
 from piecrust.templating.jinjaengine import JinjaTemplateEngine
+from piecrust.templating.pystacheengine import PystacheTemplateEngine
 
 
 class BuiltInPlugin(PieCrustPlugin):
@@ -73,7 +74,8 @@
 
     def getTemplateEngines(self):
         return [
-                JinjaTemplateEngine()]
+                JinjaTemplateEngine(),
+                PystacheTemplateEngine()]
 
     def getFormatters(self):
         return [
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/templating/pystacheengine.py	Sun Jan 04 14:59:12 2015 -0800
@@ -0,0 +1,57 @@
+import logging
+import pystache
+from piecrust.templating.base import (
+        TemplateEngine, TemplateNotFoundError, TemplatingError)
+
+
+logger = logging.getLogger(__name__)
+
+
+class PystacheTemplateEngine(TemplateEngine):
+    ENGINE_NAMES = ['mustache']
+    EXTENSIONS = ['mustache']
+
+    def __init__(self):
+        self.renderer = None
+
+    def renderString(self, txt, data, filename=None):
+        self._ensureLoaded()
+        try:
+            return self.renderer.render(txt, data)
+        except pystache.TemplateNotFoundError as ex:
+            raise TemplateNotFoundError() from ex
+        except pystache.PystacheError as ex:
+            raise TemplatingError(str(ex), filename) from ex
+
+    def renderFile(self, paths, data):
+        self._ensureLoaded()
+        tpl = None
+        logger.debug("Looking for template: %s" % paths)
+        for p in paths:
+            if not p.endswith('.mustache'):
+                raise TemplatingError(
+                        "The Mustache template engine only accepts template "
+                        "filenames with a `.mustache` extension. Got: %s" %
+                        p)
+            name = p[:-9]  # strip `.mustache`
+            try:
+                tpl = self.renderer.load_template(name)
+            except Exception as ex:
+                print(p, ex)
+                pass
+
+        if tpl is None:
+            raise TemplateNotFoundError()
+
+        try:
+            return self.renderer.render(tpl, data)
+        except pystache.PystacheError as ex:
+            raise TemplatingError(str(ex)) from ex
+
+    def _ensureLoaded(self):
+        if self.renderer:
+            return
+
+        self.renderer = pystache.Renderer(
+                search_dirs=self.app.templates_dirs)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_templating_pystacheengine.py	Sun Jan 04 14:59:12 2015 -0800
@@ -0,0 +1,64 @@
+import pytest
+from .mockutil import (
+        mock_fs, mock_fs_scope, get_simple_page, render_simple_page)
+
+
+app_config = {
+        'site': {
+            'default_format': 'none',
+            'default_template_engine': 'mustache'},
+        'foo': 'bar'}
+page_config = {'layout': 'none'}
+
+open_patches = ['pystache.common']
+
+
+@pytest.mark.parametrize(
+        'contents, expected',
+        [
+            ("Raw text", "Raw text"),
+            ("This is {{foo}}", "This is bar"),
+            ("Info:\n{{#page}}\nMy URL: {{url}}\n{{/page}}\n",
+                "Info:\nMy URL: /foo\n")
+            ])
+def test_simple(contents, expected):
+    fs = (mock_fs()
+            .withConfig(app_config)
+            .withPage('pages/foo', config=page_config, contents=contents))
+    with mock_fs_scope(fs, open_patches=open_patches):
+        app = fs.getApp()
+        page = get_simple_page(app, 'foo.md')
+        output = render_simple_page(page, '/foo')
+        assert output == expected
+
+
+def test_layout():
+    contents = "Blah\n"
+    layout = "{{content}}\nFor site: {{foo}}\n"
+    expected = "Blah\n\nFor site: bar\n"
+    fs = (mock_fs()
+            .withConfig(app_config)
+            .withAsset('templates/blah.mustache', layout)
+            .withPage('pages/foo', config={'layout': 'blah'},
+                      contents=contents))
+    with mock_fs_scope(fs, open_patches=open_patches):
+        app = fs.getApp()
+        page = get_simple_page(app, 'foo.md')
+        output = render_simple_page(page, '/foo')
+        assert output == expected
+
+
+def test_partial():
+    contents = "Info:\n{{#page}}\n{{> page_info}}\n{{/page}}\n"
+    partial = "- URL: {{url}}\n- SLUG: {{slug}}\n"
+    expected = "Info:\n- URL: /foo\n- SLUG: foo\n"
+    fs = (mock_fs()
+            .withConfig(app_config)
+            .withAsset('templates/page_info.mustache', partial)
+            .withPage('pages/foo', config=page_config, contents=contents))
+    with mock_fs_scope(fs, open_patches=open_patches):
+        app = fs.getApp()
+        page = get_simple_page(app, 'foo.md')
+        output = render_simple_page(page, '/foo')
+        assert output == expected
+