Skip to content

Commit

Permalink
Merge pull request #23 from CloudyKit/v2.0
Browse files Browse the repository at this point in the history
V2.0
  • Loading branch information
jhsx authored Aug 14, 2016
2 parents 09277d7 + e127705 commit d8dcd45
Show file tree
Hide file tree
Showing 28 changed files with 1,439 additions and 680 deletions.
110 changes: 28 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,47 @@
# Jet Template Engine for GO [![Build Status](https://travis-ci.org/CloudyKit/jet.svg?branch=master)](https://travis-ci.org/CloudyKit/jet)
# 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,secure and very fast.
Jet is a template engine developed to be easy to use, powerful, dynamic, yet secure and very fast.

* Support template inheritance extends,imports and includes statements.
* Descriptive error messages with file name and line number.
* Auto-escape.
* Simple C like Expression.
* Very fast execution, jet can execute templates faster then some pre-compiled template engines
* Very light in terms of allocations and memory foot print.
* Simple and familiar syntax.
* Ease to use.
* 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

[Documentation Wiki](https://github.com/CloudyKit/jet/wiki)
You can find the documentation in the [wiki](https://github.com/CloudyKit/jet/wiki).

#### Intellij Plugin
#### Upgrade to v2

If you use intellij there is a plugin available in https://github.com/jhsx/GoJetPlugin
There is also a very good Go plugin for intellij ("https://github.com/go-lang-plugin-org/go-lang-idea-plugin")
GoJetPlugin + Go-lang-idea-plugin = Happiness :D
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.

### Examples

#### Simple
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).

```HTML
#### IntelliJ Plugin

Hey {{name}}!
No Escape {{ "<a href=\""\">Link</a>" |unsafe}}
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!

```

#### Extends
### Examples

```HTML
{{extends "common/layout.jet"}}
You can find examples in the [wiki](https://github.com/CloudyKit/jet/wiki/Jet-template-syntax).

### Running the example application

{{block Content}}
{{.PageHeader}}
{* this is a comment *}
{{.PageContent |unsafe}}
{{end}}
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:
```


#### Range

```HTML
{{extends "common/layout.jet"}}


{{block Content}}
{{.PageHeader}}

{{range .Result}}
<div>
<div class="result-header">{{.Title}}</div>
<div class="result-description">{{.Description}}
<a href="{{.Linkme()}}">Read more</a></div>
</div>
{{end}}
{{end}}
$ cd example; go run main.go
```

#### Import Extends Yield

```HTML
{{extends "common/layout.jet"}}
{{import "common/menu.jet"}}

{{block Header}}
<!-- this block commes from "common/menu.jet" -->
<nav class="nav">{{yield menu}}</nav>
{{end}}

{{block Content}}
{{.PageHeader}}

{{range .Result}}
<div>
<div class="result-header">{{.Title}}</div>
<div class="result-description">{{.Description}}
<a href="{{.Linkme()}}">Read more</a></div>
</div>
{{end}}
{{end}}
```
#### Faster than some pre-compiled template engines

Benchmark consist of 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 peforms better than gorazor,
Ftmpl, Egon which are pre-compiled to go.

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

Expand Down Expand Up @@ -151,9 +97,9 @@ ok github.com/SlinSo/goTemplateBenchmark 36.200s

#### Contributing

Any contribution is welcome, if you find a bug please report.
All contributions are welcomeif you find a bug please report it.

#### Thanks

- @golang developers, for the awesome language, and std library
- @SlinSo for the benchmarks that i used as base to show the results above
- @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
34 changes: 17 additions & 17 deletions constructors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,8 @@ func (t *Template) newTernaryExpr(pos Pos, line int, boolean, left, right Expres
return &TernaryExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeTernaryExpr, Pos: pos, Line: line}, Boolean: boolean, Left: left, Right: right}
}

func (t *Template) newBuiltinExpr(pos Pos, line int, name string, nodetype NodeType) *BuiltinExprNode {
return &BuiltinExprNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodetype, Pos: pos, Line: line}, Name: name}
}

func (t *Template) newSet(pos Pos, line int, isLet bool, left, right []Expression) *SetNode {
return &SetNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeSet, Pos: pos, Line: line}, Let: isLet, 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 {
Expand Down Expand Up @@ -90,7 +86,7 @@ func (t *Template) newNil(pos Pos) *NilNode {
}

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
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 {
Expand All @@ -109,6 +105,10 @@ 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}}
}
Expand All @@ -121,12 +121,12 @@ func (t *Template) newRange(pos Pos, line int, set *SetNode, pipe Expression, li
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, pipe Expression, listNode *ListNode) *BlockNode {
return &BlockNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeBlock, Line: line, Pos: pos}, Name: name, Expression: pipe, List: listNode}
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, pipe Expression) *YieldNode {
return &YieldNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeYield, Pos: pos, Line: line}, Name: name, Expression: pipe}
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 {
Expand All @@ -137,30 +137,30 @@ func (t *Template) newNumber(pos Pos, text string, typ itemType) (*NumberNode, e
n := &NumberNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNumber, Pos: pos}, Text: text}
switch typ {
case itemCharConstant:
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
_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.Int64 = int64(_rune)
n.IsInt = true
n.Uint64 = uint64(rune)
n.Uint64 = uint64(_rune)
n.IsUint = true
n.Float64 = float64(rune) // odd but those are the rules.
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.
//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.
//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 {
Expand Down
75 changes: 58 additions & 17 deletions default.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,62 @@ import (
"text/template"
)

var 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)),
"unsafe": reflect.ValueOf(SafeWriter(unsafePrinter)),
"writeJson": reflect.ValueOf(jsonRenderer),
"json": reflect.ValueOf(json.Marshal),
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, 99999999999)
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{}
})),
}

}

func jsonRenderer(v interface{}) RendererFunc {
Expand All @@ -56,6 +95,8 @@ 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{}) {
Expand Down
Loading

0 comments on commit d8dcd45

Please sign in to comment.