| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| /* |
| Data-driven templates for generating textual output such as |
| HTML. See |
| http://code.google.com/p/json-template/wiki/Reference |
| for full documentation of the template language. A summary: |
| |
| Templates are executed by applying them to a data structure. |
| Annotations in the template refer to elements of the data |
| structure (typically a field of a struct or a key in a map) |
| to control execution and derive values to be displayed. |
| The template walks the structure as it executes and the |
| "cursor" @ represents the value at the current location |
| in the structure. |
| |
| Data items may be values or pointers; the interface hides the |
| indirection. |
| |
| Major constructs ({} are metacharacters; [] marks optional elements): |
| |
| {# comment } |
| |
| A one-line comment. |
| |
| {.section field} XXX [ {.or} YYY ] {.end} |
| |
| Set @ to the value of the field. It may be an explicit @ |
| to stay at the same point in the data. If the field is nil |
| or empty, execute YYY; otherwise execute XXX. |
| |
| {.repeated section field} XXX [ {.alternates with} ZZZ ] [ {.or} YYY ] {.end} |
| |
| Like .section, but field must be an array or slice. XXX |
| is executed for each element. If the array is nil or empty, |
| YYY is executed instead. If the {.alternates with} marker |
| is present, ZZZ is executed between iterations of XXX. |
| |
| {field} |
| {field|formatter} |
| |
| Insert the value of the field into the output. Field is |
| first looked for in the cursor, as in .section and .repeated. |
| If it is not found, the search continues in outer sections |
| until the top level is reached. |
| |
| If a formatter is specified, it must be named in the formatter |
| map passed to the template set up routines or in the default |
| set ("html","str","") and is used to process the data for |
| output. The formatter function has signature |
| func(wr io.Write, data interface{}, formatter string) |
| where wr is the destination for output, data is the field |
| value, and formatter is its name at the invocation site. |
| */ |
| package template |
| |
| import ( |
| "container/vector"; |
| "fmt"; |
| "io"; |
| "os"; |
| "reflect"; |
| "runtime"; |
| "strings"; |
| ) |
| |
| // Errors returned during parsing and execution. Users may extract the information and reformat |
| // if they desire. |
| type Error struct { |
| Line int; |
| Msg string; |
| } |
| |
| func (e *Error) String() string { return fmt.Sprintf("line %d: %s", e.Line, e.Msg) } |
| |
| // Most of the literals are aces. |
| var lbrace = []byte{'{'} |
| var rbrace = []byte{'}'} |
| var space = []byte{' '} |
| var tab = []byte{'\t'} |
| |
| // The various types of "tokens", which are plain text or (usually) brace-delimited descriptors |
| const ( |
| tokAlternates = iota; |
| tokComment; |
| tokEnd; |
| tokLiteral; |
| tokOr; |
| tokRepeated; |
| tokSection; |
| tokText; |
| tokVariable; |
| ) |
| |
| // FormatterMap is the type describing the mapping from formatter |
| // names to the functions that implement them. |
| type FormatterMap map[string]func(io.Writer, interface{}, string) |
| |
| // Built-in formatters. |
| var builtins = FormatterMap{ |
| "html": HTMLFormatter, |
| "str": StringFormatter, |
| "": StringFormatter, |
| } |
| |
| // The parsed state of a template is a vector of xxxElement structs. |
| // Sections have line numbers so errors can be reported better during execution. |
| |
| // Plain text. |
| type textElement struct { |
| text []byte; |
| } |
| |
| // A literal such as .meta-left or .meta-right |
| type literalElement struct { |
| text []byte; |
| } |
| |
| // A variable to be evaluated |
| type variableElement struct { |
| linenum int; |
| name string; |
| formatter string; // TODO(r): implement pipelines |
| } |
| |
| // A .section block, possibly with a .or |
| type sectionElement struct { |
| linenum int; // of .section itself |
| field string; // cursor field for this block |
| start int; // first element |
| or int; // first element of .or block |
| end int; // one beyond last element |
| } |
| |
| // A .repeated block, possibly with a .or and a .alternates |
| type repeatedElement struct { |
| sectionElement; // It has the same structure... |
| altstart int; // ... except for alternates |
| altend int; |
| } |
| |
| // Template is the type that represents a template definition. |
| // It is unchanged after parsing. |
| type Template struct { |
| fmap FormatterMap; // formatters for variables |
| // Used during parsing: |
| ldelim, rdelim []byte; // delimiters; default {} |
| buf []byte; // input text to process |
| p int; // position in buf |
| linenum int; // position in input |
| error os.Error; // error during parsing (only) |
| // Parsed results: |
| elems *vector.Vector; |
| } |
| |
| // Internal state for executing a Template. As we evaluate the struct, |
| // the data item descends into the fields associated with sections, etc. |
| // Parent is used to walk upwards to find variables higher in the tree. |
| type state struct { |
| parent *state; // parent in hierarchy |
| data reflect.Value; // the driver data for this section etc. |
| wr io.Writer; // where to send output |
| errors chan os.Error; // for reporting errors during execute |
| } |
| |
| func (parent *state) clone(data reflect.Value) *state { |
| return &state{parent, data, parent.wr, parent.errors} |
| } |
| |
| // New creates a new template with the specified formatter map (which |
| // may be nil) to define auxiliary functions for formatting variables. |
| func New(fmap FormatterMap) *Template { |
| t := new(Template); |
| t.fmap = fmap; |
| t.ldelim = lbrace; |
| t.rdelim = rbrace; |
| t.elems = vector.New(0); |
| return t; |
| } |
| |
| // Report error and stop executing. The line number must be provided explicitly. |
| func (t *Template) execError(st *state, line int, err string, args ...) { |
| st.errors <- &Error{line, fmt.Sprintf(err, args)}; |
| runtime.Goexit(); |
| } |
| |
| // Report error, save in Template to terminate parsing. |
| // The line number comes from the template state. |
| func (t *Template) parseError(err string, args ...) { |
| t.error = &Error{t.linenum, fmt.Sprintf(err, args)} |
| } |
| |
| // -- Lexical analysis |
| |
| // Is c a white space character? |
| func white(c uint8) bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n' } |
| |
| // Safely, does s[n:n+len(t)] == t? |
| func equal(s []byte, n int, t []byte) bool { |
| b := s[n:]; |
| if len(t) > len(b) { // not enough space left for a match. |
| return false |
| } |
| for i, c := range t { |
| if c != b[i] { |
| return false |
| } |
| } |
| return true; |
| } |
| |
| // nextItem returns the next item from the input buffer. If the returned |
| // item is empty, we are at EOF. The item will be either a |
| // delimited string or a non-empty string between delimited |
| // strings. Tokens stop at (but include, if plain text) a newline. |
| // Action tokens on a line by themselves drop the white space on |
| // either side, up to and including the newline. |
| func (t *Template) nextItem() []byte { |
| sawLeft := false; // are we waiting for an opening delimiter? |
| special := false; // is this a {.foo} directive, which means trim white space? |
| // Delete surrounding white space if this {.foo} is the only thing on the line. |
| trim_white := t.p == 0 || t.buf[t.p-1] == '\n'; |
| only_white := true; // we have seen only white space so far |
| var i int; |
| start := t.p; |
| Loop: |
| for i = t.p; i < len(t.buf); i++ { |
| switch { |
| case t.buf[i] == '\n': |
| t.linenum++; |
| i++; |
| break Loop; |
| case white(t.buf[i]): |
| // white space, do nothing |
| case !sawLeft && equal(t.buf, i, t.ldelim): // sawLeft checked because delims may be equal |
| // anything interesting already on the line? |
| if !only_white { |
| break Loop |
| } |
| // is it a directive or comment? |
| j := i + len(t.ldelim); // position after delimiter |
| if j+1 < len(t.buf) && (t.buf[j] == '.' || t.buf[j] == '#') { |
| special = true; |
| if trim_white && only_white { |
| start = i |
| } |
| } else if i > t.p { // have some text accumulated so stop before delimiter |
| break Loop |
| } |
| sawLeft = true; |
| i = j - 1; |
| case equal(t.buf, i, t.rdelim): |
| if !sawLeft { |
| t.parseError("unmatched closing delimiter"); |
| return nil; |
| } |
| sawLeft = false; |
| i += len(t.rdelim); |
| break Loop; |
| default: |
| only_white = false |
| } |
| } |
| if sawLeft { |
| t.parseError("unmatched opening delimiter"); |
| return nil; |
| } |
| item := t.buf[start:i]; |
| if special && trim_white { |
| // consume trailing white space |
| for ; i < len(t.buf) && white(t.buf[i]); i++ { |
| if t.buf[i] == '\n' { |
| t.linenum++; |
| i++; |
| break; // stop after newline |
| } |
| } |
| } |
| t.p = i; |
| return item; |
| } |
| |
| // Turn a byte array into a white-space-split array of strings. |
| func words(buf []byte) []string { |
| s := make([]string, 0, 5); |
| p := 0; // position in buf |
| // one word per loop |
| for i := 0; ; i++ { |
| // skip white space |
| for ; p < len(buf) && white(buf[p]); p++ { |
| } |
| // grab word |
| start := p; |
| for ; p < len(buf) && !white(buf[p]); p++ { |
| } |
| if start == p { // no text left |
| break |
| } |
| if i == cap(s) { |
| ns := make([]string, 2*cap(s)); |
| for j := range s { |
| ns[j] = s[j] |
| } |
| s = ns; |
| } |
| s = s[0 : i+1]; |
| s[i] = string(buf[start:p]); |
| } |
| return s; |
| } |
| |
| // Analyze an item and return its token type and, if it's an action item, an array of |
| // its constituent words. |
| func (t *Template) analyze(item []byte) (tok int, w []string) { |
| // item is known to be non-empty |
| if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter |
| tok = tokText; |
| return; |
| } |
| if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter |
| t.parseError("internal error: unmatched opening delimiter"); // lexing should prevent this |
| return; |
| } |
| if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents |
| t.parseError("empty directive"); |
| return; |
| } |
| // Comment |
| if item[len(t.ldelim)] == '#' { |
| tok = tokComment; |
| return; |
| } |
| // Split into words |
| w = words(item[len(t.ldelim) : len(item)-len(t.rdelim)]); // drop final delimiter |
| if len(w) == 0 { |
| t.parseError("empty directive"); |
| return; |
| } |
| if len(w) == 1 && w[0][0] != '.' { |
| tok = tokVariable; |
| return; |
| } |
| switch w[0] { |
| case ".meta-left", ".meta-right", ".space", ".tab": |
| tok = tokLiteral; |
| return; |
| case ".or": |
| tok = tokOr; |
| return; |
| case ".end": |
| tok = tokEnd; |
| return; |
| case ".section": |
| if len(w) != 2 { |
| t.parseError("incorrect fields for .section: %s", item); |
| return; |
| } |
| tok = tokSection; |
| return; |
| case ".repeated": |
| if len(w) != 3 || w[1] != "section" { |
| t.parseError("incorrect fields for .repeated: %s", item); |
| return; |
| } |
| tok = tokRepeated; |
| return; |
| case ".alternates": |
| if len(w) != 2 || w[1] != "with" { |
| t.parseError("incorrect fields for .alternates: %s", item); |
| return; |
| } |
| tok = tokAlternates; |
| return; |
| } |
| t.parseError("bad directive: %s", item); |
| return; |
| } |
| |
| // -- Parsing |
| |
| // Allocate a new variable-evaluation element. |
| func (t *Template) newVariable(name_formatter string) (v *variableElement) { |
| name := name_formatter; |
| formatter := ""; |
| bar := strings.Index(name_formatter, "|"); |
| if bar >= 0 { |
| name = name_formatter[0:bar]; |
| formatter = name_formatter[bar+1:]; |
| } |
| // Probably ok, so let's build it. |
| v = &variableElement{t.linenum, name, formatter}; |
| |
| // We could remember the function address here and avoid the lookup later, |
| // but it's more dynamic to let the user change the map contents underfoot. |
| // We do require the name to be present, though. |
| |
| // Is it in user-supplied map? |
| if t.fmap != nil { |
| if _, ok := t.fmap[formatter]; ok { |
| return |
| } |
| } |
| // Is it in builtin map? |
| if _, ok := builtins[formatter]; ok { |
| return |
| } |
| t.parseError("unknown formatter: %s", formatter); |
| return; |
| } |
| |
| // Grab the next item. If it's simple, just append it to the template. |
| // Otherwise return its details. |
| func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) { |
| tok, w = t.analyze(item); |
| if t.error != nil { |
| return |
| } |
| done = true; // assume for simplicity |
| switch tok { |
| case tokComment: |
| return |
| case tokText: |
| t.elems.Push(&textElement{item}); |
| return; |
| case tokLiteral: |
| switch w[0] { |
| case ".meta-left": |
| t.elems.Push(&literalElement{t.ldelim}) |
| case ".meta-right": |
| t.elems.Push(&literalElement{t.rdelim}) |
| case ".space": |
| t.elems.Push(&literalElement{space}) |
| case ".tab": |
| t.elems.Push(&literalElement{tab}) |
| default: |
| t.parseError("internal error: unknown literal: %s", w[0]); |
| return; |
| } |
| return; |
| case tokVariable: |
| t.elems.Push(t.newVariable(w[0])); |
| return; |
| } |
| return false, tok, w; |
| } |
| |
| // parseRepeated and parseSection are mutually recursive |
| |
| func (t *Template) parseRepeated(words []string) *repeatedElement { |
| r := new(repeatedElement); |
| t.elems.Push(r); |
| r.linenum = t.linenum; |
| r.field = words[2]; |
| // Scan section, collecting true and false (.or) blocks. |
| r.start = t.elems.Len(); |
| r.or = -1; |
| r.altstart = -1; |
| r.altend = -1; |
| Loop: |
| for t.error == nil { |
| item := t.nextItem(); |
| if t.error != nil { |
| break |
| } |
| if len(item) == 0 { |
| t.parseError("missing .end for .repeated section"); |
| break; |
| } |
| done, tok, w := t.parseSimple(item); |
| if t.error != nil { |
| break |
| } |
| if done { |
| continue |
| } |
| switch tok { |
| case tokEnd: |
| break Loop |
| case tokOr: |
| if r.or >= 0 { |
| t.parseError("extra .or in .repeated section"); |
| break Loop; |
| } |
| r.altend = t.elems.Len(); |
| r.or = t.elems.Len(); |
| case tokSection: |
| t.parseSection(w) |
| case tokRepeated: |
| t.parseRepeated(w) |
| case tokAlternates: |
| if r.altstart >= 0 { |
| t.parseError("extra .alternates in .repeated section"); |
| break Loop; |
| } |
| if r.or >= 0 { |
| t.parseError(".alternates inside .or block in .repeated section"); |
| break Loop; |
| } |
| r.altstart = t.elems.Len(); |
| default: |
| t.parseError("internal error: unknown repeated section item: %s", item); |
| break Loop; |
| } |
| } |
| if t.error != nil { |
| return nil |
| } |
| if r.altend < 0 { |
| r.altend = t.elems.Len() |
| } |
| r.end = t.elems.Len(); |
| return r; |
| } |
| |
| func (t *Template) parseSection(words []string) *sectionElement { |
| s := new(sectionElement); |
| t.elems.Push(s); |
| s.linenum = t.linenum; |
| s.field = words[1]; |
| // Scan section, collecting true and false (.or) blocks. |
| s.start = t.elems.Len(); |
| s.or = -1; |
| Loop: |
| for t.error == nil { |
| item := t.nextItem(); |
| if t.error != nil { |
| break |
| } |
| if len(item) == 0 { |
| t.parseError("missing .end for .section"); |
| break; |
| } |
| done, tok, w := t.parseSimple(item); |
| if t.error != nil { |
| break |
| } |
| if done { |
| continue |
| } |
| switch tok { |
| case tokEnd: |
| break Loop |
| case tokOr: |
| if s.or >= 0 { |
| t.parseError("extra .or in .section"); |
| break Loop; |
| } |
| s.or = t.elems.Len(); |
| case tokSection: |
| t.parseSection(w) |
| case tokRepeated: |
| t.parseRepeated(w) |
| case tokAlternates: |
| t.parseError(".alternates not in .repeated") |
| default: |
| t.parseError("internal error: unknown section item: %s", item) |
| } |
| } |
| if t.error != nil { |
| return nil |
| } |
| s.end = t.elems.Len(); |
| return s; |
| } |
| |
| func (t *Template) parse() { |
| for t.error == nil { |
| item := t.nextItem(); |
| if t.error != nil { |
| break |
| } |
| if len(item) == 0 { |
| break |
| } |
| done, tok, w := t.parseSimple(item); |
| if done { |
| continue |
| } |
| switch tok { |
| case tokOr, tokEnd, tokAlternates: |
| t.parseError("unexpected %s", w[0]) |
| case tokSection: |
| t.parseSection(w) |
| case tokRepeated: |
| t.parseRepeated(w) |
| default: |
| t.parseError("internal error: bad directive in parse: %s", item) |
| } |
| } |
| } |
| |
| // -- Execution |
| |
| // If the data for this template is a struct, find the named variable. |
| // Names of the form a.b.c are walked down the data tree. |
| // The special name "@" (the "cursor") denotes the current data. |
| // The value coming in (st.data) might need indirecting to reach |
| // a struct while the return value is not indirected - that is, |
| // it represents the actual named field. |
| func (st *state) findVar(s string) reflect.Value { |
| if s == "@" { |
| return st.data |
| } |
| data := st.data; |
| elems := strings.Split(s, ".", 0); |
| for i := 0; i < len(elems); i++ { |
| // Look up field; data must be a struct or map. |
| data = reflect.Indirect(data); |
| if data == nil { |
| return nil |
| } |
| |
| switch typ := data.Type().(type) { |
| case *reflect.StructType: |
| field, ok := typ.FieldByName(elems[i]); |
| if !ok { |
| return nil |
| } |
| data = data.(*reflect.StructValue).FieldByIndex(field.Index); |
| case *reflect.MapType: |
| data = data.(*reflect.MapValue).Elem(reflect.NewValue(elems[i])) |
| default: |
| return nil |
| } |
| } |
| return data; |
| } |
| |
| // Is there no data to look at? |
| func empty(v reflect.Value) bool { |
| v = reflect.Indirect(v); |
| if v == nil { |
| return true |
| } |
| switch v := v.(type) { |
| case *reflect.BoolValue: |
| return v.Get() == false |
| case *reflect.StringValue: |
| return v.Get() == "" |
| case *reflect.StructValue: |
| return false |
| case *reflect.ArrayValue: |
| return v.Len() == 0 |
| case *reflect.SliceValue: |
| return v.Len() == 0 |
| } |
| return true; |
| } |
| |
| // Look up a variable, up through the parent if necessary. |
| func (t *Template) varValue(name string, st *state) reflect.Value { |
| field := st.findVar(name); |
| if field == nil { |
| if st.parent == nil { |
| t.execError(st, t.linenum, "name not found: %s", name) |
| } |
| return t.varValue(name, st.parent); |
| } |
| return field; |
| } |
| |
| // Evaluate a variable, looking up through the parent if necessary. |
| // If it has a formatter attached ({var|formatter}) run that too. |
| func (t *Template) writeVariable(v *variableElement, st *state) { |
| formatter := v.formatter; |
| val := t.varValue(v.name, st).Interface(); |
| // is it in user-supplied map? |
| if t.fmap != nil { |
| if fn, ok := t.fmap[formatter]; ok { |
| fn(st.wr, val, formatter); |
| return; |
| } |
| } |
| // is it in builtin map? |
| if fn, ok := builtins[formatter]; ok { |
| fn(st.wr, val, formatter); |
| return; |
| } |
| t.execError(st, v.linenum, "missing formatter %s for variable %s", formatter, v.name); |
| } |
| |
| // Execute element i. Return next index to execute. |
| func (t *Template) executeElement(i int, st *state) int { |
| switch elem := t.elems.At(i).(type) { |
| case *textElement: |
| st.wr.Write(elem.text); |
| return i + 1; |
| case *literalElement: |
| st.wr.Write(elem.text); |
| return i + 1; |
| case *variableElement: |
| t.writeVariable(elem, st); |
| return i + 1; |
| case *sectionElement: |
| t.executeSection(elem, st); |
| return elem.end; |
| case *repeatedElement: |
| t.executeRepeated(elem, st); |
| return elem.end; |
| } |
| e := t.elems.At(i); |
| t.execError(st, 0, "internal error: bad directive in execute: %v %T\n", reflect.NewValue(e).Interface(), e); |
| return 0; |
| } |
| |
| // Execute the template. |
| func (t *Template) execute(start, end int, st *state) { |
| for i := start; i < end; { |
| i = t.executeElement(i, st) |
| } |
| } |
| |
| // Execute a .section |
| func (t *Template) executeSection(s *sectionElement, st *state) { |
| // Find driver data for this section. It must be in the current struct. |
| field := t.varValue(s.field, st); |
| if field == nil { |
| t.execError(st, s.linenum, ".section: cannot find field %s in %s", s.field, reflect.Indirect(st.data).Type()) |
| } |
| st = st.clone(field); |
| start, end := s.start, s.or; |
| if !empty(field) { |
| // Execute the normal block. |
| if end < 0 { |
| end = s.end |
| } |
| } else { |
| // Execute the .or block. If it's missing, do nothing. |
| start, end = s.or, s.end; |
| if start < 0 { |
| return |
| } |
| } |
| for i := start; i < end; { |
| i = t.executeElement(i, st) |
| } |
| } |
| |
| // Return the result of calling the Iter method on v, or nil. |
| func iter(v reflect.Value) *reflect.ChanValue { |
| for j := 0; j < v.Type().NumMethod(); j++ { |
| mth := v.Type().Method(j); |
| fv := v.Method(j); |
| ft := fv.Type().(*reflect.FuncType); |
| // TODO(rsc): NumIn() should return 0 here, because ft is from a curried FuncValue. |
| if mth.Name != "Iter" || ft.NumIn() != 1 || ft.NumOut() != 1 { |
| continue |
| } |
| ct, ok := ft.Out(0).(*reflect.ChanType); |
| if !ok || ct.Dir()&reflect.RecvDir == 0 { |
| continue |
| } |
| return fv.Call(nil)[0].(*reflect.ChanValue); |
| } |
| return nil; |
| } |
| |
| // Execute a .repeated section |
| func (t *Template) executeRepeated(r *repeatedElement, st *state) { |
| // Find driver data for this section. It must be in the current struct. |
| field := t.varValue(r.field, st); |
| if field == nil { |
| t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, reflect.Indirect(st.data).Type()) |
| } |
| |
| start, end := r.start, r.or; |
| if end < 0 { |
| end = r.end |
| } |
| if r.altstart >= 0 { |
| end = r.altstart |
| } |
| first := true; |
| |
| if array, ok := field.(reflect.ArrayOrSliceValue); ok { |
| for j := 0; j < array.Len(); j++ { |
| newst := st.clone(array.Elem(j)); |
| |
| // .alternates between elements |
| if !first && r.altstart >= 0 { |
| for i := r.altstart; i < r.altend; { |
| i = t.executeElement(i, newst) |
| } |
| } |
| first = false; |
| |
| for i := start; i < end; { |
| i = t.executeElement(i, newst) |
| } |
| } |
| } else if ch := iter(field); ch != nil { |
| for { |
| e := ch.Recv(); |
| if ch.Closed() { |
| break |
| } |
| newst := st.clone(e); |
| |
| // .alternates between elements |
| if !first && r.altstart >= 0 { |
| for i := r.altstart; i < r.altend; { |
| i = t.executeElement(i, newst) |
| } |
| } |
| first = false; |
| |
| for i := start; i < end; { |
| i = t.executeElement(i, newst) |
| } |
| } |
| } else { |
| t.execError(st, r.linenum, ".repeated: cannot repeat %s (type %s)", |
| r.field, field.Type()) |
| } |
| |
| if first { |
| // Empty. Execute the .or block, once. If it's missing, do nothing. |
| start, end := r.or, r.end; |
| if start >= 0 { |
| newst := st.clone(field); |
| for i := start; i < end; { |
| i = t.executeElement(i, newst) |
| } |
| } |
| return; |
| } |
| } |
| |
| // A valid delimiter must contain no white space and be non-empty. |
| func validDelim(d []byte) bool { |
| if len(d) == 0 { |
| return false |
| } |
| for _, c := range d { |
| if white(c) { |
| return false |
| } |
| } |
| return true; |
| } |
| |
| // -- Public interface |
| |
| // Parse initializes a Template by parsing its definition. The string |
| // s contains the template text. If any errors occur, Parse returns |
| // the error. |
| func (t *Template) Parse(s string) os.Error { |
| if !validDelim(t.ldelim) || !validDelim(t.rdelim) { |
| return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)} |
| } |
| t.buf = strings.Bytes(s); |
| t.p = 0; |
| t.linenum = 1; |
| t.parse(); |
| return t.error; |
| } |
| |
| // Execute applies a parsed template to the specified data object, |
| // generating output to wr. |
| func (t *Template) Execute(data interface{}, wr io.Writer) os.Error { |
| // Extract the driver data. |
| val := reflect.NewValue(data); |
| errors := make(chan os.Error); |
| go func() { |
| t.p = 0; |
| t.execute(0, t.elems.Len(), &state{nil, val, wr, errors}); |
| errors <- nil; // clean return; |
| }(); |
| return <-errors; |
| } |
| |
| // SetDelims sets the left and right delimiters for operations in the |
| // template. They are validated during parsing. They could be |
| // validated here but it's better to keep the routine simple. The |
| // delimiters are very rarely invalid and Parse has the necessary |
| // error-handling interface already. |
| func (t *Template) SetDelims(left, right string) { |
| t.ldelim = strings.Bytes(left); |
| t.rdelim = strings.Bytes(right); |
| } |
| |
| // Parse creates a Template with default parameters (such as {} for |
| // metacharacters). The string s contains the template text while |
| // the formatter map fmap, which may be nil, defines auxiliary functions |
| // for formatting variables. The template is returned. If any errors |
| // occur, err will be non-nil. |
| func Parse(s string, fmap FormatterMap) (t *Template, err os.Error) { |
| t = New(fmap); |
| err = t.Parse(s); |
| if err != nil { |
| t = nil |
| } |
| return; |
| } |
| |
| // MustParse is like Parse but panics if the template cannot be parsed. |
| func MustParse(s string, fmap FormatterMap) *Template { |
| t, err := Parse(s, fmap); |
| if err != nil { |
| panic("template parse error: ", err.String()) |
| } |
| return t; |
| } |