diff options
author | Armin Ronacher <armin.ronacher@active-4.com> | 2010-03-14 19:43:47 +0100 |
---|---|---|
committer | Armin Ronacher <armin.ronacher@active-4.com> | 2010-03-14 19:43:47 +0100 |
commit | 8346bd7ec3dfef84a873b3413c3ebc524b847f55 (patch) | |
tree | 122fa48c97e2587bff53f48bccf533f915b5b54c /jinja2/compiler.py | |
parent | 12a316bd5cbcbeda48013258447529f59bc8ac12 (diff) | |
download | jinja2-8346bd7ec3dfef84a873b3413c3ebc524b847f55.tar.gz |
Biggest change to Jinja since the 1.x migration: added evaluation contexts
which make it possible to keep the ahead of time optimizations and provide
dynamic activation and deactivation of autoescaping and other context
specific features.
--HG--
branch : trunk
Diffstat (limited to 'jinja2/compiler.py')
-rw-r--r-- | jinja2/compiler.py | 83 |
1 files changed, 67 insertions, 16 deletions
diff --git a/jinja2/compiler.py b/jinja2/compiler.py index ccf0811..5f355a9 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -12,6 +12,7 @@ from cStringIO import StringIO from itertools import chain from copy import deepcopy from jinja2 import nodes +from jinja2.nodes import EvalContext from jinja2.visitor import NodeVisitor, NodeTransformer from jinja2.exceptions import TemplateAssertionError from jinja2.utils import Markup, concat, escape, is_python_keyword, next @@ -141,7 +142,8 @@ class Identifiers(object): class Frame(object): """Holds compile time information for us.""" - def __init__(self, parent=None): + def __init__(self, eval_ctx, parent=None): + self.eval_ctx = eval_ctx self.identifiers = Identifiers() # a toplevel frame is the root + soft frames such as if conditions. @@ -211,7 +213,7 @@ class Frame(object): def inner(self): """Return an inner frame.""" - return Frame(self) + return Frame(self.eval_ctx, self) def soft(self): """Return a soft frame. A soft frame may not be modified as @@ -422,7 +424,7 @@ class CodeGenerator(NodeVisitor): # -- Various compilation helpers def fail(self, msg, lineno): - """Fail with a `TemplateAssertionError`.""" + """Fail with a :exc:`TemplateAssertionError`.""" raise TemplateAssertionError(msg, lineno, self.name, self.filename) def temporary_identifier(self): @@ -437,10 +439,15 @@ class CodeGenerator(NodeVisitor): def return_buffer_contents(self, frame): """Return the buffer contents of the frame.""" - if self.environment.autoescape: - self.writeline('return Markup(concat(%s))' % frame.buffer) + self.writeline('return ') + if frame.eval_ctx.volatile: + self.write('(Markup(concat(%s)) if context.eval_ctx' + '.autoescape else concat(%s))' % + (frame.buffer, frame.buffer)) + elif frame.eval_ctx.autoescape: + self.write('Markup(concat(%s))' % frame.buffer) else: - self.writeline('return concat(%s)' % frame.buffer) + self.write('concat(%s)' % frame.buffer) def indent(self): """Indent by one.""" @@ -750,6 +757,8 @@ class CodeGenerator(NodeVisitor): def visit_Template(self, node, frame=None): assert frame is None, 'no root frame allowed' + eval_ctx = EvalContext(self.environment) + from jinja2.runtime import __all__ as exported self.writeline('from __future__ import division') self.writeline('from jinja2.runtime import ' + ', '.join(exported)) @@ -789,7 +798,7 @@ class CodeGenerator(NodeVisitor): self.writeline('def root(context%s):' % envenv, extra=1) # process the root - frame = Frame() + frame = Frame(eval_ctx) frame.inspect(node.body) frame.toplevel = frame.rootlevel = True frame.require_output_check = have_extends and not self.has_known_extends @@ -818,7 +827,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 = Frame(eval_ctx) block_frame.inspect(block.body) block_frame.block = name self.writeline('def block_%s(context%s):' % (name, envenv), @@ -1224,12 +1233,15 @@ class CodeGenerator(NodeVisitor): body = [] for child in node.nodes: try: - const = child.as_const() + const = child.as_const(frame.eval_ctx) except nodes.Impossible: body.append(child) continue + # the frame can't be volatile here, becaus otherwise the + # as_const() function would raise an Impossible exception + # at that point. try: - if self.environment.autoescape: + if frame.eval_ctx.autoescape: if hasattr(const, '__html__'): const = const.__html__() else: @@ -1267,7 +1279,10 @@ class CodeGenerator(NodeVisitor): else: self.newline(item) close = 1 - if self.environment.autoescape: + if frame.eval_ctx.volatile: + self.write('(context.eval_ctx.autoescape and' + ' escape or to_string)(') + elif frame.eval_ctx.autoescape: self.write('escape(') else: self.write('to_string(') @@ -1300,7 +1315,10 @@ class CodeGenerator(NodeVisitor): for argument in arguments: self.newline(argument) close = 0 - if self.environment.autoescape: + if frame.eval_ctx.volatile: + self.write('(context.eval_ctx.autoescape and' + ' escape or to_string)(') + elif frame.eval_ctx.autoescape: self.write('escape(') close += 1 if self.environment.finalize is not None: @@ -1367,7 +1385,7 @@ class CodeGenerator(NodeVisitor): self.write(repr(val)) def visit_TemplateData(self, node, frame): - self.write(repr(node.as_const())) + self.write(repr(node.as_const(frame.eval_ctx))) def visit_Tuple(self, node, frame): self.write('(') @@ -1427,8 +1445,14 @@ class CodeGenerator(NodeVisitor): del binop, uaop def visit_Concat(self, node, frame): - self.write('%s((' % (self.environment.autoescape and - 'markup_join' or 'unicode_join')) + if frame.eval_ctx.volatile: + func_name = '(context.eval_ctx.volatile and' \ + ' markup_join or unicode_join)' + elif frame.eval_ctx.autoescape: + func_name = 'markup_join' + else: + func_name = 'unicode_join' + self.write('%s((' % func_name) for arg in node.nodes: self.visit(arg, frame) self.write(', ') @@ -1479,6 +1503,8 @@ class CodeGenerator(NodeVisitor): self.fail('no filter named %r' % node.name, node.lineno) if getattr(func, 'contextfilter', False): self.write('context, ') + elif getattr(func, 'evalcontextfilter', False): + self.write('context.eval_ctx, ') elif getattr(func, 'environmentfilter', False): self.write('environment, ') @@ -1486,7 +1512,11 @@ class CodeGenerator(NodeVisitor): # and want to write to the current buffer if node.node is not None: self.visit(node.node, frame) - elif self.environment.autoescape: + elif frame.eval_ctx.volatile: + self.write('(context.eval_ctx.autoescape and' + ' Markup(concat(%s)) or concat(%s))' % + (frame.buffer, frame.buffer)) + elif frame.eval_ctx.autoescape: self.write('Markup(concat(%s))' % frame.buffer) else: self.write('concat(%s)' % frame.buffer) @@ -1575,3 +1605,24 @@ class CodeGenerator(NodeVisitor): self.pull_locals(scope_frame) self.blockvisit(node.body, scope_frame) self.pop_scope(aliases, scope_frame) + + def visit_EvalContextModifier(self, node, frame): + for keyword in node.options: + self.writeline('context.eval_ctx.%s = ' % keyword.key) + self.visit(keyword.value, frame) + try: + val = keyword.value.as_const(frame.eval_ctx) + except nodes.Impossible: + frame.volatile = True + else: + setattr(frame.eval_ctx, keyword.key, val) + + def visit_ScopedEvalContextModifier(self, node, frame): + old_ctx_name = self.temporary_identifier() + safed_ctx = frame.eval_ctx.save() + self.writeline('%s = context.eval_ctx.save()' % old_ctx_name) + self.visit_EvalContextModifier(node, frame) + for child in node.body: + self.visit(child, frame) + frame.eval_ctx.revert(safed_ctx) + self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name) |