mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-27 01:10:36 +00:00
there is now a workaround in the compiler that makes sure it's possible to call things with python keywords. {{ foo(class=42) }} works again
--HG-- branch : trunk
This commit is contained in:
parent
de6bf71e8f
commit
2feed1d5e2
@ -1,12 +1,11 @@
|
||||
"""
|
||||
This benchmark compares some python templating engines with Jinja 2 so
|
||||
that we get a picture of how fast Jinja 2 is for a semi real world
|
||||
template. If a template engine is not installed the test is skipped.
|
||||
"""
|
||||
import sys
|
||||
from django.conf import settings
|
||||
settings.configure()
|
||||
from django.template import Template as DjangoTemplate, Context as DjangoContext
|
||||
from jinja2 import Environment as JinjaEnvironment
|
||||
from mako.template import Template as MakoTemplate
|
||||
from genshi.template import MarkupTemplate as GenshiTemplate
|
||||
from Cheetah.Template import Template as CheetahTemplate
|
||||
from timeit import Timer
|
||||
from jinja2 import Environment as JinjaEnvironment
|
||||
|
||||
context = {
|
||||
'page_title': 'mitsuhiko\'s benchmark',
|
||||
@ -51,7 +50,17 @@ jinja_template = JinjaEnvironment(
|
||||
</html>\
|
||||
""")
|
||||
|
||||
django_template = DjangoTemplate("""\
|
||||
def test_jinja():
|
||||
jinja_template.render(context)
|
||||
|
||||
try:
|
||||
from django.conf import settings
|
||||
settings.configure()
|
||||
from django.template import Template as DjangoTemplate, Context as DjangoContext
|
||||
except ImportError:
|
||||
test_django = None
|
||||
else:
|
||||
django_template = DjangoTemplate("""\
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
@ -81,7 +90,18 @@ django_template = DjangoTemplate("""\
|
||||
</html>\
|
||||
""")
|
||||
|
||||
mako_template = MakoTemplate("""\
|
||||
def test_django():
|
||||
c = DjangoContext(context)
|
||||
c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'),
|
||||
('products.html', 'Products')]
|
||||
django_template.render(c)
|
||||
|
||||
try:
|
||||
from mako.template import Template as MakoTemplate
|
||||
except ImportError:
|
||||
test_mako = None
|
||||
else:
|
||||
mako_template = MakoTemplate("""\
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
@ -111,7 +131,15 @@ mako_template = MakoTemplate("""\
|
||||
</html>\
|
||||
""")
|
||||
|
||||
genshi_template = GenshiTemplate("""\
|
||||
def test_mako():
|
||||
mako_template.render(**context)
|
||||
|
||||
try:
|
||||
from genshi.template import MarkupTemplate as GenshiTemplate
|
||||
except ImportError:
|
||||
test_genshi = None
|
||||
else:
|
||||
genshi_template = GenshiTemplate("""\
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">
|
||||
<head>
|
||||
<title>${page_title}</title>
|
||||
@ -137,7 +165,15 @@ genshi_template = GenshiTemplate("""\
|
||||
</html>\
|
||||
""")
|
||||
|
||||
cheetah_template = CheetahTemplate("""\
|
||||
def test_genshi():
|
||||
genshi_template.generate(**context).render('html', strip_whitespace=False)
|
||||
|
||||
try:
|
||||
from Cheetah.Template import Template as CheetahTemplate
|
||||
except ImportError:
|
||||
test_cheetah = None
|
||||
else:
|
||||
cheetah_template = CheetahTemplate("""\
|
||||
#import cgi
|
||||
<!doctype html>
|
||||
<html>
|
||||
@ -168,32 +204,63 @@ cheetah_template = CheetahTemplate("""\
|
||||
</html>\
|
||||
""", searchList=[dict(context)])
|
||||
|
||||
def test_jinja():
|
||||
jinja_template.render(context)
|
||||
def test_cheetah():
|
||||
unicode(cheetah_template)
|
||||
|
||||
def test_django():
|
||||
c = DjangoContext(context)
|
||||
c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]
|
||||
django_template.render(c)
|
||||
try:
|
||||
import tenjin
|
||||
except ImportError:
|
||||
test_tenjin = None
|
||||
else:
|
||||
tenjin_template = tenjin.Template()
|
||||
tenjin_template.convert("""\
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${page_title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>${page_title}</h1>
|
||||
</div>
|
||||
<ul class="navigation">
|
||||
<?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?>
|
||||
<li><a href="${href}">${caption}</a></li>
|
||||
<?py #end ?>
|
||||
</ul>
|
||||
<div class="table">
|
||||
<table>
|
||||
<?py for row in table: ?>
|
||||
<tr>
|
||||
<?py for cell in row: ?>
|
||||
<td>#{cell}</td>
|
||||
<?py #end ?>
|
||||
</tr>
|
||||
<?py #end ?>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>\
|
||||
""")
|
||||
|
||||
def test_mako():
|
||||
mako_template.render(**context)
|
||||
def test_tenjin():
|
||||
from tenjin.helpers import escape, to_str
|
||||
tenjin_template.render(context, locals())
|
||||
|
||||
def test_genshi():
|
||||
genshi_template.generate(**context).render('html', strip_whitespace=False)
|
||||
|
||||
def test_cheetah():
|
||||
unicode(cheetah_template)
|
||||
|
||||
sys.stdout.write('\r%s\n%s\n%s\n' % (
|
||||
sys.stdout.write('\r' + '\n'.join((
|
||||
'=' * 80,
|
||||
'Template Engine BigTable Benchmark'.center(80),
|
||||
'-' * 80,
|
||||
__doc__,
|
||||
'-' * 80
|
||||
))
|
||||
for test in 'jinja', 'mako', 'django', 'genshi', 'cheetah':
|
||||
)) + '\n')
|
||||
for test in 'jinja', 'tenjin', 'mako', 'django', 'genshi', 'cheetah':
|
||||
if locals()['test_' + test] is None:
|
||||
sys.stdout.write(' %-20s*not installed*\n' % test)
|
||||
continue
|
||||
t = Timer(setup='from __main__ import test_%s as bench' % test,
|
||||
stmt='bench()')
|
||||
sys.stdout.write('> %-20s<running>' % test)
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write('\r %-20s%.4f ms\n' % (test, t.timeit(number=100) / 100))
|
||||
sys.stdout.write('\r %-20s%.4f ms\n' % (test, t.timeit(number=20) / 20))
|
||||
sys.stdout.write('=' * 80 + '\n')
|
||||
|
@ -10,7 +10,9 @@
|
||||
"""
|
||||
from copy import copy
|
||||
from random import randrange
|
||||
from keyword import iskeyword
|
||||
from cStringIO import StringIO
|
||||
from itertools import chain
|
||||
from jinja2 import nodes
|
||||
from jinja2.visitor import NodeVisitor, NodeTransformer
|
||||
from jinja2.exceptions import TemplateAssertionError
|
||||
@ -163,14 +165,19 @@ class Frame(object):
|
||||
rv.name_overrides = self.name_overrides.copy()
|
||||
return rv
|
||||
|
||||
def inspect(self, nodes, hard_scope=False):
|
||||
"""Walk the node and check for identifiers. If the scope
|
||||
is hard (eg: enforce on a python level) overrides from outer
|
||||
scopes are tracked differently.
|
||||
def inspect(self, nodes, with_depenencies=False, hard_scope=False):
|
||||
"""Walk the node and check for identifiers. If the scope is hard (eg:
|
||||
enforce on a python level) overrides from outer scopes are tracked
|
||||
differently.
|
||||
|
||||
Per default filters and tests (dependencies) are not tracked. That's
|
||||
the case because filters and tests are absolutely immutable and so we
|
||||
can savely use them in closures too. The `Template` and `Block`
|
||||
visitor visits the frame with dependencies to collect them.
|
||||
"""
|
||||
visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
|
||||
for node in nodes:
|
||||
visitor.visit(node)
|
||||
visitor.visit(node, True, with_depenencies)
|
||||
|
||||
def inner(self):
|
||||
"""Return an inner frame."""
|
||||
@ -193,41 +200,63 @@ class FrameIdentifierVisitor(NodeVisitor):
|
||||
self.identifiers = identifiers
|
||||
self.hard_scope = hard_scope
|
||||
|
||||
def visit_Name(self, node):
|
||||
def visit_Name(self, node, visit_ident, visit_deps):
|
||||
"""All assignments to names go through this function."""
|
||||
if node.ctx in ('store', 'param'):
|
||||
self.identifiers.declared_locally.add(node.name)
|
||||
elif node.ctx == 'load':
|
||||
if not self.identifiers.is_declared(node.name, self.hard_scope):
|
||||
if visit_ident:
|
||||
if node.ctx in ('store', 'param'):
|
||||
self.identifiers.declared_locally.add(node.name)
|
||||
elif node.ctx == 'load' and not \
|
||||
self.identifiers.is_declared(node.name, self.hard_scope):
|
||||
self.identifiers.undeclared.add(node.name)
|
||||
|
||||
def visit_Filter(self, node):
|
||||
self.generic_visit(node)
|
||||
self.identifiers.filters.add(node.name)
|
||||
def visit_Filter(self, node, visit_ident, visit_deps):
|
||||
if visit_deps:
|
||||
self.generic_visit(node, visit_ident, True)
|
||||
self.identifiers.filters.add(node.name)
|
||||
|
||||
def visit_Test(self, node):
|
||||
self.generic_visit(node)
|
||||
self.identifiers.tests.add(node.name)
|
||||
def visit_Test(self, node, visit_ident, visit_deps):
|
||||
if visit_deps:
|
||||
self.generic_visit(node, visit_ident, True)
|
||||
self.identifiers.tests.add(node.name)
|
||||
|
||||
def visit_Macro(self, node):
|
||||
self.identifiers.declared_locally.add(node.name)
|
||||
def visit_Macro(self, node, visit_ident, visit_deps):
|
||||
if visit_ident:
|
||||
self.identifiers.declared_locally.add(node.name)
|
||||
|
||||
def visit_Import(self, node):
|
||||
self.generic_visit(node)
|
||||
self.identifiers.declared_locally.add(node.target)
|
||||
def visit_Import(self, node, visit_ident, visit_deps):
|
||||
if visit_ident:
|
||||
self.generic_visit(node, True, visit_deps)
|
||||
self.identifiers.declared_locally.add(node.target)
|
||||
|
||||
def visit_FromImport(self, node):
|
||||
self.generic_visit(node)
|
||||
self.identifiers.declared_locally.update(node.names)
|
||||
def visit_FromImport(self, node, visit_ident, visit_deps):
|
||||
if visit_ident:
|
||||
self.generic_visit(node, True, visit_deps)
|
||||
for name in node.names:
|
||||
if isinstance(name, tuple):
|
||||
self.identifiers.declared_locally.add(name[1])
|
||||
else:
|
||||
self.identifiers.declared_locally.add(name)
|
||||
|
||||
def visit_Assign(self, node):
|
||||
def visit_Assign(self, node, visit_ident, visit_deps):
|
||||
"""Visit assignments in the correct order."""
|
||||
self.visit(node.node)
|
||||
self.visit(node.target)
|
||||
self.visit(node.node, visit_ident, visit_deps)
|
||||
self.visit(node.target, visit_ident, visit_deps)
|
||||
|
||||
# stop traversing at instructions that have their own scope.
|
||||
visit_Block = visit_CallBlock = visit_FilterBlock = \
|
||||
visit_For = lambda s, n: None
|
||||
def visit_For(self, node, visit_ident, visit_deps):
|
||||
"""Visiting stops at for blocks. However the block sequence
|
||||
is visited as part of the outer scope.
|
||||
"""
|
||||
if visit_ident:
|
||||
self.visit(node.iter, True, visit_deps)
|
||||
if visit_deps:
|
||||
for child in node.iter_child_nodes(exclude=('iter',)):
|
||||
self.visit(child, False, True)
|
||||
|
||||
def ident_stop(self, node, visit_ident, visit_deps):
|
||||
if visit_deps:
|
||||
self.generic_visit(node, False, True)
|
||||
visit_CallBlock = visit_FilterBlock = ident_stop
|
||||
visit_Block = lambda s, n, a, b: None
|
||||
|
||||
|
||||
class CompilerExit(Exception):
|
||||
@ -344,10 +373,10 @@ class CodeGenerator(NodeVisitor):
|
||||
def signature(self, node, frame, have_comma=True, extra_kwargs=None):
|
||||
"""Writes a function call to the stream for the current node.
|
||||
Per default it will write a leading comma but this can be
|
||||
disabled by setting have_comma to False. If extra_kwargs is
|
||||
given it must be a string that represents a single keyword
|
||||
argument call that is inserted at the end of the regular
|
||||
keyword argument calls.
|
||||
disabled by setting have_comma to False. The extra keyword
|
||||
arguments may not include python keywords otherwise a syntax
|
||||
error could occour. The extra keyword arguments should be given
|
||||
as python dict.
|
||||
"""
|
||||
have_comma = have_comma and [True] or []
|
||||
def touch_comma():
|
||||
@ -356,20 +385,53 @@ class CodeGenerator(NodeVisitor):
|
||||
else:
|
||||
have_comma.append(True)
|
||||
|
||||
# if any of the given keyword arguments is a python keyword
|
||||
# we have to make sure that no invalid call is created.
|
||||
kwarg_workaround = False
|
||||
for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
|
||||
if iskeyword(kwarg):
|
||||
kwarg_workaround = True
|
||||
break
|
||||
|
||||
for arg in node.args:
|
||||
touch_comma()
|
||||
self.visit(arg, frame)
|
||||
for kwarg in node.kwargs:
|
||||
touch_comma()
|
||||
self.visit(kwarg, frame)
|
||||
if extra_kwargs is not None:
|
||||
touch_comma()
|
||||
self.write(extra_kwargs)
|
||||
|
||||
if not kwarg_workaround:
|
||||
for kwarg in node.kwargs:
|
||||
touch_comma()
|
||||
self.visit(kwarg, frame)
|
||||
if extra_kwargs is not None:
|
||||
for key, value in extra_kwargs.iteritems():
|
||||
touch_comma()
|
||||
self.write('%s=%s' % (key, value))
|
||||
if node.dyn_args:
|
||||
touch_comma()
|
||||
self.write('*')
|
||||
self.visit(node.dyn_args, frame)
|
||||
if node.dyn_kwargs:
|
||||
|
||||
if kwarg_workaround:
|
||||
touch_comma()
|
||||
if node.dyn_kwargs is not None:
|
||||
self.write('**dict({')
|
||||
else:
|
||||
self.write('**{')
|
||||
for kwarg in node.kwargs:
|
||||
self.write('%r: ' % kwarg.key)
|
||||
self.visit(kwarg.value, frame)
|
||||
self.write(', ')
|
||||
if extra_kwargs is not None:
|
||||
for key, value in extra_kwargs.iteritems():
|
||||
touch_comma()
|
||||
self.write('%r: %s, ' % (key, value))
|
||||
if node.dyn_kwargs is not None:
|
||||
self.write('}, **')
|
||||
self.visit(node.dyn_kwargs, frame)
|
||||
self.write(')')
|
||||
else:
|
||||
self.write('}')
|
||||
|
||||
elif node.dyn_kwargs is not None:
|
||||
touch_comma()
|
||||
self.write('**')
|
||||
self.visit(node.dyn_kwargs, frame)
|
||||
@ -448,6 +510,10 @@ class CodeGenerator(NodeVisitor):
|
||||
func_frame.accesses_caller = False
|
||||
func_frame.arguments = args = ['l_' + x.name for x in node.args]
|
||||
|
||||
if 'caller' in func_frame.identifiers.undeclared:
|
||||
func_frame.accesses_caller = True
|
||||
func_frame.identifiers.add_special('caller')
|
||||
args.append('l_caller')
|
||||
if 'kwargs' in func_frame.identifiers.undeclared:
|
||||
func_frame.accesses_kwargs = True
|
||||
func_frame.identifiers.add_special('kwargs')
|
||||
@ -456,17 +522,14 @@ class CodeGenerator(NodeVisitor):
|
||||
func_frame.accesses_varargs = True
|
||||
func_frame.identifiers.add_special('varargs')
|
||||
args.append('l_varargs')
|
||||
if 'caller' in func_frame.identifiers.undeclared:
|
||||
func_frame.accesses_caller = True
|
||||
func_frame.identifiers.add_special('caller')
|
||||
args.append('l_caller')
|
||||
return func_frame
|
||||
|
||||
# -- Visitors
|
||||
|
||||
def visit_Template(self, node, frame=None):
|
||||
assert frame is None, 'no root frame allowed'
|
||||
self.writeline('from jinja2.runtime import *')
|
||||
from jinja2.runtime import __all__ as exported
|
||||
self.writeline('from jinja2.runtime import ' + ', '.join(exported))
|
||||
self.writeline('name = %r' % self.name)
|
||||
|
||||
# do we have an extends tag at all? If not, we can save some
|
||||
@ -491,7 +554,7 @@ class CodeGenerator(NodeVisitor):
|
||||
|
||||
# process the root
|
||||
frame = Frame()
|
||||
frame.inspect(node.body)
|
||||
frame.inspect(node.body, with_depenencies=True)
|
||||
frame.toplevel = frame.rootlevel = True
|
||||
self.indent()
|
||||
self.pull_locals(frame, indent=False)
|
||||
@ -513,7 +576,7 @@ class CodeGenerator(NodeVisitor):
|
||||
# at this point we now have the blocks collected and can visit them too.
|
||||
for name, block in self.blocks.iteritems():
|
||||
block_frame = Frame()
|
||||
block_frame.inspect(block.body)
|
||||
block_frame.inspect(block.body, with_depenencies=True)
|
||||
block_frame.block = name
|
||||
block_frame.identifiers.add_special('super')
|
||||
block_frame.name_overrides['super'] = 'context.super(%r, ' \
|
||||
@ -627,21 +690,25 @@ class CodeGenerator(NodeVisitor):
|
||||
self.visit(node.template, frame)
|
||||
self.write(', %r).include(context)' % self.name)
|
||||
for name in node.names:
|
||||
if isinstance(name, tuple):
|
||||
name, alias = name
|
||||
else:
|
||||
alias = name
|
||||
self.writeline('l_%s = getattr(included_template, '
|
||||
'%r, missing)' % (name, name))
|
||||
self.writeline('if l_%s is missing:' % name)
|
||||
'%r, missing)' % (alias, name))
|
||||
self.writeline('if l_%s is missing:' % alias)
|
||||
self.indent()
|
||||
self.writeline('l_%s = environment.undefined(%r %% '
|
||||
'included_template.name)' %
|
||||
(name, 'the template %r does not export '
|
||||
(alias, 'the template %r does not export '
|
||||
'the requested name ' + repr(name)))
|
||||
self.outdent()
|
||||
if frame.toplevel:
|
||||
self.writeline('context[%r] = l_%s' % (name, name))
|
||||
self.writeline('context[%r] = l_%s' % (alias, alias))
|
||||
|
||||
def visit_For(self, node, frame):
|
||||
loop_frame = frame.inner()
|
||||
loop_frame.inspect(node.iter_child_nodes())
|
||||
loop_frame.inspect(node.iter_child_nodes(exclude=('iter',)))
|
||||
extended_loop = bool(node.else_) or \
|
||||
'loop' in loop_frame.identifiers.undeclared
|
||||
if extended_loop:
|
||||
@ -774,7 +841,8 @@ class CodeGenerator(NodeVisitor):
|
||||
self.writeline('yield ', node)
|
||||
else:
|
||||
self.writeline('%s.append(' % frame.buffer, node)
|
||||
self.visit_Call(node.call, call_frame, extra_kwargs='caller=caller')
|
||||
self.visit_Call(node.call, call_frame,
|
||||
extra_kwargs={'caller': 'caller'})
|
||||
if frame.buffer is not None:
|
||||
self.write(')')
|
||||
|
||||
|
@ -395,21 +395,20 @@ class IncludedTemplate(object):
|
||||
"""Represents an included template."""
|
||||
|
||||
def __init__(self, template, context):
|
||||
body = Markup(concat(template.root_render_func(context)))
|
||||
self._body_stream = tuple(template.root_render_func(context))
|
||||
self.__dict__.update(context.get_exported())
|
||||
self._name = template.name
|
||||
self._rendered_body = body
|
||||
self.__name__ = template.name
|
||||
|
||||
__html__ = lambda x: x._rendered_body
|
||||
__unicode__ = lambda x: unicode(x._rendered_body)
|
||||
__html__ = lambda x: Markup(concat(x._body_stream))
|
||||
__unicode__ = lambda x: unicode(concat(x._body_stream))
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self._rendered_body).encode('utf-8')
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (
|
||||
self.__class__.__name__,
|
||||
self._name
|
||||
self.__name__
|
||||
)
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
from collections import deque
|
||||
from jinja2 import nodes
|
||||
from jinja2.environment import get_spontaneous_environment
|
||||
from jinja2.runtime import Undefined
|
||||
from jinja2.runtime import Undefined, concat
|
||||
from jinja2.parser import statement_end_tokens
|
||||
from jinja2.exceptions import TemplateAssertionError
|
||||
from jinja2.utils import import_string
|
||||
@ -190,7 +190,7 @@ class TransExtension(Extension):
|
||||
else:
|
||||
assert False, 'internal parser error'
|
||||
|
||||
return referenced, u''.join(buf)
|
||||
return referenced, concat(buf)
|
||||
|
||||
def _make_node(self, singular, plural, variables, plural_expr):
|
||||
"""Generates a useful node from the data provided."""
|
||||
|
@ -92,17 +92,18 @@ class Node(object):
|
||||
raise TypeError('unknown keyword argument %r' %
|
||||
iter(kw).next())
|
||||
|
||||
def iter_fields(self):
|
||||
def iter_fields(self, exclude=()):
|
||||
"""Iterate over all fields."""
|
||||
for name in self.fields:
|
||||
try:
|
||||
yield name, getattr(self, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
if name not in exclude:
|
||||
try:
|
||||
yield name, getattr(self, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def iter_child_nodes(self):
|
||||
def iter_child_nodes(self, exclude=()):
|
||||
"""Iterate over all child nodes."""
|
||||
for field, item in self.iter_fields():
|
||||
for field, item in self.iter_fields(exclude):
|
||||
if isinstance(item, list):
|
||||
for n in item:
|
||||
if isinstance(n, Node):
|
||||
@ -243,7 +244,7 @@ class Macro(Stmt):
|
||||
|
||||
class CallBlock(Stmt):
|
||||
"""A node that represents am extended macro call."""
|
||||
fields = ('call', 'args', 'defaults', 'body')
|
||||
fields = ('call', 'body')
|
||||
|
||||
|
||||
class Set(Stmt):
|
||||
@ -279,6 +280,8 @@ class FromImport(Stmt):
|
||||
start with double underscores (which the parser asserts) this is not a
|
||||
problem for regular Jinja code, but if this node is used in an extension
|
||||
extra care must be taken.
|
||||
|
||||
The list of names may contain tuples if aliases are wanted.
|
||||
"""
|
||||
fields = ('template', 'names')
|
||||
|
||||
|
@ -13,10 +13,10 @@ from jinja2 import nodes
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
|
||||
|
||||
statement_end_tokens = set(['variable_end', 'block_end', 'in'])
|
||||
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
|
||||
'macro', 'include', 'from', 'import'])
|
||||
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
|
||||
statement_end_tokens = set(['variable_end', 'block_end', 'in'])
|
||||
_tuple_edge_tokens = set(['rparen']) | statement_end_tokens
|
||||
|
||||
|
||||
@ -178,8 +178,17 @@ class Parser(object):
|
||||
'underscores can not be '
|
||||
'imported', target.lineno,
|
||||
self.filename)
|
||||
node.names.append(target.name)
|
||||
self.stream.next()
|
||||
if self.stream.current.test('name:as'):
|
||||
self.stream.next()
|
||||
alias = self.stream.expect('name')
|
||||
if not nodes.Name(alias.value, 'store').can_assign():
|
||||
raise TemplateSyntaxError('can\'t name imported '
|
||||
'object %r.' % alias.value,
|
||||
alias.lineno, self.filename)
|
||||
node.names.append((target.name, alias.value))
|
||||
else:
|
||||
node.names.append(target.name)
|
||||
if self.stream.current.type is not 'comma':
|
||||
break
|
||||
else:
|
||||
|
@ -9,12 +9,14 @@
|
||||
:license: GNU GPL.
|
||||
"""
|
||||
from types import FunctionType
|
||||
from itertools import izip
|
||||
from jinja2.utils import Markup, partial
|
||||
from jinja2.exceptions import UndefinedError
|
||||
|
||||
|
||||
# these variables are exported to the template runtime
|
||||
__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
|
||||
'Macro', 'Markup', 'missing', 'concat']
|
||||
'Macro', 'Markup', 'missing', 'concat', 'izip']
|
||||
|
||||
|
||||
# special singleton representing missing values for the runtime
|
||||
@ -34,18 +36,18 @@ class TemplateContext(object):
|
||||
|
||||
def __init__(self, environment, parent, name, blocks):
|
||||
self.parent = parent
|
||||
self.vars = {}
|
||||
self.vars = vars = {}
|
||||
self.environment = environment
|
||||
self.exported_vars = set()
|
||||
self.name = name
|
||||
|
||||
# bind functions to the context of environment if required
|
||||
for name, obj in self.parent.iteritems():
|
||||
for name, obj in parent.iteritems():
|
||||
if type(obj) is FunctionType:
|
||||
if getattr(obj, 'contextfunction', 0):
|
||||
self.vars[name] = partial(obj, self)
|
||||
vars[name] = partial(obj, self)
|
||||
elif getattr(obj, 'environmentfunction', 0):
|
||||
self.vars[name] = partial(obj, environment)
|
||||
vars[name] = partial(obj, environment)
|
||||
|
||||
# create the initial mapping of blocks. Whenever template inheritance
|
||||
# takes place the runtime will update this mapping with the new blocks
|
||||
@ -223,17 +225,18 @@ class Macro(object):
|
||||
self._func = func
|
||||
self.name = name
|
||||
self.arguments = arguments
|
||||
self.argument_count = len(arguments)
|
||||
self.defaults = defaults
|
||||
self.catch_kwargs = catch_kwargs
|
||||
self.catch_varargs = catch_varargs
|
||||
self.caller = caller
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
arg_count = len(self.arguments)
|
||||
if not self.catch_varargs and len(args) > arg_count:
|
||||
self.argument_count = len(self.arguments)
|
||||
if not self.catch_varargs and len(args) > self.argument_count:
|
||||
raise TypeError('macro %r takes not more than %d argument(s)' %
|
||||
(self.name, len(self.arguments)))
|
||||
arguments = {}
|
||||
arguments = []
|
||||
for idx, name in enumerate(self.arguments):
|
||||
try:
|
||||
value = args[idx]
|
||||
@ -242,24 +245,28 @@ class Macro(object):
|
||||
value = kwargs.pop(name)
|
||||
except KeyError:
|
||||
try:
|
||||
value = self.defaults[idx - arg_count]
|
||||
value = self.defaults[idx - self.argument_count]
|
||||
except IndexError:
|
||||
value = self._environment.undefined(
|
||||
'parameter %r was not provided' % name)
|
||||
arguments['l_' + name] = value
|
||||
arguments.append(value)
|
||||
|
||||
# it's important that the order of these arguments does not change
|
||||
# if not also changed in the compiler's `function_scoping` method.
|
||||
# the order is caller, keyword arguments, positional arguments!
|
||||
if self.caller:
|
||||
caller = kwargs.pop('caller', None)
|
||||
if caller is None:
|
||||
caller = self._environment.undefined('No caller defined')
|
||||
arguments['l_caller'] = caller
|
||||
arguments.append(caller)
|
||||
if self.catch_kwargs:
|
||||
arguments['l_kwargs'] = kwargs
|
||||
arguments.append(kwargs)
|
||||
elif kwargs:
|
||||
raise TypeError('macro %r takes no keyword argument %r' %
|
||||
(self.name, iter(kwargs).next()))
|
||||
if self.catch_varargs:
|
||||
arguments['l_varargs'] = args[arg_count:]
|
||||
return self._func(**arguments)
|
||||
arguments.append(args[self.argument_count:])
|
||||
return self._func(*arguments)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (
|
||||
|
Loading…
Reference in New Issue
Block a user