added support for dotted names in tests and filters

--HG--
branch : trunk
This commit is contained in:
Armin Ronacher 2008-05-10 23:36:28 +02:00
parent 09c002e6fa
commit b9e7875e43
5 changed files with 54 additions and 10 deletions

View File

@ -68,13 +68,15 @@ High Level API
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
@ -82,6 +84,7 @@ High Level API
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])
@ -111,6 +114,24 @@ High Level API
: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
---------------

View File

@ -102,6 +102,13 @@ task and usually not needed as the default tags and expressions cover all
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
~~~~~~~~~~~~~~~~~

View File

@ -325,6 +325,10 @@ class CodeGenerator(NodeVisitor):
# 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
@ -473,10 +477,13 @@ class CodeGenerator(NodeVisitor):
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
@ -1215,7 +1222,7 @@ class CodeGenerator(NodeVisitor):
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,
@ -1234,7 +1241,7 @@ class CodeGenerator(NodeVisitor):
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)

View File

@ -727,6 +727,9 @@ class EnvironmentAttribute(Expr):
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')

View File

@ -676,18 +676,21 @@ class Parser(object):
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
@ -700,6 +703,9 @@ class Parser(object):
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':