diff --git a/src/lib/template/template.go b/src/lib/template/template.go index f81ac0adaf26835f9e613a07bacfdab47457f263..327d8194b83bc3ed851eaee98c460481e3513afa 100644 --- a/src/lib/template/template.go +++ b/src/lib/template/template.go @@ -2,8 +2,59 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Template library. See http://code.google.com/p/json-template/wiki/Reference -// TODO: document this here as well. +/* + 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) 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. + (TODO(r): .alternates is not yet implemented) + + {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 ( @@ -15,6 +66,7 @@ import ( "template"; ) +// Errors returned during parsing and execution. var ErrUnmatchedRDelim = os.NewError("unmatched closing delimiter") var ErrUnmatchedLDelim = os.NewError("unmatched opening delimiter") var ErrBadDirective = os.NewError("unrecognized directive name") @@ -26,7 +78,7 @@ var ErrNoVar = os.NewError("variable name not in struct"); var ErrBadType = os.NewError("unsupported type for variable"); var ErrNotStruct = os.NewError("driver must be a struct") var ErrNoFormatter = os.NewError("unknown formatter") -var ErrEmptyDelims = os.NewError("empty delimiter strings") +var ErrBadDelims = os.NewError("invalid delimiter strings") // All the literals are aces. var lbrace = []byte{ '{' } @@ -72,6 +124,7 @@ func (st *state) error(err *os.Error, args ...) { sys.Goexit(); } +// Template is the type that represents a template definition. type Template struct { fmap FormatterMap; // formatters for variables ldelim, rdelim []byte; // delimiters; default {} @@ -100,11 +153,12 @@ func childTemplate(parent *Template, buf []byte) *Template { return t; } +// 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? +// Safely, does s[n:n+len(t)] == t? func equal(s []byte, n int, t []byte) bool { b := s[n:len(s)]; if len(t) > len(b) { // not enough space left for a match. @@ -124,7 +178,7 @@ func (t *Template) executeSection(w []string, st *state) // 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. Most tokens stop at (but include, if plain text) a newline. +// 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(st *state) []byte { @@ -471,6 +525,8 @@ func (t *Template) writeVariable(st *state, name_formatter string) { panic("notreached"); } +// Execute the template. execute, executeSection and executeRepeated +// are mutually recursive. func (t *Template) execute(st *state) { for { item := t.nextItem(st); @@ -512,12 +568,25 @@ func (t *Template) doParse() { // stub for now } +// A valid delimeter must contain no white space and be non-empty. +func validDelim(d []byte) bool { + if len(d) == 0 { + return false + } + for i, c := range d { + if white(c) { + return false + } + } + return true; +} + // Parse initializes a Template by parsing its definition. The string s contains // the template text. If any errors occur, it returns the error and line number // in the text of the erroneous construct. -func (t *Template) Parse(s string) (*os.Error, int) { - if len(t.ldelim) == 0 || len(t.rdelim) == 0 { - return ErrEmptyDelims, 0 +func (t *Template) Parse(s string) (err *os.Error, eline int) { + if !validDelim(t.ldelim) || !validDelim(t.rdelim) { + return ErrBadDelims, 0 } t.init(io.StringBytes(s)); ch := make(chan *os.Error); @@ -525,11 +594,11 @@ func (t *Template) Parse(s string) (*os.Error, int) { t.doParse(); ch <- nil; // clean return; }(); - err := <-ch; + err = <-ch; if err != nil { return err, *t.linenum } - return nil, 0 + return } // Execute executes a parsed template on the specified data object, @@ -557,18 +626,22 @@ func New(fmap FormatterMap) *Template { } // 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 = io.StringBytes(left); t.rdelim = io.StringBytes(right); } // Parse creates a Template with default parameters (such as {} for -// metacharacters). The string s contains the template text and the -// formatter map fmap (which may be nil) defines auxiliary functions -// for formatting variables. It returns the template, an error report -// (or nil), and the line number in the text of the erroneous construct. -func Parse(s string, fmap FormatterMap) (*Template, *os.Error, int) { - t := New(fmap); - err, line := t.Parse(s); - return t, err, line +// 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 and eline will be the line number in the +// text of the erroneous construct. +func Parse(s string, fmap FormatterMap) (t *Template, err *os.Error, eline int) { + t = New(fmap); + err, eline = t.Parse(s); + return }