From 4007706b18b410019198dd18df463ac2a71c5b1c Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Sun, 7 Apr 2013 22:42:04 -0400 Subject: [PATCH 1/2] Add RenderTo() for rendering directly to an io.Writer. --- mustache.go | 1072 ++++++++++++++++++++++++---------------------- mustache_test.go | 310 +++++++------- 2 files changed, 718 insertions(+), 664 deletions(-) diff --git a/mustache.go b/mustache.go index 284c519..666f582 100644 --- a/mustache.go +++ b/mustache.go @@ -1,320 +1,331 @@ package mustache import ( - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "reflect" - "strings" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "reflect" + "strings" ) type textElement struct { - text []byte + text []byte } type varElement struct { - name string - raw bool + name string + raw bool } type sectionElement struct { - name string - inverted bool - startline int - elems []interface{} + name string + inverted bool + startline int + elems []interface{} } type Template struct { - data string - otag string - ctag string - p int - curline int - dir string - elems []interface{} + data string + otag string + ctag string + p int + curline int + dir string + elems []interface{} } type parseError struct { - line int - message string + line int + message string } func (p parseError) Error() string { return fmt.Sprintf("line %d: %s", p.line, p.message) } var ( - esc_quot = []byte(""") - esc_apos = []byte("'") - esc_amp = []byte("&") - esc_lt = []byte("<") - esc_gt = []byte(">") + esc_quot = []byte(""") + esc_apos = []byte("'") + esc_amp = []byte("&") + esc_lt = []byte("<") + esc_gt = []byte(">") ) // taken from pkg/template -func htmlEscape(w io.Writer, s []byte) { - var esc []byte - last := 0 - for i, c := range s { - switch c { - case '"': - esc = esc_quot - case '\'': - esc = esc_apos - case '&': - esc = esc_amp - case '<': - esc = esc_lt - case '>': - esc = esc_gt - default: - continue - } - w.Write(s[last:i]) - w.Write(esc) - last = i + 1 - } - w.Write(s[last:]) +func htmlEscape(w io.Writer, s []byte) (err error) { + var esc []byte + last := 0 + for i, c := range s { + switch c { + case '"': + esc = esc_quot + case '\'': + esc = esc_apos + case '&': + esc = esc_amp + case '<': + esc = esc_lt + case '>': + esc = esc_gt + default: + continue + } + + _, err = w.Write(s[last:i]) + if err != nil { + return + } + + _, err = w.Write(esc) + if err != nil { + return + } + + last = i + 1 + } + + _, err = w.Write(s[last:]) + return } 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:], io.EOF - } - - if tmpl.data[i] == '\n' { - newlines++ - } - - if tmpl.data[i] != s[0] { - i++ - continue - } - - match := true - for j := 1; j < len(s); j++ { - if s[j] != tmpl.data[i+j] { - match = false - break - } - } - - if match { - e := i + len(s) - text := tmpl.data[tmpl.p:e] - tmpl.p = e - - tmpl.curline += newlines - return text, nil - } else { - i++ - } - } - - //should never be here - return "", nil + 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:], io.EOF + } + + if tmpl.data[i] == '\n' { + newlines++ + } + + if tmpl.data[i] != s[0] { + i++ + continue + } + + match := true + for j := 1; j < len(s); j++ { + if s[j] != tmpl.data[i+j] { + match = false + break + } + } + + if match { + e := i + len(s) + text := tmpl.data[tmpl.p:e] + tmpl.p = e + + tmpl.curline += newlines + return text, nil + } else { + i++ + } + } + + //should never be here + return "", nil } func (tmpl *Template) parsePartial(name string) (*Template, error) { - filenames := []string{ - path.Join(tmpl.dir, name), - path.Join(tmpl.dir, name+".mustache"), - path.Join(tmpl.dir, name+".stache"), - name, - name + ".mustache", - name + ".stache", - } - var filename string - for _, name := range filenames { - f, err := os.Open(name) - if err == nil { - filename = name - f.Close() - break - } - } - if filename == "" { - return nil, errors.New(fmt.Sprintf("Could not find partial %q", name)) - } - - partial, err := ParseFile(filename) - - if err != nil { - return nil, err - } - - return partial, nil + filenames := []string{ + path.Join(tmpl.dir, name), + path.Join(tmpl.dir, name+".mustache"), + path.Join(tmpl.dir, name+".stache"), + name, + name + ".mustache", + name + ".stache", + } + var filename string + for _, name := range filenames { + f, err := os.Open(name) + if err == nil { + filename = name + f.Close() + break + } + } + if filename == "" { + return nil, errors.New(fmt.Sprintf("Could not find partial %q", name)) + } + + partial, err := ParseFile(filename) + + if err != nil { + return nil, err + } + + return partial, nil } func (tmpl *Template) parseSection(section *sectionElement) error { - for { - text, err := tmpl.readString(tmpl.otag) - - 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 = 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 == io.EOF { - //put the remaining text in a block - return parseError{tmpl.curline, "unmatched open tag"} - } - - //trim the close tag off the text - tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) - - if len(tag) == 0 { - return parseError{tmpl.curline, "empty tag"} - } - switch tag[0] { - case '!': - //ignore comment - break - case '#', '^': - name := strings.TrimSpace(tag[1:]) - - //ignore the newline when a section starts - if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { - tmpl.p += 1 - } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { - tmpl.p += 2 - } - - se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} - err := tmpl.parseSection(&se) - if err != nil { - return err - } - section.elems = append(section.elems, &se) - case '/': - name := strings.TrimSpace(tag[1:]) - if name != section.name { - return parseError{tmpl.curline, "interleaved closing tag: " + name} - } else { - return nil - } - case '>': - name := strings.TrimSpace(tag[1:]) - partial, err := tmpl.parsePartial(name) - if err != nil { - return err - } - section.elems = append(section.elems, partial) - case '=': - if tag[len(tag)-1] != '=' { - return parseError{tmpl.curline, "Invalid meta tag"} - } - tag = strings.TrimSpace(tag[1 : len(tag)-1]) - newtags := strings.SplitN(tag, " ", 2) - if len(newtags) == 2 { - tmpl.otag = newtags[0] - tmpl.ctag = newtags[1] - } - case '{': - if tag[len(tag)-1] == '}' { - //use a raw tag - section.elems = append(section.elems, &varElement{tag[1 : len(tag)-1], true}) - } - default: - section.elems = append(section.elems, &varElement{tag, false}) - } - } - - return nil + for { + text, err := tmpl.readString(tmpl.otag) + + 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 = 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 == io.EOF { + //put the remaining text in a block + return parseError{tmpl.curline, "unmatched open tag"} + } + + //trim the close tag off the text + tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) + + if len(tag) == 0 { + return parseError{tmpl.curline, "empty tag"} + } + switch tag[0] { + case '!': + //ignore comment + break + case '#', '^': + name := strings.TrimSpace(tag[1:]) + + //ignore the newline when a section starts + if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { + tmpl.p += 1 + } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { + tmpl.p += 2 + } + + se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} + err := tmpl.parseSection(&se) + if err != nil { + return err + } + section.elems = append(section.elems, &se) + case '/': + name := strings.TrimSpace(tag[1:]) + if name != section.name { + return parseError{tmpl.curline, "interleaved closing tag: " + name} + } else { + return nil + } + case '>': + name := strings.TrimSpace(tag[1:]) + partial, err := tmpl.parsePartial(name) + if err != nil { + return err + } + section.elems = append(section.elems, partial) + case '=': + if tag[len(tag)-1] != '=' { + return parseError{tmpl.curline, "Invalid meta tag"} + } + tag = strings.TrimSpace(tag[1 : len(tag)-1]) + newtags := strings.SplitN(tag, " ", 2) + if len(newtags) == 2 { + tmpl.otag = newtags[0] + tmpl.ctag = newtags[1] + } + case '{': + if tag[len(tag)-1] == '}' { + //use a raw tag + section.elems = append(section.elems, &varElement{tag[1 : len(tag)-1], true}) + } + default: + section.elems = append(section.elems, &varElement{tag, false}) + } + } + + return nil } func (tmpl *Template) parse() error { - for { - text, err := tmpl.readString(tmpl.otag) - if err == io.EOF { - //put the remaining text in a block - 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 = append(tmpl.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 == io.EOF { - //put the remaining text in a block - return parseError{tmpl.curline, "unmatched open tag"} - } - - //trim the close tag off the text - tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) - if len(tag) == 0 { - return parseError{tmpl.curline, "empty tag"} - } - switch tag[0] { - case '!': - //ignore comment - break - case '#', '^': - name := strings.TrimSpace(tag[1:]) - - if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { - tmpl.p += 1 - } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { - tmpl.p += 2 - } - - se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} - err := tmpl.parseSection(&se) - if err != nil { - return err - } - tmpl.elems = append(tmpl.elems, &se) - case '/': - return parseError{tmpl.curline, "unmatched close tag"} - case '>': - name := strings.TrimSpace(tag[1:]) - partial, err := tmpl.parsePartial(name) - if err != nil { - return err - } - tmpl.elems = append(tmpl.elems, partial) - case '=': - if tag[len(tag)-1] != '=' { - return parseError{tmpl.curline, "Invalid meta tag"} - } - tag = strings.TrimSpace(tag[1 : len(tag)-1]) - newtags := strings.SplitN(tag, " ", 2) - if len(newtags) == 2 { - tmpl.otag = newtags[0] - tmpl.ctag = newtags[1] - } - case '{': - //use a raw tag - if tag[len(tag)-1] == '}' { - tmpl.elems = append(tmpl.elems, &varElement{tag[1 : len(tag)-1], true}) - } - default: - tmpl.elems = append(tmpl.elems, &varElement{tag, false}) - } - } - - return nil + for { + text, err := tmpl.readString(tmpl.otag) + if err == io.EOF { + //put the remaining text in a block + 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 = append(tmpl.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 == io.EOF { + //put the remaining text in a block + return parseError{tmpl.curline, "unmatched open tag"} + } + + //trim the close tag off the text + tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) + if len(tag) == 0 { + return parseError{tmpl.curline, "empty tag"} + } + switch tag[0] { + case '!': + //ignore comment + break + case '#', '^': + name := strings.TrimSpace(tag[1:]) + + if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { + tmpl.p += 1 + } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { + tmpl.p += 2 + } + + se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} + err := tmpl.parseSection(&se) + if err != nil { + return err + } + tmpl.elems = append(tmpl.elems, &se) + case '/': + return parseError{tmpl.curline, "unmatched close tag"} + case '>': + name := strings.TrimSpace(tag[1:]) + partial, err := tmpl.parsePartial(name) + if err != nil { + return err + } + tmpl.elems = append(tmpl.elems, partial) + case '=': + if tag[len(tag)-1] != '=' { + return parseError{tmpl.curline, "Invalid meta tag"} + } + tag = strings.TrimSpace(tag[1 : len(tag)-1]) + newtags := strings.SplitN(tag, " ", 2) + if len(newtags) == 2 { + tmpl.otag = newtags[0] + tmpl.ctag = newtags[1] + } + case '{': + //use a raw tag + if tag[len(tag)-1] == '}' { + tmpl.elems = append(tmpl.elems, &varElement{tag[1 : len(tag)-1], true}) + } + default: + tmpl.elems = append(tmpl.elems, &varElement{tag, false}) + } + } + + return nil } // See if name is a method of the value at some level of indirection. @@ -322,288 +333,331 @@ func (tmpl *Template) parse() error { // there's trouble) and whether a method of the right name exists with // any signature. func callMethod(data reflect.Value, name string) (result reflect.Value, found bool) { - found = false - // Method set depends on pointerness, and the value may be arbitrarily - // indirect. Simplest approach is to walk down the pointer chain and - // see if we can find the method at each step. - // Most steps will see NumMethod() == 0. - for { - typ := data.Type() - if nMethod := data.Type().NumMethod(); nMethod > 0 { - for i := 0; i < nMethod; i++ { - method := typ.Method(i) - if method.Name == name { - - found = true // we found the name regardless - // does receiver type match? (pointerness might be off) - if typ == method.Type.In(0) { - return call(data, method), found - } - } - } - } - if nd := data; nd.Kind() == reflect.Ptr { - data = nd.Elem() - } else { - break - } - } - return + found = false + // Method set depends on pointerness, and the value may be arbitrarily + // indirect. Simplest approach is to walk down the pointer chain and + // see if we can find the method at each step. + // Most steps will see NumMethod() == 0. + for { + typ := data.Type() + if nMethod := data.Type().NumMethod(); nMethod > 0 { + for i := 0; i < nMethod; i++ { + method := typ.Method(i) + if method.Name == name { + + found = true // we found the name regardless + // does receiver type match? (pointerness might be off) + if typ == method.Type.In(0) { + return call(data, method), found + } + } + } + } + if nd := data; nd.Kind() == reflect.Ptr { + data = nd.Elem() + } else { + break + } + } + return } // Invoke the method. If its signature is wrong, return nil. func call(v reflect.Value, method reflect.Method) reflect.Value { - funcType := method.Type - // Method must take no arguments, meaning as a func it has one argument (the receiver) - if funcType.NumIn() != 1 { - return reflect.Value{} - } - // Method must return a single value. - if funcType.NumOut() == 0 { - return reflect.Value{} - } - // Result will be the zeroth element of the returned slice. - return method.Func.Call([]reflect.Value{v})[0] + funcType := method.Type + // Method must take no arguments, meaning as a func it has one argument (the receiver) + if funcType.NumIn() != 1 { + return reflect.Value{} + } + // Method must return a single value. + if funcType.NumOut() == 0 { + return reflect.Value{} + } + // Result will be the zeroth element of the returned slice. + return method.Func.Call([]reflect.Value{v})[0] } // 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 []interface{}, name string) reflect.Value { - defer func() { - if r := recover(); r != nil { - fmt.Printf("Panic while looking up %q: %s\n", name, r) - } - }() + defer func() { + if r := recover(); r != nil { + fmt.Printf("Panic while looking up %q: %s\n", name, r) + } + }() Outer: - 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 { - for i := 0; i < n; i++ { - m := typ.Method(i) - mtyp := m.Type - if m.Name == name && mtyp.NumIn() == 1 { - return v.Method(i).Call(nil)[0] - } - } - } - switch av := v; av.Kind() { - case reflect.Ptr: - v = av.Elem() - case reflect.Interface: - v = av.Elem() - case reflect.Struct: - ret := av.FieldByName(name) - if ret.IsValid() { - return ret - } else { - continue Outer - } - case reflect.Map: - ret := av.MapIndex(reflect.ValueOf(name)) - if ret.IsValid() { - return ret - } else { - continue Outer - } - default: - continue Outer - } - } - } - return 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 { + for i := 0; i < n; i++ { + m := typ.Method(i) + mtyp := m.Type + if m.Name == name && mtyp.NumIn() == 1 { + return v.Method(i).Call(nil)[0] + } + } + } + switch av := v; av.Kind() { + case reflect.Ptr: + v = av.Elem() + case reflect.Interface: + v = av.Elem() + case reflect.Struct: + ret := av.FieldByName(name) + if ret.IsValid() { + return ret + } else { + continue Outer + } + case reflect.Map: + ret := av.MapIndex(reflect.ValueOf(name)) + if ret.IsValid() { + return ret + } else { + continue Outer + } + default: + continue Outer + } + } + } + return reflect.Value{} } func isNil(v reflect.Value) bool { - if !v.IsValid() || v.Interface() == nil { - return true - } - - valueInd := indirect(v) - if !valueInd.IsValid() { - return true - } - switch val := valueInd; val.Kind() { - case reflect.Bool: - return !val.Bool() - } - - return false + if !v.IsValid() || v.Interface() == nil { + return true + } + + valueInd := indirect(v) + if !valueInd.IsValid() { + return true + } + switch val := valueInd; val.Kind() { + case reflect.Bool: + return !val.Bool() + } + + return false } func indirect(v reflect.Value) reflect.Value { loop: - for v.IsValid() { - switch av := v; av.Kind() { - case reflect.Ptr: - v = av.Elem() - case reflect.Interface: - v = av.Elem() - default: - break loop - } - } - return v + for v.IsValid() { + switch av := v; av.Kind() { + case reflect.Ptr: + v = av.Elem() + case reflect.Interface: + v = av.Elem() + default: + break loop + } + } + return v } -func renderSection(section *sectionElement, contextChain []interface{}, buf io.Writer) { - value := lookup(contextChain, section.name) - 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 { - return - } else { - valueInd := indirect(value) - switch val := valueInd; val.Kind() { - case reflect.Slice: - for i := 0; i < val.Len(); i++ { - contexts = append(contexts, val.Index(i)) - } - case reflect.Array: - for i := 0; i < val.Len(); i++ { - contexts = append(contexts, val.Index(i)) - } - case reflect.Map, reflect.Struct: - contexts = append(contexts, value) - default: - contexts = append(contexts, context) - } - } - - chain2 := make([]interface{}, len(contextChain)+1) - copy(chain2[1:], contextChain) - //by default we execute the section - for _, ctx := range contexts { - chain2[0] = ctx - for _, elem := range section.elems { - renderElement(elem, chain2, buf) - } - } +func renderSection(section *sectionElement, contextChain []interface{}, buf io.Writer) (err error) { + value := lookup(contextChain, section.name) + 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 { + return + } else { + valueInd := indirect(value) + switch val := valueInd; val.Kind() { + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + contexts = append(contexts, val.Index(i)) + } + case reflect.Array: + for i := 0; i < val.Len(); i++ { + contexts = append(contexts, val.Index(i)) + } + case reflect.Map, reflect.Struct: + contexts = append(contexts, value) + default: + contexts = append(contexts, context) + } + } + + chain2 := make([]interface{}, len(contextChain)+1) + copy(chain2[1:], contextChain) + //by default we execute the section + for _, ctx := range contexts { + chain2[0] = ctx + for _, elem := range section.elems { + err = renderElement(elem, chain2, buf) + if err != nil { + return + } + } + } + + return nil } -func renderElement(element interface{}, contextChain []interface{}, buf io.Writer) { - switch elem := element.(type) { - case *textElement: - buf.Write(elem.text) - case *varElement: - defer func() { - if r := recover(); r != nil { - 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()) - } else { - s := fmt.Sprint(val.Interface()) - htmlEscape(buf, []byte(s)) - } - } - case *sectionElement: - renderSection(elem, contextChain, buf) - case *Template: - elem.renderTemplate(contextChain, buf) - } +func renderElement(element interface{}, contextChain []interface{}, buf io.Writer) (err error) { + switch elem := element.(type) { + case *textElement: + _, err = buf.Write(elem.text) + if err != nil { + return + } + case *varElement: + defer func() { + if r := recover(); r != nil { + fmt.Printf("Panic while looking up %q: %s\n", elem.name, r) + } + }() + val := lookup(contextChain, elem.name) + + if val.IsValid() { + if elem.raw { + _, err = fmt.Fprint(buf, val.Interface()) + if err != nil { + return + } + } else { + s := fmt.Sprint(val.Interface()) + err = htmlEscape(buf, []byte(s)) + if err != nil { + return + } + } + } + case *sectionElement: + err = renderSection(elem, contextChain, buf) + if err != nil { + return + } + case *Template: + err = elem.renderTemplate(contextChain, buf) + if err != nil { + return + } + } + + return nil } -func (tmpl *Template) renderTemplate(contextChain []interface{}, buf io.Writer) { - for _, elem := range tmpl.elems { - renderElement(elem, contextChain, buf) - } +func (tmpl *Template) renderTemplate(contextChain []interface{}, buf io.Writer) (err error) { + for _, elem := range tmpl.elems { + err = renderElement(elem, contextChain, buf) + if err != nil { + return + } + } + + return nil } func (tmpl *Template) Render(context ...interface{}) string { - var buf bytes.Buffer - var contextChain []interface{} - for _, c := range context { - val := reflect.ValueOf(c) - contextChain = append(contextChain, val) - } - tmpl.renderTemplate(contextChain, &buf) - return buf.String() + var buf bytes.Buffer + + tmpl.RenderTo(&buf, context...) + + return buf.String() +} + +func (tmpl *Template) RenderTo(w io.Writer, context ...interface{}) error { + var contextChain []interface{} + for _, c := range context { + val := reflect.ValueOf(c) + contextChain = append(contextChain, val) + } + + return 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...) + 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, []interface{}{}} - err := tmpl.parse() + cwd := os.Getenv("CWD") + tmpl := Template{data, "{{", "}}", 0, 1, cwd, []interface{}{}} + err := tmpl.parse() - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return &tmpl, err + return &tmpl, err } func ParseFile(filename string) (*Template, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } - dirname, _ := path.Split(filename) + dirname, _ := path.Split(filename) - tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, []interface{}{}} - err = tmpl.parse() + tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, []interface{}{}} + err = tmpl.parse() - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return &tmpl, nil + return &tmpl, nil } func Render(data string, context ...interface{}) string { - tmpl, err := ParseString(data) - if err != nil { - return err.Error() - } - return tmpl.Render(context...) + 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 + } + + return tmpl.RenderTo(w, context...) } 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...) + 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...) + 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.Error() - } - - tmpl, err := ParseFile(filename) - if err != nil { - return err.Error() - } - return tmpl.RenderInLayout(layoutTmpl, context...) + layoutTmpl, err := ParseFile(layoutFile) + if err != nil { + return err.Error() + } + + 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 8a32162..0b1eaf7 100644 --- a/mustache_test.go +++ b/mustache_test.go @@ -1,188 +1,188 @@ package mustache import ( - "os" - "path" - "strings" - "testing" + "os" + "path" + "strings" + "testing" ) type Test struct { - tmpl string - context interface{} - expected string + tmpl string + context interface{} + expected string } type Data struct { - A bool - B string + A bool + B string } type User struct { - Name string - Id int64 + Name string + Id int64 } type settings struct { - Allow bool + Allow bool } func (u User) Func1() string { - return u.Name + return u.Name } func (u *User) Func2() string { - return u.Name + return u.Name } func (u *User) Func3() (map[string]string, error) { - return map[string]string{"name": u.Name}, nil + return map[string]string{"name": u.Name}, nil } func (u *User) Func4() (map[string]string, error) { - return nil, nil + return nil, nil } func (u *User) Func5() (*settings, error) { - return &settings{true}, nil + return &settings{true}, nil } func (u *User) Func6() ([]interface{}, error) { - var v []interface{} - v = append(v, &settings{true}) - return v, nil + var v []interface{} + v = append(v, &settings{true}) + return v, nil } func (u User) Truefunc1() bool { - return true + return true } func (u *User) Truefunc2() bool { - return true + return true } func makeVector(n int) []interface{} { - var v []interface{} - for i := 0; i < n; i++ { - v = append(v, &User{"Mike", 1}) - } - return v + var v []interface{} + for i := 0; i < n; i++ { + v = append(v, &User{"Mike", 1}) + } + return v } type Category struct { - Tag string - Description string + Tag string + Description string } func (c Category) DisplayName() string { - return c.Tag + " - " + c.Description + 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"}, - {`{{{var}}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, - {`{{a}}{{b}}{{c}}{{d}}`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "abcd"}, - {`0{{a}}1{{b}}23{{c}}456{{d}}89`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "0a1b23c456d89"}, - {`hello {{! comment }}world`, map[string]string{}, "hello world"}, - {`{{ a }}{{=<% %>=}}<%b %><%={{ }}=%>{{ c }}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - {`{{ a }}{{= <% %> =}}<%b %><%= {{ }}=%>{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - - //does not exist - {`{{dne}}`, map[string]string{"name": "world"}, ""}, - {`{{dne}}`, User{"Mike", 1}, ""}, - {`{{dne}}`, &User{"Mike", 1}, ""}, - {`{{#has}}{{/has}}`, &User{"Mike", 1}, ""}, - - //section tests - {`{{#A}}{{B}}{{/A}}`, Data{true, "hello"}, "hello"}, - {`{{#A}}{{{B}}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, - {`{{#A}}{{B}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, - {`{{#A}}{{B}}{{/A}}`, Data{false, "hello"}, ""}, - {`{{a}}{{#b}}{{b}}{{/b}}{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - {`{{#A}}{{B}}{{/A}}`, struct { - A []struct { - B string - } - }{[]struct { - B string - }{{"a"}, {"b"}, {"c"}}}, - "abc", - }, - {`{{#A}}{{b}}{{/A}}`, struct{ A []map[string]string }{[]map[string]string{{"b": "a"}, {"b": "b"}, {"b": "c"}}}, "abc"}, - - {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []User{{"Mike", 1}}}, "Mike"}, - - {`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": nil}, ""}, - {`{{#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{{"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"}, - {"{{#users}}\n{{Name}}\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\nMike\n"}, - {"{{#users}}\r\n{{Name}}\r\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\r\nMike\r\n"}, - - //inverted section tests - {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]string{"a": "a", "c": "c"}, "abc"}, - {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]interface{}{"a": "a", "b": false, "c": "c"}, "abc"}, - {`{{^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"}, ""}, - - //function tests - {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []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{{"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"}, - {`{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}`, &User{"Mike", 1}, "abcd"}, - {`{{#user}}{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, - {`{{#user}}{{#Func6}}{{#Allow}}abcd{{/Allow}}{{/Func6}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, - - //context chaining - {`hello {{#section}}{{name}}{{/section}}`, map[string]interface{}{"section": map[string]string{"name": "world"}}, "hello world"}, - {`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"}, + {`hello world`, nil, "hello world"}, + {`hello {{name}}`, map[string]string{"name": "world"}, "hello world"}, + {`{{var}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, + {`{{{var}}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, + {`{{a}}{{b}}{{c}}{{d}}`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "abcd"}, + {`0{{a}}1{{b}}23{{c}}456{{d}}89`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "0a1b23c456d89"}, + {`hello {{! comment }}world`, map[string]string{}, "hello world"}, + {`{{ a }}{{=<% %>=}}<%b %><%={{ }}=%>{{ c }}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + {`{{ a }}{{= <% %> =}}<%b %><%= {{ }}=%>{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + + //does not exist + {`{{dne}}`, map[string]string{"name": "world"}, ""}, + {`{{dne}}`, User{"Mike", 1}, ""}, + {`{{dne}}`, &User{"Mike", 1}, ""}, + {`{{#has}}{{/has}}`, &User{"Mike", 1}, ""}, + + //section tests + {`{{#A}}{{B}}{{/A}}`, Data{true, "hello"}, "hello"}, + {`{{#A}}{{{B}}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, + {`{{#A}}{{B}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, + {`{{#A}}{{B}}{{/A}}`, Data{false, "hello"}, ""}, + {`{{a}}{{#b}}{{b}}{{/b}}{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + {`{{#A}}{{B}}{{/A}}`, struct { + A []struct { + B string + } + }{[]struct { + B string + }{{"a"}, {"b"}, {"c"}}}, + "abc", + }, + {`{{#A}}{{b}}{{/A}}`, struct{ A []map[string]string }{[]map[string]string{{"b": "a"}, {"b": "b"}, {"b": "c"}}}, "abc"}, + + {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []User{{"Mike", 1}}}, "Mike"}, + + {`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": nil}, ""}, + {`{{#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{{"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"}, + {"{{#users}}\n{{Name}}\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\nMike\n"}, + {"{{#users}}\r\n{{Name}}\r\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\r\nMike\r\n"}, + + //inverted section tests + {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]string{"a": "a", "c": "c"}, "abc"}, + {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]interface{}{"a": "a", "b": false, "c": "c"}, "abc"}, + {`{{^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"}, ""}, + + //function tests + {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []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{{"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"}, + {`{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}`, &User{"Mike", 1}, "abcd"}, + {`{{#user}}{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, + {`{{#user}}{{#Func6}}{{#Allow}}abcd{{/Allow}}{{/Func6}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, + + //context chaining + {`hello {{#section}}{{name}}{{/section}}`, map[string]interface{}{"section": map[string]string{"name": "world"}}, "hello world"}, + {`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) { - for _, test := range tests { - output := Render(test.tmpl, test.context) - if output != test.expected { - t.Fatalf("%q expected %q got %q", test.tmpl, test.expected, output) - } - } + for _, test := range tests { + output := Render(test.tmpl, test.context) + 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" - output := RenderFile(filename, map[string]string{"name": "world"}) - if output != expected { - t.Fatalf("testfile expected %q got %q", expected, output) - } + filename := path.Join(path.Join(os.Getenv("PWD"), "tests"), "test1.mustache") + expected := "hello world" + output := RenderFile(filename, map[string]string{"name": "world"}) + if output != expected { + t.Fatalf("testfile expected %q got %q", expected, output) + } } 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) - } + 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) + } } /* @@ -197,49 +197,49 @@ func TestSectionPartial(t *testing.T) { } */ 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"}) - if output != "hello world" || output2 != "hello world" { - t.Fatalf("TestMultiContext expected %q got %q", "hello world", output) - } + 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"}) + if output != "hello world" || output2 != "hello world" { + t.Fatalf("TestMultiContext expected %q got %q", "hello world", output) + } } var malformed = []Test{ - {`{{#a}}{{}}{{/a}}`, Data{true, "hello"}, "empty tag"}, - {`{{}}`, nil, "empty tag"}, - {`{{}`, nil, "unmatched open tag"}, - {`{{`, nil, "unmatched open tag"}, + {`{{#a}}{{}}{{/a}}`, Data{true, "hello"}, "empty tag"}, + {`{{}}`, nil, "empty tag"}, + {`{{}`, nil, "unmatched open tag"}, + {`{{`, nil, "unmatched open tag"}, } func TestMalformed(t *testing.T) { - for _, test := range malformed { - output := Render(test.tmpl, test.context) - if strings.Index(output, test.expected) == -1 { - t.Fatalf("%q expected %q in error %q", test.tmpl, test.expected, output) - } - } + for _, test := range malformed { + output := Render(test.tmpl, test.context) + if strings.Index(output, test.expected) == -1 { + t.Fatalf("%q expected %q in error %q", test.tmpl, test.expected, output) + } + } } type LayoutTest struct { - layout string - tmpl string - context interface{} - expected string + 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`}, + {`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) - } - } + 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) + } + } } From bc70a840d7b036f9c1033b73007eb4b87109207a Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 8 Apr 2013 10:17:49 -0400 Subject: [PATCH 2/2] Run make format. --- mustache.go | 1098 +++++++++++++++++++++++----------------------- mustache_test.go | 310 ++++++------- 2 files changed, 704 insertions(+), 704 deletions(-) diff --git a/mustache.go b/mustache.go index 666f582..7110293 100644 --- a/mustache.go +++ b/mustache.go @@ -1,331 +1,331 @@ package mustache import ( - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "reflect" - "strings" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "reflect" + "strings" ) type textElement struct { - text []byte + text []byte } type varElement struct { - name string - raw bool + name string + raw bool } type sectionElement struct { - name string - inverted bool - startline int - elems []interface{} + name string + inverted bool + startline int + elems []interface{} } type Template struct { - data string - otag string - ctag string - p int - curline int - dir string - elems []interface{} + data string + otag string + ctag string + p int + curline int + dir string + elems []interface{} } type parseError struct { - line int - message string + line int + message string } func (p parseError) Error() string { return fmt.Sprintf("line %d: %s", p.line, p.message) } var ( - esc_quot = []byte(""") - esc_apos = []byte("'") - esc_amp = []byte("&") - esc_lt = []byte("<") - esc_gt = []byte(">") + esc_quot = []byte(""") + esc_apos = []byte("'") + esc_amp = []byte("&") + esc_lt = []byte("<") + esc_gt = []byte(">") ) // taken from pkg/template func htmlEscape(w io.Writer, s []byte) (err error) { - var esc []byte - last := 0 - for i, c := range s { - switch c { - case '"': - esc = esc_quot - case '\'': - esc = esc_apos - case '&': - esc = esc_amp - case '<': - esc = esc_lt - case '>': - esc = esc_gt - default: - continue - } - - _, err = w.Write(s[last:i]) - if err != nil { - return - } - - _, err = w.Write(esc) - if err != nil { - return - } - - last = i + 1 - } - - _, err = w.Write(s[last:]) - return + var esc []byte + last := 0 + for i, c := range s { + switch c { + case '"': + esc = esc_quot + case '\'': + esc = esc_apos + case '&': + esc = esc_amp + case '<': + esc = esc_lt + case '>': + esc = esc_gt + default: + continue + } + + _, err = w.Write(s[last:i]) + if err != nil { + return + } + + _, err = w.Write(esc) + if err != nil { + return + } + + last = i + 1 + } + + _, err = w.Write(s[last:]) + return } 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:], io.EOF - } - - if tmpl.data[i] == '\n' { - newlines++ - } - - if tmpl.data[i] != s[0] { - i++ - continue - } - - match := true - for j := 1; j < len(s); j++ { - if s[j] != tmpl.data[i+j] { - match = false - break - } - } - - if match { - e := i + len(s) - text := tmpl.data[tmpl.p:e] - tmpl.p = e - - tmpl.curline += newlines - return text, nil - } else { - i++ - } - } - - //should never be here - return "", nil + 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:], io.EOF + } + + if tmpl.data[i] == '\n' { + newlines++ + } + + if tmpl.data[i] != s[0] { + i++ + continue + } + + match := true + for j := 1; j < len(s); j++ { + if s[j] != tmpl.data[i+j] { + match = false + break + } + } + + if match { + e := i + len(s) + text := tmpl.data[tmpl.p:e] + tmpl.p = e + + tmpl.curline += newlines + return text, nil + } else { + i++ + } + } + + //should never be here + return "", nil } func (tmpl *Template) parsePartial(name string) (*Template, error) { - filenames := []string{ - path.Join(tmpl.dir, name), - path.Join(tmpl.dir, name+".mustache"), - path.Join(tmpl.dir, name+".stache"), - name, - name + ".mustache", - name + ".stache", - } - var filename string - for _, name := range filenames { - f, err := os.Open(name) - if err == nil { - filename = name - f.Close() - break - } - } - if filename == "" { - return nil, errors.New(fmt.Sprintf("Could not find partial %q", name)) - } - - partial, err := ParseFile(filename) - - if err != nil { - return nil, err - } - - return partial, nil + filenames := []string{ + path.Join(tmpl.dir, name), + path.Join(tmpl.dir, name+".mustache"), + path.Join(tmpl.dir, name+".stache"), + name, + name + ".mustache", + name + ".stache", + } + var filename string + for _, name := range filenames { + f, err := os.Open(name) + if err == nil { + filename = name + f.Close() + break + } + } + if filename == "" { + return nil, errors.New(fmt.Sprintf("Could not find partial %q", name)) + } + + partial, err := ParseFile(filename) + + if err != nil { + return nil, err + } + + return partial, nil } func (tmpl *Template) parseSection(section *sectionElement) error { - for { - text, err := tmpl.readString(tmpl.otag) - - 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 = 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 == io.EOF { - //put the remaining text in a block - return parseError{tmpl.curline, "unmatched open tag"} - } - - //trim the close tag off the text - tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) - - if len(tag) == 0 { - return parseError{tmpl.curline, "empty tag"} - } - switch tag[0] { - case '!': - //ignore comment - break - case '#', '^': - name := strings.TrimSpace(tag[1:]) - - //ignore the newline when a section starts - if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { - tmpl.p += 1 - } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { - tmpl.p += 2 - } - - se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} - err := tmpl.parseSection(&se) - if err != nil { - return err - } - section.elems = append(section.elems, &se) - case '/': - name := strings.TrimSpace(tag[1:]) - if name != section.name { - return parseError{tmpl.curline, "interleaved closing tag: " + name} - } else { - return nil - } - case '>': - name := strings.TrimSpace(tag[1:]) - partial, err := tmpl.parsePartial(name) - if err != nil { - return err - } - section.elems = append(section.elems, partial) - case '=': - if tag[len(tag)-1] != '=' { - return parseError{tmpl.curline, "Invalid meta tag"} - } - tag = strings.TrimSpace(tag[1 : len(tag)-1]) - newtags := strings.SplitN(tag, " ", 2) - if len(newtags) == 2 { - tmpl.otag = newtags[0] - tmpl.ctag = newtags[1] - } - case '{': - if tag[len(tag)-1] == '}' { - //use a raw tag - section.elems = append(section.elems, &varElement{tag[1 : len(tag)-1], true}) - } - default: - section.elems = append(section.elems, &varElement{tag, false}) - } - } - - return nil + for { + text, err := tmpl.readString(tmpl.otag) + + 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 = 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 == io.EOF { + //put the remaining text in a block + return parseError{tmpl.curline, "unmatched open tag"} + } + + //trim the close tag off the text + tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) + + if len(tag) == 0 { + return parseError{tmpl.curline, "empty tag"} + } + switch tag[0] { + case '!': + //ignore comment + break + case '#', '^': + name := strings.TrimSpace(tag[1:]) + + //ignore the newline when a section starts + if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { + tmpl.p += 1 + } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { + tmpl.p += 2 + } + + se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} + err := tmpl.parseSection(&se) + if err != nil { + return err + } + section.elems = append(section.elems, &se) + case '/': + name := strings.TrimSpace(tag[1:]) + if name != section.name { + return parseError{tmpl.curline, "interleaved closing tag: " + name} + } else { + return nil + } + case '>': + name := strings.TrimSpace(tag[1:]) + partial, err := tmpl.parsePartial(name) + if err != nil { + return err + } + section.elems = append(section.elems, partial) + case '=': + if tag[len(tag)-1] != '=' { + return parseError{tmpl.curline, "Invalid meta tag"} + } + tag = strings.TrimSpace(tag[1 : len(tag)-1]) + newtags := strings.SplitN(tag, " ", 2) + if len(newtags) == 2 { + tmpl.otag = newtags[0] + tmpl.ctag = newtags[1] + } + case '{': + if tag[len(tag)-1] == '}' { + //use a raw tag + section.elems = append(section.elems, &varElement{tag[1 : len(tag)-1], true}) + } + default: + section.elems = append(section.elems, &varElement{tag, false}) + } + } + + return nil } func (tmpl *Template) parse() error { - for { - text, err := tmpl.readString(tmpl.otag) - if err == io.EOF { - //put the remaining text in a block - 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 = append(tmpl.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 == io.EOF { - //put the remaining text in a block - return parseError{tmpl.curline, "unmatched open tag"} - } - - //trim the close tag off the text - tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) - if len(tag) == 0 { - return parseError{tmpl.curline, "empty tag"} - } - switch tag[0] { - case '!': - //ignore comment - break - case '#', '^': - name := strings.TrimSpace(tag[1:]) - - if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { - tmpl.p += 1 - } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { - tmpl.p += 2 - } - - se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} - err := tmpl.parseSection(&se) - if err != nil { - return err - } - tmpl.elems = append(tmpl.elems, &se) - case '/': - return parseError{tmpl.curline, "unmatched close tag"} - case '>': - name := strings.TrimSpace(tag[1:]) - partial, err := tmpl.parsePartial(name) - if err != nil { - return err - } - tmpl.elems = append(tmpl.elems, partial) - case '=': - if tag[len(tag)-1] != '=' { - return parseError{tmpl.curline, "Invalid meta tag"} - } - tag = strings.TrimSpace(tag[1 : len(tag)-1]) - newtags := strings.SplitN(tag, " ", 2) - if len(newtags) == 2 { - tmpl.otag = newtags[0] - tmpl.ctag = newtags[1] - } - case '{': - //use a raw tag - if tag[len(tag)-1] == '}' { - tmpl.elems = append(tmpl.elems, &varElement{tag[1 : len(tag)-1], true}) - } - default: - tmpl.elems = append(tmpl.elems, &varElement{tag, false}) - } - } - - return nil + for { + text, err := tmpl.readString(tmpl.otag) + if err == io.EOF { + //put the remaining text in a block + 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 = append(tmpl.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 == io.EOF { + //put the remaining text in a block + return parseError{tmpl.curline, "unmatched open tag"} + } + + //trim the close tag off the text + tag := strings.TrimSpace(text[0 : len(text)-len(tmpl.ctag)]) + if len(tag) == 0 { + return parseError{tmpl.curline, "empty tag"} + } + switch tag[0] { + case '!': + //ignore comment + break + case '#', '^': + name := strings.TrimSpace(tag[1:]) + + if len(tmpl.data) > tmpl.p && tmpl.data[tmpl.p] == '\n' { + tmpl.p += 1 + } else if len(tmpl.data) > tmpl.p+1 && tmpl.data[tmpl.p] == '\r' && tmpl.data[tmpl.p+1] == '\n' { + tmpl.p += 2 + } + + se := sectionElement{name, tag[0] == '^', tmpl.curline, []interface{}{}} + err := tmpl.parseSection(&se) + if err != nil { + return err + } + tmpl.elems = append(tmpl.elems, &se) + case '/': + return parseError{tmpl.curline, "unmatched close tag"} + case '>': + name := strings.TrimSpace(tag[1:]) + partial, err := tmpl.parsePartial(name) + if err != nil { + return err + } + tmpl.elems = append(tmpl.elems, partial) + case '=': + if tag[len(tag)-1] != '=' { + return parseError{tmpl.curline, "Invalid meta tag"} + } + tag = strings.TrimSpace(tag[1 : len(tag)-1]) + newtags := strings.SplitN(tag, " ", 2) + if len(newtags) == 2 { + tmpl.otag = newtags[0] + tmpl.ctag = newtags[1] + } + case '{': + //use a raw tag + if tag[len(tag)-1] == '}' { + tmpl.elems = append(tmpl.elems, &varElement{tag[1 : len(tag)-1], true}) + } + default: + tmpl.elems = append(tmpl.elems, &varElement{tag, false}) + } + } + + return nil } // See if name is a method of the value at some level of indirection. @@ -333,331 +333,331 @@ func (tmpl *Template) parse() error { // there's trouble) and whether a method of the right name exists with // any signature. func callMethod(data reflect.Value, name string) (result reflect.Value, found bool) { - found = false - // Method set depends on pointerness, and the value may be arbitrarily - // indirect. Simplest approach is to walk down the pointer chain and - // see if we can find the method at each step. - // Most steps will see NumMethod() == 0. - for { - typ := data.Type() - if nMethod := data.Type().NumMethod(); nMethod > 0 { - for i := 0; i < nMethod; i++ { - method := typ.Method(i) - if method.Name == name { - - found = true // we found the name regardless - // does receiver type match? (pointerness might be off) - if typ == method.Type.In(0) { - return call(data, method), found - } - } - } - } - if nd := data; nd.Kind() == reflect.Ptr { - data = nd.Elem() - } else { - break - } - } - return + found = false + // Method set depends on pointerness, and the value may be arbitrarily + // indirect. Simplest approach is to walk down the pointer chain and + // see if we can find the method at each step. + // Most steps will see NumMethod() == 0. + for { + typ := data.Type() + if nMethod := data.Type().NumMethod(); nMethod > 0 { + for i := 0; i < nMethod; i++ { + method := typ.Method(i) + if method.Name == name { + + found = true // we found the name regardless + // does receiver type match? (pointerness might be off) + if typ == method.Type.In(0) { + return call(data, method), found + } + } + } + } + if nd := data; nd.Kind() == reflect.Ptr { + data = nd.Elem() + } else { + break + } + } + return } // Invoke the method. If its signature is wrong, return nil. func call(v reflect.Value, method reflect.Method) reflect.Value { - funcType := method.Type - // Method must take no arguments, meaning as a func it has one argument (the receiver) - if funcType.NumIn() != 1 { - return reflect.Value{} - } - // Method must return a single value. - if funcType.NumOut() == 0 { - return reflect.Value{} - } - // Result will be the zeroth element of the returned slice. - return method.Func.Call([]reflect.Value{v})[0] + funcType := method.Type + // Method must take no arguments, meaning as a func it has one argument (the receiver) + if funcType.NumIn() != 1 { + return reflect.Value{} + } + // Method must return a single value. + if funcType.NumOut() == 0 { + return reflect.Value{} + } + // Result will be the zeroth element of the returned slice. + return method.Func.Call([]reflect.Value{v})[0] } // 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 []interface{}, name string) reflect.Value { - defer func() { - if r := recover(); r != nil { - fmt.Printf("Panic while looking up %q: %s\n", name, r) - } - }() + defer func() { + if r := recover(); r != nil { + fmt.Printf("Panic while looking up %q: %s\n", name, r) + } + }() Outer: - 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 { - for i := 0; i < n; i++ { - m := typ.Method(i) - mtyp := m.Type - if m.Name == name && mtyp.NumIn() == 1 { - return v.Method(i).Call(nil)[0] - } - } - } - switch av := v; av.Kind() { - case reflect.Ptr: - v = av.Elem() - case reflect.Interface: - v = av.Elem() - case reflect.Struct: - ret := av.FieldByName(name) - if ret.IsValid() { - return ret - } else { - continue Outer - } - case reflect.Map: - ret := av.MapIndex(reflect.ValueOf(name)) - if ret.IsValid() { - return ret - } else { - continue Outer - } - default: - continue Outer - } - } - } - return 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 { + for i := 0; i < n; i++ { + m := typ.Method(i) + mtyp := m.Type + if m.Name == name && mtyp.NumIn() == 1 { + return v.Method(i).Call(nil)[0] + } + } + } + switch av := v; av.Kind() { + case reflect.Ptr: + v = av.Elem() + case reflect.Interface: + v = av.Elem() + case reflect.Struct: + ret := av.FieldByName(name) + if ret.IsValid() { + return ret + } else { + continue Outer + } + case reflect.Map: + ret := av.MapIndex(reflect.ValueOf(name)) + if ret.IsValid() { + return ret + } else { + continue Outer + } + default: + continue Outer + } + } + } + return reflect.Value{} } func isNil(v reflect.Value) bool { - if !v.IsValid() || v.Interface() == nil { - return true - } - - valueInd := indirect(v) - if !valueInd.IsValid() { - return true - } - switch val := valueInd; val.Kind() { - case reflect.Bool: - return !val.Bool() - } - - return false + if !v.IsValid() || v.Interface() == nil { + return true + } + + valueInd := indirect(v) + if !valueInd.IsValid() { + return true + } + switch val := valueInd; val.Kind() { + case reflect.Bool: + return !val.Bool() + } + + return false } func indirect(v reflect.Value) reflect.Value { loop: - for v.IsValid() { - switch av := v; av.Kind() { - case reflect.Ptr: - v = av.Elem() - case reflect.Interface: - v = av.Elem() - default: - break loop - } - } - return v + for v.IsValid() { + switch av := v; av.Kind() { + case reflect.Ptr: + v = av.Elem() + case reflect.Interface: + v = av.Elem() + default: + break loop + } + } + return v } func renderSection(section *sectionElement, contextChain []interface{}, buf io.Writer) (err error) { - value := lookup(contextChain, section.name) - 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 { - return - } else { - valueInd := indirect(value) - switch val := valueInd; val.Kind() { - case reflect.Slice: - for i := 0; i < val.Len(); i++ { - contexts = append(contexts, val.Index(i)) - } - case reflect.Array: - for i := 0; i < val.Len(); i++ { - contexts = append(contexts, val.Index(i)) - } - case reflect.Map, reflect.Struct: - contexts = append(contexts, value) - default: - contexts = append(contexts, context) - } - } - - chain2 := make([]interface{}, len(contextChain)+1) - copy(chain2[1:], contextChain) - //by default we execute the section - for _, ctx := range contexts { - chain2[0] = ctx - for _, elem := range section.elems { - err = renderElement(elem, chain2, buf) - if err != nil { - return - } - } - } - - return nil + value := lookup(contextChain, section.name) + 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 { + return + } else { + valueInd := indirect(value) + switch val := valueInd; val.Kind() { + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + contexts = append(contexts, val.Index(i)) + } + case reflect.Array: + for i := 0; i < val.Len(); i++ { + contexts = append(contexts, val.Index(i)) + } + case reflect.Map, reflect.Struct: + contexts = append(contexts, value) + default: + contexts = append(contexts, context) + } + } + + chain2 := make([]interface{}, len(contextChain)+1) + copy(chain2[1:], contextChain) + //by default we execute the section + for _, ctx := range contexts { + chain2[0] = ctx + for _, elem := range section.elems { + err = renderElement(elem, chain2, buf) + if err != nil { + return + } + } + } + + return nil } func renderElement(element interface{}, contextChain []interface{}, buf io.Writer) (err error) { - switch elem := element.(type) { - case *textElement: - _, err = buf.Write(elem.text) - if err != nil { - return - } - case *varElement: - defer func() { - if r := recover(); r != nil { - fmt.Printf("Panic while looking up %q: %s\n", elem.name, r) - } - }() - val := lookup(contextChain, elem.name) - - if val.IsValid() { - if elem.raw { - _, err = fmt.Fprint(buf, val.Interface()) - if err != nil { - return - } - } else { - s := fmt.Sprint(val.Interface()) - err = htmlEscape(buf, []byte(s)) - if err != nil { - return - } - } - } - case *sectionElement: - err = renderSection(elem, contextChain, buf) - if err != nil { - return - } - case *Template: - err = elem.renderTemplate(contextChain, buf) - if err != nil { - return - } - } - - return nil + switch elem := element.(type) { + case *textElement: + _, err = buf.Write(elem.text) + if err != nil { + return + } + case *varElement: + defer func() { + if r := recover(); r != nil { + fmt.Printf("Panic while looking up %q: %s\n", elem.name, r) + } + }() + val := lookup(contextChain, elem.name) + + if val.IsValid() { + if elem.raw { + _, err = fmt.Fprint(buf, val.Interface()) + if err != nil { + return + } + } else { + s := fmt.Sprint(val.Interface()) + err = htmlEscape(buf, []byte(s)) + if err != nil { + return + } + } + } + case *sectionElement: + err = renderSection(elem, contextChain, buf) + if err != nil { + return + } + case *Template: + err = elem.renderTemplate(contextChain, buf) + if err != nil { + return + } + } + + return nil } func (tmpl *Template) renderTemplate(contextChain []interface{}, buf io.Writer) (err error) { - for _, elem := range tmpl.elems { - err = renderElement(elem, contextChain, buf) - if err != nil { - return - } - } - - return nil + for _, elem := range tmpl.elems { + err = renderElement(elem, contextChain, buf) + if err != nil { + return + } + } + + return nil } func (tmpl *Template) Render(context ...interface{}) string { - var buf bytes.Buffer + var buf bytes.Buffer - tmpl.RenderTo(&buf, context...) + tmpl.RenderTo(&buf, context...) - return buf.String() + return buf.String() } func (tmpl *Template) RenderTo(w io.Writer, context ...interface{}) error { - var contextChain []interface{} - for _, c := range context { - val := reflect.ValueOf(c) - contextChain = append(contextChain, val) - } + var contextChain []interface{} + for _, c := range context { + val := reflect.ValueOf(c) + contextChain = append(contextChain, val) + } - return tmpl.renderTemplate(contextChain, w) + return 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...) + 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, []interface{}{}} - err := tmpl.parse() + cwd := os.Getenv("CWD") + tmpl := Template{data, "{{", "}}", 0, 1, cwd, []interface{}{}} + err := tmpl.parse() - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return &tmpl, err + return &tmpl, err } func ParseFile(filename string) (*Template, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } - dirname, _ := path.Split(filename) + dirname, _ := path.Split(filename) - tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, []interface{}{}} - err = tmpl.parse() + tmpl := Template{string(data), "{{", "}}", 0, 1, dirname, []interface{}{}} + err = tmpl.parse() - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return &tmpl, nil + return &tmpl, nil } func Render(data string, context ...interface{}) string { - tmpl, err := ParseString(data) - if err != nil { - return err.Error() - } - return tmpl.Render(context...) + 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 - } + tmpl, err := ParseString(data) + if err != nil { + return err + } - return tmpl.RenderTo(w, context...) + return tmpl.RenderTo(w, context...) } 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...) + 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...) + 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.Error() - } - - tmpl, err := ParseFile(filename) - if err != nil { - return err.Error() - } - return tmpl.RenderInLayout(layoutTmpl, context...) + layoutTmpl, err := ParseFile(layoutFile) + if err != nil { + return err.Error() + } + + 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 0b1eaf7..8a32162 100644 --- a/mustache_test.go +++ b/mustache_test.go @@ -1,188 +1,188 @@ package mustache import ( - "os" - "path" - "strings" - "testing" + "os" + "path" + "strings" + "testing" ) type Test struct { - tmpl string - context interface{} - expected string + tmpl string + context interface{} + expected string } type Data struct { - A bool - B string + A bool + B string } type User struct { - Name string - Id int64 + Name string + Id int64 } type settings struct { - Allow bool + Allow bool } func (u User) Func1() string { - return u.Name + return u.Name } func (u *User) Func2() string { - return u.Name + return u.Name } func (u *User) Func3() (map[string]string, error) { - return map[string]string{"name": u.Name}, nil + return map[string]string{"name": u.Name}, nil } func (u *User) Func4() (map[string]string, error) { - return nil, nil + return nil, nil } func (u *User) Func5() (*settings, error) { - return &settings{true}, nil + return &settings{true}, nil } func (u *User) Func6() ([]interface{}, error) { - var v []interface{} - v = append(v, &settings{true}) - return v, nil + var v []interface{} + v = append(v, &settings{true}) + return v, nil } func (u User) Truefunc1() bool { - return true + return true } func (u *User) Truefunc2() bool { - return true + return true } func makeVector(n int) []interface{} { - var v []interface{} - for i := 0; i < n; i++ { - v = append(v, &User{"Mike", 1}) - } - return v + var v []interface{} + for i := 0; i < n; i++ { + v = append(v, &User{"Mike", 1}) + } + return v } type Category struct { - Tag string - Description string + Tag string + Description string } func (c Category) DisplayName() string { - return c.Tag + " - " + c.Description + 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"}, - {`{{{var}}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, - {`{{a}}{{b}}{{c}}{{d}}`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "abcd"}, - {`0{{a}}1{{b}}23{{c}}456{{d}}89`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "0a1b23c456d89"}, - {`hello {{! comment }}world`, map[string]string{}, "hello world"}, - {`{{ a }}{{=<% %>=}}<%b %><%={{ }}=%>{{ c }}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - {`{{ a }}{{= <% %> =}}<%b %><%= {{ }}=%>{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - - //does not exist - {`{{dne}}`, map[string]string{"name": "world"}, ""}, - {`{{dne}}`, User{"Mike", 1}, ""}, - {`{{dne}}`, &User{"Mike", 1}, ""}, - {`{{#has}}{{/has}}`, &User{"Mike", 1}, ""}, - - //section tests - {`{{#A}}{{B}}{{/A}}`, Data{true, "hello"}, "hello"}, - {`{{#A}}{{{B}}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, - {`{{#A}}{{B}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, - {`{{#A}}{{B}}{{/A}}`, Data{false, "hello"}, ""}, - {`{{a}}{{#b}}{{b}}{{/b}}{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, - {`{{#A}}{{B}}{{/A}}`, struct { - A []struct { - B string - } - }{[]struct { - B string - }{{"a"}, {"b"}, {"c"}}}, - "abc", - }, - {`{{#A}}{{b}}{{/A}}`, struct{ A []map[string]string }{[]map[string]string{{"b": "a"}, {"b": "b"}, {"b": "c"}}}, "abc"}, - - {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []User{{"Mike", 1}}}, "Mike"}, - - {`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": nil}, ""}, - {`{{#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{{"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"}, - {"{{#users}}\n{{Name}}\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\nMike\n"}, - {"{{#users}}\r\n{{Name}}\r\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\r\nMike\r\n"}, - - //inverted section tests - {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]string{"a": "a", "c": "c"}, "abc"}, - {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]interface{}{"a": "a", "b": false, "c": "c"}, "abc"}, - {`{{^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"}, ""}, - - //function tests - {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []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{{"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"}, - {`{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}`, &User{"Mike", 1}, "abcd"}, - {`{{#user}}{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, - {`{{#user}}{{#Func6}}{{#Allow}}abcd{{/Allow}}{{/Func6}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, - - //context chaining - {`hello {{#section}}{{name}}{{/section}}`, map[string]interface{}{"section": map[string]string{"name": "world"}}, "hello world"}, - {`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"}, + {`hello world`, nil, "hello world"}, + {`hello {{name}}`, map[string]string{"name": "world"}, "hello world"}, + {`{{var}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, + {`{{{var}}}`, map[string]string{"var": "5 > 2"}, "5 > 2"}, + {`{{a}}{{b}}{{c}}{{d}}`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "abcd"}, + {`0{{a}}1{{b}}23{{c}}456{{d}}89`, map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}, "0a1b23c456d89"}, + {`hello {{! comment }}world`, map[string]string{}, "hello world"}, + {`{{ a }}{{=<% %>=}}<%b %><%={{ }}=%>{{ c }}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + {`{{ a }}{{= <% %> =}}<%b %><%= {{ }}=%>{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + + //does not exist + {`{{dne}}`, map[string]string{"name": "world"}, ""}, + {`{{dne}}`, User{"Mike", 1}, ""}, + {`{{dne}}`, &User{"Mike", 1}, ""}, + {`{{#has}}{{/has}}`, &User{"Mike", 1}, ""}, + + //section tests + {`{{#A}}{{B}}{{/A}}`, Data{true, "hello"}, "hello"}, + {`{{#A}}{{{B}}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, + {`{{#A}}{{B}}{{/A}}`, Data{true, "5 > 2"}, "5 > 2"}, + {`{{#A}}{{B}}{{/A}}`, Data{false, "hello"}, ""}, + {`{{a}}{{#b}}{{b}}{{/b}}{{c}}`, map[string]string{"a": "a", "b": "b", "c": "c"}, "abc"}, + {`{{#A}}{{B}}{{/A}}`, struct { + A []struct { + B string + } + }{[]struct { + B string + }{{"a"}, {"b"}, {"c"}}}, + "abc", + }, + {`{{#A}}{{b}}{{/A}}`, struct{ A []map[string]string }{[]map[string]string{{"b": "a"}, {"b": "b"}, {"b": "c"}}}, "abc"}, + + {`{{#users}}{{Name}}{{/users}}`, map[string]interface{}{"users": []User{{"Mike", 1}}}, "Mike"}, + + {`{{#users}}gone{{Name}}{{/users}}`, map[string]interface{}{"users": nil}, ""}, + {`{{#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{{"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"}, + {"{{#users}}\n{{Name}}\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\nMike\n"}, + {"{{#users}}\r\n{{Name}}\r\n{{/users}}", map[string]interface{}{"users": makeVector(2)}, "Mike\r\nMike\r\n"}, + + //inverted section tests + {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]string{"a": "a", "c": "c"}, "abc"}, + {`{{a}}{{^b}}b{{/b}}{{c}}`, map[string]interface{}{"a": "a", "b": false, "c": "c"}, "abc"}, + {`{{^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"}, ""}, + + //function tests + {`{{#users}}{{Func1}}{{/users}}`, map[string]interface{}{"users": []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{{"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"}, + {`{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}`, &User{"Mike", 1}, "abcd"}, + {`{{#user}}{{#Func5}}{{#Allow}}abcd{{/Allow}}{{/Func5}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, + {`{{#user}}{{#Func6}}{{#Allow}}abcd{{/Allow}}{{/Func6}}{{/user}}`, map[string]interface{}{"user": &User{"Mike", 1}}, "abcd"}, + + //context chaining + {`hello {{#section}}{{name}}{{/section}}`, map[string]interface{}{"section": map[string]string{"name": "world"}}, "hello world"}, + {`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) { - for _, test := range tests { - output := Render(test.tmpl, test.context) - if output != test.expected { - t.Fatalf("%q expected %q got %q", test.tmpl, test.expected, output) - } - } + for _, test := range tests { + output := Render(test.tmpl, test.context) + 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" - output := RenderFile(filename, map[string]string{"name": "world"}) - if output != expected { - t.Fatalf("testfile expected %q got %q", expected, output) - } + filename := path.Join(path.Join(os.Getenv("PWD"), "tests"), "test1.mustache") + expected := "hello world" + output := RenderFile(filename, map[string]string{"name": "world"}) + if output != expected { + t.Fatalf("testfile expected %q got %q", expected, output) + } } 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) - } + 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) + } } /* @@ -197,49 +197,49 @@ func TestSectionPartial(t *testing.T) { } */ 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"}) - if output != "hello world" || output2 != "hello world" { - t.Fatalf("TestMultiContext expected %q got %q", "hello world", output) - } + 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"}) + if output != "hello world" || output2 != "hello world" { + t.Fatalf("TestMultiContext expected %q got %q", "hello world", output) + } } var malformed = []Test{ - {`{{#a}}{{}}{{/a}}`, Data{true, "hello"}, "empty tag"}, - {`{{}}`, nil, "empty tag"}, - {`{{}`, nil, "unmatched open tag"}, - {`{{`, nil, "unmatched open tag"}, + {`{{#a}}{{}}{{/a}}`, Data{true, "hello"}, "empty tag"}, + {`{{}}`, nil, "empty tag"}, + {`{{}`, nil, "unmatched open tag"}, + {`{{`, nil, "unmatched open tag"}, } func TestMalformed(t *testing.T) { - for _, test := range malformed { - output := Render(test.tmpl, test.context) - if strings.Index(output, test.expected) == -1 { - t.Fatalf("%q expected %q in error %q", test.tmpl, test.expected, output) - } - } + for _, test := range malformed { + output := Render(test.tmpl, test.context) + if strings.Index(output, test.expected) == -1 { + t.Fatalf("%q expected %q in error %q", test.tmpl, test.expected, output) + } + } } type LayoutTest struct { - layout string - tmpl string - context interface{} - expected string + 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`}, + {`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) - } - } + 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) + } + } }