Repository: Keats/tera (rust)

Issue:
Support removing whitespace around expressions
I had some html like this:

```html
<li><a class="p-category"
  href="{{ tag_url }}">
  {{ tag }}
</a></li>
```
This produces a space that is underlined because the whitespace is included.

![Screen Shot of link with trailing whitespace underlined](https://user-images.githubusercontent.com/2229184/60851527-1a133980-a1b9-11e9-9ffe-4c390952333c.png)

Removing the spaces in the template removes the trailing whitespace.

```html
<li><a class="p-category"
  href="{{ tag_url }}">{{ tag }}</a></li>
```

![Screen Shot of link with trailing whitespace removed](https://user-images.githubusercontent.com/2229184/60851537-25666500-a1b9-11e9-801e-15956d58e0d3.png)

Since whitespace removal for statements is supported using `{%-` and `-%}`, could a similar operator for expressions be added? In my case the whitespace wasn't hard to remove manually, but it seemed like a missing feature.

Pull request description:
Add whitespace control to expressions
Adds alternate start and end tokens for expressions (`{{-` and `-}}`) that allow the user to remove whitespace around expressions.

Closes #429 

Patch:
diff --git a/src/parser/ast.rs b/src/parser/ast.rs
index b2317f2..5500fd7 100644
--- a/src/parser/ast.rs
+++ b/src/parser/ast.rs
@@ -317,7 +317,7 @@ pub enum Node {
     /// Some actual text
     Text(String),
     /// A `{{ }}` block
-    VariableBlock(Expr),
+    VariableBlock(WS, Expr),
     /// A `{% macro hello() %}...{% endmacro %}`
     MacroDefinition(WS, MacroDefinition, WS),
 
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 7d456da..f347d9e 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -488,8 +488,22 @@ fn parse_macro_call(pair: Pair<Rule>) -> TeraResult<MacroCall> {
 }
 
 fn parse_variable_tag(pair: Pair<Rule>) -> TeraResult<Node> {
-    let p = pair.into_inner().nth(0).unwrap();
-    Ok(Node::VariableBlock(parse_logic_expr(p)?))
+    let mut ws = WS::default();
+    let mut expr = None;
+
+    for p in pair.into_inner() {
+        match p.as_rule() {
+            Rule::variable_start => {
+                ws.left = p.as_span().as_str() == "{{-";
+            }
+            Rule::variable_end => {
+                ws.right = p.as_span().as_str() == "-}}";
+            }
+            Rule::logic_expr => expr = Some(parse_logic_expr(p)?),
+            _ => unreachable!("unexpected {:?} rule in parse_variable_tag", p.as_rule()),
+        }
+    }
+    Ok(Node::VariableBlock(ws, expr.unwrap()))
 }
 
 fn parse_import_macro(pair: Pair<Rule>) -> Node {
diff --git a/src/parser/tera.pest b/src/parser/tera.pest
index f597e8c..efc3e07 100644
--- a/src/parser/tera.pest
+++ b/src/parser/tera.pest
@@ -139,8 +139,8 @@ test      = { dotted_ident ~ "is" ~ test_call }
 /// TERA
 
 // All the blocks that Tera recognises
-variable_start = _{ "{{" }
-variable_end   = _{ "}}" }
+variable_start = { "{{-" | "{{" }
+variable_end   = { "-}}" | "}}" }
 // whitespace control
 tag_start      = { "{%-" | "{%" }
 tag_end        = { "-%}" | "%}" }
diff --git a/src/parser/whitespace.rs b/src/parser/whitespace.rs
index dd8d3c6..0698f5a 100644
--- a/src/parser/whitespace.rs
+++ b/src/parser/whitespace.rs
@@ -53,7 +53,8 @@ pub fn remove_whitespace(nodes: Vec<Node>, body_ws: Option<WS>) -> Vec<Node> {
                 // empty text nodes will be skipped
                 continue;
             }
-            Node::ImportMacro(ws, _, _)
+            Node::VariableBlock(ws, _)
+            | Node::ImportMacro(ws, _, _)
             | Node::Extends(ws, _)
             | Node::Include(ws, _)
             | Node::Set(ws, _)
@@ -170,7 +171,7 @@ pub fn remove_whitespace(nodes: Vec<Node>, body_ws: Option<WS>) -> Vec<Node> {
                 res.push(Node::If(If { conditions: new_conditions, otherwise }, end_ws));
                 continue;
             }
-            Node::Super | Node::VariableBlock(_) => (),
+            Node::Super => (),
         };
 
         // If we are there, that means it's not a text node and we didn't have to modify the node
diff --git a/src/renderer/processor.rs b/src/renderer/processor.rs
index c7a6c5b..f4c2b48 100644
--- a/src/renderer/processor.rs
+++ b/src/renderer/processor.rs
@@ -59,6 +59,7 @@ fn evaluate_sub_variables<'a>(key: &str, call_stack: &CallStack<'a>) -> Result<S
     }
 
     Ok(new_key
+        .replace("/", "~1") // https://tools.ietf.org/html/rfc6901#section-3
         .replace("['", ".")
         .replace("[\"", ".")
         .replace("[", ".")
@@ -595,7 +596,10 @@ impl<'a> Processor<'a> {
                 }
             }
             ExprVal::Ident(_) => {
-                let mut res = self.eval_expression(&bool_expr).unwrap_or_else(|_| Cow::Owned(Value::Bool(false))).is_truthy();
+                let mut res = self
+                    .eval_expression(&bool_expr)
+                    .unwrap_or_else(|_| Cow::Owned(Value::Bool(false)))
+                    .is_truthy();
                 if bool_expr.negated {
                     res = !res;
                 }
@@ -905,7 +909,9 @@ impl<'a> Processor<'a> {
     fn render_node(&mut self, node: &'a Node, buffer: &mut String) -> Result<()> {
         match *node {
             Node::Text(ref s) | Node::Raw(_, ref s, _) => buffer.push_str(s),
-            Node::VariableBlock(ref expr) => buffer.push_str(&self.eval_expression(expr)?.render()),
+            Node::VariableBlock(_, ref expr) => {
+                buffer.push_str(&self.eval_expression(expr)?.render())
+            }
             Node::Set(_, ref set) => self.eval_set(set)?,
             Node::FilterSection(_, FilterSection { ref filter, ref body }, _) => {
                 let body = self.render_body(body)?;


Test patch:
diff --git a/src/parser/tests/errors.rs b/src/parser/tests/errors.rs
index ccbabf6..ee5ab0d 100644
--- a/src/parser/tests/errors.rs
+++ b/src/parser/tests/errors.rs
@@ -19,7 +19,7 @@ fn invalid_number() {
         "{{ 1.2.2 }}",
         &[
             "1:7",
-            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter"
+            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)"
         ],
     );
 }
@@ -35,7 +35,7 @@ fn wrong_start_block() {
         "{{ if true %}",
         &[
             "1:7",
-            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter"
+            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)"
         ],
     );
 }
@@ -57,7 +57,7 @@ fn unterminated_variable_block() {
         "{{ hey",
         &[
             "1:7",
-            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter"
+            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)"
         ],
     );
 }
