Przeglądaj źródła

1.update govender
2.add latest revision "github.com/CloudyKit/jet"

xormplus 8 lat temu
rodzic
commit
171d034adb

+ 201 - 0
vendor/github.com/CloudyKit/jet/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 105 - 0
vendor/github.com/CloudyKit/jet/README.md

@@ -0,0 +1,105 @@
+# Jet Template Engine for Go [![Build Status](https://travis-ci.org/CloudyKit/jet.svg?branch=master)](https://travis-ci.org/CloudyKit/jet)
+
+[![Join the chat at https://gitter.im/CloudyKit/jet](https://badges.gitter.im/CloudyKit/jet.svg)](https://gitter.im/CloudyKit/jet?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+Jet is a template engine developed to be easy to use, powerful, dynamic, yet secure and very fast.
+
+* supports template inheritance with `extends`, `import` and `include` statements
+* descriptive error messages with filename and line number
+* auto-escape
+* simple C-like expressions
+* very fast execution – Jet can execute templates faster than some pre-compiled template engines
+* very light in terms of allocations and memory footprint
+* simple and familiar syntax
+* easy to use
+
+You can find the documentation in the [wiki](https://github.com/CloudyKit/jet/wiki).
+
+#### Upgrade to v2
+
+The last release of v1 was v1.2 which is available at https://github.com/CloudyKit/jet/releases/tag/v1.2 and the tag v1.2.
+
+To upgrade to v2 a few updates to your templates are necessary – these are explained in the [upgrade guide](https://github.com/CloudyKit/jet/wiki/Upgrade-to-v2).
+
+#### IntelliJ Plugin
+
+If you use IntelliJ there is a plugin available at https://github.com/jhsx/GoJetPlugin.
+There is also a very good Go plugin for IntelliJ – see https://github.com/go-lang-plugin-org/go-lang-idea-plugin.
+GoJetPlugin + Go-lang-idea-plugin = happiness!
+
+### Examples
+
+You can find examples in the [wiki](https://github.com/CloudyKit/jet/wiki/Jet-template-syntax).
+
+### Running the example application
+
+An example application is available in the repository. Use `go get -u github.com/CloudyKit/jet` or clone the repository into `$GOPATH/github.com/CloudyKit/jet`, then do:
+```
+  $ cd examples/todos; go run main.go
+```
+
+#### Faster than some pre-compiled template engines
+
+The benchmark consists of a range over a slice of data printing the values, the benchmark is based on https://github.com/SlinSo/goTemplateBenchmark, Jet performs better than all template engines without pre-compilation,
+and performs better than gorazor, Ftmpl and Egon, all of which are pre-compiled to Go.
+
+###### Benchmarks
+
+go 1.6.2
+```
+PASS
+BenchmarkEgonSlinso-4      	 2000000	       989 ns/op	     517 B/op	       0 allocs/op
+BenchmarkQuicktemplate-4   	 1000000	      1004 ns/op	     999 B/op	       0 allocs/op
+BenchmarkEgo-4             	 1000000	      2137 ns/op	     603 B/op	       8 allocs/op
+
+BenchmarkJet-4             	  500000	      2798 ns/op	     536 B/op	       0 allocs/op
+BenchmarkJetHTML-4         	  500000	      2822 ns/op	     536 B/op	       0 allocs/op
+
+BenchmarkGorazor-4         	  500000	      3028 ns/op	     613 B/op	      11 allocs/op
+BenchmarkFtmpl-4           	  500000	      3192 ns/op	    1152 B/op	      12 allocs/op
+BenchmarkEgon-4            	  300000	      4673 ns/op	    1172 B/op	      22 allocs/op
+BenchmarkKasia-4           	  200000	      6902 ns/op	    1789 B/op	      26 allocs/op
+BenchmarkSoy-4             	  200000	      7144 ns/op	    1684 B/op	      26 allocs/op
+BenchmarkMustache-4        	  200000	      8213 ns/op	    1568 B/op	      28 allocs/op
+BenchmarkPongo2-4          	  200000	      9989 ns/op	    2949 B/op	      46 allocs/op
+BenchmarkGolang-4          	  100000	     16284 ns/op	    2039 B/op	      38 allocs/op
+BenchmarkAmber-4           	  100000	     17208 ns/op	    2050 B/op	      39 allocs/op
+BenchmarkHandlebars-4      	   50000	     29864 ns/op	    4258 B/op	      90 allocs/op
+BenchmarkAce-4             	   30000	     40771 ns/op	    5710 B/op	      77 allocs/op
+BenchmarkDamsel-4          	   20000	     95947 ns/op	   11160 B/op	     165 allocs/op
+ok  	github.com/SlinSo/goTemplateBenchmark	34.384s
+```
+
+go tip
+```
+BenchmarkQuicktemplate-4      	 2000000	       916 ns/op	     999 B/op	       0 allocs/op
+BenchmarkEgonSlinso-4         	 2000000	      1074 ns/op	     517 B/op	       0 allocs/op
+BenchmarkEgo-4                	 1000000	      1822 ns/op	     603 B/op	       8 allocs/op
+
+BenchmarkJetHTML-4            	  500000	      2627 ns/op	     536 B/op	       0 allocs/op
+BenchmarkJet-4                	  500000	      2652 ns/op	     536 B/op	       0 allocs/op
+
+BenchmarkFtmpl-4              	  500000	      2700 ns/op	    1152 B/op	      12 allocs/op
+BenchmarkGorazor-4            	  500000	      2858 ns/op	     613 B/op	      11 allocs/op
+BenchmarkEgon-4               	  500000	      4023 ns/op	     827 B/op	      22 allocs/op
+BenchmarkSoy-4                	  300000	      5590 ns/op	    1784 B/op	      26 allocs/op
+BenchmarkKasia-4              	  200000	      6487 ns/op	    1789 B/op	      26 allocs/op
+BenchmarkMustache-4           	  200000	      6515 ns/op	    1568 B/op	      28 allocs/op
+BenchmarkPongo2-4             	  200000	      7602 ns/op	    2949 B/op	      46 allocs/op
+BenchmarkAmber-4              	  100000	     13942 ns/op	    2050 B/op	      39 allocs/op
+BenchmarkGolang-4             	  100000	     16945 ns/op	    2039 B/op	      38 allocs/op
+BenchmarkHandlebars-4         	  100000	     20152 ns/op	    4258 B/op	      90 allocs/op
+BenchmarkAce-4                	   50000	     33091 ns/op	    5509 B/op	      77 allocs/op
+BenchmarkDamsel-4             	   20000	     86340 ns/op	   11159 B/op	     165 allocs/op
+PASS
+ok  	github.com/SlinSo/goTemplateBenchmark	36.200s
+```
+
+#### Contributing
+
+All contributions are welcome – if you find a bug please report it.
+
+#### Thanks
+
+- @golang developers for the awesome language and the standard library
+- @SlinSo for the benchmarks that I used as a base to show the results above

+ 228 - 0
vendor/github.com/CloudyKit/jet/constructors.go

@@ -0,0 +1,228 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+func (t *Template) newSliceExpr(pos Pos, line int, base, index, len Expression) *SliceExprNode {
+	return &SliceExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeSliceExpr, Pos: pos, Line: line}, Index: index, Base: base, EndIndex: len}
+}
+
+func (t *Template) newIndexExpr(pos Pos, line int, base, index Expression) *IndexExprNode {
+	return &IndexExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeIndexExpr, Pos: pos, Line: line}, Index: index, Base: base}
+}
+
+func (t *Template) newTernaryExpr(pos Pos, line int, boolean, left, right Expression) *TernaryExprNode {
+	return &TernaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeTernaryExpr, Pos: pos, Line: line}, Boolean: boolean, Left: left, Right: right}
+}
+
+func (t *Template) newSet(pos Pos, line int, isLet, isIndexExprGetLookup bool, left, right []Expression) *SetNode {
+	return &SetNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeSet, Pos: pos, Line: line}, Let: isLet, IndexExprGetLookup: isIndexExprGetLookup, Left: left, Right: right}
+}
+
+func (t *Template) newCallExpr(pos Pos, line int, expr Expression) *CallExprNode {
+	return &CallExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeCallExpr, Pos: pos, Line: line}, BaseExpr: expr}
+}
+func (t *Template) newNotExpr(pos Pos, line int, expr Expression) *NotExprNode {
+	return &NotExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNotExpr, Pos: pos, Line: line}, Expr: expr}
+}
+func (t *Template) newNumericComparativeExpr(pos Pos, line int, left, right Expression, item item) *NumericComparativeExprNode {
+	return &NumericComparativeExprNode{binaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNumericComparativeExpr, Pos: pos, Line: line}, Operator: item, Left: left, Right: right}}
+}
+
+func (t *Template) newComparativeExpr(pos Pos, line int, left, right Expression, item item) *ComparativeExprNode {
+	return &ComparativeExprNode{binaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeComparativeExpr, Pos: pos, Line: line}, Operator: item, Left: left, Right: right}}
+}
+
+func (t *Template) newLogicalExpr(pos Pos, line int, left, right Expression, item item) *LogicalExprNode {
+	return &LogicalExprNode{binaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeLogicalExpr, Pos: pos, Line: line}, Operator: item, Left: left, Right: right}}
+}
+
+func (t *Template) newMultiplicativeExpr(pos Pos, line int, left, right Expression, item item) *MultiplicativeExprNode {
+	return &MultiplicativeExprNode{binaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeMultiplicativeExpr, Pos: pos, Line: line}, Operator: item, Left: left, Right: right}}
+}
+
+func (t *Template) newAdditiveExpr(pos Pos, line int, left, right Expression, item item) *AdditiveExprNode {
+	return &AdditiveExprNode{binaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeAdditiveExpr, Pos: pos, Line: line}, Operator: item, Left: left, Right: right}}
+}
+
+func (t *Template) newList(pos Pos) *ListNode {
+	return &ListNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeList, Pos: pos}}
+}
+
+func (t *Template) newText(pos Pos, text string) *TextNode {
+	return &TextNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeText, Pos: pos}, Text: []byte(text)}
+}
+
+func (t *Template) newPipeline(pos Pos, line int) *PipeNode {
+	return &PipeNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodePipe, Pos: pos, Line: line}}
+}
+
+func (t *Template) newAction(pos Pos, line int) *ActionNode {
+	return &ActionNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeAction, Pos: pos, Line: line}}
+}
+
+func (t *Template) newCommand(pos Pos) *CommandNode {
+	return &CommandNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeCommand, Pos: pos}}
+}
+
+func (t *Template) newNil(pos Pos) *NilNode {
+	return &NilNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNil, Pos: pos}}
+}
+
+func (t *Template) newField(pos Pos, ident string) *FieldNode {
+	return &FieldNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeField, Pos: pos}, Ident: strings.Split(ident[1:], ".")} //[1:] to drop leading period
+}
+
+func (t *Template) newChain(pos Pos, node Node) *ChainNode {
+	return &ChainNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeChain, Pos: pos}, Node: node}
+}
+
+func (t *Template) newBool(pos Pos, true bool) *BoolNode {
+	return &BoolNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeBool, Pos: pos}, True: true}
+}
+
+func (t *Template) newString(pos Pos, orig, text string) *StringNode {
+	return &StringNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeString, Pos: pos}, Quoted: orig, Text: text}
+}
+
+func (t *Template) newEnd(pos Pos) *endNode {
+	return &endNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeEnd, Pos: pos}}
+}
+
+func (t *Template) newContent(pos Pos) *contentNode {
+	return &contentNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeContent, Pos: pos}}
+}
+
+func (t *Template) newElse(pos Pos, line int) *elseNode {
+	return &elseNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeElse, Pos: pos, Line: line}}
+}
+
+func (t *Template) newIf(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *IfNode {
+	return &IfNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeIf, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}}
+}
+
+func (t *Template) newRange(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *RangeNode {
+	return &RangeNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeRange, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}}
+}
+
+func (t *Template) newBlock(pos Pos, line int, name string, parameters *BlockParameterList, pipe Expression, listNode, contentListNode *ListNode) *BlockNode {
+	return &BlockNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeBlock, Line: line, Pos: pos}, Name: name, Parameters: parameters, Expression: pipe, List: listNode, Content: contentListNode}
+}
+
+func (t *Template) newYield(pos Pos, line int, name string, bplist *BlockParameterList, pipe Expression, content *ListNode, isContent bool) *YieldNode {
+	return &YieldNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeYield, Pos: pos, Line: line}, Name: name, Parameters: bplist, Expression: pipe, Content: content, IsContent: isContent}
+}
+
+func (t *Template) newInclude(pos Pos, line int, name, pipe Expression) *IncludeNode {
+	return &IncludeNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeInclude, Pos: pos, Line: line}, Name: name, Expression: pipe}
+}
+
+func (t *Template) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
+	n := &NumberNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNumber, Pos: pos}, Text: text}
+	// todo: optimize
+	switch typ {
+	case itemCharConstant:
+		_rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
+		if err != nil {
+			return nil, err
+		}
+		if tail != "'" {
+			return nil, fmt.Errorf("malformed character constant: %s", text)
+		}
+		n.Int64 = int64(_rune)
+		n.IsInt = true
+		n.Uint64 = uint64(_rune)
+		n.IsUint = true
+		n.Float64 = float64(_rune) //odd but those are the rules.
+		n.IsFloat = true
+		return n, nil
+	case itemComplex:
+		//fmt.Sscan can parse the pair, so let it do the work.
+		if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
+			return nil, err
+		}
+		n.IsComplex = true
+		n.simplifyComplex()
+		return n, nil
+	}
+	//Imaginary constants can only be complex unless they are zero.
+	if len(text) > 0 && text[len(text)-1] == 'i' {
+		f, err := strconv.ParseFloat(text[:len(text)-1], 64)
+		if err == nil {
+			n.IsComplex = true
+			n.Complex128 = complex(0, f)
+			n.simplifyComplex()
+			return n, nil
+		}
+	}
+	// Do integer test first so we get 0x123 etc.
+	u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
+	if err == nil {
+		n.IsUint = true
+		n.Uint64 = u
+	}
+	i, err := strconv.ParseInt(text, 0, 64)
+	if err == nil {
+		n.IsInt = true
+		n.Int64 = i
+		if i == 0 {
+			n.IsUint = true // in case of -0.
+			n.Uint64 = u
+		}
+	}
+	// If an integer extraction succeeded, promote the float.
+	if n.IsInt {
+		n.IsFloat = true
+		n.Float64 = float64(n.Int64)
+	} else if n.IsUint {
+		n.IsFloat = true
+		n.Float64 = float64(n.Uint64)
+	} else {
+		f, err := strconv.ParseFloat(text, 64)
+		if err == nil {
+			// If we parsed it as a float but it looks like an integer,
+			// it's a huge number too large to fit in an int. Reject it.
+			if !strings.ContainsAny(text, ".eE") {
+				return nil, fmt.Errorf("integer overflow: %q", text)
+			}
+			n.IsFloat = true
+			n.Float64 = f
+			// If a floating-point extraction succeeded, extract the int if needed.
+			if !n.IsInt && float64(int64(f)) == f {
+				n.IsInt = true
+				n.Int64 = int64(f)
+			}
+			if !n.IsUint && float64(uint64(f)) == f {
+				n.IsUint = true
+				n.Uint64 = uint64(f)
+			}
+		}
+	}
+
+	if !n.IsInt && !n.IsUint && !n.IsFloat {
+		return nil, fmt.Errorf("illegal number syntax: %q", text)
+	}
+
+	return n, nil
+}
+
+func (t *Template) newIdentifier(ident string, pos Pos, line int) *IdentifierNode {
+	return &IdentifierNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeIdentifier, Pos: pos, Line: line}, Ident: ident}
+}

+ 153 - 0
vendor/github.com/CloudyKit/jet/default.go

