diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..64bfa1d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+*.6
+*.8
+*.o
+*.so
+*.out
+*.go~
+*.cgo?.*
+_cgo_*
+_obj
+_test
+_testmain.go
+*.swp
diff --git a/Makefile b/Makefile
index 7936a70..6313cf2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,10 @@
-include $(GOROOT)/src/Make.inc
-TARG=mustache
+GOFMT=gofmt -s -tabs=false -tabwidth=4
+
GOFILES=\
mustache.go\
-include $(GOROOT)/src/Make.pkg
-
format:
- gofmt -s -spaces=true -tabindent=false -tabwidth=4 -w mustache.go
- gofmt -s -spaces=true -tabindent=false -tabwidth=4 -w mustache_test.go
+ ${GOFMT} -w ${GOFILES}
+ ${GOFMT} -w mustache_test.go
diff --git a/Readme.md b/Readme.md
index a3164bb..ccc964a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -8,18 +8,22 @@ For more information about mustache, check out the [mustache project page](http:
Also check out some [example mustache files](http://github.com/defunkt/mustache/tree/master/examples/)
-## Usage
+## Installation
+To install mustache.go, simply run `go get github.com/hoisie/mustache`. To use it in a program, use `import "github.com/hoisie/mustache"`
-There are only four methods in this package:
+There are only five methods in this package:
func Render(data string, context ...interface{}) string
+ func RenderTo(w io.Writer, context ...interface{})
+
func RenderFile(filename string, context ...interface{}) string
func ParseString(data string) (*template, os.Error)
func ParseFile(filename string) (*template, os.Error)
+There are also two additional methods for using layouts (explained below).
The Render method takes a string and a data source, which is generally a map or struct, and returns the output string. If the template file contains an error, the return value is a description of the error. There's a similar method, RenderFile, which takes a filename as an argument and uses that for the template contents.
@@ -29,18 +33,87 @@ The Render method takes a string and a data source, which is generally a map or
If you're planning to render the same template multiple times, you do it efficiently by compiling the template first:
- tmpl,_ := mustache.Parse("hello {{c}}")
+ tmpl,_ := mustache.ParseString("hello {{c}}")
var buf bytes.Buffer;
for i := 0; i < 10; i++ {
tmpl.Render (map[string]string { "c":"world"}, &buf)
}
+
+The RenderTo method takes an io.Writer and a context. The template is rendered immediately to the io.Writer.
+
+ func Handler(rw http.ResponseWriter, req *http.Request) {
+ tmpl,_ := mustache.ParseString("hello {{c}}")
+
+ ctx := map[string]string{ "c": "world" }
+ tmpl.RenderTo(rw, ctx)
+ }
+
+
+
For more example usage, please see `mustache_test.go`
## Escaping
mustache.go follows the official mustache HTML escaping rules. That is, if you enclose a variable with two curly brackets, `{{var}}`, the contents are HTML-escaped. For instance, strings like `5 > 2` are converted to `5 > 2`. To use raw characters, use three curly brackets `{{{var}}}`.
-
+
+## Layouts
+
+It is a common pattern to include a template file as a "wrapper" for other templates. The wrapper may include a header and a footer, for instance. Mustache.go supports this pattern with the following two methods:
+
+ func RenderInLayout(data string, layout string, context ...interface{}) string
+
+ func RenderFileInLayout(filename string, layoutFile string, context ...interface{}) string
+
+The layout file must have a variable called `{{content}}`. For example, given the following files:
+
+layout.html.mustache:
+
+
+
Hi
+
+ {{{content}}}
+
+
+
+template.html.mustache:
+
+ Hello World!
+
+A call to `RenderFileInLayout("template.html.mustache", "layout.html.mustache", nil)` will produce:
+
+
+ Hi
+
+ Hello World!
+
+
+
+## A note about method receivers
+
+Mustache.go supports calling methods on objects, but you have to be aware of Go's limitations. For example, lets's say you have the following type:
+
+ type Person struct {
+ FirstName string
+ LastName string
+ }
+
+ func (p *Person) Name1() string {
+ return p.FirstName + " " + p.LastName
+ }
+
+ func (p Person) Name2() string {
+ return p.FirstName + " " + p.LastName
+ }
+
+While they appear to be identical methods, `Name1` has a pointer receiver, and `Name2` has a value receiver. Objects of type `Person`(non-pointer) can only access `Name2`, while objects of type `*Person`(person) can access both. This is by design in the Go language.
+
+So if you write the following:
+
+ mustache.Render("{{Name1}}", Person{"John", "Smith"})
+
+It'll be blank. You either have to use `&Person{"John", "Smith"}`, or call `Name2`
+
## Supported features
* Variables
diff --git a/mustache.go b/mustache.go
index f37001f..fcfc7e1 100644
--- a/mustache.go
+++ b/mustache.go
@@ -2,7 +2,7 @@ package mustache
import (
"bytes"
- "container/vector"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -25,7 +25,7 @@ type sectionElement struct {
name string
inverted bool
startline int
- elems *vector.Vector
+ elems []interface{}
}
type Template struct {
@@ -35,7 +35,7 @@ type Template struct {
p int
curline int
dir string
- elems *vector.Vector
+ elems []interface{}
}
type parseError struct {
@@ -43,7 +43,7 @@ type parseError struct {
message string
}
-func (p parseError) String() string { return fmt.Sprintf("line %d: %s", p.line, p.message) }
+func (p parseError) Error() string { return fmt.Sprintf("line %d: %s", p.line, p.message) }
var (
esc_quot = []byte(""")
@@ -79,13 +79,13 @@ func htmlEscape(w io.Writer, s []byte) {
w.Write(s[last:])
}
-func (tmpl *Template) readString(s string) (string, os.Error) {
+func (tmpl *Template) readString(s string) (string, error) {
i := tmpl.p
newlines := 0
for true {
//are we at the end of the string?
if i+len(s) > len(tmpl.data) {
- return tmpl.data[tmpl.p:], os.EOF
+ return tmpl.data[tmpl.p:], io.EOF
}
if tmpl.data[i] == '\n' {
@@ -121,7 +121,7 @@ func (tmpl *Template) readString(s string) (string, os.Error) {
return "", nil
}
-func (tmpl *Template) parsePartial(name string) (*Template, os.Error) {
+func (tmpl *Template) parsePartial(name string) (*Template, error) {
filenames := []string{
path.Join(tmpl.dir, name),
path.Join(tmpl.dir, name+".mustache"),
@@ -133,14 +133,14 @@ func (tmpl *Template) parsePartial(name string) (*Template, os.Error) {
var filename string
for _, name := range filenames {
f, err := os.Open(name)
- f.Close()
if err == nil {
filename = name
+ f.Close()
break
}
}
if filename == "" {
- return nil, os.NewError(fmt.Sprintf("Could not find partial %q", name))
+ return nil, errors.New(fmt.Sprintf("Could not find partial %q", name))
}
partial, err := ParseFile(filename)
@@ -152,24 +152,24 @@ func (tmpl *Template) parsePartial(name string) (*Template, os.Error) {
return partial, nil
}
-func (tmpl *Template) parseSection(section *sectionElement) os.Error {
+func (tmpl *Template) parseSection(section *sectionElement) error {
for {
text, err := tmpl.readString(tmpl.otag)
- if err == os.EOF {
+ if err == io.EOF {
return parseError{section.startline, "Section " + section.name + " has no closing tag"}
}
// put text into an item
text = text[0 : len(text)-len(tmpl.otag)]
- section.elems.Push(&textElement{[]byte(text)})
+ section.elems = append(section.elems, &textElement{[]byte(text)})
if tmpl.p < len(tmpl.data) && tmpl.data[tmpl.p] == '{' {
text, err = tmpl.readString("}" + tmpl.ctag)
} else {
text, err = tmpl.readString(tmpl.ctag)
}
- if err == os.EOF {
+ if err == io.EOF {
//put the remaining text in a block
return parseError{tmpl.curline, "unmatched open tag"}
}
@@ -194,12 +194,12 @@ func (tmpl *Template) parseSection(section *sectionElement) os.Error {
tmpl.p += 2
}
- se := sectionElement{name, tag[0] == '^', tmpl.curline, new(vector.Vector)}
+ se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}}
err := tmpl.parseSection(&se)
if err != nil {
return err
}
- section.elems.Push(&se)
+ section.elems = append(section.elems, &se)
case '/':
name := strings.TrimSpace(tag[1:])
if name != section.name {
@@ -213,7 +213,7 @@ func (tmpl *Template) parseSection(section *sectionElement) os.Error {
if err != nil {
return err
}
- section.elems.Push(partial)
+ section.elems = append(section.elems, partial)
case '=':
if tag[len(tag)-1] != '=' {
return parseError{tmpl.curline, "Invalid meta tag"}
@@ -227,29 +227,28 @@ func (tmpl *Template) parseSection(section *sectionElement) os.Error {
case '{':
if tag[len(tag)-1] == '}' {
//use a raw tag
- section.elems.Push(&varElement{tag[1 : len(tag)-1], true})
+ section.elems = append(section.elems, &varElement{tag[1 : len(tag)-1], true})
}
default:
- section.elems.Push(&varElement{tag, false})
+ section.elems = append(section.elems, &varElement{tag, false})
}
}
return nil
}
-func (tmpl *Template) parse() os.Error {
+func (tmpl *Template) parse() error {
for {
text, err := tmpl.readString(tmpl.otag)
-
- if err == os.EOF {
+ if err == io.EOF {
//put the remaining text in a block
- tmpl.elems.Push(&textElement{[]byte(text)})
+ tmpl.elems = append(tmpl.elems, &textElement{[]byte(text)})
return nil
}
// put text into an item
text = text[0 : len(text)-len(tmpl.otag)]
- tmpl.elems.Push(&textElement{[]byte(text)})
+ tmpl.elems = append(tmpl.elems, &textElement{[]byte(text)})
if tmpl.p < len(tmpl.data) && tmpl.data[tmpl.p] == '{' {
text, err = tmpl.readString("}" + tmpl.ctag)
@@ -257,7 +256,7 @@ func (tmpl *Template) parse() os.Error {
text, err = tmpl.readString(tmpl.ctag)
}
- if err == os.EOF {
+ if err == io.EOF {
//put the remaining text in a block
return parseError{tmpl.curline, "unmatched open tag"}
}
@@ -280,12 +279,12 @@ func (tmpl *Template) parse() os.Error {
tmpl.p += 2
}
- se := sectionElement{name, tag[0] == '^', tmpl.curline, new(vector.Vector)}
+ se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}}
err := tmpl.parseSection(&se)
if err != nil {
return err
}
- tmpl.elems.Push(&se)
+ tmpl.elems = append(tmpl.elems, &se)
case '/':
return parseError{tmpl.curline, "unmatched close tag"}
case '>':
@@ -294,7 +293,7 @@ func (tmpl *Template) parse() os.Error {
if err != nil {
return err
}
- tmpl.elems.Push(partial)
+ tmpl.elems = append(tmpl.elems, partial)
case '=':
if tag[len(tag)-1] != '=' {
return parseError{tmpl.curline, "Invalid meta tag"}
@@ -308,10 +307,10 @@ func (tmpl *Template) parse() os.Error {
case '{':
//use a raw tag
if tag[len(tag)-1] == '}' {
- tmpl.elems.Push(&varElement{tag[1 : len(tag)-1], true})
+ tmpl.elems = append(tmpl.elems, &varElement{tag[1 : len(tag)-1], true})
}
default:
- tmpl.elems.Push(&varElement{tag, false})
+ tmpl.elems = append(tmpl.elems, &varElement{tag, false})
}
}
@@ -368,8 +367,8 @@ func call(v reflect.Value, method reflect.Method) reflect.Value {
}
// Evaluate interfaces and pointers looking for a value that can look up the name, via a
-// struct field, method, or map key, and return the result of the lookup.
-func lookup(contextChain *vector.Vector, name string) reflect.Value {
+// struct; field, method, or map key, and return the result of the lookup.
+func lookup(contextChain []interface{}, name string) reflect.Value {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Panic while looking up %q: %s\n", name, r)
@@ -377,8 +376,8 @@ func lookup(contextChain *vector.Vector, name string) reflect.Value {
}()
Outer:
- for i := contextChain.Len() - 1; i >= 0; i-- {
- v := contextChain.At(i).(reflect.Value)
+ for _, ctx := range contextChain { //i := len(contextChain) - 1; i >= 0; i-- {
+ v := ctx.(reflect.Value)
for v.IsValid() {
typ := v.Type()
if n := v.Type().NumMethod(); n > 0 {
@@ -417,7 +416,7 @@ Outer:
return reflect.Value{}
}
-func isNil(v reflect.Value) bool {
+func isEmpty(v reflect.Value) bool {
if !v.IsValid() || v.Interface() == nil {
return true
}
@@ -429,6 +428,8 @@ func isNil(v reflect.Value) bool {
switch val := valueInd; val.Kind() {
case reflect.Bool:
return !val.Bool()
+ case reflect.Slice:
+ return val.Len() == 0
}
return false
@@ -449,44 +450,46 @@ loop:
return v
}
-func renderSection(section *sectionElement, contextChain *vector.Vector, buf io.Writer) {
+func renderSection(section *sectionElement, contextChain []interface{}, buf io.Writer) {
value := lookup(contextChain, section.name)
- var context = contextChain.At(contextChain.Len() - 1).(reflect.Value)
- var contexts = new(vector.Vector)
+ var context = contextChain[len(contextChain)-1].(reflect.Value)
+ var contexts = []interface{}{}
// if the value is nil, check if it's an inverted section
- isNil := isNil(value)
- if isNil && !section.inverted || !isNil && section.inverted {
+ isEmpty := isEmpty(value)
+ if isEmpty && !section.inverted || !isEmpty && section.inverted {
return
- } else {
+ } else if !section.inverted {
valueInd := indirect(value)
switch val := valueInd; val.Kind() {
case reflect.Slice:
for i := 0; i < val.Len(); i++ {
- contexts.Push(val.Index(i))
+ contexts = append(contexts, val.Index(i))
}
case reflect.Array:
for i := 0; i < val.Len(); i++ {
- contexts.Push(val.Index(i))
+ contexts = append(contexts, val.Index(i))
}
case reflect.Map, reflect.Struct:
- contexts.Push(value)
+ contexts = append(contexts, value)
default:
- contexts.Push(context)
+ contexts = append(contexts, context)
}
+ } else if section.inverted {
+ contexts = append(contexts, context)
}
+ chain2 := make([]interface{}, len(contextChain)+1)
+ copy(chain2[1:], contextChain)
//by default we execute the section
- for j := 0; j < contexts.Len(); j++ {
- ctx := contexts.At(j).(reflect.Value)
- contextChain.Push(ctx)
- for i := 0; i < section.elems.Len(); i++ {
- renderElement(section.elems.At(i), contextChain, buf)
+ for _, ctx := range contexts {
+ chain2[0] = ctx
+ for _, elem := range section.elems {
+ renderElement(elem, chain2, buf)
}
- contextChain.Pop()
}
}
-func renderElement(element interface{}, contextChain *vector.Vector, buf io.Writer) {
+func renderElement(element interface{}, contextChain []interface{}, buf io.Writer) {
switch elem := element.(type) {
case *textElement:
buf.Write(elem.text)
@@ -496,8 +499,8 @@ func renderElement(element interface{}, contextChain *vector.Vector, buf io.Writ
fmt.Printf("Panic while looking up %q: %s\n", elem.name, r)
}
}()
-
val := lookup(contextChain, elem.name)
+
if val.IsValid() {
if elem.raw {
fmt.Fprint(buf, val.Interface())
@@ -513,26 +516,43 @@ func renderElement(element interface{}, contextChain *vector.Vector, buf io.Writ
}
}
-func (tmpl *Template) renderTemplate(contextChain *vector.Vector, buf io.Writer) {
- for i := 0; i < tmpl.elems.Len(); i++ {
- renderElement(tmpl.elems.At(i), contextChain, buf)
+func (tmpl *Template) renderTemplate(contextChain []interface{}, buf io.Writer) {
+ for _, elem := range tmpl.elems {
+ renderElement(elem, contextChain, buf)
}
}
func (tmpl *Template) Render(context ...interface{}) string {
var buf bytes.Buffer
- var contextChain vector.Vector
+ var contextChain []interface{}
for _, c := range context {
val := reflect.ValueOf(c)
- contextChain.Push(val)
+ contextChain = append(contextChain, val)
}
- tmpl.renderTemplate(&contextChain, &buf)
+ tmpl.renderTemplate(contextChain, &buf)
return buf.String()
}
-func ParseString(data string) (*Template, os.Error) {
+func (tmpl *Template) RenderTo(w io.Writer, context ...interface{}) {
+ contextChain := []interface{}{}
+ for _, c := range context {
+ val := reflect.ValueOf(c)
+ contextChain = append(contextChain, val)
+ }
+ tmpl.renderTemplate(contextChain, w)
+}
+
+func (tmpl *Template) RenderInLayout(layout *Template, context ...interface{}) string {
+ content := tmpl.Render(context...)
+ allContext := make([]interface{}, len(context)+1)
+ copy(allContext[1:], context)
+ allContext[0] = map[string]string{"content": content}
+ return layout.Render(allContext...)
+}
+
+func ParseString(data string) (*Template, error) {
cwd := os.Getenv("CWD")
- tmpl := Template{data, "{{", "}}", 0, 1, cwd, new(vector.Vector)}
+ tmpl := Template{data, "{{", "}}", 0, 1, cwd, []interface{}{}}
err := tmpl.parse()
if err != nil {
@@ -542,7 +562,7 @@ func ParseString(data string) (*Template, os.Error) {
return &tmpl, err
}
-func ParseFile(filename string) (*Template, os.Error) {
+func ParseFile(filename string) (*Template, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
@@ -550,7 +570,7 @@ func ParseFile(filename string) (*Template, os.Error) {
dirname, _ := path.Split(filename)
- tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, new(vector.Vector)}
+ tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, []interface{}{}}
err = tmpl.parse()
if err != nil {
@@ -562,20 +582,52 @@ func ParseFile(filename string) (*Template, os.Error) {
func Render(data string, context ...interface{}) string {
tmpl, err := ParseString(data)
+ if err != nil {
+ return err.Error()
+ }
+ return tmpl.Render(context...)
+}
+
+func RenderTo(w io.Writer, data string, context ...interface{}) error {
+ tmpl, err := ParseString(data)
if err != nil {
- return err.String()
+ return err
}
- return tmpl.Render(context...)
+ tmpl.RenderTo(w, context...)
+ return nil
+}
+
+func RenderInLayout(data string, layoutData string, context ...interface{}) string {
+ layoutTmpl, err := ParseString(layoutData)
+ if err != nil {
+ return err.Error()
+ }
+ tmpl, err := ParseString(data)
+ if err != nil {
+ return err.Error()
+ }
+ return tmpl.RenderInLayout(layoutTmpl, context...)
}
func RenderFile(filename string, context ...interface{}) string {
tmpl, err := ParseFile(filename)
+ if err != nil {
+ return err.Error()
+ }
+ return tmpl.Render(context...)
+}
+func RenderFileInLayout(filename string, layoutFile string, context ...interface{}) string {
+ layoutTmpl, err := ParseFile(layoutFile)
if err != nil {
- return err.String()
+ return err.Error()
}
- return tmpl.Render(context...)
+ tmpl, err := ParseFile(filename)
+ if err != nil {
+ return err.Error()
+ }
+ return tmpl.RenderInLayout(layoutTmpl, context...)
}
diff --git a/mustache_test.go b/mustache_test.go
index a12d8b7..2ac6367 100644
--- a/mustache_test.go
+++ b/mustache_test.go
@@ -1,7 +1,7 @@
package mustache
import (
- "container/vector"
+ "bytes"
"os"
"path"
"strings"
@@ -36,22 +36,22 @@ func (u *User) Func2() string {
return u.Name
}
-func (u *User) Func3() (map[string]string, os.Error) {
+func (u *User) Func3() (map[string]string, error) {
return map[string]string{"name": u.Name}, nil
}
-func (u *User) Func4() (map[string]string, os.Error) {
+func (u *User) Func4() (map[string]string, error) {
return nil, nil
}
-func (u *User) Func5() (*settings, os.Error) {
+func (u *User) Func5() (*settings, error) {
return &settings{true}, nil
}
-func (u *User) Func6() (*vector.Vector, os.Error) {
- var v vector.Vector
- v.Push(&settings{true})
- return &v, nil
+func (u *User) Func6() ([]interface{}, error) {
+ var v []interface{}
+ v = append(v, &settings{true})
+ return v, nil
}
func (u User) Truefunc1() bool {
@@ -62,16 +62,24 @@ func (u *User) Truefunc2() bool {
return true
}
-func makeVector(n int) *vector.Vector {
- v := new(vector.Vector)
+func makeVector(n int) []interface{} {
+ var v []interface{}
for i := 0; i < n; i++ {
- v.Push(&User{"Mike", 1})
+ v = append(v, &User{"Mike", 1})
}
return v
}
-var tests = []Test{
+type Category struct {
+ Tag string
+ Description string
+}
+
+func (c Category) DisplayName() string {
+ return c.Tag + " - " + c.Description
+}
+var tests = []Test{
{`hello world`, nil, "hello world"},
{`hello {{name}}`, map[string]string{"name": "world"}, "hello world"},
{`{{var}}`, map[string]string{"var": "5 > 2"}, "5 > 2"},
@@ -111,8 +119,8 @@ var tests = []Test{
{`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": (*User)(nil)}, ""},
{`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": []User{}}, ""},
- {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []*User{&User{"Mike", 1}}}, "Mike"},
- {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": vector.Vector([]interface{}{&User{"Mike", 12}})}, "Mike"},
+ {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []*User{{"Mike", 1}}}, "Mike"},
+ {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []interface{}{&User{"Mike", 12}}}, "Mike"},
{`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": makeVector(1)}, "Mike"},
{`{{Name}}`, User{"Mike", 1}, "Mike"},
{`{{Name}}`, &User{"Mike", 1}, "Mike"},
@@ -125,14 +133,15 @@ var tests = []Test{
{`{{^a}}b{{/a}}`, map[string]interface{}{"a": false}, "b"},
{`{{^a}}b{{/a}}`, map[string]interface{}{"a": true}, ""},
{`{{^a}}b{{/a}}`, map[string]interface{}{"a": "nonempty string"}, ""},
+ {`{{^a}}b{{/a}}`, map[string]interface{}{"a": []string{}}, "b"},
//function tests
{`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []User{{"Mike", 1}}}, "Mike"},
- {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []*User{&User{"Mike", 1}}}, "Mike"},
- {`{{#users}}{{Func2}}{{/users}}`, map[string]interface{}{"users": []*User{&User{"Mike", 1}}}, "Mike"},
+ {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []*User{{"Mike", 1}}}, "Mike"},
+ {`{{#users}}{{Func2}}{{/users}}`, map[string]interface{}{"users": []*User{{"Mike", 1}}}, "Mike"},
- {`{{#users}}{{#Func3}}{{name}}{{/Func3}}{{/users}}`, map[string]interface{}{"users": []*User{&User{"Mike", 1}}}, "Mike"},
- {`{{#users}}{{#Func4}}{{name}}{{/Func4}}{{/users}}`, map[string]interface{}{"users": []*User{&User{"Mike", 1}}}, ""},
+ {`{{#users}}{{#Func3}}{{name}}{{/Func3}}{{/users}}`, map[string]interface{}{"users": []*User{{"Mike", 1}}}, "Mike"},
+ {`{{#users}}{{#Func4}}{{name}}{{/Func4}}{{/users}}`, map[string]interface{}{"users": []*User{{"Mike", 1}}}, ""},
{`{{#Truefunc1}}abcd{{/Truefunc1}}`, User{"Mike", 1}, "abcd"},
{`{{#Truefunc1}}abcd{{/Truefunc1}}`, &User{"Mike", 1}, "abcd"},
{`{{#Truefunc2}}abcd{{/Truefunc2}}`, &User{"Mike", 1}, "abcd"},
@@ -145,6 +154,9 @@ var tests = []Test{
{`hello {{#section}}{{name}}{{/section}}`, map[string]interface{}{"name": "bob", "section": map[string]string{"name": "world"}}, "hello world"},
{`hello {{#bool}}{{#section}}{{name}}{{/section}}{{/bool}}`, map[string]interface{}{"bool": true, "section": map[string]string{"name": "world"}}, "hello world"},
{`{{#users}}{{canvas}}{{/users}}`, map[string]interface{}{"canvas": "hello", "users": []User{{"Mike", 1}}}, "hello"},
+ {`{{#categories}}{{DisplayName}}{{/categories}}`, map[string][]*Category{
+ "categories": {&Category{"a", "b"}},
+ }, "a - b"},
}
func TestBasic(t *testing.T) {
@@ -156,6 +168,18 @@ func TestBasic(t *testing.T) {
}
}
+func TestWriter(t *testing.T) {
+ var buf bytes.Buffer
+ for _, test := range tests {
+ buf.Reset()
+ RenderTo(&buf, test.tmpl, test.context)
+ output := buf.String()
+ if output != test.expected {
+ t.Fatalf("%q expected %q got %q", test.tmpl, test.expected, output)
+ }
+ }
+}
+
func TestFile(t *testing.T) {
filename := path.Join(path.Join(os.Getenv("PWD"), "tests"), "test1.mustache")
expected := "hello world"
@@ -167,12 +191,15 @@ func TestFile(t *testing.T) {
func TestPartial(t *testing.T) {
filename := path.Join(path.Join(os.Getenv("PWD"), "tests"), "test2.mustache")
+ println(filename)
expected := "hello world"
output := RenderFile(filename, map[string]string{"Name": "world"})
if output != expected {
t.Fatalf("testpartial expected %q got %q", expected, output)
}
}
+
+/*
func TestSectionPartial(t *testing.T) {
filename := path.Join(path.Join(os.Getenv("PWD"), "tests"), "test3.mustache")
expected := "Mike\nJoe\n"
@@ -182,7 +209,7 @@ func TestSectionPartial(t *testing.T) {
t.Fatalf("testSectionPartial expected %q got %q", expected, output)
}
}
-
+*/
func TestMultiContext(t *testing.T) {
output := Render(`{{hello}} {{World}}`, map[string]string{"hello": "hello"}, struct{ World string }{"world"})
output2 := Render(`{{hello}} {{World}}`, struct{ World string }{"world"}, map[string]string{"hello": "hello"})
@@ -206,3 +233,27 @@ func TestMalformed(t *testing.T) {
}
}
}
+
+type LayoutTest struct {
+ layout string
+ tmpl string
+ context interface{}
+ expected string
+}
+
+var layoutTests = []LayoutTest{
+ {`Header {{content}} Footer`, `Hello World`, nil, `Header Hello World Footer`},
+ {`Header {{content}} Footer`, `Hello {{s}}`, map[string]string{"s": "World"}, `Header Hello World Footer`},
+ {`Header {{content}} Footer`, `Hello {{content}}`, map[string]string{"content": "World"}, `Header Hello World Footer`},
+ {`Header {{extra}} {{content}} Footer`, `Hello {{content}}`, map[string]string{"content": "World", "extra": "extra"}, `Header extra Hello World Footer`},
+ {`Header {{content}} {{content}} Footer`, `Hello {{content}}`, map[string]string{"content": "World"}, `Header Hello World Hello World Footer`},
+}
+
+func TestLayout(t *testing.T) {
+ for _, test := range layoutTests {
+ output := RenderInLayout(test.tmpl, test.layout, test.context)
+ if output != test.expected {
+ t.Fatalf("%q expected %q got %q", test.tmpl, test.expected, output)
+ }
+ }
+}