@@ -155,7 +155,7 @@ fn invalid_operator() {
         "{{ hey =! }}",
         &[
             "1:8",
-            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter"
+            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)"
         ],
     );
 }
@@ -212,7 +212,7 @@ fn invalid_macro_call() {
         "{{ my:macro() }}",
         &[
             "1:6",
-            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter"
+            "expected `or`, `and`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)"
         ],
     );
 }
diff --git a/src/parser/tests/parser.rs b/src/parser/tests/parser.rs
index f9514fc..7002cac 100644
--- a/src/parser/tests/parser.rs
+++ b/src/parser/tests/parser.rs
@@ -52,10 +52,25 @@ fn parse_import_macro() {
     );
 }
 
+#[test]
+fn parse_variable_with_whitespace_trimming() {
+    let ast = parse("{{- id }}").unwrap();
+    assert_eq!(
+        ast[0],
+        Node::VariableBlock(
+            WS { left: true, right: false },
+            Expr::new(ExprVal::Ident("id".to_string()))
+        ),
+    );
+}
+
 #[test]
 fn parse_variable_tag_ident() {
     let ast = parse("{{ id }}").unwrap();
-    assert_eq!(ast[0], Node::VariableBlock(Expr::new(ExprVal::Ident("id".to_string()))),);
+    assert_eq!(
+        ast[0],
+        Node::VariableBlock(WS::default(), Expr::new(ExprVal::Ident("id".to_string()))),
+    );
 }
 
 #[test]
