Repository: djc/askama
Pull request #187 (2019-01-04 15:08:07)

Add support for tuple
Hi there.

I think tuples like below should be supported.
```
{% for (a, b) in v %}
{% let (a, b) = v %}
```
How about this pull request?



Patch:
diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs
index 95a7d17..67ec2c2 100644
--- a/askama_derive/src/generator.rs
+++ b/askama_derive/src/generator.rs
@@ -468,11 +468,7 @@ impl<'a> Generator<'a> {
 
         self.write_buf_writable(buf);
         buf.write("for (_loop_index, ");
-        let targets = self.visit_target(var);
-        for name in &targets {
-            self.locals.insert(name);
-            buf.write(name);
-        }
+        self.visit_target(buf, var);
         match iter {
             Expr::Range(_, _, _) => buf.writeln(&format!(") in ({}).enumerate() {{", expr_code)),
             _ => buf.writeln(&format!(") in (&{}).into_iter().enumerate() {{", expr_code)),
@@ -576,6 +572,15 @@ impl<'a> Generator<'a> {
                 self.locals.insert(name);
                 buf.write(name);
             }
+            Target::Tuple(ref targets) => {
+                buf.write("(");
+                for name in targets {
+                    self.locals.insert(name);
+                    buf.write(name);
+                    buf.write(",");
+                }
+                buf.write(")");
+            }
         }
         buf.writeln(";");
     }
@@ -593,6 +598,15 @@ impl<'a> Generator<'a> {
                 }
                 buf.write(name);
             }
+            Target::Tuple(ref targets) => {
+                buf.write("let (");
+                for name in targets {
+                    self.locals.insert(name);
+                    buf.write(name);
+                    buf.write(",");
+                }
+                buf.write(")");
+            }
         }
         buf.writeln(&format!(" = {};", &expr_buf.buf));
     }
@@ -1024,13 +1038,21 @@ impl<'a> Generator<'a> {
         DisplayWrap::Unwrapped
     }
 
-    fn visit_target_single<'t>(&mut self, name: &'t str) -> Vec<&'t str> {
-        vec![name]
-    }
-
-    fn visit_target<'t>(&mut self, target: &'t Target) -> Vec<&'t str> {
+    fn visit_target(&mut self, buf: &mut Buffer, target: &'a Target) {
         match *target {
-            Target::Name(s) => self.visit_target_single(s),
+            Target::Name(name) => {
+                self.locals.insert(name);
+                buf.write(name);
+            }
+            Target::Tuple(ref targets) => {
+                buf.write("(");
+                for name in targets {
+                    self.locals.insert(name);
+                    buf.write(name);
+                    buf.write(",");
+                }
+                buf.write(")");
+            }
         }
     }
 
diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs
index 81935f6..c5e610e 100644
--- a/askama_derive/src/parser.rs
+++ b/askama_derive/src/parser.rs
@@ -42,6 +42,7 @@ pub enum MatchParameter<'a> {
 #[derive(Debug)]
 pub enum Target<'a> {
     Name(&'a str),
+    Tuple(Vec<&'a str>),
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -275,6 +276,25 @@ named!(target_single<Input, Target>, map!(identifier,
     |s| Target::Name(s)
 ));
 
+named!(target_tuple<Input, Target>, do_parse!(
+    tag!("(") >>
+    args: opt!(do_parse!(
+        arg0: ws!(identifier) >>
+        args: many0!(do_parse!(
+            tag!(",") >>
+            argn: ws!(identifier) >>
+            (argn)
+        )) >>
+        ({
+           let mut res = vec![arg0];
+           res.extend(args);
+           res
+        })
+    )) >>
+    tag!(")") >>
+    (Target::Tuple(args.unwrap_or_default()))
+));
+
 named!(variant_name<Input, MatchVariant>, map!(identifier,
     |s| MatchVariant::Name(s)
 ));
@@ -703,7 +723,7 @@ named_args!(block_match<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!(
 named!(block_let<Input, Node>, do_parse!(
     pws: opt!(tag!("-")) >>
     ws!(tag!("let")) >>
-    var: ws!(target_single) >>
+    var: ws!(alt!(target_single | target_tuple)) >>
     val: opt!(do_parse!(
         ws!(tag!("=")) >>
         val: ws!(expr_any) >>
@@ -720,7 +740,7 @@ named!(block_let<Input, Node>, do_parse!(
 named_args!(block_for<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!(
     pws1: opt!(tag!("-")) >>
     ws!(tag!("for")) >>
-    var: ws!(target_single) >>
+    var: ws!(alt!(target_single | target_tuple)) >>
     ws!(tag!("in")) >>
     iter: ws!(expr_any) >>
     nws1: opt!(tag!("-")) >>


Test patch:
diff --git a/testing/templates/for.html b/testing/templates/for.html
index 8b4032d..f749752 100644
--- a/testing/templates/for.html
+++ b/testing/templates/for.html
@@ -1,3 +1,6 @@
 {% for s in strings %}
   {{- loop.index0 }}. {{ s }}{% if loop.first %} (first){% endif %}
 {% endfor %}
+{% for (s1, s2) in tuple_strings %}
+  {{- loop.index0 }}. {{ s1 }},{{ s2 }}{% if loop.first %} (first){% endif %}
+{% endfor %}
diff --git a/testing/templates/let.html b/testing/templates/let.html
index 5b19255..034e8d4 100644
--- a/testing/templates/let.html
+++ b/testing/templates/let.html
@@ -1,1 +1,2 @@
 {% let v = s %}{{ v }}
+{% let (v1,v2) = t %}{{ v1 }}{{ v2 }}
\ No newline at end of file
diff --git a/testing/tests/loops.rs b/testing/tests/loops.rs
index 3dfad38..b5fac7c 100644
--- a/testing/tests/loops.rs
+++ b/testing/tests/loops.rs
@@ -4,14 +4,19 @@ use askama::Template;
 #[template(path = "for.html")]
 struct ForTemplate<'a> {
     strings: Vec<&'a str>,
+    tuple_strings: Vec<(&'a str, &'a str)>,
 }
 
 #[test]
 fn test_for() {
     let s = ForTemplate {
         strings: vec!["A", "alfa", "1"],
+        tuple_strings: vec![("B", "beta")],
     };
-    assert_eq!(s.render().unwrap(), "0. A (first)\n1. alfa\n2. 1\n");
+    assert_eq!(
+        s.render().unwrap(),
+        "0. A (first)\n1. alfa\n2. 1\n\n0. B,beta (first)\n"
+    );
 }
 
 #[derive(Template)]
diff --git a/testing/tests/vars.rs b/testing/tests/vars.rs
index 87af3f6..04f9ff2 100644
--- a/testing/tests/vars.rs
+++ b/testing/tests/vars.rs
@@ -12,6 +12,22 @@ fn test_let() {
     assert_eq!(t.render().unwrap(), "foo");
 }
 
+#[derive(Template)]
+#[template(path = "let.html")]
+struct LetTupleTemplate<'a> {
+    s: &'a str,
+    t: (&'a str, &'a str),
+}
+
+#[test]
+fn test_let_tuple() {
+    let t = LetTupleTemplate {
+        s: "foo",
+        t: ("bar", "bazz"),
+    };
+    assert_eq!(t.render().unwrap(), "foo\nbarbazz");
+}
+
 #[derive(Template)]
 #[template(path = "let-decl.html")]
 struct LetDeclTemplate<'a> {