A dict of filters for this environment. As long as no template was
loaded it's safe to add new filters or remove old. For custom filters
- see :ref:`writing-filters`.
+ see :ref:`writing-filters`. For valid filter names have a look at
+ :ref:`identifier-naming`.
.. attribute:: tests
A dict of test functions for this environment. As long as no
template was loaded it's safe to modify this dict. For custom tests
- see :ref:`writing-tests`.
+ see :ref:`writing-tests`. For valid test names have a look at
+ :ref:`identifier-naming`.
.. attribute:: globals
in a template and (if the optimizer is enabled) may not be
overridden by templates. As long as no template was loaded it's safe
to modify this dict. For more details see :ref:`global-namespace`.
+ For valid object names have a look at :ref:`identifier-naming`.
.. automethod:: overlay([options])
:members: disable_buffering, enable_buffering
+.. _identifier-naming:
+
+Notes on Identifiers
+~~~~~~~~~~~~~~~~~~~~
+
+Jinja2 uses the regular Python 2.x naming rules. Valid identifiers have to
+match ``[a-zA-Z_][a-zA-Z0-9_]*``. As a matter of fact non ASCII characters
+are currently not allowed. This limitation will probably go away as soon as
+unicode identifiers are fully specified for Python 3.
+
+Filters and tests are looked up in separate namespaces and have slightly
+modified identifier syntax. Filters and tests may contain dots to group
+filters and tests by topic. For example it's perfectly valid to add a
+function into the filter dict and call it `to.unicode`. The regular
+expression for filter and test identifiers is
+``[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*```.
+
+
Undefined Types
---------------
common use cases. The i18n extension is a good example of why extensions are
useful, another one would be fragment caching.
+When writing extensions you have to keep in mind that you are working with the
+Jinja2 template compiler which does not validate the node tree you are possing
+to it. If the AST is malformed you will get all kinds of compiler or runtime
+errors that are horrible to debug. Always make sure you are using the nodes
+you create correctly. The API documentation below shows which nodes exist and
+how to use them.
+
Example Extension
~~~~~~~~~~~~~~~~~
# the current line number
self.code_lineno = 1
+ # registry of all filters and tests (global, not block local)
+ self.tests = {}
+ self.filters = {}
+
# the debug information
self.debug_info = []
self._write_debug_info = None
visitor = DependencyFinderVisitor()
for node in nodes:
visitor.visit(node)
- for name in visitor.filters:
- self.writeline('f_%s = environment.filters[%r]' % (name, name))
- for name in visitor.tests:
- self.writeline('t_%s = environment.tests[%r]' % (name, name))
+ for dependency in 'filters', 'tests':
+ mapping = getattr(self, dependency)
+ for name in getattr(visitor, dependency):
+ if name not in mapping:
+ mapping[name] = self.temporary_identifier()
+ self.writeline('%s = environment.%s[%r]' %
+ (mapping[name], dependency, name))
def collect_shadowed(self, frame):
"""This function returns all the shadowed variables in a dict
self.visit(node.step, frame)
def visit_Filter(self, node, frame, initial=None):
- self.write('f_%s(' % node.name)
+ self.write(self.filters[node.name] + '(')
func = self.environment.filters.get(node.name)
if func is None:
raise TemplateAssertionError('no filter named %r' % node.name,
self.write(')')
def visit_Test(self, node, frame):
- self.write('t_%s(' % node.name)
+ self.write(self.tests[node.name] + '(')
if node.name not in self.environment.tests:
raise TemplateAssertionError('no test named %r' % node.name,
node.lineno, self.filename)
class ExtensionAttribute(Expr):
"""Returns the attribute of an extension bound to the environment.
The identifier is the identifier of the :class:`Extension`.
+
+ This node is usually constructed by calling the
+ :meth:`~jinja2.ext.Extension.attr` method on an extension.
"""
fields = ('identifier', 'attr')
lineno=token.lineno)
def parse_filter(self, node, start_inline=False):
- lineno = self.stream.current.type
while self.stream.current.type == 'pipe' or start_inline:
if not start_inline:
self.stream.next()
token = self.stream.expect('name')
+ name = token.value
+ while self.stream.current.type is 'dot':
+ self.stream.next()
+ name += '.' + self.stream.expect('name').value
if self.stream.current.type is 'lparen':
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
else:
args = []
kwargs = []
dyn_args = dyn_kwargs = None
- node = nodes.Filter(node, token.value, args, kwargs, dyn_args,
+ node = nodes.Filter(node, name, args, kwargs, dyn_args,
dyn_kwargs, lineno=token.lineno)
start_inline = False
return node
else:
negated = False
name = self.stream.expect('name').value
+ while self.stream.current.type is 'dot':
+ self.stream.next()
+ name += '.' + self.stream.expect('name').value
dyn_args = dyn_kwargs = None
kwargs = []
if self.stream.current.type is 'lparen':