@@ -66,23 +81,29 @@ fn parse_variable_tag_ident_with_simple_filters() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::with_filters(
-            ExprVal::Ident("arr".to_string()),
-            vec![
-                FunctionCall { name: "first".to_string(), args: HashMap::new() },
-                FunctionCall { name: "join".to_string(), args: join_args },
-            ],
-        ))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::with_filters(
+                ExprVal::Ident("arr".to_string()),
+                vec![
+                    FunctionCall { name: "first".to_string(), args: HashMap::new() },
+                    FunctionCall { name: "join".to_string(), args: join_args },
+                ],
+            )
+        )
     );
 }
 
 #[test]
 fn parse_variable_tag_lit() {
     let ast = parse("{{ 2 }}{{ 3.14 }}{{ \"hey\" }}{{ true }}").unwrap();
-    assert_eq!(ast[0], Node::VariableBlock(Expr::new(ExprVal::Int(2))));
-    assert_eq!(ast[1], Node::VariableBlock(Expr::new(ExprVal::Float(3.14))));
-    assert_eq!(ast[2], Node::VariableBlock(Expr::new(ExprVal::String("hey".to_string()))),);
-    assert_eq!(ast[3], Node::VariableBlock(Expr::new(ExprVal::Bool(true))));
+    assert_eq!(ast[0], Node::VariableBlock(WS::default(), Expr::new(ExprVal::Int(2))));
+    assert_eq!(ast[1], Node::VariableBlock(WS::default(), Expr::new(ExprVal::Float(3.14))));
+    assert_eq!(
+        ast[2],
+        Node::VariableBlock(WS::default(), Expr::new(ExprVal::String("hey".to_string()))),
+    );
+    assert_eq!(ast[3], Node::VariableBlock(WS::default(), Expr::new(ExprVal::Bool(true))));
 }
 
 #[test]
@@ -91,15 +112,18 @@ fn parse_variable_tag_lit_math_expression() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Math(MathExpr {
-            lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
-            operator: MathOperator::Add,
-            rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                lhs: Box::new(Expr::new(ExprVal::Int(1))),
-                operator: MathOperator::Mul,
-                rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
-            },))),
-        },))),
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Math(MathExpr {
+                lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
+                operator: MathOperator::Add,
+                rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                    lhs: Box::new(Expr::new(ExprVal::Int(1))),
+                    operator: MathOperator::Mul,
+                    rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+                },))),
+            },))
+        ),
     );
 }
 
@@ -108,15 +132,18 @@ fn parse_variable_tag_lit_math_expression_with_parentheses() {
     let ast = parse("{{ (count + 1) * 2.5 }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Math(MathExpr {
-            lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
-                operator: MathOperator::Add,
-                rhs: Box::new(Expr::new(ExprVal::Int(1))),
-            },))),
-            operator: MathOperator::Mul,
-            rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Math(MathExpr {
+                lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                    lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
+                    operator: MathOperator::Add,
+                    rhs: Box::new(Expr::new(ExprVal::Int(1))),
+                },))),
+                operator: MathOperator::Mul,
+                rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+            },))
+        )
     );
 }
 
@@ -125,18 +152,21 @@ fn parse_variable_tag_lit_math_expression_with_parentheses_and_filter() {
     let ast = parse("{{ (count + 1) * 2.5 | round }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::with_filters(
-            ExprVal::Math(MathExpr {
-                lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                    lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
-                    operator: MathOperator::Add,
-                    rhs: Box::new(Expr::new(ExprVal::Int(1))),
-                },))),
-                operator: MathOperator::Mul,
-                rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
-            },),
-            vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
-        ))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::with_filters(
+                ExprVal::Math(MathExpr {
+                    lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                        lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
+                        operator: MathOperator::Add,
+                        rhs: Box::new(Expr::new(ExprVal::Int(1))),
+                    },))),
+                    operator: MathOperator::Mul,
+                    rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+                },),
+                vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
+            )
+        )
     );
 }
 
