test:
@(cd tests; py.test $(TESTS))
+
+documentation:
+ @(cd docs; ./generate.py)
+
+webpage:
+ @(cd www; ./generate.py)
{% endfilter %}
{% endraw %}
+Reserved Keywords
+=================
+
+Jinja has some keywords you cannot use a variable names. This limitation
+exists to make look coherent. Syntax highlighters won't mess things up and
+you will don't have unexpected output.
+
+The following keywords exist and cannot be used as identifiers:
+
+ `and`, `block`, `cycle`, `elif`, `else`, `endblock`, `endfilter`,
+ `endfor`, `endif`, `endmacro`, `endraw`, `endtrans`, `extends`, `filter`,
+ `for`, `if`, `in`, `include`, `is`, `macro`, `not`, `or`, `pluralize`,
+ `raw`, `recursive`, `set`, `trans`
+
+If you want to use such a name you have to prefix or suffix it or use
+alternative names:
+
+.. sourcecode:: jinja
+
+ {% for macro_ in macros %}
+ {{ macro_('foo') }}
+ {% endfor %}
+
+If future Jinja releases add new keywords those will be "light" keywords which
+means that they won't raise an error for several releases but yield warnings
+on the application side. But it's very unlikely that new keywords will be
+added.
+
Internationalization
====================
ur'or\b', ur'and\b', ur'not\b', ur'in\b', ur'is'
]))
-# set of names that are keywords in python but not in jinja. the lexer
-# appends three trailing underscores, the parser removes them again later
-escaped_names = set(['with', 'as', 'import', 'from', 'class', 'def',
- 'try', 'except', 'exec', 'global', 'assert',
- 'break', 'continue', 'lambda', 'return', 'raise',
- 'yield', 'while', 'pass', 'finally'])
+# set of used keywords
+keywords = set(['and', 'block', 'cycle', 'elif', 'else', 'endblock',
+ 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw',
+ 'endtrans', 'extends', 'filter', 'for', 'if', 'in',
+ 'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw',
+ 'recursive', 'set', 'trans'])
class Failure(object):
`TokenStream` to get real token instances and be able to push tokens
back to the stream. That's for example done by the parser.
- Additionally some names like "class" are escaped
+ Additionally non keywords are escaped.
"""
def filter():
for lineno, token, value in self.tokeniter(source):
- if token == 'name' and value in escaped_names:
- value += '___'
+ if token == 'name' and value not in keywords:
+ value += '_'
yield lineno, token, value
return TokenStream(filter())
try:
cache_time = path.getmtime(cache_fn)
except OSError:
- cache_time = 0
- if last_change >= cache_time:
+ cache_time = -1
+ if cache_time == -1 or last_change >= cache_time:
f = file(cache_fn, 'rb')
try:
tmpl = Template.load(environment, f)
filename = get_template_filename(self.searchpath, name)
if path.exists(filename):
return path.getmtime(filename)
- return 0
+ return -1
class PackageLoader(CachedLoaderMixin, BaseLoader):
[p for p in name.split('/') if p and p[0] != '.']))
if resource_exists(self.package_name, fn):
return path.getmtime(fn)
- return 0
+ return -1
class FunctionLoader(CachedLoaderMixin, BaseLoader):
value is None it's considered missing.
``getmtime_func`` Function used to check if templates requires
reloading. Has to return the UNIX timestamp of
- the last template change or 0 if this template
+ the last template change or ``-1`` if this template
does not exist or requires updates at any cost.
``use_memcache`` Set this to ``True`` to enable memory caching.
This is usually a good idea in production mode,
jinja.nodes
~~~~~~~~~~~
- Additional nodes for jinja. Look like nodes from the ast.
+ Additional nodes for Jinja. Look like nodes from the ast.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
class Node(ast.Node):
"""
- jinja node.
+ Jinja node.
"""
def get_items(self):
self.filename = filename
def get_items(self):
- if self.extends is not None:
- return [self.extends]
- return []
+ return self.extends is not None and [self.extends] or []
def __repr__(self):
return 'Template(%r, %r, %r)' % (
self.filters = filters
def get_items(self):
- return [self.body, self.filters]
+ return [self.body] + list(self.filters)
def __repr__(self):
- return 'Filter(%r)' % (
+ return 'Filter(%r, %r)' % (
self.body,
self.filters
)
from compiler import ast, parse
from compiler.misc import set_filename
from jinja import nodes
-from jinja.lexer import escaped_names
from jinja.datastructure import TokenStream
from jinja.exceptions import TemplateSyntaxError
try:
else_ = None
self.close_remaining_block()
- return nodes.ForLoop(lineno, ast.assign, ast.list, body, else_, bool(recursive))
+ return nodes.ForLoop(lineno, ast.assign, ast.list, body, else_,
+ bool(recursive))
def handle_if_directive(self, lineno, gen):
"""
except (StopIteration, ValueError):
raise TemplateSyntaxError('invalid syntax for set', lineno)
ast = self.parse_python(lineno, gen, '(%s)')
- return nodes.Set(lineno, str(name[2]), ast.expr)
+ # disallow keywords
+ if not name[2].endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as identifier in set statement.' %
+ name[2], lineno)
+ return nodes.Set(lineno, str(name[2][:-1]), ast.expr)
def handle_filter_directive(self, lineno, gen):
"""
if macro_name[1] != 'name':
raise TemplateSyntaxError('expected \'name\', got %r' %
macro_name[1], lineno)
- ast = self.parse_python(lineno, gen, 'def %s(%%s):pass' % str(macro_name[2]))
+ # disallow keywords as identifiers
+ elif not macro_name[2].endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as macro name.' % macro_name[2],
+ lineno)
+
+ ast = self.parse_python(lineno, gen, 'def %s(%%s):pass' %
+ str(macro_name[2][:-1]))
body = self.subparse(end_of_macro, True)
self.close_remaining_block()
'not allowed.', lineno)
if ast.argnames:
defaults = [None] * (len(ast.argnames) - len(ast.defaults)) + ast.defaults
- args = zip(ast.argnames, defaults)
+ args = []
+ for idx, argname in enumerate(ast.argnames):
+ # disallow keywords as argument names
+ if not argname.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as macro argument.' % argname,
+ lineno)
+ args.append((argname[:-1], defaults[idx]))
else:
args = None
return nodes.Macro(lineno, ast.name, args, body)
if block_name[1] != 'name':
raise TemplateSyntaxError('expected \'name\', got %r' %
block_name[1], lineno)
+ # disallow keywords
+ if not block_name[2].endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as block name.' % block_name[2],
+ lineno)
+ name = block_name[2][:-1]
if tokens:
raise TemplateSyntaxError('block got too many arguments, '
'requires one.', lineno)
# check if this block does not exist by now.
- if block_name[2] in self.blocks:
+ if name in self.blocks:
raise TemplateSyntaxError('block %r defined twice' %
- block_name[2], lineno)
- self.blocks.add(block_name[2])
+ name, lineno)
+ self.blocks.add(name)
# now parse the body and attach it to the block
body = self.subparse(end_of_block_tag, True)
self.close_remaining_block()
- return nodes.Block(lineno, block_name[2], body)
+ return nodes.Block(lineno, name, body)
def handle_extends_directive(self, lineno, gen):
"""
if arg.__class__ is not ast.Keyword:
raise TemplateSyntaxError('translation tags need explicit '
'names for values.', lineno)
+ # argument name doesn't end with "_"? that's a keyword then
+ if not arg.name.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as identifier.' % arg.name,
+ lineno)
+ # remove the last "_" before writing
if first_var is None:
- first_var = arg.name
- replacements[arg.name] = arg.expr
+ first_var = arg.name[:-1]
+ replacements[arg.name[:-1]] = arg.expr
# look for endtrans/pluralize
buf = singular = []
# nested variables
elif token == 'variable_begin':
_, variable_token, variable_name = self.tokenstream.next()
- if variable_token != 'name' or variable_name not in replacements:
+ if variable_token != 'name':
+ raise TemplateSyntaxError('can only use variable not '
+ 'constants or expressions '
+ 'in translation variable '
+ 'blocks.', lineno)
+ # plural name without trailing "_"? that's a keyword
+ if not variable_name.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword '
+ '%r as identifier in trans '
+ 'block.' % variable_name, lineno)
+ variable_name = variable_name[:-1]
+ if variable_name not in replacements:
raise TemplateSyntaxError('unregistered translation '
'variable %r.' % variable_name,
lineno)
if plural_token == 'block_end':
indicator = first_var
elif plural_token == 'name':
+ # plural name without trailing "_"? that's a keyword
+ if not plural_name.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword '
+ '%r as identifier.' %
+ plural_name, lineno)
+ plural_name = plural_name[:-1]
if plural_name not in replacements:
raise TemplateSyntaxError('unknown tranlsation '
'variable %r' %
todo = [body]
while todo:
node = todo.pop()
- if node.__class__ in (ast.AssName, ast.Name) and \
- node.name.endswith('___') and node.name[:-3] in escaped_names:
- node.name = node.name[:-3]
+ # all names excluding keywords have an trailing underline.
+ # if we find a name without trailing underline that's a keyword
+ # and this code raises an error. else strip the underline again
+ if node.__class__ in (ast.AssName, ast.Name):
+ if not node.name.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as identifier.' % node.name,
+ node.lineno)
+ node.name = node.name[:-1]
+ elif node.__class__ is ast.Getattr:
+ if not node.attrname.endswith('_'):
+ raise TemplateSyntaxError('illegal use of keyword %r '
+ 'as attribute name.' % node.name)
+ node.attrname = node.attrname[:-1]
node.filename = self.filename
todo.extend(node.getChildNodes())
return nodes.Template(self.filename, body, self.extends)
:license: BSD, see LICENSE for more details.
"""
from jinja.environment import Environment
-from jinja.loaders import FunctionLoader
+from jinja.loaders import FunctionLoader, FileSystemLoader, PackageLoader
from jinja.exceptions import TemplateNotFound
``environment`` If this is provided it must be the only
configuration value and it's used as jinja
environment.
+ ``searchpath`` If provided a new file system loader with this
+ search path is instanciated.
+ ``package`` Name of the python package containing the
+ templates. If this and ``package_path`` is
+ defined a `PackageLoader` is used.
+ ``package_path`` Path to the templates inside of a package.
``loader_func`` Function that takes the name of the template to
load. If it returns a string or unicode object
it's used to load a template. If the return
memcache_size = options.pop('memcache_size', 40)
cache_folder = options.pop('cache_folder', None)
auto_reload = options.pop('auto_reload', True)
- if loader_func is not None:
+ if 'searchpath' in options:
+ options['loader'] = FileSystemLoader(options.pop('searchpath'),
+ use_memcache, memcache_size,
+ cache_folder, auto_reload)
+ elif 'package' in options:
+ options['loader'] = PackageLoader(options.pop('package'),
+ options.pop('package_path', ''),
+ use_memcache, memcache_size,
+ cache_folder, auto_reload)
+ elif loader_func is not None:
options['loader'] = FunctionLoader(loader_func, getmtime_func,
use_memcache, memcache_size,
cache_folder, auto_reload)
tmpl = env.get_template(template)
except TemplateNotFound:
return
- return (tmpl.render(**values),)
+ return tmpl.render(**values)
return render_function
write('ctx_push()')
nodeinfo = self.nodeinfo(node.body)
if nodeinfo:
- write(nodebody)
+ write(nodeinfo)
buf.append(self.handle_node(node.body))
write('ctx_pop()')
return '\n'.join(buf)
],
keywords = ['python.templating.engines'],
packages = ['jinja', 'jinja.translators'],
- extras_require = {'plugin': ['setuptools>=0.6a2']}
+ extras_require = {'plugin': ['setuptools>=0.6a2']},
)
filesystem_loader = loaders.FileSystemLoader('loaderres/templates')
+function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get)
+
choice_loader = loaders.ChoiceLoader([dict_loader, package_loader])
pass
else:
raise AssertionError('expected template exception')
+
+
+def test_function_loader():
+ env = Environment(loader=function_loader)
+ tmpl = env.get_template('justfunction.html')
+ assert tmpl.render().strip() == 'FOO'
+ try:
+ env.get_template('missing.html')
+ except TemplateNotFound:
+ pass
+ else:
+ raise AssertionError('expected template exception')