preserve quotes between nodes in native env

This commit is contained in:
David Lord 2019-10-22 12:52:59 -07:00
parent afd6c48308
commit e5c042bb7f
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
3 changed files with 52 additions and 18 deletions

View File

@ -52,6 +52,9 @@ Unreleased
- Support :class:`os.PathLike` objects in
:class:`~loader.FileSystemLoader` and :class:`~loader.ModuleLoader`.
:issue:`870`
- :class:`~nativetypes.NativeTemplate` correctly handles quotes
between expressions. ``"'{{ a }}', '{{ b }}'"`` renders as the tuple
``('1', '2')`` rather than the string ``'1, 2'``. :issue:`1020`
Version 2.10.3

View File

@ -9,12 +9,17 @@ from jinja2.environment import Environment, Template
from jinja2.utils import concat, escape
def native_concat(nodes):
"""Return a native Python type from the list of compiled nodes. If the
result is a single node, its value is returned. Otherwise, the nodes are
concatenated as strings. If the result can be parsed with
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
string is returned.
def native_concat(nodes, preserve_quotes=True):
"""Return a native Python type from the list of compiled nodes. If
the result is a single node, its value is returned. Otherwise, the
nodes are concatenated as strings. If the result can be parsed with
:func:`ast.literal_eval`, the parsed value is returned. Otherwise,
the string is returned.
:param nodes: Iterable of nodes to concatenate.
:param preserve_quotes: Whether to re-wrap literal strings with
quotes, to preserve quotes around expressions for later parsing.
Should be ``False`` in :meth:`NativeEnvironment.render`.
"""
head = list(islice(nodes, 2))
@ -22,16 +27,25 @@ def native_concat(nodes):
return None
if len(head) == 1:
out = head[0]
raw = head[0]
else:
if isinstance(nodes, types.GeneratorType):
nodes = chain(head, nodes)
out = u''.join([text_type(v) for v in nodes])
raw = u''.join([text_type(v) for v in nodes])
try:
return literal_eval(out)
literal = literal_eval(raw)
except (ValueError, SyntaxError, MemoryError):
return out
return raw
# If literal_eval returned a string, re-wrap with the original
# quote character to avoid dropping quotes between expression nodes.
# Without this, "'{{ a }}', '{{ b }}'" results in "a, b", but should
# be ('a', 'b').
if preserve_quotes and isinstance(literal, str):
return "{quote}{}{quote}".format(literal, quote=raw[0])
return literal
class NativeCodeGenerator(CodeGenerator):
@ -200,16 +214,17 @@ class NativeCodeGenerator(CodeGenerator):
class NativeTemplate(Template):
def render(self, *args, **kwargs):
"""Render the template to produce a native Python type. If the result
is a single node, its value is returned. Otherwise, the nodes are
concatenated as strings. If the result can be parsed with
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
string is returned.
"""Render the template to produce a native Python type. If the
result is a single node, its value is returned. Otherwise, the
nodes are concatenated as strings. If the result can be parsed
with :func:`ast.literal_eval`, the parsed value is returned.
Otherwise, the string is returned.
"""
vars = dict(*args, **kwargs)
try:
return native_concat(self.root_render_func(self.new_context(vars)))
return native_concat(
self.root_render_func(self.new_context(vars)), preserve_quotes=False
)
except Exception:
exc_info = sys.exc_info()

View File

@ -109,8 +109,24 @@ class TestNativeEnvironment(object):
assert not isinstance(result, type)
assert result in ["<type 'bool'>", "<class 'bool'>"]
def test_string(self, env):
def test_string_literal_var(self, env):
t = env.from_string("[{{ 'all' }}]")
result = t.render()
assert isinstance(result, text_type)
assert result == "[all]"
def test_string_top_level(self, env):
t = env.from_string("'Jinja'")
result = t.render()
assert result == 'Jinja'
def test_tuple_of_variable_strings(self, env):
t = env.from_string("'{{ a }}', 'data', '{{ b }}', b'{{ c }}'")
result = t.render(a=1, b=2, c="bytes")
assert isinstance(result, tuple)
assert result == ("1", "data", "2", b"bytes")
def test_concat_strings_with_quotes(self, env):
t = env.from_string("--host='{{ host }}' --user \"{{ user }}\"")
result = t.render(host="localhost", user="Jinja")
assert result == "--host='localhost' --user \"Jinja\""