Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
""" | ||
The Handlebars Extension provides output templating based on the | ||
`Handlebars Templating Language <https://github.com/wbond/pybars3>`_. | ||
Requirements | ||
------------ | ||
* pybars3 (``pip install pybars3``) | ||
Configuration | ||
------------- | ||
Application Meta-data | ||
^^^^^^^^^^^^^^^^^^^^^ | ||
This extension supports the following application meta-data via | ||
``CementApp.Meta``: | ||
* **handlebars_helpers** - A dictionary of helper functions to register | ||
with the compiler. Will be merged in with | ||
``HandlebarsOutputHandler.Meta.helpers``. | ||
* **handlebars_partials** - A list of partials (template file names) to | ||
search for, and pre-load before rendering templates. Will be merged in | ||
with ``HandlebarsOutputHandler.Meta.partials``. | ||
Template Directories | ||
^^^^^^^^^^^^^^^^^^^^ | ||
To **prepend** a directory to the ``template_dirs`` list defined by the | ||
application/developer, an end-user can add the configuration option | ||
``template_dir`` to their application configuration file under the main | ||
config section: | ||
.. code-block:: text | ||
[myapp] | ||
template_dir = /path/to/my/templates | ||
Usage | ||
----- | ||
.. code-block:: python | ||
class MyApp(CementApp): | ||
class Meta: | ||
label = 'myapp' | ||
extensions = ['handlebars'] | ||
output_handler = 'handlebars' | ||
template_module = 'myapp.templates' | ||
template_dirs = [ | ||
'~/.myapp/templates', | ||
'/usr/lib/myapp/templates', | ||
] | ||
# ... | ||
Note that the above ``template_module`` and ``template_dirs`` are the | ||
auto-defined defaults but are added here for clarity. From here, you | ||
would then put a Handlebars template file in | ||
``myapp/templates/my_template.handlebars`` or | ||
``/usr/lib/myapp/templates/my_template.handlebars`` and then render a data | ||
dictionary with it: | ||
.. code-block:: python | ||
app.render(some_data, 'my_template.handlebars') | ||
Helpers | ||
^^^^^^^ | ||
Custom helper functions can easily be registered with the compiler via | ||
``CementApp.Meta.handlebars_helpers`` and/or | ||
``HandlebarsOutputHandler.Meta.helpers``. | ||
.. code-block:: python | ||
def my_custom_helper(this, arg1, arg2): | ||
# do something with arg1 and arg2 | ||
if arg1 == arg2: | ||
return True | ||
else: | ||
return False | ||
class MyApp(CementApp): | ||
class Meta: | ||
label = 'myapp' | ||
extensions = ['handlebars'] | ||
output_handler = 'handlebars' | ||
handlebars_helpers = { | ||
'myhelper' : my_custom_helper | ||
} | ||
# ... | ||
You would then access this in your template as: | ||
.. code-block:: console | ||
This is my template | ||
{{#if (myhelper this that)}} | ||
This will only appear if myhelper returns True | ||
{{/if}} | ||
See the `Handlebars Documentation <https://github.com/wbond/pybars3>`_ for | ||
more information on helpers. | ||
Partials | ||
^^^^^^^^ | ||
Though partials are supported by the library, there is no good way of | ||
automatically loading them in the context and workflow of a typical Cement | ||
application. Therefore, the extension needs a list of partial | ||
template names to know what to preload, in order to make partials work. | ||
Future versions will hopefully automate this. | ||
Example: | ||
.. code-block:: python | ||
class MyApp(CementApp): | ||
class Meta: | ||
label = 'myapp' | ||
extensions = ['handlebars'] | ||
output_handler = 'handlebars' | ||
handlebars_partials = [ | ||
'header.bars', | ||
'footer.bars', | ||
] | ||
Where ``header.bars`` and ``footer.bars`` are template names that will be | ||
searched for, and loaded just like other templates loaded from template dirs. | ||
These are then referenced in templates as: | ||
.. code-block:: console | ||
{{> "header.bars"}} | ||
This is my template | ||
{{> "footer.bars}} | ||
See the `Handlebars Documentation <https://github.com/wbond/pybars3>`_ for | ||
more information on partials. | ||
""" | ||
|
||
import sys | ||
|
||
# Monkey patch so we don't escape HTML (not clear how else to do this) | ||
# See: https://github.com/wbond/pybars3/issues/25 | ||
import pybars._compiler | ||
|
||
original_prepare = pybars._compiler.prepare | ||
def my_prepare(value, escape): | ||
return original_prepare(value, False) | ||
pybars._compiler.prepare = my_prepare | ||
|
||
from pybars import Compiler | ||
from cement.core import output, exc, handler | ||
from cement.utils.misc import minimal_logger | ||
|
||
|
||
LOG = minimal_logger(__name__) | ||
|
||
|
||
class HandlebarsOutputHandler(output.TemplateOutputHandler): | ||
|
||
""" | ||
This class implements the :ref:`IOutput <cement.core.output>` | ||
interface. It provides text output from template and uses the | ||
`Handlebars Templating Language <http://handlebarsjs.com/>`_ for Python | ||
via the ``pybars`` library. Please see the developer documentation on | ||
:ref:`Output Handling <dev_output_handling>`. | ||
**Note** This extension has an external dependency on ``pybars3``. You | ||
must include ``pybars3`` in your applications dependencies as Cement | ||
explicitly does **not** include external dependencies for optional | ||
extensions. | ||
""" | ||
|
||
class Meta: | ||
|
||
"""Handler meta-data.""" | ||
|
||
interface = output.IOutput | ||
label = 'handlebars' | ||
|
||
#: Whether or not to include ``handlebars`` as an available to choice | ||
#: to override the ``output_handler`` via command line options. | ||
overridable = False | ||
|
||
#: Custom helpers | ||
helpers = {} | ||
|
||
#: List of partials to preload | ||
partials = [] | ||
|
||
def __init__(self, *args, **kw): | ||
super(HandlebarsOutputHandler, self).__init__(*args, **kw) | ||
self._raw_partials = {} | ||
|
||
def _setup(self, app): | ||
super(HandlebarsOutputHandler, self)._setup(app) | ||
if hasattr(self.app._meta, 'handlebars_helpers'): | ||
self._meta.helpers = self.app._meta.handlebars_helpers | ||
#self._meta.helpers.update(self.app._meta.handlebars_helpers) | ||
if hasattr(self.app._meta, 'handlebars_partials'): | ||
#self._meta.partials = self._meta.partials + \ | ||
self._meta.partials = self.app._meta.handlebars_partials | ||
for partial in self._meta.partials: | ||
self._raw_partials[partial] = self.load_template(partial) | ||
|
||
def render_content(self, data, content): | ||
bars = Compiler() | ||
if sys.version_info[0] >= 3: | ||
if not isinstance(content, str): | ||
content = content.decode('utf-8') | ||
else: | ||
if not isinstance(content, unicode): # pragma: nocover # noqa | ||
content = content.decode('utf-8') # pragma: nocover | ||
content = bars.compile(content) | ||
|
||
# need to render partials | ||
partials = {} | ||
for key,val in self._raw_partials.items(): | ||
partials[key] = bars.compile(val) | ||
|
||
return content(data, helpers=self._meta.helpers, partials=partials) | ||
|
||
def render(self, data, template): | ||
""" | ||
Take a data dictionary and render it using the given template file. | ||
Required Arguments: | ||
:param data: The data dictionary to render. | ||
:keyword template: 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("rendering output using '%s' as a template." % template) | ||
res = self.render_content(data, self.load_template(template)) | ||
return res | ||
|
||
|
||
def load(app): | ||
handler.register(HandlebarsOutputHandler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.. _cement.ext.ext_handlebars: | ||
|
||
:mod:`cement.ext.ext_handlebars` | ||
-------------------------------- | ||
|
||
.. automodule:: cement.ext.ext_handlebars | ||
:members: | ||
:private-members: | ||
:show-inheritance: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ pylibmc | |
redis | ||
jinja2 | ||
watchdog | ||
pybars3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,4 @@ colorlog | |
tabulate | ||
jinja2 | ||
watchdog | ||
pybars3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
"""Tests for cement.ext.ext_handlebars.""" | ||
|
||
import sys | ||
import random | ||
|
||
from cement.core import exc, foundation, handler, backend, controller | ||
from cement.utils import test | ||
|
||
class HandlebarsTestApp(test.TestApp): | ||
class Meta: | ||
extensions = ['handlebars'] | ||
output_handler = 'handlebars' | ||
handlebars_helpers = {} | ||
#handlebars_partials = ['test_partial_template.handlebars'] | ||
|
||
class HandlebarsExtTestCase(test.CementExtTestCase): | ||
app_class = HandlebarsTestApp | ||
|
||
# def setUp(self): | ||
# super(HandlebarsExtTestCase, self).setUp() | ||
# self.reset_backend() | ||
# self.app = self.make_app('tests', | ||
# extensions=['handlebars'], | ||
# output_handler='handlebars', | ||
# argv=[] | ||
# ) | ||
|
||
def test_handlebars(self): | ||
self.app.setup() | ||
rando = random.random() | ||
res = self.app.render(dict(foo=rando), 'test_template.handlebars') | ||
handlebars_res = "foo equals %s\n" % rando | ||
self.eq(res, handlebars_res) | ||
|
||
def test_handlebars_partials(self): | ||
# FIX ME: Not sure what's going on here | ||
self.app.setup() | ||
|
||
rando = random.random() | ||
res = self.app.render(dict(foo=rando), 'test_base_template.handlebars') | ||
handlebars_res = "Inside partial > foo equals %s\n" % rando | ||
self.eq(res, handlebars_res) | ||
|
||
@test.raises(exc.FrameworkError) | ||
def test_handlebars_bad_template(self): | ||
self.app.setup() | ||
res = self.app.render(dict(foo='bar'), 'bad_template2.handlebars') | ||
|
||
@test.raises(exc.FrameworkError) | ||
def test_handlebars_nonexistent_template(self): | ||
self.app.setup() | ||
res = self.app.render(dict(foo='bar'), 'missing_template.handlebars') | ||
|
||
@test.raises(exc.FrameworkError) | ||
def test_handlebars_none_template(self): | ||
self.app.setup() | ||
try: | ||
res = self.app.render(dict(foo='bar'), None) | ||
except exc.FrameworkError as e: | ||
self.eq(e.msg, "Invalid template path 'None'.") | ||
raise | ||
|
||
@test.raises(exc.FrameworkError) | ||
def test_handlebars_bad_module(self): | ||
self.app.setup() | ||
self.app._meta.template_module = 'this_is_a_bogus_module' | ||
res = self.app.render(dict(foo='bar'), 'bad_template.handlebars') |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{{> "test_partial_template.handlebars"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Inside partial > foo equals {{foo}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
foo equals {{foo}} |