don't finalize TemplateData nodes
Finalize only applies to the output of expressions (constant or not). Add tests for context, eval, and env finalize functions.
This commit is contained in:
parent
636c7122d9
commit
719537aeec
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ venv-*/
|
||||
.coverage.*
|
||||
htmlcov
|
||||
.pytest_cache/
|
||||
/.vscode/
|
||||
|
||||
@ -34,6 +34,9 @@ Unreleased
|
||||
:issue:`755`, :pr:`938`
|
||||
- Add new ``boolean``, ``false``, ``true``, ``integer`` and ``float``
|
||||
tests. :pr:`824`
|
||||
- The environment's ``finalize`` function is only applied to the
|
||||
output of expressions (constant or not), not static template data.
|
||||
:issue:`63`
|
||||
|
||||
|
||||
Version 2.10.3
|
||||
|
||||
@ -1224,19 +1224,28 @@ class CodeGenerator(NodeVisitor):
|
||||
if self.has_known_extends and frame.require_output_check:
|
||||
return
|
||||
|
||||
finalize = text_type
|
||||
finalize_src = None
|
||||
allow_constant_finalize = True
|
||||
|
||||
if self.environment.finalize:
|
||||
func = self.environment.finalize
|
||||
if getattr(func, 'contextfunction', False) or \
|
||||
getattr(func, 'evalcontextfunction', False):
|
||||
env_finalize = self.environment.finalize
|
||||
finalize_src = "environment.finalize("
|
||||
|
||||
def finalize(value):
|
||||
return text_type(env_finalize(value))
|
||||
|
||||
if getattr(env_finalize, "contextfunction", False):
|
||||
finalize_src += "context, "
|
||||
allow_constant_finalize = False
|
||||
elif getattr(func, 'environmentfunction', False):
|
||||
finalize = lambda x: text_type(
|
||||
self.environment.finalize(self.environment, x))
|
||||
else:
|
||||
finalize = lambda x: text_type(self.environment.finalize(x))
|
||||
else:
|
||||
finalize = text_type
|
||||
elif getattr(env_finalize, "evalcontextfunction", False):
|
||||
finalize_src += "context.eval_ctx, "
|
||||
allow_constant_finalize = False
|
||||
elif getattr(env_finalize, "environmentfunction", False):
|
||||
finalize_src += "environment, "
|
||||
|
||||
def finalize(value):
|
||||
return text_type(env_finalize(self.environment, value))
|
||||
|
||||
# if we are inside a frame that requires output checking, we do so
|
||||
outdent_later = False
|
||||
@ -1250,27 +1259,37 @@ class CodeGenerator(NodeVisitor):
|
||||
body = []
|
||||
for child in node.nodes:
|
||||
try:
|
||||
if not allow_constant_finalize:
|
||||
# If the finalize function needs context, and this isn't
|
||||
# template data, evaluate the node at render.
|
||||
if not (
|
||||
allow_constant_finalize
|
||||
or isinstance(child, nodes.TemplateData)
|
||||
):
|
||||
raise nodes.Impossible()
|
||||
|
||||
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 frame.eval_ctx.autoescape:
|
||||
if hasattr(const, '__html__'):
|
||||
const = const.__html__()
|
||||
else:
|
||||
const = escape(const)
|
||||
const = finalize(const)
|
||||
const = escape(const)
|
||||
|
||||
# Only call finalize on expressions, not template data.
|
||||
if isinstance(child, nodes.TemplateData):
|
||||
const = text_type(const)
|
||||
else:
|
||||
const = finalize(const)
|
||||
except Exception:
|
||||
# if something goes wrong here we evaluate the node
|
||||
# at runtime for easier debugging
|
||||
body.append(child)
|
||||
continue
|
||||
|
||||
if body and isinstance(body[-1], list):
|
||||
body[-1].append(const)
|
||||
else:
|
||||
@ -1306,10 +1325,7 @@ class CodeGenerator(NodeVisitor):
|
||||
else:
|
||||
self.write('to_string(')
|
||||
if self.environment.finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
if getattr(self.environment.finalize,
|
||||
"contextfunction", False):
|
||||
self.write('context, ')
|
||||
self.write(finalize_src)
|
||||
close += 1
|
||||
self.visit(item, frame)
|
||||
self.write(')' * close)
|
||||
@ -1344,16 +1360,7 @@ class CodeGenerator(NodeVisitor):
|
||||
self.write('escape(')
|
||||
close += 1
|
||||
if self.environment.finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
if getattr(self.environment.finalize,
|
||||
'contextfunction', False):
|
||||
self.write('context, ')
|
||||
elif getattr(self.environment.finalize,
|
||||
'evalcontextfunction', False):
|
||||
self.write('context.eval_ctx, ')
|
||||
elif getattr(self.environment.finalize,
|
||||
'environmentfunction', False):
|
||||
self.write('environment, ')
|
||||
self.write(finalize_src)
|
||||
close += 1
|
||||
self.visit(argument, frame)
|
||||
self.write(')' * close + ', ')
|
||||
|
||||
@ -19,6 +19,9 @@ from jinja2 import Environment, Undefined, ChainableUndefined, \
|
||||
from jinja2.compiler import CodeGenerator
|
||||
from jinja2.runtime import Context
|
||||
from jinja2.utils import Cycler
|
||||
from jinja2.utils import contextfunction
|
||||
from jinja2.utils import evalcontextfunction
|
||||
from jinja2.utils import environmentfunction
|
||||
|
||||
|
||||
@pytest.mark.api
|
||||
@ -37,16 +40,50 @@ class TestExtendedAPI(object):
|
||||
tmpl = env.from_string('{{ foo["items"] }}')
|
||||
assert tmpl.render(foo={'items': 42}) == '42'
|
||||
|
||||
def test_finalizer(self, env):
|
||||
def finalize_none_empty(value):
|
||||
if value is None:
|
||||
value = u''
|
||||
return value
|
||||
env = Environment(finalize=finalize_none_empty)
|
||||
tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
|
||||
assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
|
||||
tmpl = env.from_string('<{{ none }}>')
|
||||
assert tmpl.render() == '<>'
|
||||
def test_finalize(self):
|
||||
e = Environment(finalize=lambda v: "" if v is None else v)
|
||||
t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}")
|
||||
assert t.render(seq=(None, 1, "foo")) == "||1|foo"
|
||||
|
||||
def test_finalize_constant_expression(self):
|
||||
e = Environment(finalize=lambda v: "" if v is None else v)
|
||||
t = e.from_string("<{{ none }}>")
|
||||
assert t.render() == "<>"
|
||||
|
||||
def test_no_finalize_template_data(self):
|
||||
e = Environment(finalize=lambda v: type(v).__name__)
|
||||
t = e.from_string("<{{ value }}>")
|
||||
# If template data was finalized, it would print "strintstr".
|
||||
assert t.render(value=123) == "<int>"
|
||||
|
||||
def test_context_finalize(self):
|
||||
@contextfunction
|
||||
def finalize(context, value):
|
||||
return value * context["scale"]
|
||||
|
||||
e = Environment(finalize=finalize)
|
||||
t = e.from_string("{{ value }}")
|
||||
assert t.render(value=5, scale=3) == "15"
|
||||
|
||||
def test_eval_finalize(self):
|
||||
@evalcontextfunction
|
||||
def finalize(eval_ctx, value):
|
||||
return str(eval_ctx.autoescape) + value
|
||||
|
||||
e = Environment(finalize=finalize, autoescape=True)
|
||||
t = e.from_string("{{ value }}")
|
||||
assert t.render(value="<script>") == "True<script>"
|
||||
|
||||
def test_env_autoescape(self):
|
||||
@environmentfunction
|
||||
def finalize(env, value):
|
||||
return " ".join(
|
||||
(env.variable_start_string, repr(value), env.variable_end_string)
|
||||
)
|
||||
|
||||
e = Environment(finalize=finalize)
|
||||
t = e.from_string("{{ value }}")
|
||||
assert t.render(value="hello") == "{{ 'hello' }}"
|
||||
|
||||
def test_cycler(self, env):
|
||||
items = 1, 2, 3
|
||||
|
||||
Loading…
Reference in New Issue
Block a user