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) + } + } +}