]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
preserve quotes between nodes in native env 1086/head
authorDavid Lord <davidism@gmail.com>
Tue, 22 Oct 2019 19:52:59 +0000 (12:52 -0700)
committerDavid Lord <davidism@gmail.com>
Tue, 22 Oct 2019 19:58:17 +0000 (12:58 -0700)
CHANGES.rst
jinja2/nativetypes.py
tests/test_nativetypes.py

index bc3114def59b800083df6d352a84261d342bd6a0..738e9fedea08fc9f30754277c3d92c42e4a5e13b 100644 (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
index 31309bca7fae4e4c148fa08f1e91ae45f9257f05..b638c91df0335e402d7b7377dabab488380d84d1 100644 (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()
 
index 769bbc0a584af3aaf43d77f47bf84ee9dbb27780..a98ac5b8d1ef3b14240afa65683e706a885714f5 100644 (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\""