@@ -145,14 +175,17 @@ fn parse_variable_math_on_filter() {
     let ast = parse("{{ a | length - 1 }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Math(MathExpr {
-            lhs: Box::new(Expr::with_filters(
-                ExprVal::Ident("a".to_string()),
-                vec![FunctionCall { name: "length".to_string(), args: HashMap::new() },],
-            )),
-            operator: MathOperator::Sub,
-            rhs: Box::new(Expr::new(ExprVal::Int(1))),
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Math(MathExpr {
+                lhs: Box::new(Expr::with_filters(
+                    ExprVal::Ident("a".to_string()),
+                    vec![FunctionCall { name: "length".to_string(), args: HashMap::new() },],
+                )),
+                operator: MathOperator::Sub,
+                rhs: Box::new(Expr::new(ExprVal::Int(1))),
+            },))
+        )
     );
 }
 
@@ -161,11 +194,14 @@ fn parse_variable_tag_simple_logic_expression() {
     let ast = parse("{{ 1 > 2 }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr {
-            lhs: Box::new(Expr::new(ExprVal::Int(1))),
-            operator: LogicOperator::Gt,
-            rhs: Box::new(Expr::new(ExprVal::Int(2))),
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Logic(LogicExpr {
+                lhs: Box::new(Expr::new(ExprVal::Int(1))),
+                operator: LogicOperator::Gt,
+                rhs: Box::new(Expr::new(ExprVal::Int(2))),
+            },))
+        )
     );
 }
 
@@ -174,19 +210,22 @@ fn parse_variable_tag_math_and_logic_expression() {
     let ast = parse("{{ count + 1 * 2.5 and admin }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr {
-            lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
-                operator: MathOperator::Add,
-                rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                    lhs: Box::new(Expr::new(ExprVal::Int(1))),
-                    operator: MathOperator::Mul,
-                    rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Logic(LogicExpr {
+                lhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                    lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
+                    operator: MathOperator::Add,
+                    rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                        lhs: Box::new(Expr::new(ExprVal::Int(1))),
+                        operator: MathOperator::Mul,
+                        rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+                    },))),
                 },))),
-            },))),
-            operator: LogicOperator::And,
-            rhs: Box::new(Expr::new(ExprVal::Ident("admin".to_string()))),
-        },)))
+                operator: LogicOperator::And,
+                rhs: Box::new(Expr::new(ExprVal::Ident("admin".to_string()))),
+            },))
+        )
     );
 }
 
@@ -195,29 +234,35 @@ fn parse_variable_tag_math_with_filters_and_logic_expression() {
     let ast = parse("{{ count + 1 * 2.5 | round and admin }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr {
-            lhs: Box::new(Expr::with_filters(
-                ExprVal::Math(MathExpr {
-                    lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
-                    operator: MathOperator::Add,
-                    rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
-                        lhs: Box::new(Expr::new(ExprVal::Int(1))),
-                        operator: MathOperator::Mul,
-                        rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
-                    },))),
-                },),
-                vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
-            )),
-            operator: LogicOperator::And,
-            rhs: Box::new(Expr::new(ExprVal::Ident("admin".to_string()))),
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Logic(LogicExpr {
+                lhs: Box::new(Expr::with_filters(
+                    ExprVal::Math(MathExpr {
+                        lhs: Box::new(Expr::new(ExprVal::Ident("count".to_string()))),
+                        operator: MathOperator::Add,
+                        rhs: Box::new(Expr::new(ExprVal::Math(MathExpr {
+                            lhs: Box::new(Expr::new(ExprVal::Int(1))),
+                            operator: MathOperator::Mul,
+                            rhs: Box::new(Expr::new(ExprVal::Float(2.5))),
+                        },))),
+                    },),
+                    vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
+                )),
+                operator: LogicOperator::And,
+                rhs: Box::new(Expr::new(ExprVal::Ident("admin".to_string()))),
+            },))
+        )
     );
 }
 
 #[test]
 fn parse_variable_tag_simple_negated_expr() {
     let ast = parse("{{ not id }}").unwrap();
-    assert_eq!(ast[0], Node::VariableBlock(Expr::new_negated(ExprVal::Ident("id".to_string()))));
+    assert_eq!(
+        ast[0],
+        Node::VariableBlock(WS::default(), Expr::new_negated(ExprVal::Ident("id".to_string())))
+    );
 }
 
 #[test]
