=================
Extending stencil
=================
Stencil allows you to easily add new tags and filters.
Filters
=======
As noted in :ref:`custom_filters`, you can easily register new filter
functions.
Here is an example of adding an ``escape`` filter:
.. code-block:: python
escape_html = lambda text: (
text.replace('&', '&')
.replace("<", "<")
.replace(">", ">")
.replace('"', """)
.replace("'", "'")
)
_js_escapes = {
ord('\\'): '\\u005C',
ord("'"): '\\u0027',
ord('"'): '\\u0022',
ord('>'): '\\u003E',
ord('<'): '\\u003C',
ord('&'): '\\u0026',
ord('='): '\\u003D',
ord('-'): '\\u002D',
ord(';'): '\\u003B',
ord('\u2028'): '\\u2028',
ord('\u2029'): '\\u2029'
}
# Escape every ASCII character with a value less than 32.
_js_escapes.update((ord('%c' % z), '\\u%04X' % z) for z in range(32))
escape_js = lambda text: text.translate(_js_escapes)
def escape(value, mode='html'):
if mode == 'html':
return escape_html(value)
elif mode == 'js':
return escape_js(value)
raise ValueError('Invalid escape mode: %r' % mode)
stencil.FILTERS['escape'] = escape
And we use it in our template:
.. code-block:: html
Now we can use it:
.. code-block:: python
>>> from stencil import TemplateLoader, Context
>>> ctx = Context({'value': ''})
>>> tmp.render(ctx)
u''
.. _extending_tags:
Tags
====
All tags derive from the ``stencil.BlockNode`` class, and self-register with
``stencil`` on declaration.
.. code-block:: python
from stencil import BlockNode
class MyTag(BlockNode):
name = 'my' # This is matched in {% my %}
When ``stencil`` finds a tag matching this name, it will call the
``BlockNode.parse`` classmethod, passing it the rest of the tag content, and
the template instance. This method must return a BlockNode sub-class instance.
.. code-block:: python
class MyTag(BlockNode):
@classmethod
def parse(cls, content, parser):
return cls(content)
The default action is to just return an instance of the class, passed the tag
content.
When a template is rendered, a blocks ``render`` method will be called, passed
a ``Context`` instance, and a file-like object to output to.
Tags with children
------------------
Some tags contain `child` nodes (e.g. ``for``, ``if``, ``block``).
To do this they build a ``Nodelist``:
.. code-block:: python
class MyBlock(BlockNode):
@classmethod
def parse(self, content, parser):
nodelist = parser.parse_nodelist({'endmyblock',})
return cls(nodelist)
This will consume tags until it reaches one with a name found in the list. The
tags are added to a ``Nodelist`` instance, except the matching one which it
stored in ``Nodelist.endnode``.
A ``Nodelist`` can be rendered easily by calling their ``render`` method, which
works just like a ``BlockNode``.
.. code-block:: python
nodelist.render(context, output)
Expressions
-----------
To have an argument resolved as an expression, use the ``parse_expression``
function. This will parse then value passed, and construct an ``Expression``
instance.
Then in render, call ``Expression.resolve(context)`` to get its value.
For more fine grained parsing, and to parse ``key=expr`` syntax, use a
``Tokens`` class.
.. code-block:: python
tokens = Tokens(content)
This provides several useful methods:
.. code-block:: python
value = tokens.parse_argument()
Parses a single argument, be it a string, float or int literal, or a lookup.
The result is suitable for passing as the second argument to
``resolve_lookup``, or as the first to ``Expression``.
.. code-block:: python
value = resolve_lookup(value)
.. code-block:: python
value, filters = tokens.parse_filter_expression()
Parse a filter expression, returning a value (as from ``parse_argument``, and a
list of (filter name, \*args) tuples.
.. code-block:: python
kwargs = tokens.parse_kwargs()
Parse `key=filter-expression` sequences, and construct a dict of
`key: Expression()` items.
.. code-block:: python
tokens.assert_end()
Asserts the current token to be parsed is an end marker, or raises and
assertion error with a message showing where the token was.