@@ -0,0 +1,153 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"encoding/json"
+	"fmt"
+	"html"
+	"io"
+	"net/url"
+	"reflect"
+	"strings"
+	"text/template"
+)
+
+var defaultExtensions = []string{
+	".html.jet",
+	".jet.html",
+	".jet",
+}
+
+var defaultVariables map[string]reflect.Value
+
+func init() {
+	defaultVariables = map[string]reflect.Value{
+		"lower":     reflect.ValueOf(strings.ToLower),
+		"upper":     reflect.ValueOf(strings.ToUpper),
+		"hasPrefix": reflect.ValueOf(strings.HasPrefix),
+		"hasSuffix": reflect.ValueOf(strings.HasSuffix),
+		"repeat":    reflect.ValueOf(strings.Repeat),
+		"replace":   reflect.ValueOf(strings.Replace),
+		"split":     reflect.ValueOf(strings.Split),
+		"trimSpace": reflect.ValueOf(strings.TrimSpace),
+		"map":       reflect.ValueOf(newMap),
+		"html":      reflect.ValueOf(html.EscapeString),
+		"url":       reflect.ValueOf(url.QueryEscape),
+		"safeHtml":  reflect.ValueOf(SafeWriter(template.HTMLEscape)),
+		"safeJs":    reflect.ValueOf(SafeWriter(template.JSEscape)),
+		"raw":       reflect.ValueOf(SafeWriter(unsafePrinter)),
+		"unsafe":    reflect.ValueOf(SafeWriter(unsafePrinter)),
+		"writeJson": reflect.ValueOf(jsonRenderer),
+		"json":      reflect.ValueOf(json.Marshal),
+		"isset": reflect.ValueOf(Func(func(a Arguments) reflect.Value {
+			a.RequireNumOfArguments("isset", 1, -1)
+			for i := 0; i < len(a.argExpr); i++ {
+				if !a.runtime.isSet(a.argExpr[i]) {
+					return valueBoolFALSE
+				}
+			}
+			return valueBoolTRUE
+		})),
+		"len": reflect.ValueOf(Func(func(a Arguments) reflect.Value {
+			a.RequireNumOfArguments("len", 1, 1)
+
+			expression := a.Get(0)
+			if expression.Kind() == reflect.Ptr {
+				expression = expression.Elem()
+			}
+
+			switch expression.Kind() {
+			case reflect.Array, reflect.Chan, reflect.Slice, reflect.Map, reflect.String:
+				return reflect.ValueOf(expression.Len())
+			case reflect.Struct:
+				return reflect.ValueOf(expression.NumField())
+			}
+
+			a.Panicf("inválid value type %s in len builtin", expression.Type())
+			return reflect.Value{}
+		})),
+		"includeIfExists": reflect.ValueOf(Func(func(a Arguments) reflect.Value {
+
+			a.RequireNumOfArguments("includeIfExists", 1, 2)
+			t, err := a.runtime.set.GetTemplate(a.Get(0).String())
+			// If template exists but returns an error then panic instead of failing silently
+			if t != nil && err != nil {
+				panic(err)
+			}
+			if err != nil {
+				return hiddenFALSE
+			}
+
+			a.runtime.newScope()
+			a.runtime.blocks = t.processedBlocks
+			Root := t.Root
+			if t.extends != nil {
+				Root = t.extends.Root
+			}
+
+			if a.NumOfArguments() > 1 {
+				c := a.runtime.context
+				a.runtime.context = a.Get(1)
+				a.runtime.executeList(Root)
+				a.runtime.context = c
+			} else {
+				a.runtime.executeList(Root)
+			}
+
+			a.runtime.releaseScope()
+
+			return hiddenTRUE
+		})),
+	}
+}
+
+type hiddenBool bool
+
+func (m hiddenBool) Render(r *Runtime) {
+
+}
+
+var hiddenTRUE = reflect.ValueOf(hiddenBool(true))
+var hiddenFALSE = reflect.ValueOf(hiddenBool(false))
+
+func jsonRenderer(v interface{}) RendererFunc {
+	return func(r *Runtime) {
+		err := json.NewEncoder(r.Writer).Encode(v)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+func unsafePrinter(w io.Writer, b []byte) {
+	w.Write(b)
+}
+
+// SafeWriter escapee func. Functions implementing this type will write directly into the writer,
+// skipping the escape phase; use this type to create special types of escapee funcs.
+type SafeWriter func(io.Writer, []byte)
+
+func newMap(values ...interface{}) (nmap map[string]interface{}) {
+	if len(values)%2 > 0 {
+		panic("new map: invalid number of arguments on call to map")
+	}
+	nmap = make(map[string]interface{})
+
+	for i := 0; i < len(values); i += 2 {
+		nmap[fmt.Sprint(values[i])] = values[i+1]
+	}
+	return
+}

+ 1612 - 0
vendor/github.com/CloudyKit/jet/eval.go

@@ -0,0 +1,1612 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"fmt"
+	"github.com/CloudyKit/fastprinter"
+	"io"
+	"reflect"
+	"runtime"
+	"strconv"
+	"sync"
+)
+
+var (
+	funcType       = reflect.TypeOf(Func(nil))
+	stringerType   = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+	rangerType     = reflect.TypeOf((*Ranger)(nil)).Elem()
+	rendererType   = reflect.TypeOf((*Renderer)(nil)).Elem()
+	safeWriterType = reflect.TypeOf(SafeWriter(nil))
+	pool_State     = sync.Pool{
+		New: func() interface{} {
+			return &Runtime{scope: &scope{}, escapeeWriter: new(escapeeWriter)}
+		},
+	}
+)
+
+// Renderer any resulting value from an expression in an action that implements this
+// interface will not be printed, instead, we will invoke his Render() method which will be responsible
+// to render his self
+type Renderer interface {
+	Render(*Runtime)
+}
+
+// RendererFunc func implementing interface Renderer
+type RendererFunc func(*Runtime)
+
+func (renderer RendererFunc) Render(r *Runtime) {
+	renderer(r)
+}
+
+// Ranger a value implementing a ranger interface is able to iterate on his value
+// and can be used directly in a range statement
+type Ranger interface {
+	Range() (reflect.Value, reflect.Value, bool)
+}
+
+type escapeeWriter struct {
+	Writer  io.Writer
+	escapee SafeWriter
+	set     *Set
+}
+
+func (w *escapeeWriter) Write(b []byte) (int, error) {
+	if w.set.escapee == nil {
+		w.Writer.Write(b)
+	} else {
+		w.set.escapee(w.Writer, b)
+	}
+	return 0, nil
+}
+
+// Runtime this type holds the state of the execution of an template
+type Runtime struct {
+	*escapeeWriter
+	*scope
+	content func(*Runtime, Expression)
+
+	translator Translator
+	context    reflect.Value
+}
+
+// Context returns the current context value
+func (r *Runtime) Context() reflect.Value {
+	return r.context
+}
+
+func (st *Runtime) newScope() {
+	st.scope = &scope{parent: st.scope, variables: make(VarMap), blocks: st.blocks}
+}
+
+func (st *Runtime) releaseScope() {
+	st.scope = st.scope.parent
+}
+
+type scope struct {
+	parent    *scope
+	variables VarMap
+	blocks    map[string]*BlockNode
+}
+
+// YieldBlock yields a block in the current context, will panic if the context is not available
+func (st *Runtime) YieldBlock(name string, context interface{}) {
+	block, has := st.getBlock(name)
+
+	if has == false {
+		panic(fmt.Errorf("Block %q was not found!!", name))
+	}
+
+	if context != nil {
+		current := st.context
+		st.context = reflect.ValueOf(context)
+		st.executeList(block.List)
+		st.context = current
+	}
+
+	st.executeList(block.List)
+}
+
+func (st *scope) getBlock(name string) (block *BlockNode, has bool) {
+	block, has = st.blocks[name]
+	for !has && st.parent != nil {
+		st = st.parent
+		block, has = st.blocks[name]
+	}
+	return
+}
+
+// YieldTemplate yields a template same as include
+func (st *Runtime) YieldTemplate(name string, context interface{}) {
+
+	t, err := st.set.GetTemplate(name)
+	if err != nil {
+		panic(fmt.Errorf("include: template %q was not found", name))
+	}
+
+	st.newScope()
+	st.blocks = t.processedBlocks
+
+	Root := t.Root
+	if t.extends != nil {
+		Root = t.extends.Root
+	}
+
+	if context != nil {
+		c := st.context
+		st.context = reflect.ValueOf(context)
+		st.executeList(Root)
+		st.context = c
+	} else {
+		st.executeList(Root)
+	}
+
+	st.releaseScope()
+}
+
+// Set sets variable ${name} in the current template scope
+func (state *Runtime) Set(name string, val interface{}) {
+	state.setValue(name, reflect.ValueOf(val))
+}
+
+func (state *Runtime) setValue(name string, val reflect.Value) bool {
+	sc := state.scope
+	initial := sc
+
+	// try to resolve variables in the current scope
+	_, ok := sc.variables[name]
+
+	// if not found walks parent scopes
+	for !ok && sc.parent != nil {
+		sc = sc.parent
+		_, ok = sc.variables[name]
+	}
+
+	if ok {
+		sc.variables[name] = val
+		return false
+	}
+
+	for initial.variables == nil && initial.parent != nil {
+		initial = initial.parent
+	}
+
+	if initial.variables != nil {
+		sc.variables[name] = val
+		return false
+	}
+	return true
+}
+
+// Resolve resolves a value from the execution context
+func (state *Runtime) Resolve(name string) reflect.Value {
+
+	if name == "." {
+		return state.context
+	}
+
+	sc := state.scope
+	// try to resolve variables in the current scope
+	vl, ok := sc.variables[name]
+	// if not found walks parent scopes
+	for !ok && sc.parent != nil {
+		sc = sc.parent
+		vl, ok = sc.variables[name]
+	}
+
+	// if not found check globals
+	if !ok {
+		state.set.gmx.RLock()
+		vl, ok = state.set.globals[name]
+		state.set.gmx.RUnlock()
+		// not found check defaultVariables
+		if !ok {
+			vl, ok = defaultVariables[name]
+		}
+	}
+	return vl
+}
+
+func (st *Runtime) recover(err *error) {
+	pool_State.Put(st)
+	if recovered := recover(); recovered != nil {
+		var is bool
+		if _, is = recovered.(runtime.Error); is {
+			panic(recovered)
+		}
+		*err, is = recovered.(error)
+		if !is {
+			panic(recovered)
+		}
+	}
+}
+
+func (st *Runtime) executeSet(left Expression, right reflect.Value) {
+	typ := left.Type()
+	if typ == NodeIdentifier {
+		st.setValue(left.(*IdentifierNode).Ident, right)
+		return
+	}
+	var value reflect.Value
+	var fields []string
+	if typ == NodeChain {
+		chain := left.(*ChainNode)
+		value = st.evalPrimaryExpressionGroup(chain.Node)
+		fields = chain.Field
+	} else {
+		fields = left.(*FieldNode).Ident
+		value = st.context
+	}
+	lef := len(fields) - 1
+	for i := 0; i < lef; i++ {
+		value = getFieldOrMethodValue(fields[i], value)
+		if !value.IsValid() {
+			left.errorf("identifier %q is not available in the current scope", fields[i])
+		}
+	}
+
+RESTART:
+	switch value.Kind() {
+	case reflect.Ptr:
+		value = value.Elem()
+		goto RESTART
+	case reflect.Struct:
+		value = value.FieldByName(fields[lef])
+		if !value.IsValid() {
+			left.errorf("identifier %q is not available in the current scope", fields[lef])
+		}
+		value.Set(right)
+	case reflect.Map:
+		value.SetMapIndex(reflect.ValueOf(&fields[lef]).Elem(), right)
+	}
+}
+
+func (st *Runtime) executeSetList(set *SetNode) {
+	if set.IndexExprGetLookup {
+		value := st.evalPrimaryExpressionGroup(set.Right[0])
+		st.executeSet(set.Left[0], value)
+		if value.IsValid() {
+			st.executeSet(set.Left[1], valueBoolTRUE)
+		} else {
+			st.executeSet(set.Left[1], valueBoolFALSE)
+		}
+	} else {
+		for i := 0; i < len(set.Left); i++ {
+			st.executeSet(set.Left[i], st.evalPrimaryExpressionGroup(set.Right[i]))
+		}
+	}
+}
+
+func (st *Runtime) executeLetList(set *SetNode) {
+	if set.IndexExprGetLookup {
+		value := st.evalPrimaryExpressionGroup(set.Right[0])
+
+		st.variables[set.Left[0].(*IdentifierNode).Ident] = value
+
+		if value.IsValid() {
+			st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolTRUE
+		} else {
+			st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolFALSE
+		}
+
+	} else {
+		for i := 0; i < len(set.Left); i++ {
+			st.variables[set.Left[i].(*IdentifierNode).Ident] = st.evalPrimaryExpressionGroup(set.Right[i])
+		}
+	}
+}
+
+func (st *Runtime) executeYieldBlock(block *BlockNode, blockParam, yieldParam *BlockParameterList, expression Expression, content *ListNode) {
+
+	needNewScope := len(blockParam.List) > 0 || len(yieldParam.List) > 0
+	if needNewScope {
+		st.newScope()
+		for i := 0; i < len(yieldParam.List); i++ {
+			p := &yieldParam.List[i]
+			st.variables[p.Identifier] = st.evalPrimaryExpressionGroup(p.Expression)
+		}
+		for i := 0; i < len(blockParam.List); i++ {
+			p := &blockParam.List[i]
+			if _, found := st.variables[p.Identifier]; !found {
+				if p.Expression == nil {
+					st.variables[p.Identifier] = valueBoolFALSE
+				} else {
+					st.variables[p.Identifier] = st.evalPrimaryExpressionGroup(p.Expression)
+				}
+			}
+		}
+	}
+
+	mycontent := st.content
+	if content != nil {
+		myscope := st.scope
+		st.content = func(st *Runtime, expression Expression) {
+			outscope := st.scope
+			outcontent := st.content
+
+			st.scope = myscope
+			st.content = mycontent
+
+			if expression != nil {
+				context := st.context
+				st.context = st.evalPrimaryExpressionGroup(expression)
+				st.executeList(content)
+				st.context = context
+			} else {
+				st.executeList(content)
+			}
+
+			st.scope = outscope
+			st.content = outcontent
+		}
+	}
+
+	if expression != nil {
+		context := st.context
+		st.context = st.evalPrimaryExpressionGroup(expression)
+		st.executeList(block.List)
+		st.context = context
+	} else {
+		st.executeList(block.List)
+	}
+
+	st.content = mycontent
+	if needNewScope {
+		st.releaseScope()
+	}
+}
+
+func (st *Runtime) executeList(list *ListNode) {
+	inNewSCOPE := false
+
+	for i := 0; i < len(list.Nodes); i++ {
+		node := list.Nodes[i]
+		switch node.Type() {
+
+		case NodeText:
+			node := node.(*TextNode)
+			_, err := st.Writer.Write(node.Text)
+			if err != nil {
+				node.error(err)
+			}
+		case NodeAction:
+			node := node.(*ActionNode)
+			if node.Set != nil {
+				if node.Set.Let {
+					if !inNewSCOPE {
+						st.newScope() //creates new scope in the back state
+						inNewSCOPE = true
+					}
+					st.executeLetList(node.Set)
+				} else {
+					st.executeSetList(node.Set)
+				}
+			}
+			if node.Pipe != nil {
+				v, safeWriter := st.evalPipelineExpression(node.Pipe)
+				if !safeWriter && v.IsValid() {
+					if v.Type().Implements(rendererType) {
+						v.Interface().(Renderer).Render(st)
+					} else {
+						_, err := fastprinter.PrintValue(st.escapeeWriter, v)
+						if err != nil {
+							node.error(err)
+						}
+					}
+				}
+			}
+		case NodeIf:
+			node := node.(*IfNode)
+			var isLet bool
+			if node.Set != nil {
+				if node.Set.Let {
+					isLet = true
+					st.newScope()
+					st.executeLetList(node.Set)
+				} else {
+					st.executeSetList(node.Set)
+				}
+			}
+
+			if castBoolean(st.evalPrimaryExpressionGroup(node.Expression)) {
+				st.executeList(node.List)
+			} else if node.ElseList != nil {
+				st.executeList(node.ElseList)
+			}
+			if isLet {
+				st.releaseScope()
+			}
+		case NodeRange:
+			node := node.(*RangeNode)
+			var expression reflect.Value
+
+			isSet := node.Set != nil
+			isLet := false
+			isKeyVal := false
+
+			context := st.context
+
+			if isSet {
+				isKeyVal = len(node.Set.Left) > 1
+				expression = st.evalPrimaryExpressionGroup(node.Set.Right[0])
+				if node.Set.Let {
+					isLet = true
+					st.newScope()
+				}
+			} else {
+				expression = st.evalPrimaryExpressionGroup(node.Expression)
+			}
+
+			ranger := getRanger(expression)
+			indexValue, rangeValue, end := ranger.Range()
+			if !end {
+				for !end {
+					if isSet {
+						if isLet {
+							if isKeyVal {
+								st.variables[node.Set.Left[0].String()] = indexValue
+								st.variables[node.Set.Left[1].String()] = rangeValue
+							} else {
+								st.variables[node.Set.Left[0].String()] = rangeValue
+							}
+						} else {
+							if isKeyVal {
+								st.executeSet(node.Set.Left[0], indexValue)
+								st.executeSet(node.Set.Left[1], rangeValue)
+							} else {
+								st.executeSet(node.Set.Left[0], rangeValue)
+							}
+						}
+					} else {
+						st.context = rangeValue
+					}
+					st.executeList(node.List)
+					indexValue, rangeValue, end = ranger.Range()
+				}
+			} else if node.ElseList != nil {
+				st.executeList(node.ElseList)
+			}
+			st.context = context
+			if isLet {
+				st.releaseScope()
+			}
+		case NodeYield:
+			node := node.(*YieldNode)
+			if node.IsContent {
+				if st.content != nil {
+					st.content(st, node.Expression)
+				}
+			} else {
+				block, has := st.getBlock(node.Name)
+				if has == false || block == nil {
+					node.errorf("unresolved block %q!!", node.Name)
+				}
+				st.executeYieldBlock(block, block.Parameters, node.Parameters, node.Expression, node.Content)
+			}
+		case NodeBlock:
+			node := node.(*BlockNode)
+			block, has := st.getBlock(node.Name)
+			if has == false {
+				block = node
+			}
+			st.executeYieldBlock(block, block.Parameters, block.Parameters, block.Expression, block.Content)
+		case NodeInclude:
+			node := node.(*IncludeNode)
+			var Name string
+
+			name := st.evalPrimaryExpressionGroup(node.Name)
+			if name.Type().Implements(stringerType) {
+				Name = name.String()
+			} else if name.Kind() == reflect.String {
+				Name = name.String()
+			} else {
+				node.errorf("unexpected expression type %q in template yielding", getTypeString(name))
+			}
+
+			t, err := st.set.getTemplate(Name, node.TemplateName)
+			if err != nil {
+				node.error(err)
+			} else {
+				st.newScope()
+				st.blocks = t.processedBlocks
+				var context reflect.Value
+				if node.Expression != nil {
+					context = st.context
+					st.context = st.evalPrimaryExpressionGroup(node.Expression)
+				}
+				Root := t.Root
+				for t.extends != nil {
+					t = t.extends
+					Root = t.Root
+				}
+				st.executeList(Root)
+				st.releaseScope()
+				if node.Expression != nil {
+					st.context = context
+				}
+			}
+		}
+	}
+	if inNewSCOPE {
+		st.releaseScope()
+	}
+}
+
+var (
+	valueBoolTRUE  = reflect.ValueOf(true)
+	valueBoolFALSE = reflect.ValueOf(false)
+)
+
+func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value {
+	switch node.Type() {
+	case NodeAdditiveExpr:
+		return st.evalAdditiveExpression(node.(*AdditiveExprNode))
+	case NodeMultiplicativeExpr:
+		return st.evalMultiplicativeExpression(node.(*MultiplicativeExprNode))
+	case NodeComparativeExpr:
+		return st.evalComparativeExpression(node.(*ComparativeExprNode))
+	case NodeNumericComparativeExpr:
+		return st.evalNumericComparativeExpression(node.(*NumericComparativeExprNode))
+	case NodeLogicalExpr:
+		return st.evalLogicalExpression(node.(*LogicalExprNode))
+	case NodeNotExpr:
+		return boolValue(!castBoolean(st.evalPrimaryExpressionGroup(node.(*NotExprNode).Expr)))
+	case NodeTernaryExpr:
+		node := node.(*TernaryExprNode)
+		if castBoolean(st.evalPrimaryExpressionGroup(node.Boolean)) {
+			return st.evalPrimaryExpressionGroup(node.Left)
+		}
+		return st.evalPrimaryExpressionGroup(node.Right)
+	case NodeCallExpr:
+		node := node.(*CallExprNode)
+		baseExpr := st.evalBaseExpressionGroup(node.BaseExpr)
+		if baseExpr.Kind() != reflect.Func {
+			node.errorf("node %q is not func kind %q", node.BaseExpr, baseExpr.Type())
+		}
+		return st.evalCallExpression(baseExpr, node.Args)
+	case NodeIndexExpr:
+		node := node.(*IndexExprNode)
+
+		baseExpression := st.evalPrimaryExpressionGroup(node.Base)
+		indexExpression := st.evalPrimaryExpressionGroup(node.Index)
+		indexType := indexExpression.Type()
+
+		if baseExpression.Kind() == reflect.Interface {
+			baseExpression = baseExpression.Elem()
+		}
+
+		if baseExpression.Kind() == reflect.Ptr {
+			baseExpression = baseExpression.Elem()
+		}
+
+		switch baseExpression.Kind() {
+		case reflect.Map:
+			key := baseExpression.Type().Key()
+			if !indexType.AssignableTo(key) {
+				if indexType.ConvertibleTo(key) {
+					indexExpression = indexExpression.Convert(key)
+				} else {
+					node.errorf("%s is not assignable|convertible to map key %s", indexType.String(), key.String())
+				}
+			}
+			return baseExpression.MapIndex(indexExpression)
+		case reflect.Array, reflect.String, reflect.Slice:
+			if canNumber(indexType.Kind()) {
+				return baseExpression.Index(int(castInt64(indexExpression)))
+			} else {
+				node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
+			}
+		case reflect.Struct:
+			if canNumber(indexType.Kind()) {
+				return baseExpression.Field(int(castInt64(indexExpression)))
+			} else if indexType.Kind() == reflect.String {
+				return getFieldOrMethodValue(indexExpression.String(), baseExpression)
+			} else {
+				node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
+			}
+		default:
+			node.errorf("indexing is not supported in value type %s", baseExpression.Kind().String())
+		}
+	case NodeSliceExpr:
+		node := node.(*SliceExprNode)
+		baseExpression := st.evalPrimaryExpressionGroup(node.Base)
+
+		var index, length int
+		if node.Index != nil {
+			indexExpression := st.evalPrimaryExpressionGroup(node.Index)
+			if canNumber(indexExpression.Kind()) {
+				index = int(castInt64(indexExpression))
+			} else {
+				node.Index.errorf("non numeric value in index expression kind %s", indexExpression.Kind().String())
+			}
+		}
+
+		if node.EndIndex != nil {
+			indexExpression := st.evalPrimaryExpressionGroup(node.EndIndex)
+			if canNumber(indexExpression.Kind()) {
+				length = int(castInt64(indexExpression))
+			} else {
+				node.EndIndex.errorf("non numeric value in index expression kind %s", indexExpression.Kind().String())
+			}
+		} else {
+			length = baseExpression.Len()
+		}
+
+		return baseExpression.Slice(index, length)
+	}
+	return st.evalBaseExpressionGroup(node)
+}
+
+func (st *Runtime) isSet(node Node) bool {
+	nodeType := node.Type()
+
+	switch nodeType {
+	case NodeIndexExpr:
+		node := node.(*IndexExprNode)
+		if !st.isSet(node.Base) {
+			return false
+		}
+
+		if !st.isSet(node.Index) {
+			return false
+		}
+
+		baseExpression := st.evalPrimaryExpressionGroup(node.Base)
+		indexExpression := st.evalPrimaryExpressionGroup(node.Index)
+
+		indexType := indexExpression.Type()
+		if baseExpression.Kind() == reflect.Ptr {
+			baseExpression = baseExpression.Elem()
+		}
+
+		switch baseExpression.Kind() {
+		case reflect.Map:
+			key := baseExpression.Type().Key()
+			if !indexType.AssignableTo(key) {
+				if indexType.ConvertibleTo(key) {
+					indexExpression = indexExpression.Convert(key)
+				} else {
+					node.errorf("%s is not assignable|convertible to map key %s", indexType.String(), key.String())
+				}
+			}
+			return baseExpression.MapIndex(indexExpression).IsValid()
+		case reflect.Array, reflect.String, reflect.Slice:
+			if canNumber(indexType.Kind()) {
+				i := int(castInt64(indexExpression))
+				return i >= 0 && i < baseExpression.Len()
+			} else {
+				node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
+			}
+		case reflect.Struct:
+			if canNumber(indexType.Kind()) {
+				i := int(castInt64(indexExpression))
+				return i >= 0 && i < baseExpression.NumField()
+			} else if indexType.Kind() == reflect.String {
+				return getFieldOrMethodValue(indexExpression.String(), baseExpression).IsValid()
+			} else {
+				node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
+			}
+		default:
+			node.errorf("indexing is not supported in value type %s", baseExpression.Kind().String())
+		}
+	case NodeIdentifier:
+		if st.Resolve(node.String()).IsValid() == false {
+			return false
+		}
+	case NodeField:
+		node := node.(*FieldNode)
+		resolved := st.context
+		for i := 0; i < len(node.Ident); i++ {
+			resolved = getFieldOrMethodValue(node.Ident[i], resolved)
+			if !resolved.IsValid() {
+				return false
+			}
+		}
+	case NodeChain:
+		node := node.(*ChainNode)
+		var value = st.evalPrimaryExpressionGroup(node.Node)
+		if !value.IsValid() {
+			return false
+		}
+		for i := 0; i < len(node.Field); i++ {
+			value := getFieldOrMethodValue(node.Field[i], value)
+			if !value.IsValid() {
+				return false
+			}
+		}
+	default:
+		//todo: maybe work some edge cases
+		if !(nodeType > beginExpressions && nodeType < endExpressions) {
+			node.errorf("unexpected %q node in isset clause", node)
+		}
+	}
+	return true
+}
+
+func (st *Runtime) evalNumericComparativeExpression(node *NumericComparativeExprNode) reflect.Value {
+	left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
+	isTrue := false
+	kind := left.Kind()
+
+	// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
+	// this is necessary for expressions like 4*1.23
+	needFloatPromotion := !isFloat(kind) && isFloat(right.Kind())
+
+	switch node.Operator.typ {
+	case itemGreat:
+		if isInt(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Int()) > right.Float()
+			} else {
+				isTrue = left.Int() > toInt(right)
+			}
+		} else if isFloat(kind) {
+			isTrue = left.Float() > toFloat(right)
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Uint()) > right.Float()
+			} else {
+				isTrue = left.Uint() > toUint(right)
+			}
+		} else {
+			node.Left.errorf("a non numeric value in numeric comparative expression")
+		}
+	case itemGreatEquals:
+		if isInt(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Int()) >= right.Float()
+			} else {
+				isTrue = left.Int() >= toInt(right)
+			}
+		} else if isFloat(kind) {
+			isTrue = left.Float() >= toFloat(right)
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Uint()) >= right.Float()
+			} else {
+				isTrue = left.Uint() >= toUint(right)
+			}
+		} else {
+			node.Left.errorf("a non numeric value in numeric comparative expression")
+		}
+	case itemLess:
+		if isInt(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Int()) < right.Float()
+			} else {
+				isTrue = left.Int() < toInt(right)
+			}
+		} else if isFloat(kind) {
+			isTrue = left.Float() < toFloat(right)
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Uint()) < right.Float()
+			} else {
+				isTrue = left.Uint() < toUint(right)
+			}
+		} else {
+			node.Left.errorf("a non numeric value in numeric comparative expression")
+		}
+	case itemLessEquals:
+		if isInt(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Int()) <= right.Float()
+			} else {
+				isTrue = left.Int() <= toInt(right)
+			}
+		} else if isFloat(kind) {
+			isTrue = left.Float() <= toFloat(right)
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				isTrue = float64(left.Uint()) <= right.Float()
+			} else {
+				isTrue = left.Uint() <= toUint(right)
+			}
+		} else {
+			node.Left.errorf("a non numeric value in numeric comparative expression")
+		}
+	}
+	return boolValue(isTrue)
+}
+
+func (st *Runtime) evalLogicalExpression(node *LogicalExprNode) reflect.Value {
+	isTrue := castBoolean(st.evalPrimaryExpressionGroup(node.Left))
+	if node.Operator.typ == itemAnd {
+		isTrue = isTrue && castBoolean(st.evalPrimaryExpressionGroup(node.Right))
+	} else {
+		isTrue = isTrue || castBoolean(st.evalPrimaryExpressionGroup(node.Right))
+	}
+	return boolValue(isTrue)
+}
+
+func boolValue(isTrue bool) reflect.Value {
+	if isTrue {
+		return valueBoolTRUE
+	}
+	return valueBoolFALSE
+}
+
+func (st *Runtime) evalComparativeExpression(node *ComparativeExprNode) reflect.Value {
+	left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
+	if node.Operator.typ == itemNotEquals {
+		return boolValue(!checkEquality(left, right))
+	}
+	return boolValue(checkEquality(left, right))
+}
+
+func toInt(v reflect.Value) int64 {
+	kind := v.Kind()
+	if isInt(kind) {
+		return v.Int()
+	} else if isFloat(kind) {
+		return int64(v.Float())
+	} else if isUint(kind) {
+		return int64(v.Uint())
+	} else if kind == reflect.String {
+		n, e := strconv.ParseInt(v.String(), 10, 0)
+		if e != nil {
+			panic(e)
+		}
+		return n
+	} else if kind == reflect.Bool {
+		if v.Bool() {
+			return 0
+		}
+		return 1
+	}
+	panic(fmt.Errorf("type: %q can't be converted to int64", v.Type()))
+}
+
+func toUint(v reflect.Value) uint64 {
+	kind := v.Kind()
+	if isUint(kind) {
+		return v.Uint()
+	} else if isInt(kind) {
+		return uint64(v.Int())
+	} else if isFloat(kind) {
+		return uint64(v.Float())
+	} else if kind == reflect.String {
+		n, e := strconv.ParseUint(v.String(), 10, 0)
+		if e != nil {
+			panic(e)
+		}
+		return n
+	} else if kind == reflect.Bool {
+		if v.Bool() {
+			return 0
+		}
+		return 1
+	}
+	panic(fmt.Errorf("type: %q can't be converted to uint64", v.Type()))
+}
+
+func toFloat(v reflect.Value) float64 {
+	kind := v.Kind()
+	if isFloat(kind) {
+		return v.Float()
+	} else if isInt(kind) {
+		return float64(v.Int())
+	} else if isUint(kind) {
+		return float64(v.Uint())
+	} else if kind == reflect.String {
+		n, e := strconv.ParseFloat(v.String(), 0)
+		if e != nil {
+			panic(e)
+		}
+		return n
+	} else if kind == reflect.Bool {
+		if v.Bool() {
+			return 0
+		}
+		return 1
+	}
+	panic(fmt.Errorf("type: %q can't be converted to float64", v.Type()))
+}
+
+func (st *Runtime) evalMultiplicativeExpression(node *MultiplicativeExprNode) reflect.Value {
+	left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
+	kind := left.Kind()
+	// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
+	// this is necessary for expressions like 4*1.23
+	needFloatPromotion := !isFloat(kind) && isFloat(right.Kind())
+	switch node.Operator.typ {
+	case itemMul:
+		if isInt(kind) {
+			if needFloatPromotion {
+				// do the promotion and calculates
+				left = reflect.ValueOf(float64(left.Int()) * right.Float())
+			} else {
+				// do not need float promotion
+				left = reflect.ValueOf(left.Int() * toInt(right))
+			}
+		} else if isFloat(kind) {
+			left = reflect.ValueOf(left.Float() * toFloat(right))
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				left = reflect.ValueOf(float64(left.Uint()) * right.Float())
+			} else {
+				left = reflect.ValueOf(left.Uint() * toUint(right))
+			}
+		} else {
+			node.Left.errorf("a non numeric value in multiplicative expression")
+		}
+	case itemDiv:
+		if isInt(kind) {
+			if needFloatPromotion {
+				left = reflect.ValueOf(float64(left.Int()) / right.Float())
+			} else {
+				left = reflect.ValueOf(left.Int() / toInt(right))
+			}
+		} else if isFloat(kind) {
+			left = reflect.ValueOf(left.Float() / toFloat(right))
+		} else if isUint(kind) {
+			if needFloatPromotion {
+				left = reflect.ValueOf(float64(left.Uint()) / right.Float())
+			} else {
+				left = reflect.ValueOf(left.Uint() / toUint(right))
+			}
+		} else {
+			node.Left.errorf("a non numeric value in multiplicative expression")
+		}
+	case itemMod:
+		if isInt(kind) {
+			left = reflect.ValueOf(left.Int() % toInt(right))
+		} else if isFloat(kind) {
+			left = reflect.ValueOf(int64(left.Float()) % toInt(right))
+		} else if isUint(kind) {
+			left = reflect.ValueOf(left.Uint() % toUint(right))
+		} else {
+			node.Left.errorf("a non numeric value in multiplicative expression")
+		}
+	}
+	return left
+}
+
+func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value {
+
+	isAdditive := node.Operator.typ == itemAdd
+	if node.Left == nil {
+		right := st.evalPrimaryExpressionGroup(node.Right)
+		kind := right.Kind()
+		// todo: optimize
+		// todo:
+		if isInt(kind) {
+			if isAdditive {
+				return reflect.ValueOf(+right.Int())
+			} else {
+				return reflect.ValueOf(-right.Int())
+			}
+		} else if isUint(kind) {
+			if isAdditive {
+				return right
+			} else {
+				return reflect.ValueOf(-int64(right.Uint()))
+			}
+		} else if isFloat(kind) {
+			if isAdditive {
+				return reflect.ValueOf(+right.Float())
+			} else {
+				return reflect.ValueOf(-right.Float())
+			}
+		}
+		node.Left.errorf("a non numeric value in additive expression")
+	}
+
+	left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
+	kind := left.Kind()
+	// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
+	// this is necessary for expressions like 4+1.23
+	needFloatPromotion := !isFloat(kind) && kind != reflect.String && isFloat(right.Kind())
+	if needFloatPromotion {
+		if isInt(kind) {
+			if isAdditive {
+				left = reflect.ValueOf(float64(left.Int()) + right.Float())
+			} else {
+				left = reflect.ValueOf(float64(left.Int()) - right.Float())
+			}
+		} else if isUint(kind) {
+			if isAdditive {
+				left = reflect.ValueOf(float64(left.Uint()) + right.Float())
+			} else {
+				left = reflect.ValueOf(float64(left.Uint()) - right.Float())
+			}
+		} else {
+			node.Left.errorf("a non numeric value in additive expression")
+		}
+	} else {
+		if isInt(kind) {
+			if isAdditive {
+				left = reflect.ValueOf(left.Int() + toInt(right))
+			} else {
+				left = reflect.ValueOf(left.Int() - toInt(right))
+			}
+		} else if isFloat(kind) {
+			if isAdditive {
+				left = reflect.ValueOf(left.Float() + toFloat(right))
+			} else {
+				left = reflect.ValueOf(left.Float() - toFloat(right))
+			}
+		} else if isUint(kind) {
+			if isAdditive {
+				left = reflect.ValueOf(left.Uint() + toUint(right))
+			} else {
+				left = reflect.ValueOf(left.Uint() - toUint(right))
+			}
+		} else if kind == reflect.String {
+			if isAdditive {
+				left = reflect.ValueOf(left.String() + fmt.Sprint(right))
+			} else {
+				node.Right.errorf("minus signal is not allowed with strings")
+			}
+		} else {
+			node.Left.errorf("a non numeric value in additive expression")
+		}
+	}
+
+	return left
+}
+
+func getTypeString(value reflect.Value) string {
+	if value.IsValid() {
+		return value.Type().String()
+	}
+	return "nil"
+}
+
+func (st *Runtime) evalBaseExpressionGroup(node Node) reflect.Value {
+	switch node.Type() {
+	case NodeNil:
+		return reflect.ValueOf(nil)
+	case NodeBool:
+		if node.(*BoolNode).True {
+			return valueBoolTRUE
+		}
+		return valueBoolFALSE
+	case NodeString:
+		return reflect.ValueOf(&node.(*StringNode).Text).Elem()
+	case NodeIdentifier:
+		resolved := st.Resolve(node.(*IdentifierNode).Ident)
+		if !resolved.IsValid() {
+			node.errorf("identifier %q is not available in the current scope %v", node, st.variables)
+		}
+		return resolved
+	case NodeField:
+		node := node.(*FieldNode)
+		resolved := st.context
+		for i := 0; i < len(node.Ident); i++ {
+			fieldResolved := getFieldOrMethodValue(node.Ident[i], resolved)
+			if !fieldResolved.IsValid() {
+				node.errorf("there is no field or method %q in %s", node.Ident[i], getTypeString(resolved))
+			}
+			resolved = fieldResolved
+		}
+		return resolved
+	case NodeChain:
+		node := node.(*ChainNode)
+		var resolved = st.evalPrimaryExpressionGroup(node.Node)
+		for i := 0; i < len(node.Field); i++ {
+			fieldValue := getFieldOrMethodValue(node.Field[i], resolved)
+			if !fieldValue.IsValid() {
+				node.errorf("there is no field or method %q in %s", node.Field[i], getTypeString(resolved))
+			}
+			resolved = fieldValue
+		}
+		return resolved
+	case NodeNumber:
+		node := node.(*NumberNode)
+		if node.IsFloat {
+			return reflect.ValueOf(&node.Float64).Elem()
+		}
+
+		if node.IsInt {
+			return reflect.ValueOf(&node.Int64).Elem()
+		}
+
+		if node.IsUint {
+			return reflect.ValueOf(&node.Uint64).Elem()
+		}
+	}
+	node.errorf("unexpected node type %s in unary expression evaluating", node)
+	return reflect.Value{}
+}
+
+func (st *Runtime) evalCallExpression(baseExpr reflect.Value, args []Expression, values ...reflect.Value) reflect.Value {
+
+	if funcType.AssignableTo(baseExpr.Type()) {
+		return baseExpr.Interface().(Func)(Arguments{runtime: st, argExpr: args, argVal: values})
+	}
+
+	i := len(args) + len(values)
+	var returns []reflect.Value
+	if i <= 10 {
+		returns = reflect_Call10(i, st, baseExpr, args, values...)
+	} else {
+		returns = reflect_Call(make([]reflect.Value, i, i), st, baseExpr, args, values...)
+	}
+
+	if len(returns) == 0 {
+		return reflect.Value{}
+	}
+
+	return returns[0]
+}
+
+func (st *Runtime) evalCommandExpression(node *CommandNode) (reflect.Value, bool) {
+	term := st.evalPrimaryExpressionGroup(node.BaseExpr)
+	if node.Call {
+		if term.Kind() == reflect.Func {
+			if term.Type() == safeWriterType {
+				st.evalSafeWriter(term, node)
+				return reflect.Value{}, true
+			}
+			return st.evalCallExpression(term, node.Args), false
+		} else {
+			node.Args[0].errorf("command %q type %s is not func", node.Args[0], term.Type())
+		}
+	}
+	return term, false
+}
+
+type escapeWriter struct {
+	rawWriter  io.Writer
+	safeWriter SafeWriter
+}
+
+func (w *escapeWriter) Write(b []byte) (int, error) {
+	w.safeWriter(w.rawWriter, b)
+	return 0, nil
+}
+
+func (st *Runtime) evalSafeWriter(term reflect.Value, node *CommandNode, v ...reflect.Value) {
+
+	sw := &escapeWriter{rawWriter: st.Writer, safeWriter: term.Interface().(SafeWriter)}
+	for i := 0; i < len(v); i++ {
+		fastprinter.PrintValue(sw, v[i])
+	}
+	for i := 0; i < len(node.Args); i++ {
+		fastprinter.PrintValue(sw, st.evalPrimaryExpressionGroup(node.Args[i]))
+	}
+}
+
+func (st *Runtime) evalCommandPipeExpression(node *CommandNode, value reflect.Value) (reflect.Value, bool) {
+	term := st.evalPrimaryExpressionGroup(node.BaseExpr)
+	if term.Kind() == reflect.Func {
+		if term.Type() == safeWriterType {
+			st.evalSafeWriter(term, node, value)
+			return reflect.Value{}, true
+		}
+		return st.evalCallExpression(term, node.Args, value), false
+	} else {
+		node.BaseExpr.errorf("pipe command %q type %s is not func", node.BaseExpr, term.Type())
+	}
+	return term, false
+}
+
+func (st *Runtime) evalPipelineExpression(node *PipeNode) (value reflect.Value, safeWriter bool) {
+	value, safeWriter = st.evalCommandExpression(node.Cmds[0])
+	for i := 1; i < len(node.Cmds); i++ {
+		if safeWriter {
+			node.Cmds[i].errorf("unexpected command %s, writer command should be the last command", node.Cmds[i])
+		}
+		value, safeWriter = st.evalCommandPipeExpression(node.Cmds[i], value)
+	}
+	return
+}
+
+func reflect_Call(arguments []reflect.Value, st *Runtime, fn reflect.Value, args []Expression, values ...reflect.Value) []reflect.Value {
+	typ := fn.Type()
+	numIn := typ.NumIn()
+
+	isVariadic := typ.IsVariadic()
+	if isVariadic {
+		numIn--
+	}
+	i, j := 0, 0
+
+	for ; i < numIn && i < len(values); i++ {
+		in := typ.In(i)
+		term := values[i]
+		if !term.Type().AssignableTo(in) {
+			term = term.Convert(in)
+		}
+		arguments[i] = term
+	}
+
+	if isVariadic {
+		in := typ.In(numIn).Elem()
+		for ; i < len(values); i++ {
+			term := values[i]
+			if !term.Type().AssignableTo(in) {
+				term = term.Convert(in)
+			}
+			arguments[i] = term
+		}
+	}
+
+	for ; i < numIn && j < len(args); i, j = i+1, j+1 {
+		in := typ.In(i)
+		term := st.evalPrimaryExpressionGroup(args[j])
+		if !term.Type().AssignableTo(in) {
+			term = term.Convert(in)
+		}
+		arguments[i] = term
+	}
+
+	if isVariadic {
+		in := typ.In(numIn).Elem()
+		for ; j < len(args); i, j = i+1, j+1 {
+			term := st.evalPrimaryExpressionGroup(args[j])
+			if !term.Type().AssignableTo(in) {
+				term = term.Convert(in)
+			}
+			arguments[i] = term
+		}
+	}
+	return fn.Call(arguments[0:i])
+}
+
+func reflect_Call10(i int, st *Runtime, fn reflect.Value, args []Expression, values ...reflect.Value) []reflect.Value {
+	var arguments [10]reflect.Value
+	return reflect_Call(arguments[0:i], st, fn, args, values...)
+}
+
+func isUint(kind reflect.Kind) bool {
+	return kind >= reflect.Uint && kind <= reflect.Uint64
+}
+func isInt(kind reflect.Kind) bool {
+	return kind >= reflect.Int && kind <= reflect.Int64
+}
+func isFloat(kind reflect.Kind) bool {
+	return kind == reflect.Float32 || kind == reflect.Float64
+}
+
+// checkEquality of two reflect values in the semantic of the jet runtime
+func checkEquality(v1, v2 reflect.Value) bool {
+
+	if !v1.IsValid() || !v2.IsValid() {
+		return v1.IsValid() == v2.IsValid()
+	}
+
+	v1Type := v1.Type()
+	v2Type := v2.Type()
+
+	// fast path
+	if v1Type != v2.Type() && !v2Type.AssignableTo(v1Type) && !v2Type.ConvertibleTo(v1Type) {
+		return false
+	}
+
+	kind := v1.Kind()
+	if isInt(kind) {
+		return v1.Int() == toInt(v2)
+	}
+	if isFloat(kind) {
+		return v1.Float() == toFloat(v2)
+	}
+	if isUint(kind) {
+		return v1.Uint() == toUint(v2)
+	}
+
+	switch kind {
+	case reflect.Bool:
+		return v1.Bool() == castBoolean(v2)
+	case reflect.String:
+		return v1.String() == v2.String()
+	case reflect.Array:
+		vlen := v1.Len()
+		if vlen == v2.Len() {
+			return false
+		}
+		for i := 0; i < vlen; i++ {
+			if !checkEquality(v1.Index(i), v2.Index(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Slice:
+
+		if v1.IsNil() != v2.IsNil() {
+			return false
+		}
+
+		vlen := v1.Len()
+		if vlen != v2.Len() {
+			return false
+		}
+
+		if v1.CanAddr() && v2.CanAddr() && v1.Pointer() == v2.Pointer() {
+			return true
+		}
+
+		for i := 0; i < vlen; i++ {
+			if !checkEquality(v1.Index(i), v2.Index(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Interface:
+		if v1.IsNil() || v2.IsNil() {
+			return v1.IsNil() == v2.IsNil()
+		}
+		return checkEquality(v1.Elem(), v2.Elem())
+	case reflect.Ptr:
+		return v1.Pointer() == v2.Pointer()
+	case reflect.Struct:
+		numField := v1.NumField()
+		for i, n := 0, numField; i < n; i++ {
+			if !checkEquality(v1.Field(i), v2.Field(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Map:
+		if v1.IsNil() != v2.IsNil() {
+			return false
+		}
+		if v1.Len() != v2.Len() {
+			return false
+		}
+		if v1.Pointer() == v2.Pointer() {
+			return true
+		}
+		for _, k := range v1.MapKeys() {
+			val1 := v1.MapIndex(k)
+			val2 := v2.MapIndex(k)
+			if !val1.IsValid() || !val2.IsValid() || !checkEquality(v1.MapIndex(k), v2.MapIndex(k)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Func:
+		return v1.IsNil() && v2.IsNil()
+	default:
+		// Normal equality suffices
+		return v1.Interface() == v2.Interface()
+	}
+}
+
+func castBoolean(v reflect.Value) bool {
+	kind := v.Kind()
+	switch kind {
+	case reflect.Ptr:
+		return v.IsNil() == false
+	case reflect.Bool:
+		return v.Bool()
+	case reflect.Array:
+		numItems := v.Len()
+		for i, n := 0, numItems; i < n; i++ {
+			if !castBoolean(v.Index(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Struct:
+		numField := v.NumField()
+		for i, n := 0, numField; i < n; i++ {
+			if !castBoolean(v.Field(i)) {
+				return false
+			}
+		}
+		return true
+	case reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() > 0
+	default:
+		if isInt(kind) {
+			return v.Int() > 0
+		}
+		if isUint(kind) {
+			return v.Uint() > 0
+		}
+		if isFloat(kind) {
+			return v.Float() > 0
+		}
+	}
+	return false
+}
+
+func canNumber(kind reflect.Kind) bool {
+	return isInt(kind) || isUint(kind) || isFloat(kind)
+}
+
+func castInt64(v reflect.Value) int64 {
+	kind := v.Kind()
+	switch {
+	case isInt(kind):
+		return v.Int()
+	case isUint(kind):
+		return int64(v.Uint())
+	case isFloat(kind):
+		return int64(v.Float())
+	}
+	return 0
+}
+
+var cachedStructsMutex = sync.RWMutex{}
+var cachedStructsFieldIndex = map[reflect.Type]map[string][]int{}
+
+func getFieldOrMethodValue(key string, v reflect.Value) reflect.Value {
+	value := getValue(key, v)
+	if value.Kind() == reflect.Interface {
+		value = value.Elem()
+	}
+	return value
+}
+
+func getValue(key string, v reflect.Value) reflect.Value {
+
+	if !v.IsValid() {
+		return reflect.Value{}
+	}
+
+	value := v.MethodByName(key)
+
+	if value.IsValid() {
+		return value
+	}
+
+	k := v.Kind()
+	if k == reflect.Ptr || k == reflect.Interface {
+		v = v.Elem()
+		k = v.Kind()
+		value = v.MethodByName(key)
+		if value.IsValid() {
+			return value
+		}
+	} else if v.CanAddr() {
+		value = v.Addr().MethodByName(key)
+		if value.IsValid() {
+			return value
+		}
+	}
+
+	if k == reflect.Struct {
+		typ := v.Type()
+		cachedStructsMutex.RLock()
+		cache, ok := cachedStructsFieldIndex[typ]
+		cachedStructsMutex.RUnlock()
+		if !ok {
+			cachedStructsMutex.Lock()
+			if cache, ok = cachedStructsFieldIndex[typ]; !ok {
+				cache = make(map[string][]int)
+				buildCache(typ, cache, nil)
+				cachedStructsFieldIndex[typ] = cache
+			}
+			cachedStructsMutex.Unlock()
+		}
+		if id, ok := cache[key]; ok {
+			return v.FieldByIndex(id)
+		}
+		return reflect.Value{}
+	} else if k == reflect.Map {
+		return v.MapIndex(reflect.ValueOf(key))
+	}
+	return reflect.Value{}
+}
+
+func buildCache(typ reflect.Type, cache map[string][]int, parent []int) {
+	numFields := typ.NumField()
+	max := len(parent) + 1
+
+	for i := 0; i < numFields; i++ {
+
+		index := make([]int, max)
+		copy(index, parent)
+		index[len(parent)] = i
+
+		field := typ.Field(i)
+		if field.Anonymous {
+			typ := field.Type
+			if typ.Kind() == reflect.Struct {
+				buildCache(typ, cache, index)
+			}
+		}
+		cache[field.Name] = index
+	}
+}
+
+func getRanger(v reflect.Value) Ranger {
+	tuP := v.Type()
+	if tuP.Implements(rangerType) {
+		return v.Interface().(Ranger)
+	}
+	k := tuP.Kind()
+	switch k {
+	case reflect.Ptr, reflect.Interface:
+		v = v.Elem()
+		k = v.Kind()
+		fallthrough
+	case reflect.Slice, reflect.Array:
+		sliceranger := pool_sliceRanger.Get().(*sliceRanger)
+		sliceranger.i = -1
+		sliceranger.len = v.Len()
+		sliceranger.v = v
+		return sliceranger
+	case reflect.Map:
+		mapranger := pool_mapRanger.Get().(*mapRanger)
+		*mapranger = mapRanger{v: v, keys: v.MapKeys(), len: v.Len()}
+		return mapranger
+	case reflect.Chan:
+		chanranger := pool_chanRanger.Get().(*chanRanger)
+		*chanranger = chanRanger{v: v}
+		return chanranger
+	}
+	panic(fmt.Errorf("type %s is not rangeable", tuP))
+}
+
+var (
+	pool_sliceRanger = sync.Pool{
+		New: func() interface{} {
+			return new(sliceRanger)
+		},
+	}
+	pool_mapRanger = sync.Pool{
+		New: func() interface{} {
+			return new(mapRanger)
+		},
+	}
+	pool_chanRanger = sync.Pool{
+		New: func() interface{} {
+			return new(chanRanger)
+		},
+	}
+)
+
+type sliceRanger struct {
+	v   reflect.Value
+	len int
+	i   int
+}
+
+func (s *sliceRanger) Range() (index, value reflect.Value, end bool) {
+	s.i++
+	index = reflect.ValueOf(&s.i).Elem()
+	if s.i < s.len {
+		value = s.v.Index(s.i)
+		return
+	}
+	pool_sliceRanger.Put(s)
+	end = true
+	return
+}
+
+type chanRanger struct {
+	v reflect.Value
+}
+
+func (s *chanRanger) Range() (_, value reflect.Value, end bool) {
+	value, end = s.v.Recv()
+	if end {
+		pool_chanRanger.Put(s)
+	}
+	return
+}
+
+type mapRanger struct {
+	v    reflect.Value
+	keys []reflect.Value
+	len  int
+	i    int
+}
+
+func (s *mapRanger) Range() (index, value reflect.Value, end bool) {
+	if s.i < s.len {
+		index = s.keys[s.i]
+		value = s.v.MapIndex(index)
+		s.i++
+		return
+	}
+	end = true
+	pool_mapRanger.Put(s)
+	return
+}

+ 69 - 0
vendor/github.com/CloudyKit/jet/func.go

@@ -0,0 +1,69 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"fmt"
+	"reflect"
+)
+
+// Arguments holds the arguments passed to jet.Func.
+type Arguments struct {
+	runtime *Runtime
+	argExpr []Expression
+	argVal  []reflect.Value
+}
+
+// Get gets an argument by index.
+func (a *Arguments) Get(argumentIndex int) reflect.Value {
+	if argumentIndex < len(a.argVal) {
+		return a.argVal[argumentIndex]
+	}
+	if argumentIndex < len(a.argVal)+len(a.argExpr) {
+		return a.runtime.evalPrimaryExpressionGroup(a.argExpr[argumentIndex-len(a.argVal)])
+	}
+	return reflect.Value{}
+}
+
+// Panicf panics with formatted error message.
+func (a *Arguments) Panicf(format string, v ...interface{}) {
+	panic(fmt.Errorf(format, v...))
+}
+
+// RequireNumOfArguments panics if the number of arguments is not in the range specified by min and max.
+// In case there is no minimum pass -1, in case there is no maximum pass -1 respectively.
+func (a *Arguments) RequireNumOfArguments(funcname string, min, max int) {
+	num := len(a.argExpr) + len(a.argVal)
+	if min >= 0 && num < min {
+		a.Panicf("unexpected number of arguments in a call to %s", funcname)
+	} else if max >= 0 && num > max {
+		a.Panicf("unexpected number of arguments in a call to %s", funcname)
+	}
+}
+
+// NumOfArguments returns the number of arguments
+func (a *Arguments) NumOfArguments() int {
+	return len(a.argExpr) + len(a.argVal)
+}
+
+// Runtime get the Runtime context
+func (a *Arguments) Runtime() *Runtime {
+	return a.runtime
+}
+
+// Func function implementing this type is called directly, which is faster than calling through reflect.
+// If a function is being called many times in the execution of a template, you may consider implementing
+// a wrapper for that function implementing a Func.
+type Func func(Arguments) reflect.Value

+ 663 - 0
vendor/github.com/CloudyKit/jet/lex.go

@@ -0,0 +1,663 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"fmt"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+// item represents a token or text string returned from the scanner.
+type item struct {
+	typ itemType // The type of this item.
+	pos Pos      // The starting position, in bytes, of this item in the input string.
+	val string   // The value of this item.
+}
+
+func (i item) String() string {
+	switch {
+	case i.typ == itemEOF:
+		return "EOF"
+	case i.typ == itemError:
+		return i.val
+	case i.typ > itemKeyword:
+		return fmt.Sprintf("<%s>", i.val)
+	case len(i.val) > 10:
+		return fmt.Sprintf("%.10q...", i.val)
+	}
+	return fmt.Sprintf("%q", i.val)
+}
+
+// itemType identifies the type of lex items.
+type itemType int
+
+const (
+	itemError        itemType = iota // error occurred; value is text of error
+	itemBool                         // boolean constant
+	itemChar                         // printable ASCII character; grab bag for comma etc.
+	itemCharConstant                 // character constant
+	itemComplex                      // complex constant (1+2i); imaginary is just a number
+	itemEOF
+	itemField      // alphanumeric identifier starting with '.'
+	itemIdentifier // alphanumeric identifier not starting with '.'
+	itemLeftDelim  // left action delimiter
+	itemLeftParen  // '(' inside action
+	itemNumber     // simple number, including imaginary
+	itemPipe       // pipe symbol
+	itemRawString  // raw quoted string (includes quotes)
+	itemRightDelim // right action delimiter
+	itemRightParen // ')' inside action
+	itemSpace      // run of spaces separating arguments
+	itemString     // quoted string (includes quotes)
+	itemText       // plain text
+	itemAssign
+	itemEquals
+	itemNotEquals
+	itemGreat
+	itemGreatEquals
+	itemLess
+	itemLessEquals
+	itemComma
+	itemColonComma
+	itemAdd
+	itemMinus
+	itemMul
+	itemDiv
+	itemMod
+	itemColon
+	itemTernary
+	itemLeftBrackets
+	itemRightBrackets
+	// Keywords appear after all the rest.
+	itemKeyword // used only to delimit the keywords
+	itemExtends
+	itemBlock
+	itemYield
+	itemContent
+	itemInclude
+	itemElse
+	itemEnd
+	itemIf
+	itemNil
+	itemRange
+	itemImport
+	itemAnd
+	itemOr
+	itemNot
+	itemMSG
+	itemTrans
+)
+
+var key = map[string]itemType{
+	"extends": itemExtends,
+	"import":  itemImport,
+
+	"include": itemInclude,
+	"block":   itemBlock,
+	"yield":   itemYield,
+
+	"else": itemElse,
+	"end":  itemEnd,
+	"if":   itemIf,
+
+	"range": itemRange,
+	"nil":   itemNil,
+	"and":   itemAnd,
+	"or":    itemOr,
+	"not":   itemNot,
+
+	"content": itemContent,
+	"msg":     itemMSG,
+	"trans":   itemTrans,
+}
+
+const eof = -1
+
+// stateFn represents the state of the scanner as a function that returns the next state.
+type stateFn func(*lexer) stateFn
+
+// lexer holds the state of the scanner.
+type lexer struct {
+	name       string    // the name of the input; used only for error reports
+	input      string    // the string being scanned
+	state      stateFn   // the next lexing function to enter
+	pos        Pos       // current position in the input
+	start      Pos       // start position of this item
+	width      Pos       // width of last rune read from input
+	lastPos    Pos       // position of most recent item returned by nextItem
+	items      chan item // channel of scanned items
+	parenDepth int       // nesting depth of ( ) exprs
+	lastType   itemType
+}
+
+// next returns the next rune in the input.
+func (l *lexer) next() rune {
+	if int(l.pos) >= len(l.input) {
+		l.width = 0
+		return eof
+	}
+	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+	l.width = Pos(w)
+	l.pos += l.width
+	return r
+}
+
+// peek returns but does not consume the next rune in the input.
+func (l *lexer) peek() rune {
+	r := l.next()
+	l.backup()
+	return r
+}
+
+// backup steps back one rune. Can only be called once per call of next.
+func (l *lexer) backup() {
+	l.pos -= l.width
+}
+
+// emit passes an item back to the client.
+func (l *lexer) emit(t itemType) {
+	l.lastType = t
+	l.items <- item{t, l.start, l.input[l.start:l.pos]}
+	l.start = l.pos
+}
+
+// ignore skips over the pending input before this point.
+func (l *lexer) ignore() {
+	l.start = l.pos
+}
+
+// accept consumes the next rune if it's from the valid set.
+func (l *lexer) accept(valid string) bool {
+	if strings.IndexRune(valid, l.next()) >= 0 {
+		return true
+	}
+	l.backup()
+	return false
+}
+
+// acceptRun consumes a run of runes from the valid set.
+func (l *lexer) acceptRun(valid string) {
+	for strings.IndexRune(valid, l.next()) >= 0 {
+	}
+	l.backup()
+}
+
+// lineNumber reports which line we're on, based on the position of
+// the previous item returned by nextItem. Doing it this way
+// means we don't have to worry about peek double counting.
+func (l *lexer) lineNumber() int {
+	return 1 + strings.Count(l.input[:l.lastPos], "\n")
+}
+
+// errorf returns an error token and terminates the scan by passing
+// back a nil pointer that will be the next state, terminating l.nextItem.
+func (l *lexer) errorf(format string, args ...interface{}) stateFn {
+	l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
+	return nil
+}
+
+// nextItem returns the next item from the input.
+// Called by the parser, not in the lexing goroutine.
+func (l *lexer) nextItem() item {
+	item := <-l.items
+	l.lastPos = item.pos
+	return item
+}
+
+// drain drains the output so the lexing goroutine will exit.
+// Called by the parser, not in the lexing goroutine.
+func (l *lexer) drain() {
+	for range l.items {
+	}
+}
+
+// lex creates a new scanner for the input string.
+func lex(name, input string) *lexer {
+
+	l := &lexer{
+		name:  name,
+		input: input,
+		items: make(chan item),
+	}
+	go l.run()
+	return l
+}
+
+// run runs the state machine for the lexer.
+func (l *lexer) run() {
+	for l.state = lexText; l.state != nil; {
+		l.state = l.state(l)
+	}
+	close(l.items)
+}
+
+const (
+	leftDelim    = "{{"
+	rightDelim   = "}}"
+	leftComment  = "{*"
+	rightComment = "*}"
+)
+
+// state functions
+func lexText(l *lexer) stateFn {
+	for {
+		if i := strings.IndexByte(l.input[l.pos:], '{'); i == -1 {
+			l.pos = Pos(len(l.input))
+			break
+		} else {
+			l.pos += Pos(i)
+			if strings.HasPrefix(l.input[l.pos:], leftDelim) {
+				if l.pos > l.start {
+					l.emit(itemText)
+				}
+				return lexLeftDelim
+			}
+			if strings.HasPrefix(l.input[l.pos:], leftComment) {
+				if l.pos > l.start {
+					l.emit(itemText)
+				}
+				return lexComment
+			}
+		}
+		if l.next() == eof {
+			break
+		}
+	}
+	// Correctly reached EOF.
+	if l.pos > l.start {
+		l.emit(itemText)
+	}
+	l.emit(itemEOF)
+	return nil
+}
+
+func lexLeftDelim(l *lexer) stateFn {
+	l.pos += Pos(len(leftDelim))
+	l.emit(itemLeftDelim)
+	l.parenDepth = 0
+	return lexInsideAction
+}
+
+// lexComment scans a comment. The left comment marker is known to be present.
+func lexComment(l *lexer) stateFn {
+	l.pos += Pos(len(leftComment))
+	i := strings.Index(l.input[l.pos:], rightComment)
+	if i < 0 {
+		return l.errorf("unclosed comment")
+	}
+	l.pos += Pos(i + len(rightComment))
+	l.ignore()
+	return lexText
+}
+
+// lexRightDelim scans the right delimiter, which is known to be present.
+func lexRightDelim(l *lexer) stateFn {
+	l.pos += Pos(len(rightDelim))
+	l.emit(itemRightDelim)
+	return lexText
+}
+
+// lexInsideAction scans the elements inside action delimiters.
+func lexInsideAction(l *lexer) stateFn {
+	// Either number, quoted string, or identifier.
+	// Spaces separate arguments; runs of spaces turn into itemSpace.
+	// Pipe symbols separate and are emitted.
+	if strings.HasPrefix(l.input[l.pos:], rightDelim) {
+		if l.parenDepth == 0 {
+			return lexRightDelim
+		}
+		return l.errorf("unclosed left paren")
+	}
+	switch r := l.next(); {
+	case r == eof || isEndOfLine(r):
+		return l.errorf("unclosed action")
+	case isSpace(r):
+		return lexSpace
+	case r == ',':
+		l.emit(itemComma)
+	case r == ';':
+		l.emit(itemColonComma)
+	case r == '*':
+		l.emit(itemMul)
+	case r == '/':
+		l.emit(itemDiv)
+	case r == '%':
+		l.emit(itemMod)
+	case r == '-':
+
+		if r := l.peek(); '0' <= r && r <= '9' &&
+			itemAdd != l.lastType &&
+			itemMinus != l.lastType &&
+			itemNumber != l.lastType &&
+			itemIdentifier != l.lastType &&
+			itemString != l.lastType &&
+			itemRawString != l.lastType &&
+			itemCharConstant != l.lastType &&
+			itemBool != l.lastType &&
+			itemField != l.lastType &&
+			itemChar != l.lastType &&
+			itemTrans != l.lastType {
+			l.backup()
+			return lexNumber
+		}
+		l.emit(itemMinus)
+	case r == '+':
+		if r := l.peek(); '0' <= r && r <= '9' &&
+			itemAdd != l.lastType &&
+			itemMinus != l.lastType &&
+			itemNumber != l.lastType &&
+			itemIdentifier != l.lastType &&
+			itemString != l.lastType &&
+			itemRawString != l.lastType &&
+			itemCharConstant != l.lastType &&
+			itemBool != l.lastType &&
+			itemField != l.lastType &&
+			itemChar != l.lastType &&
+			itemTrans != l.lastType {
+			l.backup()
+			return lexNumber
+		}
+		l.emit(itemAdd)
+	case r == '?':
+		l.emit(itemTernary)
+	case r == '&':
+		if l.next() == '&' {
+			l.emit(itemAnd)
+		} else {
+			l.backup()
+		}
+	case r == '<':
+		if l.next() == '=' {
+			l.emit(itemLessEquals)
+		} else {
+			l.backup()
+			l.emit(itemLess)
+		}
+	case r == '>':
+		if l.next() == '=' {
+			l.emit(itemGreatEquals)
+		} else {
+			l.backup()
+			l.emit(itemGreat)
+		}
+	case r == '!':
+		if l.next() == '=' {
+			l.emit(itemNotEquals)
+		} else {
+			l.backup()
+			l.emit(itemNot)
+		}
+
+	case r == '=':
+		if l.next() == '=' {
+			l.emit(itemEquals)
+		} else {
+			l.backup()
+			l.emit(itemAssign)
+		}
+	case r == ':':
+		if l.next() == '=' {
+			l.emit(itemAssign)
+		} else {
+			l.backup()
+			l.emit(itemColon)
+		}
+	case r == '|':
+		if l.next() == '|' {
+			l.emit(itemOr)
+		} else {
+			l.backup()
+			l.emit(itemPipe)
+		}
+	case r == '"':
+		return lexQuote
+	case r == '`':
+		return lexRawQuote
+	case r == '\'':
+		return lexChar
+	case r == '.':
+		// special look-ahead for ".field" so we don't break l.backup().
+		if l.pos < Pos(len(l.input)) {
+			r := l.input[l.pos]
+			if r < '0' || '9' < r {
+				return lexField
+			}
+		}
+		fallthrough // '.' can start a number.
+	case '0' <= r && r <= '9':
+		l.backup()
+		return lexNumber
+	case isAlphaNumeric(r):
+		l.backup()
+		return lexIdentifier
+	case r == '[':
+		l.emit(itemLeftBrackets)
+	case r == ']':
+		l.emit(itemRightBrackets)
+	case r == '(':
+		l.emit(itemLeftParen)
+		l.parenDepth++
+	case r == ')':
+		l.emit(itemRightParen)
+		l.parenDepth--
+		if l.parenDepth < 0 {
+			return l.errorf("unexpected right paren %#U", r)
+		}
+	case r <= unicode.MaxASCII && unicode.IsPrint(r):
+		l.emit(itemChar)
+		return lexInsideAction
+	default:
+		return l.errorf("unrecognized character in action: %#U", r)
+	}
+	return lexInsideAction
+}
+
+// lexSpace scans a run of space characters.
+// One space has already been seen.
+func lexSpace(l *lexer) stateFn {
+	for isSpace(l.peek()) {
+		l.next()
+	}
+	l.emit(itemSpace)
+	return lexInsideAction
+}
+
+// lexIdentifier scans an alphanumeric.
+func lexIdentifier(l *lexer) stateFn {
+Loop:
+	for {
+		switch r := l.next(); {
+		case isAlphaNumeric(r):
+		// absorb.
+		default:
+			l.backup()
+			word := l.input[l.start:l.pos]
+			if !l.atTerminator() {
+				return l.errorf("bad character %#U", r)
+			}
+			switch {
+			case key[word] > itemKeyword:
+				l.emit(key[word])
+			case word[0] == '.':
+				l.emit(itemField)
+			case word == "true", word == "false":
+				l.emit(itemBool)
+			default:
+				l.emit(itemIdentifier)
+			}
+			break Loop
+		}
+	}
+	return lexInsideAction
+}
+
+// lexField scans a field: .Alphanumeric.
+// The . has been scanned.
+func lexField(l *lexer) stateFn {
+
+	if l.atTerminator() {
+		// Nothing interesting follows -> "." or "$".
+		l.emit(itemIdentifier)
+		return lexInsideAction
+	}
+
+	var r rune
+	for {
+		r = l.next()
+		if !isAlphaNumeric(r) {
+			l.backup()
+			break
+		}
+	}
+	if !l.atTerminator() {
+		return l.errorf("bad character %#U", r)
+	}
+	l.emit(itemField)
+	return lexInsideAction
+}
+
+// atTerminator reports whether the input is at valid termination character to
+// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
+// like "$x+2" not being acceptable without a space, in case we decide one
+// day to implement arithmetic.
+func (l *lexer) atTerminator() bool {
+	r := l.peek()
+	if isSpace(r) || isEndOfLine(r) {
+		return true
+	}
+	switch r {
+	case eof, '.', ',', '|', ':', ')', '=', '(', ';', '?', '[', ']', '+', '-', '/', '%', '*', '&', '!', '<', '>':
+		return true
+	}
+	// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
+	// succeed but should fail) but only in extremely rare cases caused by willfully
+	// bad choice of delimiter.
+	if rd, _ := utf8.DecodeRuneInString(rightDelim); rd == r {
+		return true
+	}
+	return false
+}
+
+// lexChar scans a character constant. The initial quote is already
+// scanned. Syntax checking is done by the parser.
+func lexChar(l *lexer) stateFn {
+Loop:
+	for {
+		switch l.next() {
+		case '\\':
+			if r := l.next(); r != eof && r != '\n' {
+				break
+			}
+			fallthrough
+		case eof, '\n':
+			return l.errorf("unterminated character constant")
+		case '\'':
+			break Loop
+		}
+	}
+	l.emit(itemCharConstant)
+	return lexInsideAction
+}
+
+// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
+// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
+// and "089" - but when it's wrong the input is invalid and the parser (via
+// strconv) will notice.
+func lexNumber(l *lexer) stateFn {
+	if !l.scanNumber() {
+		return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
+	}
+
+	l.emit(itemNumber)
+	return lexInsideAction
+}
+
+func (l *lexer) scanNumber() bool {
+	// Optional leading sign.
+	l.accept("+-")
+	// Is it hex?
+	digits := "0123456789"
+	if l.accept("0") && l.accept("xX") {
+		digits = "0123456789abcdefABCDEF"
+	}
+	l.acceptRun(digits)
+	if l.accept(".") {
+		l.acceptRun(digits)
+	}
+	if l.accept("eE") {
+		l.accept("+-")
+		l.acceptRun("0123456789")
+	}
+	//Is it imaginary?
+	l.accept("i")
+	//Next thing mustn't be alphanumeric.
+	if isAlphaNumeric(l.peek()) {
+		l.next()
+		return false
+	}
+	return true
+}
+
+// lexQuote scans a quoted string.
+func lexQuote(l *lexer) stateFn {
+Loop:
+	for {
+		switch l.next() {
+		case '\\':
+			if r := l.next(); r != eof && r != '\n' {
+				break
+			}
+			fallthrough
+		case eof, '\n':
+			return l.errorf("unterminated quoted string")
+		case '"':
+			break Loop
+		}
+	}
+	l.emit(itemString)
+	return lexInsideAction
+}
+
+// lexRawQuote scans a raw quoted string.
+func lexRawQuote(l *lexer) stateFn {
+Loop:
+	for {
+		switch l.next() {
+		case eof:
+			return l.errorf("unterminated raw quoted string")
+		case '`':
+			break Loop
+		}
+	}
+	l.emit(itemRawString)
+	return lexInsideAction
+}
+
+// isSpace reports whether r is a space character.
+func isSpace(r rune) bool {
+	return r == ' ' || r == '\t'
+}
+
+// isEndOfLine reports whether r is an end-of-line character.
+func isEndOfLine(r rune) bool {
+	return r == '\r' || r == '\n'
+}
+
+// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
+func isAlphaNumeric(r rune) bool {
+	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
+}

+ 95 - 0
vendor/github.com/CloudyKit/jet/loader.go

@@ -0,0 +1,95 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"errors"
+	"io"
+	"os"
+	"path"
+	"path/filepath"
+)
+
+// Loader is a minimal interface required for loading templates.
+type Loader interface {
+	// Open opens the underlying reader with template content.
+	Open(name string) (io.ReadCloser, error)
+	// Exists checks for template existence and returns full path.
+	Exists(name string) (string, bool)
+}
+
+// hasAddPath is an optional Loader interface. Most probably useful for OS file system only, thus unexported.
+type hasAddPath interface {
+	AddPath(path string)
+}
+
+// hasAddGopathPath is an optional Loader interface. Most probably useful for OS file system only, thus unexported.
+type hasAddGopathPath interface {
+	AddGopathPath(path string)
+}
+
+// OSFileSystemLoader implements Loader interface using OS file system (os.File).
+type OSFileSystemLoader struct {
+	dirs []string
+}
+
+// NewOSFileSystemLoader returns an initialized OSFileSystemLoader.
+func NewOSFileSystemLoader(paths ...string) *OSFileSystemLoader {
+	return &OSFileSystemLoader{dirs: paths}
+}
+
+// Open opens a file from OS file system.
+func (l *OSFileSystemLoader) Open(name string) (io.ReadCloser, error) {
+	return os.Open(name)
+}
+
+// Exists checks if the template name exists by walking the list of template paths
+// returns string with the full path of the template and bool true if the template file was found
+func (l *OSFileSystemLoader) Exists(name string) (string, bool) {
+	for i := 0; i < len(l.dirs); i++ {
+		fileName := path.Join(l.dirs[i], name)
+		if _, err := os.Stat(fileName); err == nil {
+			return fileName, true
+		}
+	}
+	return "", false
+}
+
+// AddPath adds the path to the internal list of paths searched when loading templates.
+func (l *OSFileSystemLoader) AddPath(path string) {
+	l.dirs = append(l.dirs, path)
+}
+
+// AddGopathPath adds a path located in the GOPATH.
+// Example: l.AddGopathPath("github.com/CloudyKit/jet/example/views")
+func (l *OSFileSystemLoader) AddGopathPath(path string) {
+	paths := filepath.SplitList(os.Getenv("GOPATH"))
+	for i := 0; i < len(paths); i++ {
+		var err error
+		path, err = filepath.Abs(filepath.Join(paths[i], "src", path))
+		if err != nil {
+			panic(errors.New("Can't add this path err: " + err.Error()))
+		}
+
+		if fstats, err := os.Stat(path); os.IsNotExist(err) == false && fstats.IsDir() {
+			l.AddPath(path)
+			return
+		}
+	}
+
+	if fstats, err := os.Stat(path); os.IsNotExist(err) == false && fstats.IsDir() {
+		l.AddPath(path)
+	}
+}

+ 666 - 0
vendor/github.com/CloudyKit/jet/node.go

@@ -0,0 +1,666 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+var textFormat = "%s" //Changed to "%q" in tests for better error messages.
+
+type Node interface {
+	Type() NodeType
+	String() string
+	Position() Pos
+	line() int
+	error(error)
+	errorf(string, ...interface{})
+}
+
+type Expression interface {
+	Node
+}
+
+// Pos represents a byte position in the original input text from which
+// this template was parsed.
+type Pos int
+
+func (p Pos) Position() Pos {
+	return p
+}
+
+// NodeType identifies the type of a parse tree node.
+type NodeType int
+
+type NodeBase struct {
+	TemplateName string
+	Line         int
+	NodeType
+	Pos
+}
+
+func (node *NodeBase) line() int {
+	return node.Line
+}
+
+func (node *NodeBase) error(err error) {
+	node.errorf("%s", err)
+}
+
+func (node *NodeBase) errorf(format string, v ...interface{}) {
+	panic(fmt.Errorf("Jet Runtime Error(%q:%d): %s", node.TemplateName, node.Line, fmt.Sprintf(format, v...)))
+}
+
+// Type returns itself and provides an easy default implementation
+// for embedding in a Node. Embedded in all non-trivial Nodes.
+func (t NodeType) Type() NodeType {
+	return t
+}
+
+const (
+	NodeText       NodeType = iota //Plain text.
+	NodeAction                     //A non-control action such as a field evaluation.
+	NodeChain                      //A sequence of field accesses.
+	NodeCommand                    //An element of a pipeline.
+	nodeElse                       //An else action. Not added to tree.
+	nodeEnd                        //An end action. Not added to tree.
+	NodeField                      //A field or method name.
+	NodeIdentifier                 //An identifier; always a function name.
+	NodeIf                         //An if action.
+	NodeList                       //A list of Nodes.
+	NodePipe                       //A pipeline of commands.
+	NodeRange                      //A range action.
+	nodeContent
+	//NodeWith                       //A with action.
+	NodeBlock
+	NodeInclude
+	NodeYield
+	NodeSet
+	beginExpressions
+	NodeString //A string constant.
+	NodeNil    //An untyped nil constant.
+	NodeNumber //A numerical constant.
+	NodeBool   //A boolean constant.
+	NodeAdditiveExpr
+	NodeMultiplicativeExpr
+	NodeComparativeExpr
+	NodeNumericComparativeExpr
+	NodeLogicalExpr
+	NodeCallExpr
+	NodeNotExpr
+	NodeTernaryExpr
+	NodeIndexExpr
+	NodeSliceExpr
+	endExpressions
+)
+
+// Nodes.
+
+// ListNode holds a sequence of nodes.
+type ListNode struct {
+	NodeBase
+	Nodes []Node //The element nodes in lexical order.
+}
+
+func (l *ListNode) append(n Node) {
+	l.Nodes = append(l.Nodes, n)
+}
+
+func (l *ListNode) String() string {
+	b := new(bytes.Buffer)
+	for _, n := range l.Nodes {
+		fmt.Fprint(b, n)
+	}
+	return b.String()
+}
+
+// TextNode holds plain text.
+type TextNode struct {
+	NodeBase
+	Text []byte
+}
+
+func (t *TextNode) String() string {
+	return fmt.Sprintf(textFormat, t.Text)
+}
+
+// PipeNode holds a pipeline with optional declaration
+type PipeNode struct {
+	NodeBase                //The line number in the input. Deprecated: Kept for compatibility.
+	Cmds     []*CommandNode //The commands in lexical order.
+}
+
+func (p *PipeNode) append(command *CommandNode) {
+	p.Cmds = append(p.Cmds, command)
+}
+
+func (p *PipeNode) String() string {
+	s := ""
+	for i, c := range p.Cmds {
+		if i > 0 {
+			s += " | "
+		}
+		s += c.String()
+	}
+	return s
+}
+
+// ActionNode holds an action (something bounded by delimiters).
+// Control actions have their own nodes; ActionNode represents simple
+// ones such as field evaluations and parenthesized pipelines.
+type ActionNode struct {
+	NodeBase
+	Set  *SetNode
+	Pipe *PipeNode
+}
+
+func (a *ActionNode) String() string {
+	if a.Set != nil {
+		if a.Pipe == nil {
+			return fmt.Sprintf("{{%s}}", a.Set)
+		}
+		return fmt.Sprintf("{{%s;%s}}", a.Set, a.Pipe)
+	}
+	return fmt.Sprintf("{{%s}}", a.Pipe)
+}
+
+// CommandNode holds a command (a pipeline inside an evaluating action).
+type CommandNode struct {
+	NodeBase
+	Call     bool
+	BaseExpr Expression
+	Args     []Expression
+}
+
+func (c *CommandNode) append(arg Node) {
+	c.Args = append(c.Args, arg)
+}
+
+func (c *CommandNode) String() string {
+	s := c.BaseExpr.String()
+	if c.Call {
+		s += ":"
+	}
+	for i, arg := range c.Args {
+		if i > 0 {
+			s += ", "
+		}
+		if _, ok := arg.(*PipeNode); ok {
+			s += "(" + arg.String() + ")"
+		} else {
+			s += arg.String()
+		}
+
+	}
+	return s
+}
+
+// IdentifierNode holds an identifier.
+type IdentifierNode struct {
+	NodeBase
+	Ident string //The identifier's name.
+}
+
+func (i *IdentifierNode) String() string {
+	return i.Ident
+}
+
+// NilNode holds the special identifier 'nil' representing an untyped nil constant.
+type NilNode struct {
+	NodeBase
+}
+
+func (n *NilNode) String() string {
+	return "nil"
+}
+
+// FieldNode holds a field (identifier starting with '.').
+// The names may be chained ('.x.y').
+// The period is dropped from each ident.
+type FieldNode struct {
+	NodeBase
+	Ident []string //The identifiers in lexical order.
+}
+
+func (f *FieldNode) String() string {
+	s := ""
+	for _, id := range f.Ident {
+		s += "." + id
+	}
+	return s
+}
+
+// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
+// The names may be chained ('.x.y').
+// The periods are dropped from each ident.
+type ChainNode struct {
+	NodeBase
+	Node  Node
+	Field []string //The identifiers in lexical order.
+}
+
+// Add adds the named field (which should start with a period) to the end of the chain.
+func (c *ChainNode) Add(field string) {
+	if len(field) == 0 || field[0] != '.' {
+		panic("no dot in field")
+	}
+	field = field[1:] //Remove leading dot.
+	if field == "" {
+		panic("empty field")
+	}
+	c.Field = append(c.Field, field)
+}
+
+func (c *ChainNode) String() string {
+	s := c.Node.String()
+	if _, ok := c.Node.(*PipeNode); ok {
+		s = "(" + s + ")"
+	}
+	for _, field := range c.Field {
+		s += "." + field
+	}
+	return s
+}
+
+// BoolNode holds a boolean constant.
+type BoolNode struct {
+	NodeBase
+	True bool //The value of the boolean constant.
+}
+
+func (b *BoolNode) String() string {
+	if b.True {
+		return "true"
+	}
+	return "false"
+}
+
+// NumberNode holds a number: signed or unsigned integer, float, or complex.
+// The value is parsed and stored under all the types that can represent the value.
+// This simulates in a small amount of code the behavior of Go's ideal constants.
+type NumberNode struct {
+	NodeBase
+
+	IsInt      bool       //Number has an integral value.
+	IsUint     bool       //Number has an unsigned integral value.
+	IsFloat    bool       //Number has a floating-point value.
+	IsComplex  bool       //Number is complex.
+	Int64      int64      //The signed integer value.
+	Uint64     uint64     //The unsigned integer value.
+	Float64    float64    //The floating-point value.
+	Complex128 complex128 //The complex value.
+	Text       string     //The original textual representation from the input.
+}
+
+// simplifyComplex pulls out any other types that are represented by the complex number.
+// These all require that the imaginary part be zero.
+func (n *NumberNode) simplifyComplex() {
+	n.IsFloat = imag(n.Complex128) == 0
+	if n.IsFloat {
+		n.Float64 = real(n.Complex128)
+		n.IsInt = float64(int64(n.Float64)) == n.Float64
+		if n.IsInt {
+			n.Int64 = int64(n.Float64)
+		}
+		n.IsUint = float64(uint64(n.Float64)) == n.Float64
+		if n.IsUint {
+			n.Uint64 = uint64(n.Float64)
+		}
+	}
+}
+
+func (n *NumberNode) String() string {
+	return n.Text
+}
+
+// StringNode holds a string constant. The value has been "unquoted".
+type StringNode struct {
+	NodeBase
+
+	Quoted string //The original text of the string, with quotes.
+	Text   string //The string, after quote processing.
+}
+
+func (s *StringNode) String() string {
+	return s.Quoted
+}
+
+// endNode represents an {{end}} action.
+// It does not appear in the final parse tree.
+type endNode struct {
+	NodeBase
+}
+
+func (e *endNode) String() string {
+	return "{{end}}"
+}
+
+// endNode represents an {{end}} action.
+// It does not appear in the final parse tree.
+type contentNode struct {
+	NodeBase
+}
+
+func (e *contentNode) String() string {
+	return "{{content}}"
+}
+
+// elseNode represents an {{else}} action. Does not appear in the final tree.
+type elseNode struct {
+	NodeBase //The line number in the input. Deprecated: Kept for compatibility.
+}
+
+func (e *elseNode) String() string {
+	return "{{else}}"
+}
+
+// SetNode represents a set action, ident( ',' ident)* '=' expression ( ',' expression )*
+type SetNode struct {
+	NodeBase
+	Let                bool
+	IndexExprGetLookup bool
+	Left               []Expression
+	Right              []Expression
+}
+
+func (set *SetNode) String() string {
+	var s = ""
+
+	for i, v := range set.Left {
+		if i > 0 {
+			s += ", "
+		}
+		s += v.String()
+	}
+
+	if set.Let {
+		s += ":="
+	} else {
+		s += "="
+	}
+
+	for i, v := range set.Right {
+		if i > 0 {
+			s += ", "
+		}
+		s += v.String()
+	}
+
+	return s
+}
+
+// BranchNode is the common representation of if, range, and with.
+type BranchNode struct {
+	NodeBase
+	Set        *SetNode
+	Expression Expression
+	List       *ListNode
+	ElseList   *ListNode
+}
+
+func (b *BranchNode) String() string {
+
+	if b.NodeType == NodeRange {
+		s := ""
+		if b.Set != nil {
+			s = b.Set.String()
+		} else {
+			s = b.Expression.String()
+		}
+
+		if b.ElseList != nil {
+			return fmt.Sprintf("{{range %s}}%s{{else}}%s{{end}}", s, b.List, b.ElseList)
+		}
+		return fmt.Sprintf("{{range %s}}%s{{end}}", s, b.List)
+	} else {
+		s := ""
+		if b.Set != nil {
+			s = b.Set.String() + ";"
+		}
+		if b.ElseList != nil {
+			return fmt.Sprintf("{{if %s%s}}%s{{else}}%s{{end}}", s, b.Expression, b.List, b.ElseList)
+		}
+		return fmt.Sprintf("{{if %s%s}}%s{{end}}", s, b.Expression, b.List)
+	}
+}
+
+// IfNode represents an {{if}} action and its commands.
+type IfNode struct {
+	BranchNode
+}
+
+// RangeNode represents a {{range}} action and its commands.
+type RangeNode struct {
+	BranchNode
+}
+
+type BlockParameter struct {
+	Identifier string
+	Expression Expression
+}
+
+type BlockParameterList struct {
+	NodeBase
+	List []BlockParameter
+}
+
+func (bplist *BlockParameterList) Param(name string) (Expression, int) {
+	for i := 0; i < len(bplist.List); i++ {
+		param := &bplist.List[i]
+		if param.Identifier == name {
+			return param.Expression, i
+		}
+	}
+	return nil, -1
+}
+
+func (bplist *BlockParameterList) String() (str string) {
+	buff := bytes.NewBuffer(nil)
+	for _, bp := range bplist.List {
+		if bp.Identifier == "" {
+			fmt.Fprintf(buff, "%s,", bp.Expression)
+		} else {
+			if bp.Expression == nil {
+				fmt.Fprintf(buff, "%s,", bp.Identifier)
+			} else {
+				fmt.Fprintf(buff, "%s=%s,", bp.Identifier, bp.Expression)
+			}
+		}
+	}
+	if buff.Len() > 0 {
+		str = buff.String()[0 : buff.Len()-1]
+	}
+	return
+}
+
+// BlockNode represents a {{block }} action.
+type BlockNode struct {
+	NodeBase        //The line number in the input. Deprecated: Kept for compatibility.
+	Name     string //The name of the template (unquoted).
+
+	Parameters *BlockParameterList
+	Expression Expression //The command to evaluate as dot for the template.
+
+	List    *ListNode
+	Content *ListNode
+}
+
+func (t *BlockNode) String() string {
+	if t.Content != nil {
+		if t.Expression == nil {
+			return fmt.Sprintf("{{block %s(%s)}}%s{{content}}%s{{end}}", t.Name, t.Parameters, t.List, t.Content)
+		}
+		return fmt.Sprintf("{{block %s(%s) %s}}%s{{content}}%s{{end}}", t.Name, t.Parameters, t.Expression, t.List, t.Content)
+	}
+	if t.Expression == nil {
+		return fmt.Sprintf("{{block %s(%s)}}%s{{end}}", t.Name, t.Parameters, t.List)
+	}
+	return fmt.Sprintf("{{block %s(%s) %s}}%s{{end}}", t.Name, t.Parameters, t.Expression, t.List)
+}
+
+// YieldNode represents a {{yield}} action
+type YieldNode struct {
+	NodeBase          //The line number in the input. Deprecated: Kept for compatibility.
+	Name       string //The name of the template (unquoted).
+	Parameters *BlockParameterList
+	Expression Expression //The command to evaluate as dot for the template.
+	Content    *ListNode
+	IsContent  bool
+}
+
+func (t *YieldNode) String() string {
+	if t.IsContent {
+		if t.Expression == nil {
+			return "{{yield content}}"
+		}
+		return fmt.Sprintf("{{yield content %s}}", t.Expression)
+	}
+
+	if t.Content != nil {
+		if t.Expression == nil {
+			return fmt.Sprintf("{{yield %s(%s) content}}%s{{end}}", t.Name, t.Parameters, t.Content)
+		}
+		return fmt.Sprintf("{{yield %s(%s) %s content}}%s{{end}}", t.Name, t.Parameters, t.Expression, t.Content)
+	}
+
+	if t.Expression == nil {
+		return fmt.Sprintf("{{yield %s(%s)}}", t.Name, t.Parameters)
+	}
+	return fmt.Sprintf("{{yield %s(%s) %s}}", t.Name, t.Parameters, t.Expression)
+}
+
+// IncludeNode represents a {{include }} action.
+type IncludeNode struct {
+	NodeBase
+	Name       Expression
+	Expression Expression
+}
+
+func (t *IncludeNode) String() string {
+	if t.Expression == nil {
+		return fmt.Sprintf("{{include %s}}", t.Name)
+	}
+	return fmt.Sprintf("{{include %s %s}}", t.Name, t.Expression)
+}
+
+type binaryExprNode struct {
+	NodeBase
+	Operator    item
+	Left, Right Expression
+}
+
+func (node *binaryExprNode) String() string {
+	return fmt.Sprintf("%s %s %s", node.Left, node.Operator.val, node.Right)
+}
+
+// AdditiveExprNode represents an add or subtract expression
+// ex: expression ( '+' | '-' ) expression
+type AdditiveExprNode struct {
+	binaryExprNode
+}
+
+// MultiplicativeExprNode represents a multiplication, division, or module expression
+// ex: expression ( '*' | '/' | '%' ) expression
+type MultiplicativeExprNode struct {
+	binaryExprNode
+}
+
+// LogicalExprNode represents a boolean expression, 'and' or 'or'
+// ex: expression ( '&&' | '||' ) expression
+type LogicalExprNode struct {
+	binaryExprNode
+}
+
+// ComparativeExprNode represents a comparative expression
+// ex: expression ( '==' | '!=' ) expression
+type ComparativeExprNode struct {
+	binaryExprNode
+}
+
+// NumericComparativeExprNode represents a numeric comparative expression
+// ex: expression ( '<' | '>' | '<=' | '>=' ) expression
+type NumericComparativeExprNode struct {
+	binaryExprNode
+}
+
+// NotExprNode represents a negate expression
+// ex: '!' expression
+type NotExprNode struct {
+	NodeBase
+	Expr Expression
+}
+
+func (s *NotExprNode) String() string {
+	return fmt.Sprintf("!%s", s.Expr)
+}
+
+// CallExprNode represents a call expression
+// ex: expression '(' (expression (',' expression)* )? ')'
+type CallExprNode struct {
+	NodeBase
+	BaseExpr Expression
+	Args     []Expression
+}
+
+func (s *CallExprNode) String() string {
+	arguments := ""
+	for i, expr := range s.Args {
+		if i > 0 {
+			arguments += ", "
+		}
+		arguments += expr.String()
+	}
+	return fmt.Sprintf("%s(%s)", s.BaseExpr, arguments)
+}
+
+// TernaryExprNod represents a ternary expression,
+// ex: expression '?' expression ':' expression
+type TernaryExprNode struct {
+	NodeBase
+	Boolean, Left, Right Expression
+}
+
+func (s *TernaryExprNode) String() string {
+	return fmt.Sprintf("%s?%s:%s", s.Boolean, s.Left, s.Right)
+}
+
+type IndexExprNode struct {
+	NodeBase
+	Base  Expression
+	Index Expression
+}
+
+func (s *IndexExprNode) String() string {
+	return fmt.Sprintf("%s[%s]", s.Base, s.Index)
+}
+
+type SliceExprNode struct {
+	NodeBase
+	Base     Expression
+	Index    Expression
+	EndIndex Expression
+}
+
+func (s *SliceExprNode) String() string {
+	var index_string, len_string string
+	if s.Index != nil {
+		index_string = s.Index.String()
+	}
+	if s.EndIndex != nil {
+		len_string = s.EndIndex.String()
+	}
+	return fmt.Sprintf("%s[%s:%s]", s.Base, index_string, len_string)
+}

+ 987 - 0
vendor/github.com/CloudyKit/jet/parse.go

@@ -0,0 +1,987 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jet
+
+import (
+	"bytes"
+	"fmt"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+func unquote(text string) (string, error) {
+	return strconv.Unquote(text)
+}
+
+// Template is the representation of a single parsed template.
+type Template struct {
+	Name      string // name of the template represented by the tree.
+	ParseName string // name of the top-level template during parsing, for error messages.
+
+	set     *Set
+	extends *Template
+	imports []*Template
+
+	processedBlocks map[string]*BlockNode
+	passedBlocks    map[string]*BlockNode
+	Root            *ListNode // top-level root of the tree.
+
+	text string // text parsed to create the template (or its parent)
+
+	// Parsing only; cleared after parse.
+	lex       *lexer
+	token     [3]item // three-token lookahead for parser.
+	peekCount int
+}
+
+// next returns the next token.
+func (t *Template) next() item {
+	if t.peekCount > 0 {
+		t.peekCount--
+	} else {
+		t.token[0] = t.lex.nextItem()
+	}
+	return t.token[t.peekCount]
+}
+
+// backup backs the input stream up one token.
+func (t *Template) backup() {
+	t.peekCount++
+}
+
+// backup2 backs the input stream up two tokens.
+// The zeroth token is already there.
+func (t *Template) backup2(t1 item) {
+	t.token[1] = t1
+	t.peekCount = 2
+}
+
+// backup3 backs the input stream up three tokens
+// The zeroth token is already there.
+func (t *Template) backup3(t2, t1 item) {
+	// Reverse order: we're pushing back.
+	t.token[1] = t1
+	t.token[2] = t2
+	t.peekCount = 3
+}
+
+// peek returns but does not consume the next token.
+func (t *Template) peek() item {
+	if t.peekCount > 0 {
+		return t.token[t.peekCount-1]
+	}
+	t.peekCount = 1
+	t.token[0] = t.lex.nextItem()
+	return t.token[0]
+}
+
+// nextNonSpace returns the next non-space token.
+func (t *Template) nextNonSpace() (token item) {
+	for {
+		token = t.next()
+		if token.typ != itemSpace {
+			break
+		}
+	}
+	return token
+}
+
+// peekNonSpace returns but does not consume the next non-space token.
+func (t *Template) peekNonSpace() (token item) {
+	for {
+		token = t.next()
+		if token.typ != itemSpace {
+			break
+		}
+	}
+	t.backup()
+	return token
+}
+
+// errorf formats the error and terminates processing.
+func (t *Template) errorf(format string, args ...interface{}) {
+	t.Root = nil
+	format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
+	panic(fmt.Errorf(format, args...))
+}
+
+// error terminates processing.
+func (t *Template) error(err error) {
+	t.errorf("%s", err)
+}
+
+// expect consumes the next token and guarantees it has the required type.
+func (t *Template) expect(expected itemType, context string) item {
+	token := t.nextNonSpace()
+	if token.typ != expected {
+		t.unexpected(token, context)
+	}
+	return token
+}
+
+// expectOneOf consumes the next token and guarantees it has one of the required types.
+func (t *Template) expectOneOf(expected1, expected2 itemType, context string) item {
+	token := t.nextNonSpace()
+	if token.typ != expected1 && token.typ != expected2 {
+		t.unexpected(token, context)
+	}
+	return token
+}
+
+// unexpected complains about the token and terminates processing.
+func (t *Template) unexpected(token item, context string) {
+	t.errorf("unexpected %s in %s", token, context)
+}
+
+// recover is the handler that turns panics into returns from the top level of Parse.
+func (t *Template) recover(errp *error) {
+	e := recover()
+	if e != nil {
+		if _, ok := e.(runtime.Error); ok {
+			panic(e)
+		}
+		if t != nil {
+			t.lex.drain()
+			t.stopParse()
+		}
+		*errp = e.(error)
+	}
+	return
+}
+
+func (s *Set) parse(name, text string) (t *Template, err error) {
+	t = &Template{Name: name, text: text, set: s, passedBlocks: make(map[string]*BlockNode)}
+	defer t.recover(&err)
+
+	t.ParseName = t.Name
+	t.startParse(lex(t.Name, text))
+	t.parseTemplate()
+	t.stopParse()
+
+	if t.extends != nil {
+		t.addBlocks(t.extends.processedBlocks)
+	}
+	for _, _import := range t.imports {
+		t.addBlocks(_import.processedBlocks)
+	}
+	t.addBlocks(t.passedBlocks)
+	return t, err
+}
+
+func (t *Template) expectString(context string) string {
+	token := t.expectOneOf(itemString, itemRawString, context)
+	s, err := unquote(token.val)
+	if err != nil {
+		t.error(err)
+	}
+	return s
+}
+
+// parse is the top-level parser for a template, essentially the same
+// It runs to EOF.
+func (t *Template) parseTemplate() (next Node) {
+	t.Root = t.newList(t.peek().pos)
+	// {{ extends|import stringLiteral }}
+	for t.peek().typ != itemEOF {
+		delim := t.next()
+		if delim.typ == itemText && strings.TrimSpace(delim.val) == "" {
+			continue //skips empty text nodes
+		}
+		if delim.typ == itemLeftDelim {
+			token := t.nextNonSpace()
+			if token.typ == itemExtends || token.typ == itemImport {
+				s := t.expectString("extends|import")
+				if token.typ == itemExtends {
+					if t.extends != nil {
+						t.errorf("Unexpected extends clause, only one extends clause is valid per template")
+					} else if len(t.imports) > 0 {
+						t.errorf("Unexpected extends clause, all import clause should come after extends clause")
+					}
+					var err error
+					t.extends, err = t.set.getTemplateWhileParsing(t.Name, s)
+					if err != nil {
+						t.error(err)
+					}
+				} else {
+					tt, err := t.set.getTemplateWhileParsing(t.Name, s)
+					if err != nil {
+						t.error(err)
+					}
+					t.imports = append(t.imports, tt)
+				}
+				t.expect(itemRightDelim, "extends|import")
+			} else {
+				t.backup2(delim)
+				break
+			}
+		} else {
+			t.backup()
+			break
+		}
+	}
+
+	for t.peek().typ != itemEOF {
+		switch n := t.textOrAction(); n.Type() {
+		case nodeEnd, nodeElse, nodeContent:
+			t.errorf("unexpected %s", n)
+		default:
+			t.Root.append(n)
+		}
+	}
+	return nil
+}
+
+// startParse initializes the parser, using the lexer.
+func (t *Template) startParse(lex *lexer) {
+	t.Root = nil
+	t.lex = lex
+}
+
+// stopParse terminates parsing.
+func (t *Template) stopParse() {
+	t.lex = nil
+}
+
+// IsEmptyTree reports whether this tree (node) is empty of everything but space.
+func IsEmptyTree(n Node) bool {
+	switch n := n.(type) {
+	case nil:
+		return true
+	case *ActionNode:
+	case *IfNode:
+	case *ListNode:
+		for _, node := range n.Nodes {
+			if !IsEmptyTree(node) {
+				return false
+			}
+		}
+		return true
+	case *RangeNode:
+	case *IncludeNode:
+	case *TextNode:
+		return len(bytes.TrimSpace(n.Text)) == 0
+	case *BlockNode:
+	case *YieldNode:
+	default:
+		panic("unknown node: " + n.String())
+	}
+	return false
+}
+
+func (t *Template) blockParametersList(isDeclaring bool, context string) *BlockParameterList {
+	block := &BlockParameterList{}
+
+	t.expect(itemLeftParen, context)
+	for {
+		var expression Expression
+		next := t.nextNonSpace()
+		if next.typ == itemIdentifier {
+			identifier := next.val
+			next2 := t.nextNonSpace()
+			switch next2.typ {
+			case itemComma, itemRightParen:
+				block.List = append(block.List, BlockParameter{Identifier: identifier})
+				next = next2
+			case itemAssign:
+				expression, next = t.parseExpression(context)
+				block.List = append(block.List, BlockParameter{Identifier: identifier, Expression: expression})
+			default:
+				if !isDeclaring {
+					switch next2.typ {
+					case itemComma, itemRightParen:
+					default:
+						t.backup2(next)
+						expression, next = t.parseExpression(context)
+						block.List = append(block.List, BlockParameter{Expression: expression})
+					}
+				} else {
+					t.unexpected(next2, context)
+				}
+			}
+		} else if !isDeclaring {
+			switch next.typ {
+			case itemComma, itemRightParen:
+			default:
+				t.backup()
+				expression, next = t.parseExpression(context)
+				block.List = append(block.List, BlockParameter{Expression: expression})
+			}
+		}
+
+		if next.typ != itemComma {
+			t.backup()
+			break
+		}
+	}
+	t.expect(itemRightParen, context)
+	return block
+}
+
+func (t *Template) parseBlock() Node {
+
+	const context = "block clause"
+	var pipe Expression
+
+	name := t.expect(itemIdentifier, context)
+	bplist := t.blockParametersList(true, context)
+
+	if t.peekNonSpace().typ != itemRightDelim {
+		pipe = t.expression(context)
+	}
+
+	t.expect(itemRightDelim, context)
+
+	list, end := t.itemList()
+	var contentList *ListNode
+
+	if end.Type() == nodeContent {
+		contentList, end = t.itemList()
+		if end.Type() != nodeEnd {
+			t.errorf("unexpected %s in %s", end, context)
+		}
+	} else if end.Type() != nodeEnd {
+		t.errorf("unexpected %s in %s", end, context)
+	}
+
+	block := t.newBlock(name.pos, t.lex.lineNumber(), name.val, bplist, pipe, list, contentList)
+	t.passedBlocks[block.Name] = block
+	return block
+}
+
+func (t *Template) parseYield() Node {
+	const context = "yield clause"
+
+	var (
+		pipe    Expression
+		name    item
+		bplist  *BlockParameterList
+		content *ListNode
+		end     Node
+	)
+
+	// content yield {{yield content}}
+	name = t.nextNonSpace()
+	if name.typ == itemContent {
+		if t.peekNonSpace().typ != itemRightDelim {
+			pipe = t.expression(context)
+		}
+		t.expect(itemRightDelim, context)
+		return t.newYield(name.pos, t.lex.lineNumber(), "", nil, pipe, nil, true)
+	} else if name.typ != itemIdentifier {
+		t.unexpected(name, context)
+	}
+	bplist = t.blockParametersList(false, context)
+	typ := t.peekNonSpace().typ
+	if typ != itemRightDelim {
+		if typ == itemContent {
+			t.nextNonSpace()
+			t.expect(itemRightDelim, context)
+			content, end = t.itemList()
+			if end.Type() != nodeEnd {
+				t.errorf("unexpected %s in %s", end, context)
+			}
+		} else {
+			pipe = t.expression("yield")
+			if t.peekNonSpace().typ == itemContent {
+				t.nextNonSpace()
+				t.expect(itemRightDelim, context)
+				content, end = t.itemList()
+				if end.Type() != nodeEnd {
+					t.errorf("unexpected %s in %s", end, context)
+				}
+			} else {
+				t.expect(itemRightDelim, context)
+			}
+		}
+	} else {
+		t.expect(itemRightDelim, context)
+	}
+
+	return t.newYield(name.pos, t.lex.lineNumber(), name.val, bplist, pipe, content, false)
+}
+
+func (t *Template) parseInclude() Node {
+	var pipe Expression
+
+	name := t.expression("include")
+
+	if t.nextNonSpace().typ != itemRightDelim {
+		t.backup()
+		pipe = t.expression("include")
+		t.expect(itemRightDelim, "include invocation")
+
+	}
+
+	return t.newInclude(name.Position(), t.lex.lineNumber(), name, pipe)
+}
+
+// itemListBlock:
+//	textOrAction*
+// Terminates at {{end}} or {{else}}, returned separately.
+func (t *Template) itemListBlock() (list *ListNode, next Node) {
+	list = t.newList(t.peekNonSpace().pos)
+	for t.peekNonSpace().typ != itemEOF {
+		n := t.textOrAction()
+		switch n.Type() {
+		case nodeEnd, nodeContent:
+			return list, n
+		}
+		list.append(n)
+	}
+	t.errorf("unexpected EOF")
+	return
+}
+
+// itemListControl:
+//	textOrAction*
+// Terminates at {{end}}, returned separately.
+func (t *Template) itemList() (list *ListNode, next Node) {
+	list = t.newList(t.peekNonSpace().pos)
+	for t.peekNonSpace().typ != itemEOF {
+		n := t.textOrAction()
+		switch n.Type() {
+		case nodeEnd, nodeElse, nodeContent:
+			return list, n
+		}
+		list.append(n)
+	}
+	t.errorf("unexpected EOF")
+	return
+}
+
+// textOrAction:
+//	text | action
+func (t *Template) textOrAction() Node {
+	switch token := t.nextNonSpace(); token.typ {
+	case itemText:
+		return t.newText(token.pos, token.val)
+	case itemLeftDelim:
+		return t.action()
+	default:
+		t.unexpected(token, "input")
+	}
+	return nil
+}
+
+func (t *Template) action() (n Node) {
+	switch token := t.nextNonSpace(); token.typ {
+	case itemElse:
+		return t.elseControl()
+	case itemEnd:
+		return t.endControl()
+	case itemContent:
+		return t.contentControl()
+	case itemIf:
+		return t.ifControl()
+	case itemRange:
+		return t.rangeControl()
+	case itemBlock:
+		return t.parseBlock()
+	case itemInclude:
+		return t.parseInclude()
+	case itemYield:
+		return t.parseYield()
+	}
+
+	t.backup()
+	action := t.newAction(t.peek().pos, t.lex.lineNumber())
+
+	expr := t.assignmentOrExpression("command")
+	if expr.Type() == NodeSet {
+		action.Set = expr.(*SetNode)
+		expr = nil
+	}
+	if action.Set == nil || t.expectOneOf(itemColonComma, itemRightDelim, "command").typ == itemColonComma {
+		action.Pipe = t.pipeline("command", expr)
+	}
+	return action
+}
+
+func (t *Template) logicalExpression(context string) (Expression, item) {
+	left, endtoken := t.comparativeExpression(context)
+	for endtoken.typ == itemAnd || endtoken.typ == itemOr {
+		right, rightendtoken := t.comparativeExpression(context)
+		left, endtoken = t.newLogicalExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
+	}
+	return left, endtoken
+}
+
+func (t *Template) parseExpression(context string) (Expression, item) {
+	expression, endtoken := t.logicalExpression(context)
+	if endtoken.typ == itemTernary {
+		var left, right Expression
+		left, endtoken = t.parseExpression(context)
+		if endtoken.typ != itemColon {
+			t.unexpected(endtoken, "ternary expression")
+		}
+		right, endtoken = t.parseExpression(context)
+		expression = t.newTernaryExpr(expression.Position(), t.lex.lineNumber(), expression, left, right)
+	}
+	return expression, endtoken
+}
+
+func (t *Template) comparativeExpression(context string) (Expression, item) {
+	left, endtoken := t.numericComparativeExpression(context)
+	for endtoken.typ == itemEquals || endtoken.typ == itemNotEquals {
+		right, rightendtoken := t.numericComparativeExpression(context)
+		left, endtoken = t.newComparativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
+	}
+	return left, endtoken
+}
+
+func (t *Template) numericComparativeExpression(context string) (Expression, item) {
+	left, endtoken := t.additiveExpression(context)
+	for endtoken.typ >= itemGreat && endtoken.typ <= itemLessEquals {
+		right, rightendtoken := t.additiveExpression(context)
+		left, endtoken = t.newNumericComparativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
+	}
+	return left, endtoken
+}
+
+func (t *Template) additiveExpression(context string) (Expression, item) {
+	left, endtoken := t.multiplicativeExpression(context)
+	for endtoken.typ == itemAdd || endtoken.typ == itemMinus {
+		right, rightendtoken := t.multiplicativeExpression(context)
+		left, endtoken = t.newAdditiveExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
+	}
+	return left, endtoken
+}
+
+func (t *Template) multiplicativeExpression(context string) (left Expression, endtoken item) {
+	left, endtoken = t.unaryExpression(context)
+	for endtoken.typ >= itemMul && endtoken.typ <= itemMod {
+		right, rightendtoken := t.unaryExpression(context)
+		left, endtoken = t.newMultiplicativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
+	}
+
+	return left, endtoken
+}
+
+func (t *Template) unaryExpression(context string) (Expression, item) {
+	next := t.nextNonSpace()
+	switch next.typ {
+	case itemNot:
+		expr, endToken := t.comparativeExpression(context)
+		return t.newNotExpr(expr.Position(), t.lex.lineNumber(), expr), endToken
+	case itemMinus, itemAdd:
+		return t.newAdditiveExpr(next.pos, t.lex.lineNumber(), nil, t.operand(), next), t.nextNonSpace()
+	default:
+		t.backup()
+	}
+	operand := t.operand()
+	return operand, t.nextNonSpace()
+}
+
+func (t *Template) assignmentOrExpression(context string) (operand Expression) {
+
+	t.peekNonSpace()
+	line := t.lex.lineNumber()
+	var right, left []Expression
+
+	var isSet bool
+	var isLet bool
+	var returned item
+	operand, returned = t.parseExpression(context)
+	pos := operand.Position()
+	if returned.typ == itemComma || returned.typ == itemAssign {
+		isSet = true
+	} else {
+		if operand == nil {
+			t.unexpected(returned, context)
+		}
+		t.backup()
+		return operand
+	}
+
+	if isSet {
+	leftloop:
+		for {
+			switch operand.Type() {
+			case NodeField, NodeChain, NodeIdentifier:
+				left = append(left, operand)
+			default:
+				t.errorf("unexpected node in assign")
+			}
+
+			switch returned.typ {
+			case itemComma:
+				operand, returned = t.parseExpression(context)
+			case itemAssign:
+				isLet = returned.val == ":="
+				break leftloop
+			default:
+				t.unexpected(returned, "assignment")
+			}
+		}
+
+		if isLet {
+			for _, operand := range left {
+				if operand.Type() != NodeIdentifier {
+					t.errorf("unexpected node type %s in variable declaration", operand)
+				}
+			}
+		}
+
+		for {
+			operand, returned = t.parseExpression("assignment")
+			right = append(right, operand)
+			if returned.typ != itemComma {
+				t.backup()
+				break
+			}
+		}
+
+		var isIndexExprGetLookup bool
+
+		if context == "range" {
+			if len(left) > 2 || len(right) > 1 {
+				t.errorf("unexpected number of operands in assign on range")
+			}
+		} else {
+			if len(left) != len(right) {
+				if len(left) == 2 && len(right) == 1 && right[0].Type() == NodeIndexExpr {
+					isIndexExprGetLookup = true
+				} else {
+					t.errorf("unexpected number of operands in assign on range")
+				}
+			}
+		}
+		operand = t.newSet(pos, line, isLet, isIndexExprGetLookup, left, right)
+		return
+
+	}
+	return
+}
+
+func (t *Template) expression(context string) Expression {
+	expr, tk := t.parseExpression(context)
+	if expr == nil {
+		t.unexpected(tk, context)
+	}
+	t.backup()
+	return expr
+}
+
+func (t *Template) pipeline(context string, baseExprMutate Expression) (pipe *PipeNode) {
+	pos := t.peekNonSpace().pos
+	pipe = t.newPipeline(pos, t.lex.lineNumber())
+	var token item
+	if baseExprMutate != nil {
+		//special case
+		pipe.append(t.command(baseExprMutate))
+		token = t.nextNonSpace()
+		if token.typ == itemPipe {
+			token = t.nextNonSpace()
+		} else {
+			t.backup()
+			t.expect(itemRightDelim, context)
+			return
+		}
+	} else {
+		token = t.nextNonSpace()
+	}
+
+loop:
+	for {
+		switch token.typ {
+		case itemBool, itemCharConstant, itemComplex, itemField, itemIdentifier,
+			itemNumber, itemNil, itemRawString, itemString, itemLeftParen, itemNot:
+			t.backup()
+			pipe.append(t.command(nil))
+			token = t.nextNonSpace()
+			if token.typ == itemPipe {
+				token = t.nextNonSpace()
+				continue loop
+			} else {
+				t.backup()
+				break loop
+			}
+		default:
+			t.backup()
+			break loop
+		}
+	}
+
+	t.expect(itemRightDelim, context)
+	return
+}
+
+func (t *Template) command(baseExpr Expression) *CommandNode {
+	cmd := t.newCommand(t.peekNonSpace().pos)
+
+	if baseExpr == nil {
+		cmd.BaseExpr = t.expression("command")
+	} else {
+		cmd.BaseExpr = baseExpr
+	}
+
+	if t.nextNonSpace().typ == itemColon {
+		cmd.Call = true
+		cmd.Args = t.parseArguments()
+	} else {
+		t.backup()
+	}
+
+	if cmd.BaseExpr == nil {
+		t.errorf("empty command")
+	}
+	return cmd
+}
+
+// operand:
+//	term .Field*
+// An operand is a space-separated component of a command,
+// a term possibly followed by field accesses.
+// A nil return means the next item is not an operand.
+func (t *Template) operand() Expression {
+	node := t.term()
+	if node == nil {
+		t.errorf("unexpected token %s on operand", t.next())
+	}
+RESET:
+	if t.peek().typ == itemField {
+		chain := t.newChain(t.peek().pos, node)
+		for t.peekNonSpace().typ == itemField {
+			chain.Add(t.next().val)
+		}
+		// Compatibility with original API: If the term is of type NodeField
+		// or NodeVariable, just put more fields on the original.
+		// Otherwise, keep the Chain node.
+		// Obvious parsing errors involving literal values are detected here.
+		// More complex error cases will have to be handled at execution time.
+		switch node.Type() {
+		case NodeField:
+			node = t.newField(chain.Position(), chain.String())
+		case NodeBool, NodeString, NodeNumber, NodeNil:
+			t.errorf("unexpected . after term %q", node.String())
+		default:
+			node = chain
+		}
+	}
+	nodeTYPE := node.Type()
+	if nodeTYPE == NodeIdentifier ||
+		nodeTYPE == NodeCallExpr ||
+		nodeTYPE == NodeField ||
+		nodeTYPE == NodeChain ||
+		nodeTYPE == NodeIndexExpr {
+		switch t.nextNonSpace().typ {
+		case itemLeftParen:
+			callExpr := t.newCallExpr(node.Position(), t.lex.lineNumber(), node)
+			callExpr.Args = t.parseArguments()
+			t.expect(itemRightParen, "call expression")
+			node = callExpr
+			goto RESET
+		case itemLeftBrackets:
+			base := node
+			var index Expression
+			var next item
+
+			//found colon is slice expression
+			if t.peekNonSpace().typ != itemColon {
+				index, next = t.parseExpression("index|slice expression")
+			} else {
+				next = t.nextNonSpace()
+			}
+
+			switch next.typ {
+			case itemColon:
+				var lenexpr Expression
+				if t.peekNonSpace().typ != itemRightBrackets {
+					lenexpr = t.expression("index expression")
+				}
+				node = t.newSliceExpr(node.Position(), node.line(), base, index, lenexpr)
+			case itemRightBrackets:
+				node = t.newIndexExpr(node.Position(), node.line(), base, index)
+				fallthrough
+			default:
+				t.backup()
+			}
+
+			t.expect(itemRightBrackets, "index expression")
+			goto RESET
+		default:
+			t.backup()
+		}
+	}
+	return node
+}
+
+func (t *Template) parseArguments() (args []Expression) {
+	if t.peekNonSpace().typ != itemRightParen {
+	loop:
+		for {
+			expr, endtoken := t.parseExpression("call expression")
+			args = append(args, expr)
+			switch endtoken.typ {
+			case itemComma:
+				continue loop
+			default:
+				t.backup()
+				break loop
+			}
+		}
+	}
+	return
+}
+
+func (t *Template) checkPipeline(pipe *PipeNode, context string) {
+
+	// Reject empty pipelines
+	if len(pipe.Cmds) == 0 {
+		t.errorf("missing value for %s", context)
+	}
+
+	// Only the first command of a pipeline can start with a non executable operand
+	for i, c := range pipe.Cmds[1:] {
+		switch c.Args[0].Type() {
+		case NodeBool, NodeNil, NodeNumber, NodeString:
+			// With A|B|C, pipeline stage 2 is B
+			t.errorf("non executable command in pipeline stage %d", i+2)
+		}
+	}
+}
+
+func (t *Template) parseControl(allowElseIf bool, context string) (pos Pos, line int, set *SetNode, expression Expression, list, elseList *ListNode) {
+	line = t.lex.lineNumber()
+
+	expression = t.assignmentOrExpression(context)
+	pos = expression.Position()
+	if expression.Type() == NodeSet {
+		set = expression.(*SetNode)
+		if context != "range" {
+			t.expect(itemColonComma, context)
+			expression = t.expression(context)
+		} else {
+			expression = nil
+		}
+	}
+
+	t.expect(itemRightDelim, context)
+	var next Node
+	list, next = t.itemList()
+	switch next.Type() {
+	case nodeEnd: //done
+	case nodeElse:
+		if allowElseIf {
+			// Special case for "else if". If the "else" is followed immediately by an "if",
+			// the elseControl will have left the "if" token pending. Treat
+			//	{{if a}}_{{else if b}}_{{end}}
+			// as
+			//	{{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
+			// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
+			// is assumed. This technique works even for long if-else-if chains.
+			if t.peek().typ == itemIf {
+				t.next() // Consume the "if" token.
+				elseList = t.newList(next.Position())
+				elseList.append(t.ifControl())
+				// Do not consume the next item - only one {{end}} required.
+				break
+			}
+		}
+		elseList, next = t.itemList()
+		if next.Type() != nodeEnd {
+			t.errorf("expected end; found %s", next)
+		}
+	}
+	return pos, line, set, expression, list, elseList
+}
+
+// If:
+//	{{if expression}} itemList {{end}}
+//	{{if expression}} itemList {{else}} itemList {{end}}
+// If keyword is past.
+func (t *Template) ifControl() Node {
+	return t.newIf(t.parseControl(true, "if"))
+}
+
+// Range:
+//	{{range expression}} itemList {{end}}
+//	{{range expression}} itemList {{else}} itemList {{end}}
+// Range keyword is past.
+func (t *Template) rangeControl() Node {
+	return t.newRange(t.parseControl(false, "range"))
+}
+
+// End:
+//	{{end}}
+// End keyword is past.
+func (t *Template) endControl() Node {
+	return t.newEnd(t.expect(itemRightDelim, "end").pos)
+}
+
+// Content:
+//	{{content}}
+// Content keyword is past.
+func (t *Template) contentControl() Node {
+	return t.newContent(t.expect(itemRightDelim, "content").pos)
+}
+
+// Else:
+//	{{else}}
+// Else keyword is past.
+func (t *Template) elseControl() Node {
+	// Special case for "else if".
+	peek := t.peekNonSpace()
+	if peek.typ == itemIf {
+		// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
+		return t.newElse(peek.pos, t.lex.lineNumber())
+	}
+	return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
+}
+
+// term:
+//	literal (number, string, nil, boolean)
+//	function (identifier)
+//	.
+//	.Field
+//	variable
+//	'(' expression ')'
+// A term is a simple "expression".
+// A nil return means the next item is not a term.
+func (t *Template) term() Node {
+	switch token := t.nextNonSpace(); token.typ {
+	case itemError:
+		t.errorf("%s", token.val)
+	case itemIdentifier:
+		return t.newIdentifier(token.val, token.pos, t.lex.lineNumber())
+	case itemNil:
+		return t.newNil(token.pos)
+	case itemField:
+		return t.newField(token.pos, token.val)
+	case itemBool:
+		return t.newBool(token.pos, token.val == "true")
+	case itemCharConstant, itemComplex, itemNumber:
+		number, err := t.newNumber(token.pos, token.val, token.typ)
+		if err != nil {
+			t.error(err)
+		}
+		return number
+	case itemLeftParen:
+		pipe := t.expression("parenthesized expression")
+		if token := t.next(); token.typ != itemRightParen {
+			t.errorf("unclosed right paren: unexpected %s", token)
+		}
+		return pipe
+	case itemString, itemRawString:
+		s, err := unquote(token.val)
+		if err != nil {
+			t.error(err)
+		}
+		return t.newString(token.pos, token.val, s)
+	}
+	t.backup()
+	return nil
+}

+ 8 - 0
vendor/github.com/CloudyKit/jet/profile.sh

@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+go test -run="^$" -bench="Range" -benchmem -c -cpuprofile=./pprof.out
+go test -run="^$" -bench="Range" -benchmem -cpuprofile=./pprof.out
+go tool pprof --pdf --focus="$1" jet.test pprof.out >> out.pdf
+rm jet.test
+rm pprof.out
+open out.pdf

+ 11 - 0
vendor/github.com/CloudyKit/jet/stress.bash

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash -e
+
+go test -c
+# comment above and uncomment below to enable the race builder
+#go test -c -race
+PKG=$(basename $(pwd))
+
+while true ; do
+        export GOMAXPROCS=$[ 1 + $[ RANDOM % 128 ]]
+        ./$PKG.test $@ 2>&1
+done

+ 403 - 0
vendor/github.com/CloudyKit/jet/template.go

@@ -0,0 +1,403 @@
+// Copyright 2016 José Santos <henrique_1609@me.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Jet is a fast and dynamic template engine for the Go programming language, set of features
+// includes very fast template execution, a dynamic and flexible language, template inheritance, low number of allocations,
+// special interfaces to allow even further optimizations.
+
+package jet
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"path"
+	"reflect"
+	"strings"
+	"sync"
+	"text/template"
+)
+
+// Set is responsible to load,invoke parse and cache templates and relations
+// every jet template is associated with one set.
+// create a set with jet.NewSet(escapeeFn) returns a pointer to the Set
+type Set struct {
+	loader            Loader
+	templates         map[string]*Template // parsed templates
+	escapee           SafeWriter           // escapee to use at runtime
+	globals           VarMap               // global scope for this template set
+	tmx               *sync.RWMutex        // template parsing mutex
+	gmx               *sync.RWMutex        // global variables map mutex
+	defaultExtensions []string
+	developmentMode   bool
+}
+
+// SetDevelopmentMode set's development mode on/off, in development mode template will be recompiled on every run
+func (s *Set) SetDevelopmentMode(b bool) *Set {
+	s.developmentMode = b
+	return s
+}
+
+func (a *Set) LookupGlobal(key string) (val interface{}, found bool) {
+	a.gmx.RLock()
+	val, found = a.globals[key]
+	a.gmx.RUnlock()
+	return
+}
+
+// AddGlobal add or set a global variable into the Set
+func (s *Set) AddGlobal(key string, i interface{}) *Set {
+	s.gmx.Lock()
+	if s.globals == nil {
+		s.globals = make(VarMap)
+	}
+	s.globals[key] = reflect.ValueOf(i)
+	s.gmx.Unlock()
+	return s
+}
+
+func (s *Set) AddGlobalFunc(key string, fn Func) *Set {
+	return s.AddGlobal(key, fn)
+}
+
+// NewSetLoader creates a new set with custom Loader
+func NewSetLoader(escapee SafeWriter, loader Loader) *Set {
+	return &Set{loader: loader, tmx: &sync.RWMutex{}, gmx: &sync.RWMutex{}, escapee: escapee, templates: make(map[string]*Template), defaultExtensions: append([]string{}, defaultExtensions...)}
+}
+
+// NewHTMLSetLoader creates a new set with custom Loader
+func NewHTMLSetLoader(loader Loader) *Set {
+	return NewSetLoader(template.HTMLEscape, loader)
+}
+
+// NewSet creates a new set, dirs is a list of directories to be searched for templates
+func NewSet(escapee SafeWriter, dirs ...string) *Set {
+	return NewSetLoader(escapee, &OSFileSystemLoader{dirs: dirs})
+}
+
+// NewHTMLSet creates a new set, dirs is a list of directories to be searched for templates
+func NewHTMLSet(dirs ...string) *Set {
+	return NewSet(template.HTMLEscape, dirs...)
+}
+
+// AddPath add path to the lookup list, when loading a template the Set will
+// look into the lookup list for the file matching the provided name.
+func (s *Set) AddPath(path string) {
+	if loader, ok := s.loader.(hasAddPath); ok {
+		loader.AddPath(path)
+	} else {
+		panic(fmt.Sprintf("AddPath() not supported on custom loader of type %T", s.loader))
+	}
+}
+
+// AddGopathPath add path based on GOPATH env to the lookup list, when loading a template the Set will
+// look into the lookup list for the file matching the provided name.
+func (s *Set) AddGopathPath(path string) {
+	if loader, ok := s.loader.(hasAddGopathPath); ok {
+		loader.AddGopathPath(path)
+	} else {
+		panic(fmt.Sprintf("AddGopathPath() not supported on custom loader of type %T", s.loader))
+	}
+}
+
+// resolveName try to resolve a template name, the steps as follow
+//	1. try provided path
+//	2. try provided path+defaultExtensions
+// ex: set.resolveName("catalog/products.list") with defaultExtensions set to []string{".html.jet",".jet"}
+//	try catalog/products.list
+//	try catalog/products.list.html.jet
+//	try catalog/products.list.jet
+func (s *Set) resolveName(name string) (newName, fileName string, foundLoaded, foundFile bool) {
+	newName = name
+	if _, foundLoaded = s.templates[newName]; foundLoaded {
+		return
+	}
+
+	if fileName, foundFile = s.loader.Exists(name); foundFile {
+		return
+	}
+
+	for _, extension := range s.defaultExtensions {
+		newName = name + extension
+		if _, foundLoaded = s.templates[newName]; foundLoaded {
+			return
+		}
+		if fileName, foundFile = s.loader.Exists(newName); foundFile {
+			return
+		}
+	}
+
+	return
+}
+
+func (s *Set) resolveNameSibling(name, sibling string) (newName, fileName string, foundLoaded, foundFile, isRelativeName bool) {
+	if sibling != "" {
+		i := strings.LastIndex(sibling, "/")
+		if i != -1 {
+			if newName, fileName, foundLoaded, foundFile = s.resolveName(path.Join(sibling[:i+1], name)); foundFile || foundLoaded {
+				isRelativeName = true
+				return
+			}
+		}
+	}
+	newName, fileName, foundLoaded, foundFile = s.resolveName(name)
+	return
+}
+
+// Parse parses the template, this method will link the template to the set but not the set to
+func (s *Set) Parse(name, content string) (*Template, error) {
+	sc := *s
+	sc.developmentMode = true
+
+	sc.tmx.RLock()
+	t, err := sc.parse(name, content)
+	sc.tmx.RUnlock()
+
+	return t, err
+}
+
+func (s *Set) loadFromFile(name, fileName string) (template *Template, err error) {
+	f, err := s.loader.Open(fileName)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	content, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, err
+	}
+	return s.parse(name, string(content))
+}
+
+func (s *Set) getTemplateWhileParsing(parentName, name string) (template *Template, err error) {
+	name = path.Clean(name)
+
+	if s.developmentMode {
+		if newName, fileName, _, foundPath, _ := s.resolveNameSibling(name, parentName); foundPath {
+			return s.loadFromFile(newName, fileName)
+		} else {
+			return nil, fmt.Errorf("template %s can't be loaded", name)
+		}
+	}
+
+	if newName, fileName, foundLoaded, foundPath, isRelative := s.resolveNameSibling(name, parentName); foundPath {
+		template, err = s.loadFromFile(newName, fileName)
+		s.templates[newName] = template
+
+		if !isRelative {
+			s.templates[name] = template
+		}
+	} else if foundLoaded {
+		template = s.templates[newName]
+		if !isRelative && name != newName {
+			s.templates[name] = template
+		}
+	} else {
+		err = fmt.Errorf("template %s can't be loaded", name)
+	}
+	return
+}
+
+// getTemplate gets a template already loaded by name
+func (s *Set) getTemplate(name, sibling string) (template *Template, err error) {
+	name = path.Clean(name)
+
+	if s.developmentMode {
+		s.tmx.RLock()
+		defer s.tmx.RUnlock()
+		if newName, fileName, foundLoaded, foundFile, _ := s.resolveNameSibling(name, sibling); foundFile || foundLoaded {
+			if foundFile {
+				template, err = s.loadFromFile(newName, fileName)
+			} else {
+				template, _ = s.templates[newName]
+			}
+		} else {
+			err = fmt.Errorf("template %s can't be loaded", name)
+		}
+		return
+	}
+
+	//fast path
+	s.tmx.RLock()
+	newName, fileName, foundLoaded, foundFile, isRelative := s.resolveNameSibling(name, sibling)
+
+	if foundLoaded {
+		template = s.templates[newName]
+		s.tmx.RUnlock()
+		if !isRelative && name != newName {
+			// creates an alias
+			s.tmx.Lock()
+			if _, found := s.templates[name]; !found {
+				s.templates[name] = template
+			}
+			s.tmx.Unlock()
+		}
+		return
+	}
+	s.tmx.RUnlock()
+
+	//not found parses and cache
+	s.tmx.Lock()
+	defer s.tmx.Unlock()
+
+	newName, fileName, foundLoaded, foundFile, isRelative = s.resolveNameSibling(name, sibling)
+	if foundLoaded {
+		template = s.templates[newName]
+		if !isRelative && name != newName {
+			// creates an alias
+			if _, found := s.templates[name]; !found {
+				s.templates[name] = template
+			}
+		}
+	} else if foundFile {
+		template, err = s.loadFromFile(newName, fileName)
+
+		if !isRelative && name != newName {
+			// creates an alias
+			if _, found := s.templates[name]; !found {
+				s.templates[name] = template
+			}
+		}
+
+		s.templates[newName] = template
+	} else {
+		err = fmt.Errorf("template %s can't be loaded", name)
+	}
+	return
+}
+
+func (s *Set) GetTemplate(name string) (template *Template, err error) {
+	template, err = s.getTemplate(name, "")
+	return
+}
+
+func (s *Set) LoadTemplate(name, content string) (template *Template, err error) {
+	if s.developmentMode {
+		s.tmx.RLock()
+		defer s.tmx.RUnlock()
+		template, err = s.parse(name, content)
+		return
+	}
+
+	//fast path
+	var found bool
+	s.tmx.RLock()
+	if template, found = s.templates[name]; found {
+		s.tmx.RUnlock()
+		return
+	}
+	s.tmx.RUnlock()
+
+	//not found parses and cache
+	s.tmx.Lock()
+	defer s.tmx.Unlock()
+
+	if template, found = s.templates[name]; found {
+		return
+	}
+
+	if template, err = s.parse(name, content); err == nil {
+		s.templates[name] = template
+	}
+
+	return
+}
+
+func (t *Template) String() (template string) {
+	if t.extends != nil {
+		if len(t.Root.Nodes) > 0 && len(t.imports) == 0 {
+			template += fmt.Sprintf("{{extends %q}}", t.extends.ParseName)
+		} else {
+			template += fmt.Sprintf("{{extends %q}}", t.extends.ParseName)
+		}
+	}
+
+	for k, _import := range t.imports {
+		if t.extends == nil && k == 0 {
+			template += fmt.Sprintf("{{import %q}}", _import.ParseName)
+		} else {
+			template += fmt.Sprintf("\n{{import %q}}", _import.ParseName)
+		}
+	}
+
+	if t.extends != nil || len(t.imports) > 0 {
+		if len(t.Root.Nodes) > 0 {
+			template += "\n" + t.Root.String()
+		}
+	} else {
+		template += t.Root.String()
+	}
+	return
+}
+
+func (t *Template) addBlocks(blocks map[string]*BlockNode) {
+	if len(blocks) > 0 {
+		if t.processedBlocks == nil {
+			t.processedBlocks = make(map[string]*BlockNode)
+		}
+		for key, value := range blocks {
+			t.processedBlocks[key] = value
+		}
+	}
+}
+
+type VarMap map[string]reflect.Value
+
+func (scope VarMap) Set(name string, v interface{}) VarMap {
+	scope[name] = reflect.ValueOf(v)
+	return scope
+}
+
+func (scope VarMap) SetFunc(name string, v Func) VarMap {
+	scope[name] = reflect.ValueOf(v)
+	return scope
+}
+
+func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap {
+	scope[name] = reflect.ValueOf(v)
+	return scope
+}
+
+// Execute executes the template in the w Writer
+func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error {
+	return t.ExecuteI18N(nil, w, variables, data)
+}
+
+type Translator interface {
+	Msg(key, defaultValue string) string
+	Trans(format, defaultFormat string, v ...interface{}) string
+}
+
+func (t *Template) ExecuteI18N(translator Translator, w io.Writer, variables VarMap, data interface{}) (err error) {
+	st := pool_State.Get().(*Runtime)
+	defer st.recover(&err)
+
+	st.blocks = t.processedBlocks
+	st.translator = translator
+	st.variables = variables
+	st.set = t.set
+	st.Writer = w
+
+	// resolve extended template
+	for t.extends != nil {
+		t = t.extends
+	}
+
+	if data != nil {
+		st.context = reflect.ValueOf(data)
+	}
+
+	st.executeList(t.Root)
+	return
+}

+ 6 - 0
vendor/vendor.json

@@ -8,6 +8,12 @@
 			"revision": "54457d8e98c60cb91f18947facfa991ce3ea2ba3",
 			"revisionTime": "2016-05-30T17:42:08Z"
 		},
+		{
+			"checksumSHA1": "SwujWL+oEHm5IKf0owxCTtNzFGQ=",
+			"path": "github.com/CloudyKit/jet",
+			"revision": "2b064536b25ab0e9c54245f9e2cc5bd4766033fe",
+			"revisionTime": "2017-10-30T20:14:56Z"
+		},
 		{
 			"checksumSHA1": "bOXGGxVxF/wJxdefTWRSctfQ7+k=",
 			"path": "github.com/Unknwon/goconfig",