- added `batch` and `slice` filters for batching or slicing sequences
-- added `sum`, `abs` and `round` filters. This fixes #238
+- added `sum`, `abs`, `round` and `sort` filters. This fixes #238
- added `striptags` and `xmlattr` filters for easier SGML/XML processing
If the iterator is inifite it will crash however, so makes sure you don't
pass something like that to a template!
+- added `rendetemplate` to render included templates in an isolated
+ environment and get the outout back.
+
+- added `simplefilter` decorator.
+
Version 1.0
-----------
*new in Jinja 1.1*
+`rendertemplate`
+
+ Loads and renders a template with a copy of the current context. This works
+ in many situations like the ``{% include %}`` tag, just that it does not
+ include a template and merges it into the template structure but renders
+ it completely independent and returns the rendered data as string.
+
+ *new in Jinja 1.1*
+
Global Constants
================
For information regarding the visibility of macros have a look at the
`Scopes and Variable Behavior`_ section.
+Extended Macro Call
+===================
+
+*new in Jinja 1.1*
+
+Jinja 1.1 adds a new special tag that you can use to pass some evaluable
+template code to a macro. Here an example macro that uses the features of
+the ``{% call %}`` tag:
+
+.. sourcecode:: html+jinja
+
+ {% macro dialog title %}
+ <div class="dialog">
+ <h3>{{ title }}</h3>
+ <div class="text">
+ {{ caller() }}
+ </div>
+ </div>
+ {% endmacro %}
+
+Called the normal way `caller` will be undefined, but if you call it
+using the new `{% call %}` tag you can pass it some data:
+
+.. sourcecode:: html+jinja
+
+ {% call dialog('Hello World') %}
+ This is an example dialog
+ {% endcall %}
+
+Now the data wrapped will be inserted where you put the `caller` call.
+
+If you pass `caller()` some keyword arguments those are added to the
+namespace of the wrapped template data:
+
+.. sourcecode:: html+jinja
+
+ {% macro makelist items %}
+ <ul>
+ {%- for item in items %}
+ <li>{{ caller(item=item) }}</li>
+ {%- endfor %}
+ </ul>
+ {%- endmacro %}
+
+ {% call makelist([1, 2, 3, 4, 5, 6]) -%}
+ [[{{ item }}]]
+ {%- endcall %}
+
+This will then produce this output:
+
+.. sourcecode:: html
+
+ <ul>
+ <li>[[1]]</li>
+ <li>[[2]]</li>
+ <li>[[3]]</li>
+ <li>[[4]]</li>
+ <li>[[5]]</li>
+ <li>[[6]]</li>
+ </ul>
+
+
Template Inclusion
==================
This is intended because it makes it possible to include macros from other
templates.
+*new in Jinja 1.1* you can now render an included template to a string that is
+evaluated in an indepdendent environment by calling `rendertemplate`. See the
+documentation for this function in the `builtins`_ documentation.
+
Filtering Blocks
================
{% endfor %}
-Bleeding Edge
-=============
-
-Here are some features documented that are new in the SVN version and might
-change.
-
-``{% call %}``:
-
- A new tag that allows to pass a macro a block with template data:
-
- .. sourcecode:: html+jinja
-
- {% macro dialog title %}
- <div class="dialog">
- <h3>{{ title }}</h3>
- <div class="text">
- {{ caller() }}
- </div>
- </div>
- {% endmacro %}
-
- Called the normal way `caller` will be undefined, but if you call it
- using the new `{% call %}` tag you can pass it some data:
-
- .. sourcecode:: html+jinja
-
- {% call dialog('Hello World') %}
- This is an example dialog
- {% endcall %}
-
- If you pass `caller()` some keyword arguments those are added to the
- namespace of the wrapped template data:
-
- .. sourcecode:: html+jinja
-
- {% macro makelist items %}
- <ul>
- {%- for item in items %}
- <li>{{ caller(item=item) }}</li>
- {%- endfor %}
- </ul>
- {%- endmacro %}
-
- {% call makelist([1, 2, 3, 4, 5, 6]) -%}
- [[{{ item }}]]
- {%- endcall %}
-
- This will then produce this output:
-
- .. sourcecode:: html
-
- <ul>
- <li>[[1]]</li>
- <li>[[2]]</li>
- <li>[[3]]</li>
- <li>[[4]]</li>
- <li>[[5]]</li>
- <li>[[6]]</li>
- </ul>
-
.. _slicing chapter: http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
.. _Scopes and Variable Behavior: scopes.txt
+.. _builtins: builtins.txt
If you're using Jinja with django and want to use the django filters in Jinja
have a look at the `developer recipies`_ page.
+*new in Jinja 1.1* additionally to the `stringfilter` decorator there is now
+a similar decorator that works exactly the same but does not convert values
+to unicode:
+
+.. sourcecode:: python
+
+ from jinja.filters import simplefilter
+
+ @simplefilter
+ def do_add(value, to_add):
+ return value + to_add
+
.. _designer documentation: builtins.txt
.. _developer recipies: devrecipies.txt
__all__ = ['Environment']
+#: minor speedup
+_getattr = getattr
+
+
class Environment(object):
"""
The Jinja environment.
except (AttributeError, SecurityException):
pass
if obj is self.undefined_singleton:
- return getattr(self.undefined_singleton, name)
+ return _getattr(obj, name)
return self.undefined_singleton
def get_attributes(self, obj, attributes):
args += tuple(dyn_args)
if dyn_kwargs is not None:
kwargs.update(dyn_kwargs)
- if getattr(f, 'jinja_unsafe_call', False) or \
- getattr(f, 'alters_data', False):
+ if _getattr(f, 'jinja_unsafe_call', False) or \
+ _getattr(f, 'alters_data', False):
return self.undefined_singleton
- if getattr(f, 'jinja_context_callable', False):
+ if _getattr(f, 'jinja_context_callable', False):
args = (self, context) + args
return f(*args, **kwargs)
Function call without arguments. Because of the smaller signature and
fewer logic here we have a bit of redundant code.
"""
- if getattr(f, 'jinja_unsafe_call', False) or \
- getattr(f, 'alters_data', False):
+ if _getattr(f, 'jinja_unsafe_call', False) or \
+ _getattr(f, 'alters_data', False):
return self.undefined_singleton
- if getattr(f, 'jinja_context_callable', False):
+ if _getattr(f, 'jinja_context_callable', False):
return f(self, context)
return f()
return u''
elif value is self.undefined_singleton:
return unicode(value)
- elif getattr(value, 'jinja_no_finalization', False):
+ elif _getattr(value, 'jinja_no_finalization', False):
return value
val = self.to_unicode(value)
if self.default_filters:
import re
from random import choice
from urllib import urlencode, quote
-from jinja.utils import urlize, escape, reversed
+from jinja.utils import urlize, escape, reversed, sorted
from jinja.datastructure import TemplateData
from jinja.exceptions import FilterArgumentError
+_striptags_re = re.compile(r'(<!--.*?-->|<[^>]+>)')
+
+
def stringfilter(f):
"""
Decorator for filters that just work on unicode objects.
return decorator
+def simplefilter(f):
+ """
+ Decorator for simplifying filters. Filter arguments are passed
+ to the decorated function without environment and context. The
+ source value is the first argument. (like stringfilter but
+ without unicode conversion)
+ """
+ def decorator(*args):
+ def wrapped(env, context, value):
+ return f(value, *args)
+ return wrapped
+ try:
+ decorator.__doc__ = f.__doc__
+ decorator.__name__ = f.__name__
+ except:
+ pass
+ return decorator
+
+
def do_replace(s, old, new, count=None):
"""
Return a copy of the value with all occurrences of a substring
...
</ul>
- As you can see it automatically appends a space in front of the item
+ As you can see it automatically prepends a space in front of the item
if the filter returned something. You can disable this by passing
`false` as only argument to the filter.
return wrapped
-def do_striptags(value, rex=re.compile(r'<[^>]+>')):
+def do_striptags(value):
"""
Strip SGML/XML tags and replace adjacent whitespace by one space.
*new in Jinja 1.1*
"""
- return ' '.join(rex.sub('', value).split())
+ return ' '.join(_striptags_re.sub('', value).split())
do_striptags = stringfilter(do_striptags)
return wrapped
+def do_sort(reverse=False):
+ """
+ Sort a sequence. Per default it sorts ascending, if you pass it
+ `True` as first argument it will reverse the sorting.
+
+ *new in Jinja 1.1*
+ """
+ def wrapped(env, context, value):
+ return sorted(value, reverse=reverse)
+ return wrapped
+
+
FILTERS = {
'replace': do_replace,
'upper': do_upper,
'batch': do_batch,
'sum': do_sum,
'abs': do_abs,
- 'round': do_round
+ 'round': do_round,
+ 'sort': do_sort
}
#: mapping of unsupported syntax elements.
#: the value represents the feature name that appears
#: in the exception.
- self.unsupported = {
- ast.ListComp: 'list comprehensions'
- }
+ self.unsupported = {ast.ListComp: 'list comprehension'}
#: because of python2.3 compatibility add generator
#: expressions only to the list of unused features
#: if it exists.
if hasattr(ast, 'GenExpr'):
- self.unsupported.update({
- ast.GenExpr: 'generator expressions'
- })
+ self.unsupported[ast.GenExpr] = 'generator expression'
+
+ #: if expressions are unsupported too (so far)
+ if hasattr(ast, 'IfExp'):
+ self.unsupported[ast.IfExp] = 'inline if expression'
# -- public methods
def clear(self):
del self[:]
-# support for a working reversed()
+# support for a working reversed() in 2.3
try:
reversed = reversed
except NameError:
except TypeError:
return iter(tuple(iterable)[::-1])
-# support for python 2.3/2.4
+# set support for python 2.3
try:
set = set
except NameError:
from sets import Set as set
+# sorted support (just a simplified version)
+try:
+ sorted = sorted
+except NameError:
+ def sorted(seq, reverse=False):
+ rv = list(seq)
+ rv.sort(reverse=reverse)
+ return rv
+
+#: function types
+callable_types = (FunctionType, MethodType)
+
+
#: number of maximal range items
MAX_RANGE = 1000000
return _from_string_env.from_string(source)
+#: minor speedup
+_getattr = getattr
+
def get_attribute(obj, name):
"""
Return the attribute from name. Raise either `AttributeError`
"""
if not isinstance(name, basestring):
raise AttributeError(name)
- if name[:2] == name[-2:] == '__' or name[:2] == '::':
+ if name[:2] == name[-2:] == '__':
raise SecurityException('not allowed to access internal attributes')
- if (obj.__class__ is FunctionType and name.startswith('func_') or
- obj.__class__ is MethodType and name.startswith('im_')):
+ if obj.__class__ in callable_types and name.startswith('func_') or \
+ name.startswith('im_'):
raise SecurityException('not allowed to access function attributes')
- r = getattr(obj, 'jinja_allowed_attributes', None)
+ r = _getattr(obj, 'jinja_allowed_attributes', None)
if r is not None and name not in r:
- raise SecurityException('not allowed attribute accessed')
- return getattr(obj, name)
+ raise SecurityException('disallowed attribute accessed')
+ return _getattr(obj, name)
def safe_range(start, stop=None, step=None):
{{ 2.1234|round(2, 'floor') }}|{{ 2.1|round(0, 'ceil') }}'''
XMLATTR = '''{{ {'foo': 42, 'bar': 23, 'fish': none,
'spam': missing, 'blub:blub': '<?>'}|xmlattr }}'''
+SORT = '''{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}'''
+
def test_capitalize(env):
def test_striptags(env):
tmpl = env.from_string(STRIPTAGS)
out = tmpl.render(foo=' <p>just a small \n <a href="#">'
- 'example</a> link</p>\n<p>to a webpage</p>')
+ 'example</a> link</p>\n<p>to a webpage</p> '
+ '<!-- <p>and some commented stuff</p> -->')
assert out == 'just a small example link to a webpage'
assert 'foo="42"' in out
assert 'bar="23"' in out
assert 'blub:blub="<?>"' in out
+
+
+def test_sort(env):
+ tmpl = env.from_string(SORT)
+ assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
{% for item in seq %}{% cycle through %}{% endfor %}'''
SCOPE = '''{% for item in seq %}{% endfor %}{{ item }}'''
VARLEN = '''{% for item in iter %}{{ item }}{% endfor %}'''
+NONITER = '''{% for item in none %}...{% endfor %}'''
def test_simple(env):
tmpl = env.from_string(VARLEN)
output = tmpl.render(iter=inner())
assert output == '01234'
+
+
+def test_noniter(env):
+ tmpl = env.from_string(NONITER)
+ assert not tmpl.render()
{{ test(1, 2, 3) }}\
'''
+SIMPLECALL = '''\
+{% macro test %}[[{{ caller() }}]]{% endmacro %}\
+{% call test() %}data{% endcall %}\
+'''
+
+COMPLEXCALL = '''\
+{% macro test %}[[{{ caller(data='data') }}]]{% endmacro %}\
+{% call test() %}{{ data }}{% endcall %}\
+'''
+
+CALLERUNDEFINED = '''\
+{% set caller = 42 %}\
+{% macro test() %}{{ caller is not defined }}{% endmacro %}\
+{{ test() }}\
+'''
+
def test_simple(env):
tmpl = env.from_string(SIMPLE)
def test_varargs(env):
tmpl = env.from_string(VARARGS)
assert tmpl.render() == '1|2|3'
+
+
+def test_simple_call(env):
+ tmpl = env.from_string(SIMPLECALL)
+ assert tmpl.render() == '[[data]]'
+
+
+def test_complex_call(env):
+ tmpl = env.from_string(COMPLEXCALL)
+ assert tmpl.render() == '[[data]]'
+
+
+def test_caller_undefined(env):
+ tmpl = env.from_string(CALLERUNDEFINED)
+ assert tmpl.render() == 'True'
:license: BSD, see LICENSE for more details.
"""
-KEYWORDS = '''
+KEYWORDS = '''\
{{ with }}
{{ as }}
{{ import }}
{{ yield }}
{{ while }}
{{ pass }}
-{{ finally }}
-'''
-
+{{ finally }}'''
UNPACKING = '''{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}'''
-
RAW = '''{% raw %}{{ FOO }} and {% BAR %}{% endraw %}'''
-
CALL = '''{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}'''
env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
tmpl = env.from_string(CALL)
assert tmpl.render() == 'abdfh'
+
+
+def test_stringfilter(env):
+ from jinja.filters import stringfilter
+ f = stringfilter(lambda f, x: f + x)
+ assert f('42')(env, None, 23) == '2342'
+
+
+def test_simplefilter(env):
+ from jinja.filters import simplefilter
+ f = simplefilter(lambda f, x: f + x)
+ assert f(42)(env, None, 23) == 65