From: David Lord Date: Tue, 22 Oct 2019 19:52:59 +0000 (-0700) Subject: preserve quotes between nodes in native env X-Git-Tag: 2.11.0~34^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F1086%2Fhead;p=thirdparty%2Fjinja.git preserve quotes between nodes in native env --- diff --git a/CHANGES.rst b/CHANGES.rst index bc3114de..738e9fed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 diff --git a/jinja2/nativetypes.py b/jinja2/nativetypes.py index 31309bca..b638c91d 100644 --- a/jinja2/nativetypes.py +++ b/jinja2/nativetypes.py @@ -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() diff --git a/tests/test_nativetypes.py b/tests/test_nativetypes.py index 769bbc0a..a98ac5b8 100644 --- a/tests/test_nativetypes.py +++ b/tests/test_nativetypes.py @@ -109,8 +109,24 @@ class TestNativeEnvironment(object): assert not isinstance(result, type) assert result in ["", ""] - 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\""