summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2017-01-08 11:10:55 +0100
committerArmin Ronacher <armin.ronacher@active-4.com>2017-01-08 11:10:55 +0100
commit9aa267e08a1b2ce7a0d1f9737fe2614173cf4f8b (patch)
treeb94063d1ea92a8d545f80f00e385dafbf64ce10f
parent62d72eea41528602b88d162377e1cbd8f24f0cc4 (diff)
downloadjinja2-feature/overlay-scopes.tar.gz
WIP for overlay scopesfeature/overlay-scopes
-rw-r--r--jinja2/compiler.py49
-rw-r--r--jinja2/idtracking.py7
-rw-r--r--jinja2/nodes.py8
-rw-r--r--jinja2/runtime.py2
4 files changed, 61 insertions, 5 deletions
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 9a6ac6a..18cc2ec 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -302,6 +302,9 @@ class CodeGenerator(NodeVisitor):
# Tracks parameter definition blocks
self._param_def_block = []
+ # Tracks the current context.
+ self._context_reference_stack = ['context']
+
# -- Various compilation helpers
def fail(self, msg, lineno):
@@ -473,8 +476,8 @@ class CodeGenerator(NodeVisitor):
if action == VAR_LOAD_PARAMETER:
pass
elif action == VAR_LOAD_RESOLVE:
- self.writeline('%s = resolve(%r)' %
- (target, param))
+ self.writeline('%s = %s(%r)' %
+ (target, self.get_resolve_func(), param))
elif action == VAR_LOAD_ALIAS:
self.writeline('%s = %s' % (target, param))
elif action == VAR_LOAD_UNDEFINED:
@@ -626,6 +629,27 @@ class CodeGenerator(NodeVisitor):
if self._param_def_block:
self._param_def_block[-1].discard(target)
+ def push_context_reference(self, target):
+ self._context_reference_stack.append(target)
+
+ def pop_context_reference(self):
+ self._context_reference_stack.pop()
+
+ def get_context_ref(self):
+ return self._context_reference_stack[-1]
+
+ def get_resolve_func(self):
+ target = self._context_reference_stack[-1]
+ if target == 'context':
+ return 'resolve'
+ return '%s.resolve' % target
+
+ def derive_context(self, frame):
+ return '%s.derived(%s)' % (
+ self.get_context_ref(),
+ self.dump_local_context(frame),
+ )
+
def parameter_is_undeclared(self, target):
"""Checks if a given target is an undeclared parameter."""
if not self._param_def_block:
@@ -793,8 +817,11 @@ class CodeGenerator(NodeVisitor):
self.writeline('if parent_template is None:')
self.indent()
level += 1
- context = node.scoped and (
- 'context.derived(%s)' % self.dump_local_context(frame)) or 'context'
+
+ if node.scoped:
+ context = self.derive_context(frame)
+ else:
+ context = self.get_context_ref()
if supports_yield_from and not self.environment.is_async and \
frame.buffer is None:
@@ -1619,6 +1646,20 @@ class CodeGenerator(NodeVisitor):
self.blockvisit(node.body, scope_frame)
self.leave_frame(scope_frame)
+ def visit_OverlayScope(self, node, frame):
+ ctx = self.temporary_identifier()
+ self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
+ self.writeline('%s.vars = ' % ctx)
+ self.visit(node.context, frame)
+ self.push_context_reference(ctx)
+
+ scope_frame = Frame(frame.eval_ctx)
+ scope_frame.symbols.analyze_node(node)
+ self.enter_frame(scope_frame)
+ self.blockvisit(node.body, scope_frame)
+ self.leave_frame(scope_frame)
+ self.pop_context_reference()
+
def visit_EvalContextModifier(self, node, frame):
for keyword in node.options:
self.writeline('context.eval_ctx.%s = ' % keyword.key)
diff --git a/jinja2/idtracking.py b/jinja2/idtracking.py
index b00dab8..697d4a2 100644
--- a/jinja2/idtracking.py
+++ b/jinja2/idtracking.py
@@ -167,6 +167,10 @@ class RootVisitor(NodeVisitor):
for child in node.iter_child_nodes(exclude=('call',)):
self.sym_visitor.visit(child)
+ def visit_OverlayScope(self, node, **kwargs):
+ for child in node.body:
+ self.sym_visitor.visit(child)
+
def visit_For(self, node, for_branch='body', **kwargs):
if node.test is not None:
self.sym_visitor.visit(node.test)
@@ -258,3 +262,6 @@ class FrameSymbolVisitor(NodeVisitor):
def visit_Block(self, node, **kwargs):
"""Stop visiting at blocks."""
+
+ def visit_OverlayScope(self, node, **kwargs):
+ """Do not visit into overlay scopes."""
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index d1a4c38..a7641a4 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -905,6 +905,14 @@ class Scope(Stmt):
fields = ('body',)
+class OverlayScope(Stmt):
+ """An overlay scope for extensions. This is a largely unoptimized scope
+ that however can be used to introduce completely arbitrar variables into
+ a sub scope from a dictionary or dictionary like object.
+ """
+ fields = ('context', 'body')
+
+
class EvalContextModifier(Stmt):
"""Modifies the eval context. For each option that should be modified,
a :class:`Keyword` has to be added to the :attr:`options` list.
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 958ddfd..4f0f399 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -24,7 +24,7 @@ from jinja2._compat import imap, text_type, iteritems, \
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join', 'to_string', 'identity',
- 'TemplateNotFound']
+ 'make_overlay_resolve', 'TemplateNotFound']
#: the name of the function that is used to convert something into
#: a string. We can just use the text type here.