summaryrefslogtreecommitdiff
path: root/src/pkg/template
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2009-06-09 09:53:44 -0700
committerRob Pike <r@golang.org>2009-06-09 09:53:44 -0700
commit7249ea4df2b4f12a4e7ed446f270cea87e4ffd34 (patch)
tree7032a11d0cac2ae4d3e90f7a189b575b5a50f848 /src/pkg/template
parentacf6ef7a82b3fe61516a1bac4563706552bdf078 (diff)
downloadgolang-7249ea4df2b4f12a4e7ed446f270cea87e4ffd34.tar.gz
mv src/lib to src/pkg
tests: all.bash passes, gobuild still works, godoc still works. R=rsc OCL=30096 CL=30102
Diffstat (limited to 'src/pkg/template')
-rw-r--r--src/pkg/template/Makefile68
-rw-r--r--src/pkg/template/format.go54
-rw-r--r--src/pkg/template/template.go808
-rw-r--r--src/pkg/template/template_test.go331
4 files changed, 1261 insertions, 0 deletions
diff --git a/src/pkg/template/Makefile b/src/pkg/template/Makefile
new file mode 100644
index 000000000..e91c08818
--- /dev/null
+++ b/src/pkg/template/Makefile
@@ -0,0 +1,68 @@
+# 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.
+
+# DO NOT EDIT. Automatically generated by gobuild.
+# gobuild -m >Makefile
+
+D=
+
+include $(GOROOT)/src/Make.$(GOARCH)
+AR=gopack
+
+default: packages
+
+clean:
+ rm -rf *.[$(OS)] *.a [$(OS)].out _obj
+
+test: packages
+ gotest
+
+coverage: packages
+ gotest
+ 6cov -g `pwd` | grep -v '_test\.go:'
+
+%.$O: %.go
+ $(GC) -I_obj $*.go
+
+%.$O: %.c
+ $(CC) $*.c
+
+%.$O: %.s
+ $(AS) $*.s
+
+O1=\
+ format.$O\
+
+O2=\
+ template.$O\
+
+
+phases: a1 a2
+_obj$D/template.a: phases
+
+a1: $(O1)
+ $(AR) grc _obj$D/template.a format.$O
+ rm -f $(O1)
+
+a2: $(O2)
+ $(AR) grc _obj$D/template.a template.$O
+ rm -f $(O2)
+
+
+newpkg: clean
+ mkdir -p _obj$D
+ $(AR) grc _obj$D/template.a
+
+$(O1): newpkg
+$(O2): a1
+$(O3): a2
+
+nuke: clean
+ rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/template.a
+
+packages: _obj$D/template.a
+
+install: packages
+ test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D
+ cp _obj$D/template.a $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/template.a
diff --git a/src/pkg/template/format.go b/src/pkg/template/format.go
new file mode 100644
index 000000000..4fb5393b9
--- /dev/null
+++ b/src/pkg/template/format.go
@@ -0,0 +1,54 @@
+// 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.
+
+// Template library: default formatters
+
+package template
+
+import (
+ "fmt";
+ "io";
+ "reflect";
+)
+
+// StringFormatter formats into the default string representation.
+// It is stored under the name "str" and is the default formatter.
+// You can override the default formatter by storing your default
+// under the name "" in your custom formatter map.
+func StringFormatter(w io.Writer, value interface{}, format string) {
+ fmt.Fprint(w, value);
+}
+
+
+var esc_amp = io.StringBytes("&amp;")
+var esc_lt = io.StringBytes("&lt;")
+var esc_gt = io.StringBytes("&gt;")
+
+// HtmlEscape writes to w the properly escaped HTML equivalent
+// of the plain text data s.
+func HtmlEscape(w io.Writer, s []byte) {
+ last := 0;
+ for i, c := range s {
+ if c == '&' || c == '<' || c == '>' {
+ w.Write(s[last:i]);
+ switch c {
+ case '&':
+ w.Write(esc_amp);
+ case '<':
+ w.Write(esc_lt);
+ case '>':
+ w.Write(esc_gt);
+ }
+ last = i+1;
+ }
+ }
+ w.Write(s[last:len(s)]);
+}
+
+// HtmlFormatter formats arbitrary values for HTML
+func HtmlFormatter(w io.Writer, value interface{}, format string) {
+ var b io.ByteBuffer;
+ fmt.Fprint(&b, value);
+ HtmlEscape(w, b.Data());
+}
diff --git a/src/pkg/template/template.go b/src/pkg/template/template.go
new file mode 100644
index 000000000..a5e9b0c7d
--- /dev/null
+++ b/src/pkg/template/template.go
@@ -0,0 +1,808 @@
+// 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) 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";
+ "template";
+)
+
+// 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
+ errors chan os.Error; // for error reporting 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.errors = make(chan os.Error);
+ t.elems = vector.New(0);
+ return t;
+}
+
+// Generic error handler, called only from execError or parseError.
+func error(errors chan os.Error, line int, err string, args ...) {
+ errors <- &Error{line, fmt.Sprintf(err, args)};
+ runtime.Goexit();
+}
+
+// Report error and stop executing. The line number must be provided explicitly.
+func (t *Template) execError(st *state, line int, err string, args ...) {
+ error(st.errors, line, err, args);
+}
+
+// Report error and stop parsing. The line number comes from the template state.
+func (t *Template) parseError(err string, args ...) {
+ error(t.errors, t.linenum, 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:len(s)];
+ 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")
+ }
+ sawLeft = false;
+ i += len(t.rdelim);
+ break Loop;
+ default:
+ only_white = false;
+ }
+ }
+ if sawLeft {
+ t.parseError("unmatched opening delimiter")
+ }
+ 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' {
+ 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
+ }
+ if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
+ t.parseError("empty directive")
+ }
+ // 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")
+ }
+ 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)
+ }
+ tok = tokSection;
+ return;
+ case ".repeated":
+ if len(w) != 3 || w[1] != "section" {
+ t.parseError("incorrect fields for .repeated: %s", item)
+ }
+ tok = tokRepeated;
+ return;
+ case ".alternates":
+ if len(w) != 2 || w[1] != "with" {
+ t.parseError("incorrect fields for .alternates: %s", item)
+ }
+ 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:len(name_formatter)];
+ }
+ // 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 fn, ok := t.fmap[formatter]; ok {
+ return
+ }
+ }
+ // Is it in builtin map?
+ if fn, 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);
+ 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;
+ case tokVariable:
+ t.elems.Push(t.newVariable(w[0]));
+ return;
+ }
+ return false, tok, w
+}
+
+// parseSection and parseRepeated are mutually recursive
+func (t *Template) parseSection(words []string) *sectionElement
+
+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 {
+ item := t.nextItem();
+ if len(item) == 0 {
+ t.parseError("missing .end for .repeated section")
+ }
+ done, tok, w := t.parseSimple(item);
+ if done {
+ continue
+ }
+ switch tok {
+ case tokEnd:
+ break Loop;
+ case tokOr:
+ if r.or >= 0 {
+ t.parseError("extra .or in .repeated section");
+ }
+ 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");
+ }
+ if r.or >= 0 {
+ t.parseError(".alternates inside .or block in .repeated section");
+ }
+ r.altstart = t.elems.Len();
+ default:
+ t.parseError("internal error: unknown repeated section item: %s", item);
+ }
+ }
+ 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 {
+ item := t.nextItem();
+ if len(item) == 0 {
+ t.parseError("missing .end for .section")
+ }
+ done, tok, w := t.parseSimple(item);
+ if done {
+ continue
+ }
+ switch tok {
+ case tokEnd:
+ break Loop;
+ case tokOr:
+ if s.or >= 0 {
+ t.parseError("extra .or in .section");
+ }
+ 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);
+ }
+ }
+ s.end = t.elems.Len();
+ return s;
+}
+
+func (t *Template) parse() {
+ for {
+ item := t.nextItem();
+ 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.
+// The special name "@" (the "cursor") denotes the current data.
+func (st *state) findVar(s string) reflect.Value {
+ if s == "@" {
+ return st.data
+ }
+ data := reflect.Indirect(st.data);
+ typ, ok := data.Type().(reflect.StructType);
+ if ok {
+ for i := 0; i < typ.Len(); i++ {
+ name, ftyp, tag, offset := typ.Field(i);
+ if name == s {
+ return data.(reflect.StructValue).Field(i)
+ }
+ }
+ }
+ return nil
+}
+
+// Is there no data to look at?
+func empty(v reflect.Value, indirect_ok bool) bool {
+ v = reflect.Indirect(v);
+ if v == nil {
+ return true
+ }
+ switch v.Type().Kind() {
+ case reflect.StringKind:
+ return v.(reflect.StringValue).Get() == "";
+ case reflect.StructKind:
+ return false;
+ case reflect.ArrayKind:
+ return v.(reflect.ArrayValue).Len() == 0;
+ }
+ return true;
+}
+
+// Look up a variable, up through the parent if necessary.
+func (t *Template) varValue(v *variableElement, st *state) reflect.Value {
+ field := st.findVar(v.name);
+ if field == nil {
+ if st.parent == nil {
+ t.execError(st, t.linenum, "name not found: %s", v.name)
+ }
+ return t.varValue(v, 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, st).Interface();
+ // is it in user-supplied map?
+ if t.fmap != nil {
+ if fn, ok := t.fmap[v.formatter]; ok {
+ fn(st.wr, val, v.formatter);
+ return;
+ }
+ }
+ // is it in builtin map?
+ if fn, ok := builtins[v.formatter]; ok {
+ fn(st.wr, val, v.formatter);
+ return;
+ }
+ t.execError(st, v.linenum, "missing formatter %s for variable %s", v.formatter, v.name)
+}
+
+// execute{|Element|Section|Repeated} are mutually recursive
+func (t *Template) executeSection(s *sectionElement, st *state)
+func (t *Template) executeRepeated(r *repeatedElement, st *state)
+
+// 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 := st.findVar(s.field);
+ 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, true) {
+ // 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)
+ }
+}
+
+// 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 := st.findVar(r.field);
+ if field == nil {
+ t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, reflect.Indirect(st.data).Type());
+ }
+ field = reflect.Indirect(field);
+
+ // Must be an array/slice
+ if field != nil && field.Kind() != reflect.ArrayKind {
+ t.execError(st, r.linenum, ".repeated: %s has bad type %s", r.field, field.Type());
+ }
+ if empty(field, true) {
+ // 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
+ }
+ // Execute the normal block.
+ start, end := r.start, r.or;
+ if end < 0 {
+ end = r.end
+ }
+ if r.altstart >= 0 {
+ end = r.altstart
+ }
+ if field != nil {
+ array := field.(reflect.ArrayValue);
+ for j := 0; j < array.Len(); j++ {
+ newst := st.clone(array.Elem(j));
+ for i := start; i < end; {
+ i = t.executeElement(i, newst)
+ }
+ // If appropriate, do .alternates between elements
+ if j < array.Len() - 1 && r.altstart >= 0 {
+ for i := r.altstart; i < r.altend; i++ {
+ i = t.executeElement(i, newst)
+ }
+ }
+ }
+ }
+}
+
+// A valid delimiter 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;
+}
+
+// -- 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 = io.StringBytes(s);
+ t.p = 0;
+ t.linenum = 0;
+ go func() {
+ t.parse();
+ t.errors <- nil; // clean return;
+ }();
+ return <-t.errors;
+}
+
+// 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 = 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 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);
+ return
+}
diff --git a/src/pkg/template/template_test.go b/src/pkg/template/template_test.go
new file mode 100644
index 000000000..9a81d274c
--- /dev/null
+++ b/src/pkg/template/template_test.go
@@ -0,0 +1,331 @@
+// 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.
+
+package template
+
+import (
+ "fmt";
+ "io";
+ "os";
+ "reflect";
+ "template";
+ "testing";
+)
+
+type Test struct {
+ in, out string
+}
+
+type T struct {
+ item string;
+ value string;
+}
+
+type S struct {
+ header string;
+ integer int;
+ raw string;
+ data []T;
+ pdata []*T;
+ empty []*T;
+ emptystring string;
+ null []*T;
+}
+
+var t1 = T{ "ItemNumber1", "ValueNumber1" }
+var t2 = T{ "ItemNumber2", "ValueNumber2" }
+
+func uppercase(v interface{}) string {
+ s := v.(string);
+ t := "";
+ for i := 0; i < len(s); i++ {
+ c := s[i];
+ if 'a' <= c && c <= 'z' {
+ c = c + 'A' - 'a'
+ }
+ t += string(c);
+ }
+ return t;
+}
+
+func plus1(v interface{}) string {
+ i := v.(int);
+ return fmt.Sprint(i + 1);
+}
+
+func writer(f func(interface{}) string) (func(io.Writer, interface{}, string)) {
+ return func(w io.Writer, v interface{}, format string) {
+ io.WriteString(w, f(v));
+ }
+}
+
+
+var formatters = FormatterMap {
+ "uppercase" : writer(uppercase),
+ "+1" : writer(plus1),
+}
+
+var tests = []*Test {
+ // Simple
+ &Test{ "", "" },
+ &Test{ "abc\ndef\n", "abc\ndef\n" },
+ &Test{ " {.meta-left} \n", "{" },
+ &Test{ " {.meta-right} \n", "}" },
+ &Test{ " {.space} \n", " " },
+ &Test{ " {.tab} \n", "\t" },
+ &Test{ " {#comment} \n", "" },
+
+ // Variables at top level
+ &Test{
+ "{header}={integer}\n",
+
+ "Header=77\n"
+ },
+
+ // Section
+ &Test{
+ "{.section data }\n"
+ "some text for the section\n"
+ "{.end}\n",
+
+ "some text for the section\n"
+ },
+ &Test{
+ "{.section data }\n"
+ "{header}={integer}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{header}={integer}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data present\n"
+ },
+ &Test{
+ "{.section empty }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data not present\n"
+ },
+ &Test{
+ "{.section null }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data not present\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{header}={integer}\n"
+ "{.section @ }\n"
+ "{header}={integer}\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ "Header=77\n"
+ },
+ &Test{
+ "{.section data}{.end} {header}\n",
+
+ " Header\n"
+ },
+
+ // Repeated
+ &Test{
+ "{.section pdata }\n"
+ "{.repeated section @ }\n"
+ "{item}={value}\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "ItemNumber1=ValueNumber1\n"
+ "ItemNumber2=ValueNumber2\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{.repeated section @ }\n"
+ "{item}={value}\n"
+ "{.or}\n"
+ "this should not appear\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "ItemNumber1=ValueNumber1\n"
+ "ItemNumber2=ValueNumber2\n"
+ },
+ &Test{
+ "{.section @ }\n"
+ "{.repeated section empty }\n"
+ "{item}={value}\n"
+ "{.or}\n"
+ "this should appear: empty field\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "this should appear: empty field\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{.repeated section @ }\n"
+ "{item}={value}\n"
+ "{.alternates with}DIVIDER\n"
+ "{.or}\n"
+ "this should not appear\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "ItemNumber1=ValueNumber1\n"
+ "DIVIDER\n"
+ "ItemNumber2=ValueNumber2\n"
+ },
+
+ // Formatters
+ &Test{
+ "{.section pdata }\n"
+ "{header|uppercase}={integer|+1}\n"
+ "{header|html}={integer|str}\n"
+ "{.end}\n",
+
+ "HEADER=78\n"
+ "Header=77\n"
+ },
+
+ &Test{
+ "{raw}\n"
+ "{raw|html}\n",
+
+ "&<>!@ #$%^\n"
+ "&amp;&lt;&gt;!@ #$%^\n"
+ },
+
+ &Test{
+ "{.section emptystring}emptystring{.end}\n"
+ "{.section header}header{.end}\n",
+
+ "\nheader\n"
+ },
+}
+
+func TestAll(t *testing.T) {
+ s := new(S);
+ // initialized by hand for clarity.
+ s.header = "Header";
+ s.integer = 77;
+ s.raw = "&<>!@ #$%^";
+ s.data = []T{ t1, t2 };
+ s.pdata = []*T{ &t1, &t2 };
+ s.empty = []*T{ };
+ s.null = nil;
+
+ var buf io.ByteBuffer;
+ for i, test := range tests {
+ buf.Reset();
+ tmpl, err := Parse(test.in, formatters);
+ if err != nil {
+ t.Error("unexpected parse error:", err);
+ continue;
+ }
+ err = tmpl.Execute(s, &buf);
+ if err != nil {
+ t.Error("unexpected execute error:", err)
+ }
+ if string(buf.Data()) != test.out {
+ t.Errorf("for %q: expected %q got %q", test.in, test.out, string(buf.Data()));
+ }
+ }
+}
+
+func TestStringDriverType(t *testing.T) {
+ tmpl, err := Parse("template: {@}", nil);
+ if err != nil {
+ t.Error("unexpected parse error:", err)
+ }
+ var b io.ByteBuffer;
+ err = tmpl.Execute("hello", &b);
+ if err != nil {
+ t.Error("unexpected execute error:", err)
+ }
+ s := string(b.Data());
+ if s != "template: hello" {
+ t.Errorf("failed passing string as data: expected %q got %q", "template: hello", s)
+ }
+}
+
+func TestTwice(t *testing.T) {
+ tmpl, err := Parse("template: {@}", nil);
+ if err != nil {
+ t.Error("unexpected parse error:", err)
+ }
+ var b io.ByteBuffer;
+ err = tmpl.Execute("hello", &b);
+ if err != nil {
+ t.Error("unexpected parse error:", err)
+ }
+ s := string(b.Data());
+ text := "template: hello";
+ if s != text {
+ t.Errorf("failed passing string as data: expected %q got %q", text, s);
+ }
+ err = tmpl.Execute("hello", &b);
+ if err != nil {
+ t.Error("unexpected parse error:", err)
+ }
+ s = string(b.Data());
+ text += text;
+ if s != text {
+ t.Errorf("failed passing string as data: expected %q got %q", text, s);
+ }
+}
+
+func TestCustomDelims(t *testing.T) {
+ // try various lengths. zero should catch error.
+ for i := 0; i < 7; i++ {
+ for j := 0; j < 7; j++ {
+ tmpl := New(nil);
+ // first two chars deliberately the same to test equal left and right delims
+ ldelim := "$!#$%^&"[0:i];
+ rdelim := "$*&^%$!"[0:j];
+ tmpl.SetDelims(ldelim, rdelim);
+ // if braces, this would be template: {@}{.meta-left}{.meta-right}
+ text := "template: " +
+ ldelim + "@" + rdelim +
+ ldelim + ".meta-left" + rdelim +
+ ldelim + ".meta-right" + rdelim;
+ err := tmpl.Parse(text);
+ if err != nil {
+ if i == 0 || j == 0 { // expected
+ continue
+ }
+ t.Error("unexpected parse error:", err)
+ } else if i == 0 || j == 0 {
+ t.Errorf("expected parse error for empty delimiter: %d %d %q %q", i, j, ldelim, rdelim);
+ continue;
+ }
+ var b io.ByteBuffer;
+ err = tmpl.Execute("hello", &b);
+ s := string(b.Data());
+ if s != "template: hello" + ldelim + rdelim {
+ t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s)
+ }
+ }
+ }
+}