mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 15:25:19 +00:00
Merge pull request #36 from Kijewski/rest-pattern-struct#2
Add support for `..` in let pattern matching for structs (alternative take)
This commit is contained in:
commit
eb419049bf
@ -12,10 +12,9 @@ use crate::input::{Source, TemplateInput};
|
|||||||
use crate::{CompileError, CRATE};
|
use crate::{CompileError, CRATE};
|
||||||
|
|
||||||
use parser::node::{
|
use parser::node::{
|
||||||
Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Target, Whitespace,
|
Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
|
||||||
Ws,
|
|
||||||
};
|
};
|
||||||
use parser::{Expr, Filter, Node, WithSpan};
|
use parser::{Expr, Filter, Node, Target, WithSpan};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub(crate) struct Generator<'a> {
|
pub(crate) struct Generator<'a> {
|
||||||
@ -1788,8 +1787,8 @@ impl<'a> Generator<'a> {
|
|||||||
target: &Target<'a>,
|
target: &Target<'a>,
|
||||||
) {
|
) {
|
||||||
match target {
|
match target {
|
||||||
Target::Name("_") => {
|
Target::Placeholder(s) | Target::Rest(s) => {
|
||||||
buf.write("_");
|
buf.write(s);
|
||||||
}
|
}
|
||||||
Target::Name(name) => {
|
Target::Name(name) => {
|
||||||
let name = normalize_identifier(name);
|
let name = normalize_identifier(name);
|
||||||
@ -1824,6 +1823,11 @@ impl<'a> Generator<'a> {
|
|||||||
buf.write(SeparatedPath(path));
|
buf.write(SeparatedPath(path));
|
||||||
buf.write(" { ");
|
buf.write(" { ");
|
||||||
for (name, target) in targets {
|
for (name, target) in targets {
|
||||||
|
if let Target::Rest(s) = target {
|
||||||
|
buf.write(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
buf.write(normalize_identifier(name));
|
buf.write(normalize_identifier(name));
|
||||||
buf.write(": ");
|
buf.write(": ");
|
||||||
self.visit_target(buf, initialized, false, target);
|
self.visit_target(buf, initialized, false, target);
|
||||||
|
@ -22,6 +22,9 @@ pub mod expr;
|
|||||||
pub use expr::{Expr, Filter};
|
pub use expr::{Expr, Filter};
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
|
||||||
|
mod target;
|
||||||
|
pub use target::Target;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -3,19 +3,15 @@ use std::str;
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{tag, take_till};
|
use nom::bytes::complete::{tag, take_till};
|
||||||
use nom::character::complete::char;
|
use nom::character::complete::char;
|
||||||
use nom::combinator::{
|
use nom::combinator::{complete, consumed, cut, eof, map, not, opt, peek, recognize, value};
|
||||||
complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value,
|
|
||||||
};
|
|
||||||
use nom::error::ErrorKind;
|
use nom::error::ErrorKind;
|
||||||
use nom::error_position;
|
use nom::error_position;
|
||||||
use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
|
use nom::multi::{many0, many1, separated_list0};
|
||||||
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
|
use nom::sequence::{delimited, pair, preceded, tuple};
|
||||||
|
|
||||||
use crate::{not_ws, ErrorContext, ParseResult, WithSpan};
|
use crate::{
|
||||||
|
filter, identifier, is_ws, keyword, not_ws, skip_till, str_lit, ws, ErrorContext, Expr, Filter,
|
||||||
use super::{
|
ParseResult, State, Target, WithSpan,
|
||||||
bool_lit, char_lit, filter, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till,
|
|
||||||
str_lit, ws, Expr, Filter, PathOrIdentifier, State,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -177,150 +173,6 @@ impl<'a> Node<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Target<'a> {
|
|
||||||
Name(&'a str),
|
|
||||||
Tuple(Vec<&'a str>, Vec<Target<'a>>),
|
|
||||||
Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
|
|
||||||
NumLit(&'a str),
|
|
||||||
StrLit(&'a str),
|
|
||||||
CharLit(&'a str),
|
|
||||||
BoolLit(&'a str),
|
|
||||||
Path(Vec<&'a str>),
|
|
||||||
OrChain(Vec<Target<'a>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Target<'a> {
|
|
||||||
/// Parses multiple targets with `or` separating them
|
|
||||||
pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
|
||||||
map(
|
|
||||||
separated_list1(ws(tag("or")), |i| s.nest(i, |i| Self::parse_one(i, s))),
|
|
||||||
|mut opts| match opts.len() {
|
|
||||||
1 => opts.pop().unwrap(),
|
|
||||||
_ => Self::OrChain(opts),
|
|
||||||
},
|
|
||||||
)(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a single target without an `or`, unless it is wrapped in parentheses.
|
|
||||||
fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
|
||||||
let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
|
|
||||||
let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some());
|
|
||||||
let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
|
|
||||||
|
|
||||||
let (i, lit) = opt(Self::lit)(i)?;
|
|
||||||
if let Some(lit) = lit {
|
|
||||||
return Ok((i, lit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, Self::Tuple(Vec::new(), Vec::new())));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (i, first_target) = Self::parse(i, s)?;
|
|
||||||
let (i, is_unused_paren) = opt_closing_paren(i)?;
|
|
||||||
if is_unused_paren {
|
|
||||||
return Ok((i, first_target));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut targets = vec![first_target];
|
|
||||||
let (i, _) = cut(tuple((
|
|
||||||
fold_many0(
|
|
||||||
preceded(ws(char(',')), |i| Self::parse(i, s)),
|
|
||||||
|| (),
|
|
||||||
|_, target| {
|
|
||||||
targets.push(target);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
opt(ws(char(','))),
|
|
||||||
ws(cut(char(')'))),
|
|
||||||
)))(i)?;
|
|
||||||
return Ok((i, Self::Tuple(Vec::new(), targets)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = |i| {
|
|
||||||
map_res(path_or_identifier, |v| match v {
|
|
||||||
PathOrIdentifier::Path(v) => Ok(v),
|
|
||||||
PathOrIdentifier::Identifier(v) => Err(v),
|
|
||||||
})(i)
|
|
||||||
};
|
|
||||||
|
|
||||||
// match structs
|
|
||||||
let (i, path) = opt(path)(i)?;
|
|
||||||
if let Some(path) = path {
|
|
||||||
let i_before_matching_with = i;
|
|
||||||
let (i, _) = opt(ws(keyword("with")))(i)?;
|
|
||||||
|
|
||||||
let (i, is_unnamed_struct) = opt_opening_paren(i)?;
|
|
||||||
if is_unnamed_struct {
|
|
||||||
let (i, targets) = alt((
|
|
||||||
map(char(')'), |_| Vec::new()),
|
|
||||||
terminated(
|
|
||||||
cut(separated_list1(ws(char(',')), |i| Self::parse(i, s))),
|
|
||||||
pair(opt(ws(char(','))), ws(cut(char(')')))),
|
|
||||||
),
|
|
||||||
))(i)?;
|
|
||||||
return Ok((i, Self::Tuple(path, targets)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (i, is_named_struct) = opt_opening_brace(i)?;
|
|
||||||
if is_named_struct {
|
|
||||||
let (i, targets) = alt((
|
|
||||||
map(char('}'), |_| Vec::new()),
|
|
||||||
terminated(
|
|
||||||
cut(separated_list1(ws(char(',')), |i| Self::named(i, s))),
|
|
||||||
pair(opt(ws(char(','))), ws(cut(char('}')))),
|
|
||||||
),
|
|
||||||
))(i)?;
|
|
||||||
return Ok((i, Self::Struct(path, targets)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok((i_before_matching_with, Self::Path(path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// neither literal nor struct nor path
|
|
||||||
let (new_i, name) = identifier(i)?;
|
|
||||||
Ok((new_i, Self::verify_name(i, name)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lit(i: &'a str) -> ParseResult<'a, Self> {
|
|
||||||
alt((
|
|
||||||
map(str_lit, Self::StrLit),
|
|
||||||
map(char_lit, Self::CharLit),
|
|
||||||
map(num_lit, Self::NumLit),
|
|
||||||
map(bool_lit, Self::BoolLit),
|
|
||||||
))(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> {
|
|
||||||
let (i, (src, target)) = pair(
|
|
||||||
identifier,
|
|
||||||
opt(preceded(ws(char(':')), |i| Self::parse(i, s))),
|
|
||||||
)(init_i)?;
|
|
||||||
|
|
||||||
let target = match target {
|
|
||||||
Some(target) => target,
|
|
||||||
None => Self::verify_name(init_i, src)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((i, (src, target)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_name(input: &'a str, name: &'a str) -> Result<Self, nom::Err<ErrorContext<'a>>> {
|
|
||||||
match name {
|
|
||||||
"self" | "writer" => Err(nom::Err::Failure(ErrorContext::new(
|
|
||||||
format!("cannot use `{name}` as a name"),
|
|
||||||
input,
|
|
||||||
))),
|
|
||||||
_ => Ok(Self::Name(name)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct When<'a> {
|
pub struct When<'a> {
|
||||||
pub ws: Ws,
|
pub ws: Ws,
|
||||||
@ -347,7 +199,7 @@ impl<'a> When<'a> {
|
|||||||
WithSpan::new(
|
WithSpan::new(
|
||||||
Self {
|
Self {
|
||||||
ws: Ws(pws, nws),
|
ws: Ws(pws, nws),
|
||||||
target: Target::Name("_"),
|
target: Target::Placeholder("_"),
|
||||||
nodes,
|
nodes,
|
||||||
},
|
},
|
||||||
start,
|
start,
|
||||||
|
213
rinja_parser/src/target.rs
Normal file
213
rinja_parser/src/target.rs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::{char, one_of};
|
||||||
|
use nom::combinator::{consumed, map, map_res, opt};
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use nom::sequence::{pair, preceded};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws,
|
||||||
|
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Target<'a> {
|
||||||
|
Name(&'a str),
|
||||||
|
Tuple(Vec<&'a str>, Vec<Target<'a>>),
|
||||||
|
Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
|
||||||
|
NumLit(&'a str),
|
||||||
|
StrLit(&'a str),
|
||||||
|
CharLit(&'a str),
|
||||||
|
BoolLit(&'a str),
|
||||||
|
Path(Vec<&'a str>),
|
||||||
|
OrChain(Vec<Target<'a>>),
|
||||||
|
Placeholder(&'a str),
|
||||||
|
Rest(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Target<'a> {
|
||||||
|
/// Parses multiple targets with `or` separating them
|
||||||
|
pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||||
|
map(
|
||||||
|
separated_list1(ws(tag("or")), |i| s.nest(i, |i| Self::parse_one(i, s))),
|
||||||
|
|mut opts| match opts.len() {
|
||||||
|
1 => opts.pop().unwrap(),
|
||||||
|
_ => Self::OrChain(opts),
|
||||||
|
},
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a single target without an `or`, unless it is wrapped in parentheses.
|
||||||
|
fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||||
|
let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
|
||||||
|
let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
|
||||||
|
|
||||||
|
let (i, lit) = opt(Self::lit)(i)?;
|
||||||
|
if let Some(lit) = lit {
|
||||||
|
return Ok((i, lit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// match tuples and unused parentheses
|
||||||
|
let (i, target_is_tuple) = opt_opening_paren(i)?;
|
||||||
|
if target_is_tuple {
|
||||||
|
let (i, (singleton, mut targets)) = collect_targets(i, s, ')', Self::unnamed)?;
|
||||||
|
if singleton {
|
||||||
|
return Ok((i, targets.pop().unwrap()));
|
||||||
|
}
|
||||||
|
return Ok((i, Self::Tuple(Vec::new(), only_one_rest_pattern(targets)?)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = |i| {
|
||||||
|
map_res(path_or_identifier, |v| match v {
|
||||||
|
PathOrIdentifier::Path(v) => Ok(v),
|
||||||
|
PathOrIdentifier::Identifier(v) => Err(v),
|
||||||
|
})(i)
|
||||||
|
};
|
||||||
|
|
||||||
|
// match structs
|
||||||
|
let (i, path) = opt(path)(i)?;
|
||||||
|
if let Some(path) = path {
|
||||||
|
let i_before_matching_with = i;
|
||||||
|
let (i, _) = opt(ws(keyword("with")))(i)?;
|
||||||
|
|
||||||
|
let (i, is_unnamed_struct) = opt_opening_paren(i)?;
|
||||||
|
if is_unnamed_struct {
|
||||||
|
let (i, (_, targets)) = collect_targets(i, s, ')', Self::unnamed)?;
|
||||||
|
return Ok((i, Self::Tuple(path, only_one_rest_pattern(targets)?)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (i, is_named_struct) = opt_opening_brace(i)?;
|
||||||
|
if is_named_struct {
|
||||||
|
let (i, (_, targets)) = collect_targets(i, s, '}', Self::named)?;
|
||||||
|
return Ok((i, Self::Struct(path, targets)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((i_before_matching_with, Self::Path(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither literal nor struct nor path
|
||||||
|
let (new_i, name) = identifier(i)?;
|
||||||
|
let target = match name {
|
||||||
|
"_" => Self::Placeholder(name),
|
||||||
|
_ => verify_name(i, name)?,
|
||||||
|
};
|
||||||
|
Ok((new_i, target))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lit(i: &'a str) -> ParseResult<'a, Self> {
|
||||||
|
alt((
|
||||||
|
map(str_lit, Self::StrLit),
|
||||||
|
map(char_lit, Self::CharLit),
|
||||||
|
map(num_lit, Self::NumLit),
|
||||||
|
map(bool_lit, Self::BoolLit),
|
||||||
|
))(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unnamed(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||||
|
alt((Self::rest, |i| Self::parse(i, s)))(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> {
|
||||||
|
let (i, rest) = opt(consumed(Self::rest))(init_i)?;
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
let (_, chr) = ws(opt(one_of(",:")))(i)?;
|
||||||
|
if let Some(chr) = chr {
|
||||||
|
return Err(nom::Err::Failure(ErrorContext::new(
|
||||||
|
format!(
|
||||||
|
"unexpected `{chr}` character after `..`\n\
|
||||||
|
note that in a named struct, `..` must come last to ignore other members"
|
||||||
|
),
|
||||||
|
i,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return Ok((i, rest));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (i, (src, target)) = pair(
|
||||||
|
identifier,
|
||||||
|
opt(preceded(ws(char(':')), |i| Self::parse(i, s))),
|
||||||
|
)(init_i)?;
|
||||||
|
|
||||||
|
if src == "_" {
|
||||||
|
return Err(nom::Err::Failure(ErrorContext::new(
|
||||||
|
"cannot use placeholder `_` as source in named struct",
|
||||||
|
init_i,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = match target {
|
||||||
|
Some(target) => target,
|
||||||
|
None => verify_name(init_i, src)?,
|
||||||
|
};
|
||||||
|
Ok((i, (src, target)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rest(i: &'a str) -> ParseResult<'a, Self> {
|
||||||
|
map(tag(".."), Self::Rest)(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_name<'a>(
|
||||||
|
input: &'a str,
|
||||||
|
name: &'a str,
|
||||||
|
) -> Result<Target<'a>, nom::Err<ErrorContext<'a>>> {
|
||||||
|
match name {
|
||||||
|
"self" | "writer" => Err(nom::Err::Failure(ErrorContext::new(
|
||||||
|
format!("cannot use `{name}` as a name"),
|
||||||
|
input,
|
||||||
|
))),
|
||||||
|
_ => Ok(Target::Name(name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_targets<'a, T>(
|
||||||
|
i: &'a str,
|
||||||
|
s: &State<'_>,
|
||||||
|
delim: char,
|
||||||
|
mut one: impl FnMut(&'a str, &State<'_>) -> ParseResult<'a, T>,
|
||||||
|
) -> ParseResult<'a, (bool, Vec<T>)> {
|
||||||
|
let opt_comma = |i| map(ws(opt(char(','))), |o| o.is_some())(i);
|
||||||
|
let opt_end = |i| map(ws(opt(char(delim))), |o| o.is_some())(i);
|
||||||
|
|
||||||
|
let (i, has_end) = opt_end(i)?;
|
||||||
|
if has_end {
|
||||||
|
return Ok((i, (false, Vec::new())));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (i, targets) = opt(separated_list1(ws(char(',')), |i| one(i, s)))(i)?;
|
||||||
|
let Some(targets) = targets else {
|
||||||
|
return Err(nom::Err::Failure(ErrorContext::new(
|
||||||
|
"expected comma separated list of members",
|
||||||
|
i,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (i, (has_comma, has_end)) = pair(opt_comma, opt_end)(i)?;
|
||||||
|
if !has_end {
|
||||||
|
let msg = match has_comma {
|
||||||
|
true => format!("expected member, or `{delim}` as terminator"),
|
||||||
|
false => format!("expected `,` for more members, or `{delim}` as terminator"),
|
||||||
|
};
|
||||||
|
return Err(nom::Err::Failure(ErrorContext::new(msg, i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let singleton = !has_comma && targets.len() == 1;
|
||||||
|
Ok((i, (singleton, targets)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn only_one_rest_pattern(targets: Vec<Target<'_>>) -> Result<Vec<Target<'_>>, ParseErr<'_>> {
|
||||||
|
let snd_wildcard = targets
|
||||||
|
.iter()
|
||||||
|
.filter_map(|t| match t {
|
||||||
|
Target::Rest(s) => Some(s),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.nth(1);
|
||||||
|
if let Some(snd_wildcard) = snd_wildcard {
|
||||||
|
return Err(nom::Err::Failure(ErrorContext::new(
|
||||||
|
"`..` can only be used once per tuple pattern",
|
||||||
|
snd_wildcard,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(targets)
|
||||||
|
}
|
@ -119,3 +119,73 @@ fn test_let_destruct_with_path_and_with_keyword() {
|
|||||||
};
|
};
|
||||||
assert_eq!(t.render().unwrap(), "hello");
|
assert_eq!(t.render().unwrap(), "hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "
|
||||||
|
{%- if let RestPattern2 { a, b } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
{%- if let RestPattern2 { a, b, } = x -%}hello {{ b }}{%- endif -%}
|
||||||
|
{%- if let RestPattern2 { a, .. } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
",
|
||||||
|
ext = "html"
|
||||||
|
)]
|
||||||
|
struct RestPattern {
|
||||||
|
x: RestPattern2,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RestPattern2 {
|
||||||
|
a: u32,
|
||||||
|
b: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_has_rest_pattern() {
|
||||||
|
let t = RestPattern {
|
||||||
|
x: RestPattern2 { a: 0, b: 1 },
|
||||||
|
};
|
||||||
|
assert_eq!(t.render().unwrap(), "hello 0hello 1hello 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct X {
|
||||||
|
a: u32,
|
||||||
|
b: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "
|
||||||
|
{%- if let X { a, .. } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
",
|
||||||
|
ext = "html"
|
||||||
|
)]
|
||||||
|
struct T1 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_t1() {
|
||||||
|
let t = T1 {
|
||||||
|
x: X { a: 1, b: 2 },
|
||||||
|
};
|
||||||
|
assert_eq!(t.render().unwrap(), "hello 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "
|
||||||
|
{%- if let X { .. } = x -%}hello{%- endif -%}
|
||||||
|
",
|
||||||
|
ext = "html"
|
||||||
|
)]
|
||||||
|
struct T2 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_t2() {
|
||||||
|
let t = T2 {
|
||||||
|
x: X { a: 1, b: 2 },
|
||||||
|
};
|
||||||
|
assert_eq!(t.render().unwrap(), "hello");
|
||||||
|
}
|
||||||
|
78
testing/tests/rest_pattern.rs
Normal file
78
testing/tests/rest_pattern.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use rinja::Template;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "{% if let (a, ..) = abc %}-{{a}}-{% endif %}", ext = "txt")]
|
||||||
|
struct Tmpl {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-1-");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ab() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{% if let (a, b, ..) = abc %}-{{a}}{{b}}-{% endif %}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct Tmpl {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-12-");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn abc() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{% if let (a, b, c, ..) = abc %}-{{a}}{{b}}{{c}}-{% endif %}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct Tmpl1 {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl1 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||||
|
|
||||||
|
assert_eq!(Tmpl2 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{% if let (a, b, c, ..) = abc %}-{{a}}{{b}}{{c}}-{% endif %}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct Tmpl2 {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl2 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bc() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{% if let (.., b, c) = abc %}-{{b}}{{c}}-{% endif %}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct Tmpl {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-23-");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn c() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "{% if let (.., c) = abc %}-{{c}}-{% endif %}", ext = "txt")]
|
||||||
|
struct Tmpl {
|
||||||
|
abc: (u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-3-");
|
||||||
|
}
|
48
testing/tests/ui/let_destructuring_has_rest.rs
Normal file
48
testing/tests/ui/let_destructuring_has_rest.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use rinja::Template;
|
||||||
|
|
||||||
|
struct X {
|
||||||
|
a: u32,
|
||||||
|
b: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "
|
||||||
|
{%- if let X { a, .., } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
", ext = "html")]
|
||||||
|
struct T1 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "
|
||||||
|
{%- if let X { a .. } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
", ext = "html")]
|
||||||
|
struct T2 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "
|
||||||
|
{%- if let X { a, 1 } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
", ext = "html")]
|
||||||
|
struct T3 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "
|
||||||
|
{%- if let X { a, .., b } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
", ext = "html")]
|
||||||
|
struct T4 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = "
|
||||||
|
{%- if let X { .., b } = x -%}hello {{ a }}{%- endif -%}
|
||||||
|
", ext = "html")]
|
||||||
|
struct T5 {
|
||||||
|
x: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
52
testing/tests/ui/let_destructuring_has_rest.stderr
Normal file
52
testing/tests/ui/let_destructuring_has_rest.stderr
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
error: unexpected `,` character after `..`
|
||||||
|
note that in a named struct, `..` must come last to ignore other members
|
||||||
|
failed to parse template source at row 2, column 20 near:
|
||||||
|
", } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||||
|
--> tests/ui/let_destructuring_has_rest.rs:8:10
|
||||||
|
|
|
||||||
|
8 | #[derive(Template)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: expected `,` for more members, or `}` as terminator
|
||||||
|
failed to parse template source at row 2, column 17 near:
|
||||||
|
".. } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||||
|
--> tests/ui/let_destructuring_has_rest.rs:16:10
|
||||||
|
|
|
||||||
|
16 | #[derive(Template)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: expected member, or `}` as terminator
|
||||||
|
failed to parse template source at row 2, column 18 near:
|
||||||
|
"1 } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||||
|
--> tests/ui/let_destructuring_has_rest.rs:24:10
|
||||||
|
|
|
||||||
|
24 | #[derive(Template)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: unexpected `,` character after `..`
|
||||||
|
note that in a named struct, `..` must come last to ignore other members
|
||||||
|
failed to parse template source at row 2, column 20 near:
|
||||||
|
", b } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||||
|
--> tests/ui/let_destructuring_has_rest.rs:32:10
|
||||||
|
|
|
||||||
|
32 | #[derive(Template)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: unexpected `,` character after `..`
|
||||||
|
note that in a named struct, `..` must come last to ignore other members
|
||||||
|
failed to parse template source at row 2, column 17 near:
|
||||||
|
", b } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||||
|
--> tests/ui/let_destructuring_has_rest.rs:40:10
|
||||||
|
|
|
||||||
|
40 | #[derive(Template)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
Loading…
x
Reference in New Issue
Block a user