From 468f376bfc2cf3e09addac37cd144de56b5f93bf Mon Sep 17 00:00:00 2001 From: Anthony Nowell Date: Wed, 27 Sep 2017 01:53:35 -0600 Subject: implement basic match functionality --- askama_shared/src/generator.rs | 42 +++++++++++++++++++++++++++++++- askama_shared/src/parser.rs | 54 ++++++++++++++++++++++++++++++++++++++++++ testing/templates/match.html | 6 +++++ testing/tests/matches.rs | 18 ++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 testing/templates/match.html create mode 100644 testing/tests/matches.rs diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index dcdea32..6fca3fb 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1,6 +1,6 @@ use filters; use input::TemplateInput; -use parser::{self, Cond, Expr, Macro, Node, Target, WS}; +use parser::{self, Cond, Expr, Macro, Node, Target, When, WS}; use path; use quote::{Tokens, ToTokens}; @@ -339,6 +339,9 @@ impl<'a> Generator<'a> { Node::Cond(ref conds, ref ws) => { self.write_cond(state, conds, ws); }, + Node::Match(ref ws1, ref expr, ref inter, ref arms, ref ws2) => { + self.write_match(state, ws1, expr, inter, arms, ws2); + }, Node::Loop(ref ws1, ref var, ref iter, ref body, ref ws2) => { self.write_loop(state, ws1, var, iter, body, ws2); }, @@ -428,6 +431,43 @@ impl<'a> Generator<'a> { self.writeln("}"); } + fn write_match(&mut self, state: &'a State, ws1: &WS, expr: &Expr, inter: &'a str, arms: + &'a [When], ws2: &WS) { + self.flush_ws(ws1); + if !inter.is_empty() { + self.next_ws = Some(inter); + } + + self.write("match "); + self.visit_expr(expr); + self.writeln(" {"); + + for arm in arms { + let &(ref ws, ref variant, ref params, ref body) = arm; + self.locals.push(); + self.write(variant); + if params.len() > 0 { + self.write("("); + for (i, param) in params.iter().enumerate() { + self.locals.insert(param); + if i > 0 { + self.write(", "); + } + self.write(param); + } + self.write(")"); + } + self.writeln(" => {"); + self.handle_ws(ws); + self.handle(state, body, AstLevel::Nested); + self.writeln("}"); + self.locals.pop(); + } + + self.writeln("}"); + self.handle_ws(ws2); + } + fn write_loop(&mut self, state: &'a State, ws1: &WS, var: &'a Target, iter: &Expr, body: &'a [Node], ws2: &WS) { self.handle_ws(ws1); diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 5bdcb9b..ec4dcbd 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -40,6 +40,7 @@ pub enum Node<'a> { LetDecl(WS, Target<'a>), Let(WS, Target<'a>, Expr<'a>), Cond(Vec<(WS, Option>, Vec>)>, WS), + Match(WS, Expr<'a>, &'a str, Vec>, WS), Loop(WS, Target<'a>, Expr<'a>, Vec>, WS), Extends(Expr<'a>), BlockDef(WS, &'a str, Vec>, WS), @@ -49,6 +50,7 @@ pub enum Node<'a> { } pub type Cond<'a> = (WS, Option>, Vec>); +pub type When<'a> = (WS, &'a str, Vec<&'a str>, Vec>); fn split_ws_parts(s: &[u8]) -> Node { if s.is_empty() { @@ -210,6 +212,12 @@ named!(parameters>, do_parse!( (vals.unwrap_or_default()) )); +named!(with_parameters>, do_parse!( + tag_s!("with") >> + params: ws!(parameters) >> + (params) +)); + named!(expr_group, map!( delimited!(char!('('), expr_any, char!(')')), |s| Expr::Group(Box::new(s)) @@ -359,6 +367,51 @@ named!(block_if, do_parse!( }) )); +named!(when_block, do_parse!( + tag_s!("{%") >> + pws: opt!(tag_s!("-")) >> + ws!(tag_s!("when")) >> + variant: ws!(identifier) >> + params: opt!(ws!(with_parameters)) >> + nws: opt!(tag_s!("-")) >> + tag_s!("%}") >> + block: parse_template >> + (WS(pws.is_some(), nws.is_some()), variant, params.unwrap_or_default(), block) +)); + +named!(block_match, do_parse!( + pws1: opt!(tag_s!("-")) >> + ws!(tag_s!("match")) >> + expr: ws!(expr_any) >> + nws1: opt!(tag_s!("-")) >> + tag_s!("%}") >> + inter: take_content >> + arms: many1!(when_block) >> + ws!(tag_s!("{%")) >> + pws2: opt!(tag_s!("-")) >> + ws!(tag_s!("endmatch")) >> + nws2: opt!(tag_s!("-")) >> + ({ + let inter = match inter { + Node::Lit(lws, val, rws) => { + assert!(val.is_empty(), + "only whitespace allowed between match and first when, found {}", val); + assert!(rws.is_empty(), + "only whitespace allowed between match and first when, found {}", rws); + lws + }, + _ => panic!("only literals allowed between match and first when"), + }; + Node::Match( + WS(pws1.is_some(), nws1.is_some()), + expr, + inter, + arms, + WS(pws2.is_some(), nws2.is_some()), + ) + }) +)); + named!(block_let, do_parse!( pws: opt!(tag_s!("-")) >> ws!(tag_s!("let")) >> @@ -471,6 +524,7 @@ named!(block_node, do_parse!( block_let | block_if | block_for | + block_match | block_extends | block_include | block_import | diff --git a/testing/templates/match.html b/testing/templates/match.html new file mode 100644 index 0000000..51950b4 --- /dev/null +++ b/testing/templates/match.html @@ -0,0 +1,6 @@ +{% match item %} +{% when Some with (val) %} +Found {{val}} +{% when None %} +Not Found +{% endmatch %} diff --git a/testing/tests/matches.rs b/testing/tests/matches.rs new file mode 100644 index 0000000..4a55615 --- /dev/null +++ b/testing/tests/matches.rs @@ -0,0 +1,18 @@ +#[macro_use] +extern crate askama; + +use askama::Template; + +#[derive(Template)] +#[template(path = "match.html")] +struct MatchTemplate<'a> { + item: Option<&'a str>, +} + +#[test] +fn test_match_option() { + let s = MatchTemplate { + item: Some("foo"), + }; + assert_eq!(s.render().unwrap(), "\n\nFound foo\n"); +} -- cgit