@@ -225,12 +270,15 @@ fn parse_test() {
     let ast = parse("{{ a is divisibleby(2) }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Test(Test {
-            ident: "a".to_string(),
-            negated: false,
-            name: "divisibleby".to_string(),
-            args: vec![Expr::new(ExprVal::Int(2))]
-        })))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Test(Test {
+                ident: "a".to_string(),
+                negated: false,
+                name: "divisibleby".to_string(),
+                args: vec![Expr::new(ExprVal::Int(2))]
+            }))
+        )
     );
 }
 
@@ -239,19 +287,22 @@ fn parse_variable_tag_negated_expr() {
     let ast = parse("{{ not id and not true and not 1 + 1 }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr {
-            lhs: Box::new(Expr::new(ExprVal::Logic(LogicExpr {
-                lhs: Box::new(Expr::new_negated(ExprVal::Ident("id".to_string()))),
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Logic(LogicExpr {
+                lhs: Box::new(Expr::new(ExprVal::Logic(LogicExpr {
+                    lhs: Box::new(Expr::new_negated(ExprVal::Ident("id".to_string()))),
+                    operator: LogicOperator::And,
+                    rhs: Box::new(Expr::new_negated(ExprVal::Bool(true))),
+                },))),
                 operator: LogicOperator::And,
-                rhs: Box::new(Expr::new_negated(ExprVal::Bool(true))),
-            },))),
-            operator: LogicOperator::And,
-            rhs: Box::new(Expr::new_negated(ExprVal::Math(MathExpr {
-                lhs: Box::new(Expr::new(ExprVal::Int(1))),
-                operator: MathOperator::Add,
-                rhs: Box::new(Expr::new(ExprVal::Int(1))),
-            },))),
-        },)))
+                rhs: Box::new(Expr::new_negated(ExprVal::Math(MathExpr {
+                    lhs: Box::new(Expr::new(ExprVal::Int(1))),
+                    operator: MathOperator::Add,
+                    rhs: Box::new(Expr::new(ExprVal::Int(1))),
+                },))),
+            },))
+        )
     );
 }
 
@@ -260,12 +311,15 @@ fn parse_variable_tag_simple_test() {
     let ast = parse("{{ id is defined }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Test(Test {
-            ident: "id".to_string(),
-            negated: false,
-            name: "defined".to_string(),
-            args: vec![],
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Test(Test {
+                ident: "id".to_string(),
+                negated: false,
+                name: "defined".to_string(),
+                args: vec![],
+            },))
+        )
     );
 }
 
@@ -274,12 +328,15 @@ fn parse_variable_tag_simple_negated_test() {
     let ast = parse("{{ id is not defined }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Test(Test {
-            ident: "id".to_string(),
-            negated: true,
-            name: "defined".to_string(),
-            args: vec![],
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Test(Test {
+                ident: "id".to_string(),
+                negated: true,
+                name: "defined".to_string(),
+                args: vec![],
+            },))
+        )
     );
 }
 
@@ -288,16 +345,19 @@ fn parse_variable_tag_test_as_expression() {
     let ast = parse("{{ user is defined and user.admin }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr {
-            lhs: Box::new(Expr::new(ExprVal::Test(Test {
-                ident: "user".to_string(),
-                negated: false,
-                name: "defined".to_string(),
-                args: vec![],
-            },))),
-            operator: LogicOperator::And,
-            rhs: Box::new(Expr::new(ExprVal::Ident("user.admin".to_string()))),
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::Logic(LogicExpr {
+                lhs: Box::new(Expr::new(ExprVal::Test(Test {
+                    ident: "user".to_string(),
+                    negated: false,
+                    name: "defined".to_string(),
+                    args: vec![],
+                },))),
+                operator: LogicOperator::And,
+                rhs: Box::new(Expr::new(ExprVal::Ident("user.admin".to_string()))),
+            },))
+        )
     );
 }
 
@@ -309,11 +369,14 @@ fn parse_variable_tag_macro_call() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::MacroCall(MacroCall {
-            namespace: "macros".to_string(),
-            name: "get_time".to_string(),
-            args,
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::MacroCall(MacroCall {
+                namespace: "macros".to_string(),
+                name: "get_time".to_string(),
+                args,
+            },))
+        )
     );
 }
 
