cleanup: Replace ir_nodeset_contains() + ir_nodeset_insert() by just the latter.
[libfirm] / scripts / jinja2 / nodes.py
index 424c1cd..f9da1da 100644 (file)
 import operator
 from itertools import chain, izip
 from collections import deque
-from jinja2.utils import Markup
+from jinja2.utils import Markup, MethodType, FunctionType
+
+
+#: the types we support for context functions
+_context_function_types = (FunctionType, MethodType)
 
 
 _binop_to_func = {
@@ -67,6 +71,37 @@ class NodeType(type):
         return type.__new__(cls, name, bases, d)
 
 
+class EvalContext(object):
+    """Holds evaluation time information.  Custom attributes can be attached
+    to it in extensions.
+    """
+
+    def __init__(self, environment, template_name=None):
+        self.environment = environment
+        if callable(environment.autoescape):
+            self.autoescape = environment.autoescape(template_name)
+        else:
+            self.autoescape = environment.autoescape
+        self.volatile = False
+
+    def save(self):
+        return self.__dict__.copy()
+
+    def revert(self, old):
+        self.__dict__.clear()
+        self.__dict__.update(old)
+
+
+def get_eval_context(node, ctx):
+    if ctx is None:
+        if node.environment is None:
+            raise RuntimeError('if no eval context is passed, the '
+                               'node must have an attached '
+                               'environment.')
+        return EvalContext(node.environment)
+    return ctx
+
+
 class Node(object):
     """Baseclass for all Jinja2 nodes.  There are a number of nodes available
     of different types.  There are three major types:
@@ -312,19 +347,16 @@ class Expr(Node):
     """Baseclass for all expressions."""
     abstract = True
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
         """Return the value of the expression as constant or raise
-        :exc:`Impossible` if this was not possible:
+        :exc:`Impossible` if this was not possible.
 
-        >>> Add(Const(23), Const(42)).as_const()
-        65
-        >>> Add(Const(23), Name('var', 'load')).as_const()
-        Traceback (most recent call last):
-          ...
-        Impossible
+        An :class:`EvalContext` can be provided, if none is given
+        a default context is created which requires the nodes to have
+        an attached environment.
 
-        This requires the `environment` attribute of all nodes to be
-        set to the environment that created the nodes.
+        .. versionchanged:: 2.4
+           the `eval_ctx` parameter was added.
         """
         raise Impossible()
 
@@ -339,11 +371,16 @@ class BinExpr(Expr):
     operator = None
     abstract = True
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        # intercepted operators cannot be folded at compile time
+        if self.environment.sandboxed and \
+           self.operator in self.environment.intercepted_binops:
+            raise Impossible()
         f = _binop_to_func[self.operator]
         try:
-            return f(self.left.as_const(), self.right.as_const())
-        except:
+            return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
+        except Exception:
             raise Impossible()
 
 
@@ -353,11 +390,16 @@ class UnaryExpr(Expr):
     operator = None
     abstract = True
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        # intercepted operators cannot be folded at compile time
+        if self.environment.sandboxed and \
+           self.operator in self.environment.intercepted_unops:
+            raise Impossible()
         f = _uaop_to_func[self.operator]
         try:
-            return f(self.node.as_const())
-        except:
+            return f(self.node.as_const(eval_ctx))
+        except Exception:
             raise Impossible()
 
 
@@ -389,7 +431,7 @@ class Const(Literal):
     """
     fields = ('value',)
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
         return self.value
 
     @classmethod
@@ -408,8 +450,11 @@ class TemplateData(Literal):
     """A constant template string."""
     fields = ('data',)
 
-    def as_const(self):
-        if self.environment.autoescape:
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile:
+            raise Impossible()
+        if eval_ctx.autoescape:
             return Markup(self.data)
         return self.data
 
@@ -421,8 +466,9 @@ class Tuple(Literal):
     """
     fields = ('items', 'ctx')
 
-    def as_const(self):
-        return tuple(x.as_const() for x in self.items)
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return tuple(x.as_const(eval_ctx) for x in self.items)
 
     def can_assign(self):
         for item in self.items:
@@ -435,8 +481,9 @@ class List(Literal):
     """Any list literal such as ``[1, 2, 3]``"""
     fields = ('items',)
 
-    def as_const(self):
-        return [x.as_const() for x in self.items]
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return [x.as_const(eval_ctx) for x in self.items]
 
 
 class Dict(Literal):
@@ -445,24 +492,27 @@ class Dict(Literal):
     """
     fields = ('items',)
 
-    def as_const(self):
-        return dict(x.as_const() for x in self.items)
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return dict(x.as_const(eval_ctx) for x in self.items)
 
 
 class Pair(Helper):
     """A key, value pair for dicts."""
     fields = ('key', 'value')
 
-    def as_const(self):
-        return self.key.as_const(), self.value.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
 
 
 class Keyword(Helper):
     """A key, value pair for keyword arguments where key is a string."""
     fields = ('key', 'value')
 
-    def as_const(self):
-        return self.key, self.value.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return self.key, self.value.as_const(eval_ctx)
 
 
 class CondExpr(Expr):
@@ -471,15 +521,16 @@ class CondExpr(Expr):
     """
     fields = ('test', 'expr1', 'expr2')
 
-    def as_const(self):
-        if self.test.as_const():
-            return self.expr1.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if self.test.as_const(eval_ctx):
+            return self.expr1.as_const(eval_ctx)
 
         # if we evaluate to an undefined object, we better do that at runtime
         if self.expr2 is None:
             raise Impossible()
 
-        return self.expr2.as_const()
+        return self.expr2.as_const(eval_ctx)
 
 
 class Filter(Expr):
@@ -491,8 +542,9 @@ class Filter(Expr):
     """
     fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
 
-    def as_const(self, obj=None):
-        if self.node is obj is None:
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile or self.node is None:
             raise Impossible()
         # we have to be careful here because we call filter_ below.
         # if this variable would be called filter, 2to3 would wrap the
@@ -502,25 +554,26 @@ class Filter(Expr):
         filter_ = self.environment.filters.get(self.name)
         if filter_ is None or getattr(filter_, 'contextfilter', False):
             raise Impossible()
-        if obj is None:
-            obj = self.node.as_const()
-        args = [x.as_const() for x in self.args]
-        if getattr(filter_, 'environmentfilter', False):
+        obj = self.node.as_const(eval_ctx)
+        args = [x.as_const(eval_ctx) for x in self.args]
+        if getattr(filter_, 'evalcontextfilter', False):
+            args.insert(0, eval_ctx)
+        elif getattr(filter_, 'environmentfilter', False):
             args.insert(0, self.environment)
-        kwargs = dict(x.as_const() for x in self.kwargs)
+        kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
         if self.dyn_args is not None:
             try:
-                args.extend(self.dyn_args.as_const())
-            except:
+                args.extend(self.dyn_args.as_const(eval_ctx))
+            except Exception:
                 raise Impossible()
         if self.dyn_kwargs is not None:
             try:
-                kwargs.update(self.dyn_kwargs.as_const())
-            except:
+                kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+            except Exception:
                 raise Impossible()
         try:
             return filter_(obj, *args, **kwargs)
-        except:
+        except Exception:
             raise Impossible()
 
 
@@ -540,30 +593,36 @@ class Call(Expr):
     """
     fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
 
-    def as_const(self):
-        obj = self.node.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile:
+            raise Impossible()
+        obj = self.node.as_const(eval_ctx)
 
         # don't evaluate context functions
-        args = [x.as_const() for x in self.args]
-        if getattr(obj, 'contextfunction', False):
-            raise Impossible()
-        elif getattr(obj, 'environmentfunction', False):
-            args.insert(0, self.environment)
+        args = [x.as_const(eval_ctx) for x in self.args]
+        if isinstance(obj, _context_function_types):
+            if getattr(obj, 'contextfunction', False):
+                raise Impossible()
+            elif getattr(obj, 'evalcontextfunction', False):
+                args.insert(0, eval_ctx)
+            elif getattr(obj, 'environmentfunction', False):
+                args.insert(0, self.environment)
 
-        kwargs = dict(x.as_const() for x in self.kwargs)
+        kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
         if self.dyn_args is not None:
             try:
-                args.extend(self.dyn_args.as_const())
-            except:
+                args.extend(self.dyn_args.as_const(eval_ctx))
+            except Exception:
                 raise Impossible()
         if self.dyn_kwargs is not None:
             try:
-                kwargs.update(self.dyn_kwargs.as_const())
-            except:
+                kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+            except Exception:
                 raise Impossible()
         try:
             return obj(*args, **kwargs)
-        except:
+        except Exception:
             raise Impossible()
 
 
@@ -571,13 +630,14 @@ class Getitem(Expr):
     """Get an attribute or item from an expression and prefer the item."""
     fields = ('node', 'arg', 'ctx')
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
         if self.ctx != 'load':
             raise Impossible()
         try:
-            return self.environment.getitem(self.node.as_const(),
-                                            self.arg.as_const())
-        except:
+            return self.environment.getitem(self.node.as_const(eval_ctx),
+                                            self.arg.as_const(eval_ctx))
+        except Exception:
             raise Impossible()
 
     def can_assign(self):
@@ -590,12 +650,14 @@ class Getattr(Expr):
     """
     fields = ('node', 'attr', 'ctx')
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
         if self.ctx != 'load':
             raise Impossible()
         try:
-            return self.environment.getattr(self.node.as_const(), arg)
-        except:
+            eval_ctx = get_eval_context(self, eval_ctx)
+            return self.environment.getattr(self.node.as_const(eval_ctx),
+                                            self.attr)
+        except Exception:
             raise Impossible()
 
     def can_assign(self):
@@ -608,11 +670,12 @@ class Slice(Expr):
     """
     fields = ('start', 'stop', 'step')
 
-    def as_const(self):
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
         def const(obj):
             if obj is None:
-                return obj
-            return obj.as_const()
+                return None
+            return obj.as_const(eval_ctx)
         return slice(const(self.start), const(self.stop), const(self.step))
 
 
@@ -622,8 +685,9 @@ class Concat(Expr):
     """
     fields = ('nodes',)
 
-    def as_const(self):
-        return ''.join(unicode(x.as_const()) for x in self.nodes)
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
 
 
 class Compare(Expr):
@@ -632,14 +696,15 @@ class Compare(Expr):
     """
     fields = ('expr', 'ops')
 
-    def as_const(self):
-        result = value = self.expr.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        result = value = self.expr.as_const(eval_ctx)
         try:
             for op in self.ops:
-                new_value = op.expr.as_const()
+                new_value = op.expr.as_const(eval_ctx)
                 result = _cmpop_to_func[op.op](value, new_value)
                 value = new_value
-        except:
+        except Exception:
             raise Impossible()
         return result
 
@@ -695,16 +760,18 @@ class And(BinExpr):
     """Short circuited AND."""
     operator = 'and'
 
-    def as_const(self):
-        return self.left.as_const() and self.right.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
 
 
 class Or(BinExpr):
     """Short circuited OR."""
     operator = 'or'
 
-    def as_const(self):
-        return self.left.as_const() or self.right.as_const()
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
 
 
 class Not(UnaryExpr):
@@ -769,12 +836,40 @@ class MarkSafe(Expr):
     """Mark the wrapped expression as safe (wrap it as `Markup`)."""
     fields = ('expr',)
 
-    def as_const(self):
-        return Markup(self.expr.as_const())
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        return Markup(self.expr.as_const(eval_ctx))
+
+
+class MarkSafeIfAutoescape(Expr):
+    """Mark the wrapped expression as safe (wrap it as `Markup`) but
+    only if autoescaping is active.
+
+    .. versionadded:: 2.5
+    """
+    fields = ('expr',)
+
+    def as_const(self, eval_ctx=None):
+        eval_ctx = get_eval_context(self, eval_ctx)
+        if eval_ctx.volatile:
+            raise Impossible()
+        expr = self.expr.as_const(eval_ctx)
+        if eval_ctx.autoescape:
+            return Markup(expr)
+        return expr
 
 
 class ContextReference(Expr):
-    """Returns the current template context."""
+    """Returns the current template context.  It can be used like a
+    :class:`Name` node, with a ``'load'`` ctx and will return the
+    current :class:`~jinja2.runtime.Context` object.
+
+    Here an example that assigns the current template name to a
+    variable named `foo`::
+
+        Assign(Name('foo', ctx='store'),
+               Getattr(ContextReference(), 'name'))
+    """
 
 
 class Continue(Stmt):
@@ -790,6 +885,25 @@ class Scope(Stmt):
     fields = ('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.
+
+    Example to change the `autoescape` setting::
+
+        EvalContextModifier(options=[Keyword('autoescape', Const(True))])
+    """
+    fields = ('options',)
+
+
+class ScopedEvalContextModifier(EvalContextModifier):
+    """Modifies the eval context and reverts it later.  Works exactly like
+    :class:`EvalContextModifier` but will only modify the
+    :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
+    """
+    fields = ('body',)
+
+
 # make sure nobody creates custom nodes
 def _failing_new(*args, **kwargs):
     raise TypeError('can\'t create custom node types')