mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-30 02:40:58 +00:00
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
This commit is contained in:
parent
12a316bd5c
commit
8346bd7ec3
@ -54,9 +54,11 @@ from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||
TemplateAssertionError
|
||||
|
||||
# decorators and public utilities
|
||||
from jinja2.filters import environmentfilter, contextfilter
|
||||
from jinja2.filters import environmentfilter, contextfilter, \
|
||||
evalcontextfilter
|
||||
from jinja2.utils import Markup, escape, clear_caches, \
|
||||
environmentfunction, contextfunction, is_undefined
|
||||
environmentfunction, evalcontextfunction, contextfunction, \
|
||||
is_undefined
|
||||
|
||||
__all__ = [
|
||||
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
|
||||
@ -66,5 +68,6 @@ __all__ = [
|
||||
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
||||
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
||||
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined'
|
||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
||||
'evalcontextfilter', 'evalcontextfunction'
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -158,8 +158,8 @@ class Environment(object):
|
||||
`None` implicitly into an empty string here.
|
||||
|
||||
`autoescape`
|
||||
If set to true the XML/HTML autoescaping feature is enabled.
|
||||
For more details about auto escaping see
|
||||
If set to true the XML/HTML autoescaping feature is enabled by
|
||||
default. For more details about auto escaping see
|
||||
:class:`~jinja2.utils.Markup`.
|
||||
|
||||
`loader`
|
||||
@ -493,6 +493,7 @@ class Environment(object):
|
||||
raise TemplateSyntaxError('chunk after expression',
|
||||
parser.stream.current.lineno,
|
||||
None, None)
|
||||
expr.set_environment(self)
|
||||
except TemplateSyntaxError:
|
||||
exc_info = sys.exc_info()
|
||||
if exc_info is not None:
|
||||
|
@ -357,6 +357,20 @@ class WithExtension(Extension):
|
||||
return node
|
||||
|
||||
|
||||
class AutoEscapeExtension(Extension):
|
||||
"""Changes auto escape rules for a scope."""
|
||||
tags = set(['autoescape'])
|
||||
|
||||
def parse(self, parser):
|
||||
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
|
||||
node.options = [
|
||||
nodes.Keyword('autoescape', parser.parse_expression())
|
||||
]
|
||||
node.body = parser.parse_statements(('name:endautoescape',),
|
||||
drop_needle=True)
|
||||
return nodes.Scope([node])
|
||||
|
||||
|
||||
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
|
||||
babel_style=True):
|
||||
"""Extract localizable strings from the given template node. Per
|
||||
@ -529,3 +543,4 @@ i18n = InternationalizationExtension
|
||||
do = ExprStmtExtension
|
||||
loopcontrols = LoopControlExtension
|
||||
with_ = WithExtension
|
||||
autoescape = AutoEscapeExtension
|
||||
|
@ -25,18 +25,24 @@ def contextfilter(f):
|
||||
"""Decorator for marking context dependent filters. The current
|
||||
:class:`Context` will be passed as first argument.
|
||||
"""
|
||||
if getattr(f, 'environmentfilter', False):
|
||||
raise TypeError('filter already marked as environment filter')
|
||||
f.contextfilter = True
|
||||
return f
|
||||
|
||||
|
||||
def evalcontextfilter(f):
|
||||
"""Decorator for marking eval-context dependent filters. An eval
|
||||
context object is passed as first argument.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
f.evalcontextfilter = True
|
||||
return f
|
||||
|
||||
|
||||
def environmentfilter(f):
|
||||
"""Decorator for marking evironment dependent filters. The current
|
||||
:class:`Environment` is passed to the filter as first argument.
|
||||
"""
|
||||
if getattr(f, 'contextfilter', False):
|
||||
raise TypeError('filter already marked as context filter')
|
||||
f.environmentfilter = True
|
||||
return f
|
||||
|
||||
@ -48,8 +54,8 @@ def do_forceescape(value):
|
||||
return escape(unicode(value))
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_replace(environment, s, old, new, count=None):
|
||||
@evalcontextfilter
|
||||
def do_replace(eval_ctx, s, old, new, count=None):
|
||||
"""Return a copy of the value with all occurrences of a substring
|
||||
replaced with a new one. The first argument is the substring
|
||||
that should be replaced, the second is the replacement string.
|
||||
@ -66,7 +72,7 @@ def do_replace(environment, s, old, new, count=None):
|
||||
"""
|
||||
if count is None:
|
||||
count = -1
|
||||
if not environment.autoescape:
|
||||
if not eval_ctx.autoescape:
|
||||
return unicode(s).replace(unicode(old), unicode(new), count)
|
||||
if hasattr(old, '__html__') or hasattr(new, '__html__') and \
|
||||
not hasattr(s, '__html__'):
|
||||
@ -86,8 +92,8 @@ def do_lower(s):
|
||||
return soft_unicode(s).lower()
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_xmlattr(_environment, d, autospace=True):
|
||||
@evalcontextfilter
|
||||
def do_xmlattr(_eval_ctx, d, autospace=True):
|
||||
"""Create an SGML/XML attribute string based on the items in a dict.
|
||||
All values that are neither `none` nor `undefined` are automatically
|
||||
escaped:
|
||||
@ -117,7 +123,7 @@ def do_xmlattr(_environment, d, autospace=True):
|
||||
)
|
||||
if autospace and rv:
|
||||
rv = u' ' + rv
|
||||
if _environment.autoescape:
|
||||
if _eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
@ -212,8 +218,8 @@ def do_default(value, default_value=u'', boolean=False):
|
||||
return value
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_join(environment, value, d=u''):
|
||||
@evalcontextfilter
|
||||
def do_join(eval_ctx, value, d=u''):
|
||||
"""Return a string which is the concatenation of the strings in the
|
||||
sequence. The separator between elements is an empty string per
|
||||
default, you can define it with the optional parameter:
|
||||
@ -227,7 +233,7 @@ def do_join(environment, value, d=u''):
|
||||
-> 123
|
||||
"""
|
||||
# no automatic escaping? joining is a lot eaiser then
|
||||
if not environment.autoescape:
|
||||
if not eval_ctx.autoescape:
|
||||
return unicode(d).join(imap(unicode, value))
|
||||
|
||||
# if the delimiter doesn't have an html representation we check
|
||||
@ -309,8 +315,8 @@ def do_pprint(value, verbose=False):
|
||||
return pformat(value, verbose=verbose)
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
|
||||
@evalcontextfilter
|
||||
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
|
||||
"""Converts URLs in plain text into clickable links.
|
||||
|
||||
If you pass the filter an additional integer it will shorten the urls
|
||||
@ -323,7 +329,7 @@ def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
|
||||
links are shortened to 40 chars and defined with rel="nofollow"
|
||||
"""
|
||||
rv = urlize(value, trim_url_limit, nofollow)
|
||||
if environment.autoescape:
|
||||
if eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
|
185
jinja2/nodes.py
185
jinja2/nodes.py
@ -67,6 +67,31 @@ class NodeType(type):
|
||||
return type.__new__(cls, name, bases, d)
|
||||
|
||||
|
||||
class EvalContext(object):
|
||||
"""Holds evaluation time information"""
|
||||
|
||||
def __init__(self, environment):
|
||||
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 +337,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,10 +361,11 @@ 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)
|
||||
f = _binop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.left.as_const(), self.right.as_const())
|
||||
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
@ -353,10 +376,11 @@ 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)
|
||||
f = _uaop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.node.as_const())
|
||||
return f(self.node.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
@ -389,7 +413,7 @@ class Const(Literal):
|
||||
"""
|
||||
fields = ('value',)
|
||||
|
||||
def as_const(self):
|
||||
def as_const(self, eval_ctx=None):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
@ -408,8 +432,8 @@ class TemplateData(Literal):
|
||||
"""A constant template string."""
|
||||
fields = ('data',)
|
||||
|
||||
def as_const(self):
|
||||
if self.environment.autoescape:
|
||||
def as_const(self, eval_ctx=None):
|
||||
if get_eval_context(self, eval_ctx).autoescape:
|
||||
return Markup(self.data)
|
||||
return self.data
|
||||
|
||||
@ -421,8 +445,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 +460,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 +471,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 +500,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 +521,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,20 +533,21 @@ 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())
|
||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
if self.dyn_kwargs is not None:
|
||||
try:
|
||||
kwargs.update(self.dyn_kwargs.as_const())
|
||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
try:
|
||||
@ -540,25 +572,30 @@ 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]
|
||||
args = [x.as_const(eval_ctx) for x in self.args]
|
||||
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())
|
||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
if self.dyn_kwargs is not None:
|
||||
try:
|
||||
kwargs.update(self.dyn_kwargs.as_const())
|
||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
try:
|
||||
@ -571,12 +608,13 @@ 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())
|
||||
return self.environment.getitem(self.node.as_const(eval_ctx),
|
||||
self.arg.as_const(eval_ctx))
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
@ -590,11 +628,12 @@ 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)
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.environment.getattr(self.node.as_const(eval_ctx), arg)
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
@ -608,11 +647,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 +662,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,11 +673,12 @@ 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:
|
||||
@ -695,16 +737,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,8 +813,9 @@ 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 ContextReference(Expr):
|
||||
@ -790,6 +835,16 @@ class Scope(Stmt):
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
class EvalContextModifier(Stmt):
|
||||
"""Modifies the eval context"""
|
||||
fields = ('options',)
|
||||
|
||||
|
||||
class ScopedEvalContextModifier(EvalContextModifier):
|
||||
"""Modifies the eval context and reverts it later."""
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
# make sure nobody creates custom nodes
|
||||
def _failing_new(*args, **kwargs):
|
||||
raise TypeError('can\'t create custom node types')
|
||||
|
@ -10,6 +10,7 @@
|
||||
"""
|
||||
import sys
|
||||
from itertools import chain, imap
|
||||
from jinja2.nodes import EvalContext
|
||||
from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
|
||||
concat, MethodType, FunctionType, internalcode, next
|
||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||
@ -106,13 +107,14 @@ class Context(object):
|
||||
method that doesn't fail with a `KeyError` but returns an
|
||||
:class:`Undefined` object for missing variables.
|
||||
"""
|
||||
__slots__ = ('parent', 'vars', 'environment', 'exported_vars', 'name',
|
||||
'blocks', '__weakref__')
|
||||
__slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
|
||||
'name', 'blocks', '__weakref__')
|
||||
|
||||
def __init__(self, environment, parent, name, blocks):
|
||||
self.parent = parent
|
||||
self.vars = {}
|
||||
self.environment = environment
|
||||
self.eval_ctx = EvalContext(self.environment)
|
||||
self.exported_vars = set()
|
||||
self.name = name
|
||||
|
||||
@ -174,6 +176,8 @@ class Context(object):
|
||||
if isinstance(__obj, _context_function_types):
|
||||
if getattr(__obj, 'contextfunction', 0):
|
||||
args = (__self,) + args
|
||||
elif getattr(__obj, 'evalcontextfunction', 0):
|
||||
args = (__self.eval_ctx,) + args
|
||||
elif getattr(__obj, 'environmentfunction', 0):
|
||||
args = (__self.environment,) + args
|
||||
return __obj(*args, **kwargs)
|
||||
@ -182,6 +186,7 @@ class Context(object):
|
||||
"""Internal helper function to create a derived context."""
|
||||
context = new_context(self.environment, self.name, {},
|
||||
self.parent, True, None, locals)
|
||||
context.eval_ctx = self.eval_ctx
|
||||
context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
|
||||
return context
|
||||
|
||||
|
@ -256,8 +256,60 @@ class InternationalizationTestCase(JinjaTestCase):
|
||||
]
|
||||
|
||||
|
||||
class AutoEscapeTestCase(JinjaTestCase):
|
||||
|
||||
def test_scoped_setting(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('''
|
||||
{{ "<HelloWorld>" }}
|
||||
{% autoescape false %}
|
||||
{{ "<HelloWorld>" }}
|
||||
{% endautoescape %}
|
||||
{{ "<HelloWorld>" }}
|
||||
''')
|
||||
assert tmpl.render().split() == \
|
||||
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=False)
|
||||
tmpl = env.from_string('''
|
||||
{{ "<HelloWorld>" }}
|
||||
{% autoescape true %}
|
||||
{{ "<HelloWorld>" }}
|
||||
{% endautoescape %}
|
||||
{{ "<HelloWorld>" }}
|
||||
''')
|
||||
assert tmpl.render().split() == \
|
||||
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||
|
||||
def test_nonvolatile(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
|
||||
assert tmpl.render() == ' foo="<test>"'
|
||||
tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
|
||||
'|xmlattr|escape }}{% endautoescape %}')
|
||||
assert tmpl.render() == ' foo="&lt;test&gt;"'
|
||||
|
||||
def test_volatile(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
|
||||
'|xmlattr|escape }}{% endautoescape %}')
|
||||
assert tmpl.render(foo=False) == ' foo="&lt;test&gt;"'
|
||||
assert tmpl.render(foo=True) == ' foo="<test>"'
|
||||
|
||||
def test_scoping(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||
tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
|
||||
'{% endautoescape %}{{ x }}{{ "<y>" }}')
|
||||
assert tmpl.render(x=1) == '<x>1<y>'
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ExtensionsTestCase))
|
||||
suite.addTest(unittest.makeSuite(InternationalizationTestCase))
|
||||
suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
|
||||
return suite
|
||||
|
@ -127,6 +127,18 @@ def contextfunction(f):
|
||||
return f
|
||||
|
||||
|
||||
def evalcontextfunction(f):
|
||||
"""This decoraotr can be used to mark a function or method as an eval
|
||||
context callable. This is similar to the :func:`contextfunction`
|
||||
but instead of passing the context, an evaluation context object is
|
||||
passed.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
f.evalcontextfunction = True
|
||||
return f
|
||||
|
||||
|
||||
def environmentfunction(f):
|
||||
"""This decorator can be used to mark a function or method as environment
|
||||
callable. This decorator works exactly like the :func:`contextfunction`
|
||||
|
Loading…
Reference in New Issue
Block a user