@@ -329,11 +392,14 @@ fn parse_variable_tag_macro_call_with_array() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::MacroCall(MacroCall {
-            namespace: "macros".to_string(),
-            name: "get_time".to_string(),
-            args,
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::MacroCall(MacroCall {
+                namespace: "macros".to_string(),
+                name: "get_time".to_string(),
+                args,
+            },))
+        )
     );
 }
 #[test]
@@ -344,14 +410,17 @@ fn parse_variable_tag_macro_call_with_filter() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::with_filters(
-            ExprVal::MacroCall(MacroCall {
-                namespace: "macros".to_string(),
-                name: "get_time".to_string(),
-                args,
-            },),
-            vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
-        ))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::with_filters(
+                ExprVal::MacroCall(MacroCall {
+                    namespace: "macros".to_string(),
+                    name: "get_time".to_string(),
+                    args,
+                },),
+                vec![FunctionCall { name: "round".to_string(), args: HashMap::new() },],
+            )
+        )
     );
 }
 
@@ -363,10 +432,10 @@ fn parse_variable_tag_global_function() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::FunctionCall(FunctionCall {
-            name: "get_time".to_string(),
-            args,
-        },)))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::FunctionCall(FunctionCall { name: "get_time".to_string(), args },))
+        )
     );
 }
 
@@ -378,11 +447,14 @@ fn parse_in_condition() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::In(In {
-            lhs: Box::new(Expr::new(ExprVal::Ident("b".to_string()))),
-            rhs: Box::new(Expr::new(ExprVal::Ident("c".to_string()))),
-            negated: false,
-        })))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::In(In {
+                lhs: Box::new(Expr::new(ExprVal::Ident("b".to_string()))),
+                rhs: Box::new(Expr::new(ExprVal::Ident("c".to_string()))),
+                negated: false,
+            }))
+        )
     );
 }
 
@@ -394,11 +466,14 @@ fn parse_negated_in_condition() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::In(In {
-            lhs: Box::new(Expr::new(ExprVal::Ident("b".to_string()))),
-            rhs: Box::new(Expr::new(ExprVal::Ident("c".to_string()))),
-            negated: true,
-        })))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::In(In {
+                lhs: Box::new(Expr::new(ExprVal::Ident("b".to_string()))),
+                rhs: Box::new(Expr::new(ExprVal::Ident("c".to_string()))),
+                negated: true,
+            }))
+        )
     );
 }
 
@@ -410,13 +485,16 @@ fn parse_variable_tag_global_function_with_filter() {
 
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::with_filters(
-            ExprVal::FunctionCall(FunctionCall { name: "get_time".to_string(), args },),
-            vec![
-                FunctionCall { name: "round".to_string(), args: HashMap::new() },
-                FunctionCall { name: "upper".to_string(), args: HashMap::new() },
-            ],
-        ))
+        Node::VariableBlock(
+            WS::default(),
+            Expr::with_filters(
+                ExprVal::FunctionCall(FunctionCall { name: "get_time".to_string(), args },),
+                vec![
+                    FunctionCall { name: "round".to_string(), args: HashMap::new() },
+                    FunctionCall { name: "upper".to_string(), args: HashMap::new() },
+                ],
+            )
+        )
     );
 }
 
@@ -612,7 +690,7 @@ fn parse_simple_macro_definition() {
                 args,
                 body: vec![
                     Node::Text("A: ".to_string()),
-                    Node::VariableBlock(Expr::new(ExprVal::Ident("a".to_string()))),
+                    Node::VariableBlock(WS::default(), Expr::new(ExprVal::Ident("a".to_string()))),
                 ],
             },
             WS::default(),
@@ -807,16 +885,25 @@ fn parse_continue() {
 #[test]
 fn parse_string_concat_can_merge() {
     let ast = parse("{{ `hello` ~ 'hey' }}").unwrap();
-    assert_eq!(ast[0], Node::VariableBlock(Expr::new(ExprVal::String("hellohey".to_string()))),);
+    assert_eq!(
+        ast[0],
+        Node::VariableBlock(WS::default(), Expr::new(ExprVal::String("hellohey".to_string()))),
+    );
 }
 #[test]
 fn parse_string_concat() {
     let ast = parse("{{ `hello` ~ ident }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::StringConcat(StringConcat {
-            values: vec![ExprVal::String("hello".to_string()), ExprVal::Ident("ident".to_string()),]
-        }))),
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::StringConcat(StringConcat {
+                values: vec![
+                    ExprVal::String("hello".to_string()),
+                    ExprVal::Ident("ident".to_string()),
+                ]
+            }))
+        ),
     );
 }
 
