summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/template/template.go109
1 files changed, 91 insertions, 18 deletions
diff --git a/src/lib/template/template.go b/src/lib/template/template.go
index f81ac0ada..327d8194b 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
}