aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askama_shared/src/generator.rs19
-rw-r--r--askama_shared/src/parser.rs48
-rw-r--r--testing/tests/let_destructoring.rs107
3 files changed, 166 insertions, 8 deletions
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index a7f52ca..36d1841 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -824,9 +824,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
_ => false,
}
}
- Target::Tuple(targets) => targets
+ Target::Tuple(_, targets) => targets
.iter()
.any(|target| self.is_shadowing_variable(target)),
+ Target::Struct(_, named_targets) => named_targets
+ .iter()
+ .any(|(_, target)| self.is_shadowing_variable(target)),
}
}
@@ -1521,7 +1524,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}
buf.write(name);
}
- Target::Tuple(targets) => {
+ Target::Tuple(path, targets) => {
+ buf.write(&path.join("::"));
buf.write("(");
for target in targets {
self.visit_target(buf, initialized, target);
@@ -1529,6 +1533,17 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}
buf.write(")");
}
+ Target::Struct(path, targets) => {
+ buf.write(&path.join("::"));
+ buf.write(" { ");
+ for (name, target) in targets {
+ buf.write(normalize_identifier(name));
+ buf.write(": ");
+ self.visit_target(buf, initialized, target);
+ buf.write(",");
+ }
+ buf.write(" }");
+ }
}
}
diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs
index a1aabe7..261887b 100644
--- a/askama_shared/src/parser.rs
+++ b/askama_shared/src/parser.rs
@@ -4,7 +4,7 @@ use nom::character::complete::{anychar, char, digit1};
use nom::combinator::{complete, map, opt, recognize, value};
use nom::error::{Error, ParseError};
use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
-use nom::sequence::{delimited, pair, preceded, tuple};
+use nom::sequence::{delimited, pair, preceded, terminated, tuple};
use nom::{self, error_position, Compare, IResult, InputTake};
use std::str;
@@ -138,7 +138,8 @@ pub struct Macro<'a> {
#[derive(Debug, PartialEq)]
pub enum Target<'a> {
Name(&'a str),
- Tuple(Vec<Target<'a>>),
+ Tuple(Vec<&'a str>, Vec<Target<'a>>),
+ Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -404,15 +405,21 @@ fn variant_path(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
})(i)
}
+fn named_target(i: &[u8]) -> IResult<&[u8], (&str, Target<'_>)> {
+ let (i, (src, target)) = pair(identifier, opt(preceded(ws(tag(":")), target)))(i)?;
+ Ok((i, (src, target.unwrap_or(Target::Name(src)))))
+}
+
fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> {
let mut opt_opening_paren = map(opt(ws(tag("("))), |o| o.is_some());
let mut opt_closing_paren = map(opt(ws(tag(")"))), |o| o.is_some());
+ // match tuples and unused parentheses
let (i, target_is_tuple) = opt_opening_paren(i)?;
if target_is_tuple {
let (i, is_empty_tuple) = opt_closing_paren(i)?;
if is_empty_tuple {
- return Ok((i, Target::Tuple(vec![])));
+ return Ok((i, Target::Tuple(Vec::new(), Vec::new())));
}
let (i, first_target) = target(i)?;
@@ -429,10 +436,39 @@ fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> {
opt(ws(tag(","))),
ws(tag(")")),
))(i)?;
- Ok((i, Target::Tuple(targets)))
- } else {
- map(identifier, Target::Name)(i)
+ return Ok((i, Target::Tuple(Vec::new(), targets)));
+ }
+
+ // match structs
+ let (i, path) = opt(path)(i)?;
+ if let Some(path) = path {
+ let (i, is_unnamed_struct) = opt_opening_paren(i)?;
+ if is_unnamed_struct {
+ let (i, targets) = alt((
+ map(tag(")"), |_| Vec::new()),
+ terminated(
+ separated_list1(ws(tag(",")), target),
+ pair(opt(ws(tag(","))), ws(tag(")"))),
+ ),
+ ))(i)?;
+ return Ok((i, Target::Tuple(path, targets)));
+ } else {
+ let (i, targets) = preceded(
+ ws(tag("{")),
+ alt((
+ map(tag("}"), |_| Vec::new()),
+ terminated(
+ separated_list1(ws(tag(",")), named_target),
+ pair(opt(ws(tag(","))), ws(tag("}"))),
+ ),
+ )),
+ )(i)?;
+ return Ok((i, Target::Struct(path, targets)));
+ }
}
+
+ // neither nor struct
+ map(identifier, Target::Name)(i)
}
fn variant_name(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
diff --git a/testing/tests/let_destructoring.rs b/testing/tests/let_destructoring.rs
new file mode 100644
index 0000000..20f7fff
--- /dev/null
+++ b/testing/tests/let_destructoring.rs
@@ -0,0 +1,107 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(source = "{% let (a, b, c) = v %}{{a}}{{b}}{{c}}", ext = "txt")]
+struct LetDestructoringTuple {
+ v: (i32, i32, i32),
+}
+
+#[test]
+fn test_let_destruct_tuple() {
+ let t = LetDestructoringTuple { v: (1, 2, 3) };
+ assert_eq!(t.render().unwrap(), "123");
+}
+
+struct UnnamedStruct(i32, i32, i32);
+
+#[derive(Template)]
+#[template(
+ source = "{% let UnnamedStruct(a, b, c) = v %}{{a}}{{b}}{{c}}",
+ ext = "txt"
+)]
+struct LetDestructoringUnnamedStruct {
+ v: UnnamedStruct,
+}
+
+#[test]
+fn test_let_destruct_unnamed_struct() {
+ let t = LetDestructoringUnnamedStruct {
+ v: UnnamedStruct(1, 2, 3),
+ };
+ assert_eq!(t.render().unwrap(), "123");
+}
+
+#[derive(Template)]
+#[template(
+ source = "{% let UnnamedStruct(a, b, c) = v %}{{a}}{{b}}{{c}}",
+ ext = "txt"
+)]
+struct LetDestructoringUnnamedStructRef<'a> {
+ v: &'a UnnamedStruct,
+}
+
+#[test]
+fn test_let_destruct_unnamed_struct_ref() {
+ let v = UnnamedStruct(1, 2, 3);
+ let t = LetDestructoringUnnamedStructRef { v: &v };
+ assert_eq!(t.render().unwrap(), "123");
+}
+
+struct NamedStruct {
+ a: i32,
+ b: i32,
+ c: i32,
+}
+
+#[derive(Template)]
+#[template(
+ source = "{% let NamedStruct { a, b: d, c } = v %}{{a}}{{d}}{{c}}",
+ ext = "txt"
+)]
+struct LetDestructoringNamedStruct {
+ v: NamedStruct,
+}
+
+#[test]
+fn test_let_destruct_named_struct() {
+ let t = LetDestructoringNamedStruct {
+ v: NamedStruct { a: 1, b: 2, c: 3 },
+ };
+ assert_eq!(t.render().unwrap(), "123");
+}
+
+#[derive(Template)]
+#[template(
+ source = "{% let NamedStruct { a, b: d, c } = v %}{{a}}{{d}}{{c}}",
+ ext = "txt"
+)]
+struct LetDestructoringNamedStructRef<'a> {
+ v: &'a NamedStruct,
+}
+
+#[test]
+fn test_let_destruct_named_struct_ref() {
+ let v = NamedStruct { a: 1, b: 2, c: 3 };
+ let t = LetDestructoringNamedStructRef { v: &v };
+ assert_eq!(t.render().unwrap(), "123");
+}
+
+mod some {
+ pub mod path {
+ pub struct Struct<'a>(pub &'a str);
+ }
+}
+
+#[derive(Template)]
+#[template(source = "{% let some::path::Struct(v) = v %}{{v}}", ext = "txt")]
+struct LetDestructoringWithPath<'a> {
+ v: some::path::Struct<'a>,
+}
+
+#[test]
+fn test_let_destruct_with_path() {
+ let t = LetDestructoringWithPath {
+ v: some::path::Struct("hello"),
+ };
+ assert_eq!(t.render().unwrap(), "hello");
+}