@@ -825,12 +912,15 @@ fn parse_string_concat_multiple() {
     let ast = parse("{{ `hello` ~ ident ~ 'ho' }}").unwrap();
     assert_eq!(
         ast[0],
-        Node::VariableBlock(Expr::new(ExprVal::StringConcat(StringConcat {
-            values: vec![
-                ExprVal::String("hello".to_string()),
-                ExprVal::Ident("ident".to_string()),
-                ExprVal::String("ho".to_string()),
-            ]
-        }))),
+        Node::VariableBlock(
+            WS::default(),
+            Expr::new(ExprVal::StringConcat(StringConcat {
+                values: vec![
+                    ExprVal::String("hello".to_string()),
+                    ExprVal::Ident("ident".to_string()),
+                    ExprVal::String("ho".to_string()),
+                ]
+            }))
+        ),
     );
 }
diff --git a/src/renderer/tests/basic.rs b/src/renderer/tests/basic.rs
index 61856e6..ffa375d 100644
--- a/src/renderer/tests/basic.rs
+++ b/src/renderer/tests/basic.rs
@@ -504,7 +504,7 @@ fn render_for() {
         (
             "{% for a in undefined_variable | default(value=[]) %}{{a}}{% else %}hello{% endfor %}",
             "hello"
-        ),        
+        ),
         (
             "{% for a in [] %}{{a}}{% else %}{% if 1 == 2 %}A{% else %}B{% endif %}{% endfor %}",
             "B"
@@ -849,7 +849,8 @@ fn split_on_context_value() {
 #[test]
 fn default_filter_works_in_condition() {
     let mut tera = Tera::default();
-    tera.add_raw_template("test.html", r#"{% if frobnicate|default(value=True) %}here{% endif %}"#).unwrap();
+    tera.add_raw_template("test.html", r#"{% if frobnicate|default(value=True) %}here{% endif %}"#)
+        .unwrap();
     let res = tera.render("test.html", Context::new());
     assert_eq!(res.unwrap(), "here");
-}
\ No newline at end of file
+}
diff --git a/src/renderer/tests/square_brackets.rs b/src/renderer/tests/square_brackets.rs
index c1631f9..9696cec 100644
--- a/src/renderer/tests/square_brackets.rs
+++ b/src/renderer/tests/square_brackets.rs
@@ -23,8 +23,11 @@ fn var_access_by_square_brackets() {
     let mut map = HashMap::new();
     map.insert("true", "yes");
     map.insert("false", "no");
+    map.insert("with space", "works");
+    map.insert("with/slash", "works");
     let mut deep_map = HashMap::new();
     deep_map.insert("inner_map", &map);
+    context.insert("map", &map);
     context.insert("deep_map", &deep_map);
     context.insert("bool_vec", &vec!["true", "false"]);
 
@@ -35,6 +38,8 @@ fn var_access_by_square_brackets() {
         ("{{var['c'][0]}}", "fred"),
         ("{{var['c'][zero]}}", "fred"),
         ("{{var[a]}}", "i_am_actually_b"),
+        ("{{map['with space']}}", "works"),
+        ("{{map['with/slash']}}", "works"),
         ("{{deep_map['inner_map'][bool_vec[zero]]}}", "yes"),
     ];
 
diff --git a/src/renderer/tests/whitespace.rs b/src/renderer/tests/whitespace.rs
index 81ff031..d3eb26e 100644
--- a/src/renderer/tests/whitespace.rs
+++ b/src/renderer/tests/whitespace.rs
@@ -19,6 +19,9 @@ fn can_remove_whitespace_basic() {
         ("  {% set var = 2 -%} {{var}}", "  2"),
         ("  {% raw -%}{{2}} {% endraw -%} ", "  {{2}}"),
         ("  {% filter upper -%} hey {%- endfilter -%} ", "  HEY"),
+        ("  {{ \"hello\" -}} ", "  hello"),
+        ("  {{- \"hello\" }} ", "hello "),
+        ("  {{- \"hello\" -}} ", "hello"),
     ];
 
     for (input, expected) in inputs {