Commits

Andrey Vlasovskikh committed a82c88a

Added extension point for custom template rendering libraries

Comments (0)

Files changed (7)

 
     Jekyll uses the Liquid templates system written in Ruby, but Obraz is
     written in Python and it cannot use Liquid. Obraz uses the Jinja2 templates
-    system instead. Its tag syntax is very similar to Liquid, but its set of
-    filters is quite different. The process of translation a Liquid template to
-    Jinja2 is usually straightforward. See the [Jinja2 templates
-    documentation][2] for details.
+    system by default. Note, that you can change the template system via
+    [plugins][5].
+
+    Jinja2 tag syntax is very similar to Liquid, but its set of filters is
+    quite different. The process of translation a Liquid template to Jinja2 is
+    usually straightforward. See the [Jinja2 templates documentation][2] for
+    details.
 
     Jekyll filters in Obraz: `markdownify`.
 
 This applies to third-party plugin functions as well as to the processing
 functions in Obraz itself.
 
-There are also a couple of extension points that allow to define content and
-template filters.
+There are also a couple of extension points that allow to change define content
+and template filters, or change the template system.
 
 
 Extension Points
             """Markdown Jinja2 template filter."""
             return markdown(content)
 
+* **`@obraz.template_renderer`**
+
+    Set a custom template renderer. You can change the template system used by
+    Obraz.
+
+    A template renderer is a function of type `(string: str, context: dict,
+    config: dict) -> str`.
+
+    Example:
+
+        import obraz
+        from mako.template import Template
+        from mako.lookup import TemplateLookup
+
+        @obraz.template_renderer
+        def mako_render_string(string, context, config):
+            """Render string using Mako template library."""
+            includes = os.path.join(config['source'], '_includes')
+            lookup = TemplateLookup(directories=[includes])
+            return Template(string, lookup=lookup).render(**context)
+
 
 Development Notes
 -----------------
 _processors = []
 _file_filters = {}
 _template_filters = {}
+_render_string = lambda string, context, site: string
 _default_config = {
     'source': './',
     'destination': './_site',
     return wrapper
 
 
+def template_renderer(f):
+    """Set a custom template renderer."""
+    global _render_string
+    _render_string = f
+    return f
+
+
 def loader(f):
     """Register a site source content loader."""
     _loaders.insert(0, f)
     }
 
 
-def render_string(source, s, context):
-    includes = os.path.join(source, '_includes')
+@template_renderer
+def jinja2_render_string(string, context, config):
+    includes = os.path.join(config['source'], '_includes')
     env = Environment(loader=FileSystemLoader(includes))
     env.filters.update(_template_filters)
-    t = env.from_string(s)
+    t = env.from_string(string)
     return t.render(**context)
 
 
     }
 
 
-def render_layout(source, content, page, site):
+def render_layout(content, page, site):
     name = page.get('layout', 'nil')
     if name == 'nil':
         return content
-    layout_file = os.path.join(source, '_layouts', '{0}.html'.format(name))
+    filename = '{0}.html'.format(name)
+    layout_file = os.path.join(site['source'], '_layouts', filename)
     layout = read_template(layout_file)
     if not layout:
         raise Exception("Cannot load template: '{0}'".format(layout_file))
         'page': layout,
         'content': content,
     }
-    content = render_string(source, layout['content'], context)
-    return render_layout(source, content, layout, site)
+    content = _render_string(layout['content'], context, site)
+    return render_layout(content, layout, site)
 
 
-def render_page(source, page, site):
+def render_page(page, site):
     context = {
         'site': site,
         'page': page,
     }
-    content = render_string(source, page['content'], context)
+    content = _render_string(page['content'], context, site)
     f = _file_filters.get(file_suffix(page.get('path', '')))
     if f:
         content = f(content)
     page['content'] = content
-    return render_layout(source, content, page, site)
+    return render_layout(content, page, site)
 
 
 @processor
     with open(dst, 'wb') as fd:
         fd.truncate()
         try:
-            rendered = render_page(site['source'], page, site)
+            rendered = render_page(page, site)
         except Exception as e:
             msg = "Cannot render '{0}': {1}".format(page.get('path'), e)
             raise Exception(msg)

test/data/custom_template_renderer/site/index.html

+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello, str.format!</title>
+  </head>
+  <body>
+    <h1>Hello, str.format!</h1>
+  </body>
+</html>

test/data/custom_template_renderer/src/_plugins/test1.py

+import obraz
+
+
+@obraz.template_renderer
+def string_fmt_render(string, context, site):
+    return string.format(**context)

test/data/custom_template_renderer/src/index.html

+---
+title: Hello, str.format!
+---
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>{page[title]}</title>
+  </head>
+  <body>
+    <h1>{page[title]}</h1>
+  </body>
+</html>

test/test_obraz.py

     def test_filters_after_rendering(self):
         """Issue #9."""
         self.do('filters_after_rendering')
+
+    def test_custom_template_renderer(self):
+        self.do('custom_template_renderer')