summaryrefslogtreecommitdiff
path: root/src/pkg/template
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/template')
-rw-r--r--src/pkg/template/Makefile4
-rw-r--r--src/pkg/template/doc.go91
-rw-r--r--src/pkg/template/execute.go346
-rw-r--r--src/pkg/template/parse.go (renamed from src/pkg/template/template.go)649
-rw-r--r--src/pkg/template/template_test.go43
5 files changed, 657 insertions, 476 deletions
diff --git a/src/pkg/template/Makefile b/src/pkg/template/Makefile
index 4915527b4..4f1e06527 100644
--- a/src/pkg/template/Makefile
+++ b/src/pkg/template/Makefile
@@ -6,7 +6,9 @@ include ../../Make.inc
TARG=template
GOFILES=\
+ doc.go\
+ execute.go\
format.go\
- template.go\
+ parse.go\
include ../../Make.pkg
diff --git a/src/pkg/template/doc.go b/src/pkg/template/doc.go
new file mode 100644
index 000000000..e778d801d
--- /dev/null
+++ b/src/pkg/template/doc.go
@@ -0,0 +1,91 @@
+// 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 implements data-driven templates for generating textual
+ output such as HTML.
+
+ 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.
+
+ In the following, 'Field' is one of several things, according to the data.
+
+ - The name of a field of a struct (result = data.Field),
+ - The value stored in a map under that key (result = data["Field"]), or
+ - The result of invoking a niladic single-valued method with that name
+ (result = data.Field())
+
+ If Field is a struct field or method name, it must be an exported
+ (capitalized) name.
+
+ Major constructs ({} are the default delimiters for template actions;
+ [] are the notation in this comment for 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}
+ {field1 field2 ...}
+ {field|formatter}
+ {field1 field2...|formatter}
+ {field|formatter1|formatter2}
+
+ Insert the value of the fields into the output. Each 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 the field value is a pointer, leading asterisks indicate
+ that the value to be inserted should be evaluated through the
+ pointer. For example, if x.p is of type *int, {x.p} will
+ insert the value of the pointer but {*x.p} will insert the
+ value of the underlying integer. If the value is nil or not a
+ pointer, asterisks have no effect.
+
+ 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.Writer, formatter string, data ...interface{})
+ where wr is the destination for output, data holds the field
+ values at the instantiation, and formatter is its name at
+ the invocation site. The default formatter just concatenates
+ the string representations of the fields.
+
+ Multiple formatters separated by the pipeline character | are
+ executed sequentially, with each formatter receiving the bytes
+ emitted by the one to its left.
+
+ As well as field names, one may use literals with Go syntax.
+ Integer, floating-point, and string literals are supported.
+ Raw strings may not span newlines.
+
+ The delimiter strings get their default value, "{" and "}", from
+ JSON-template. They may be set to any non-empty, space-free
+ string using the SetDelims method. Their value can be printed
+ in the output using {.meta-left} and {.meta-right}.
+*/
+package template
diff --git a/src/pkg/template/execute.go b/src/pkg/template/execute.go
new file mode 100644
index 000000000..5bc7ff7e9
--- /dev/null
+++ b/src/pkg/template/execute.go
@@ -0,0 +1,346 @@
+// 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.
+
+// Code to execute a parsed template.
+
+package template
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "strings"
+)
+
+// 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
+ buf [2]bytes.Buffer // alternating buffers used when chaining formatters
+}
+
+func (parent *state) clone(data reflect.Value) *state {
+ return &state{parent: parent, data: data, wr: parent.wr}
+}
+
+// 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 (t *Template) lookup(st *state, v reflect.Value, name string) 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 && mtyp.NumOut() == 1 {
+ if !isExported(name) {
+ t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
+ }
+ 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:
+ if !isExported(name) {
+ t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
+ }
+ return av.FieldByName(name)
+ case reflect.Map:
+ if v := av.MapIndex(reflect.ValueOf(name)); v.IsValid() {
+ return v
+ }
+ return reflect.Zero(typ.Elem())
+ default:
+ return reflect.Value{}
+ }
+ }
+ return v
+}
+
+// indirectPtr returns the item numLevels levels of indirection below the value.
+// It is forgiving: if the value is not a pointer, it returns it rather than giving
+// an error. If the pointer is nil, it is returned as is.
+func indirectPtr(v reflect.Value, numLevels int) reflect.Value {
+ for i := numLevels; v.IsValid() && i > 0; i++ {
+ if p := v; p.Kind() == reflect.Ptr {
+ if p.IsNil() {
+ return v
+ }
+ v = p.Elem()
+ } else {
+ break
+ }
+ }
+ return v
+}
+
+// Walk v through pointers and interfaces, extracting the elements within.
+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
+}
+
+// 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. Leading stars indicate
+// levels of indirection to be applied to the value.
+func (t *Template) findVar(st *state, s string) reflect.Value {
+ data := st.data
+ flattenedName := strings.TrimLeft(s, "*")
+ numStars := len(s) - len(flattenedName)
+ s = flattenedName
+ if s == "@" {
+ return indirectPtr(data, numStars)
+ }
+ for _, elem := range strings.Split(s, ".", -1) {
+ // Look up field; data must be a struct or map.
+ data = t.lookup(st, data, elem)
+ if !data.IsValid() {
+ return reflect.Value{}
+ }
+ }
+ return indirectPtr(data, numStars)
+}
+
+// Is there no data to look at?
+func empty(v reflect.Value) bool {
+ v = indirect(v)
+ if !v.IsValid() {
+ return true
+ }
+ switch v.Kind() {
+ case reflect.Bool:
+ return v.Bool() == false
+ case reflect.String:
+ return v.String() == ""
+ case reflect.Struct:
+ return false
+ case reflect.Map:
+ return false
+ case reflect.Array:
+ return v.Len() == 0
+ case reflect.Slice:
+ return v.Len() == 0
+ }
+ return false
+}
+
+// Look up a variable or method, up through the parent if necessary.
+func (t *Template) varValue(name string, st *state) reflect.Value {
+ field := t.findVar(st, name)
+ if !field.IsValid() {
+ if st.parent == nil {
+ t.execError(st, t.linenum, "name not found: %s in type %s", name, st.data.Type())
+ }
+ return t.varValue(name, st.parent)
+ }
+ return field
+}
+
+func (t *Template) format(wr io.Writer, fmt string, val []interface{}, v *variableElement, st *state) {
+ fn := t.formatter(fmt)
+ if fn == nil {
+ t.execError(st, v.linenum, "missing formatter %s for variable", fmt)
+ }
+ fn(wr, fmt, val...)
+}
+
+// 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) {
+ // Resolve field names
+ val := make([]interface{}, len(v.args))
+ for i, arg := range v.args {
+ if name, ok := arg.(fieldName); ok {
+ val[i] = t.varValue(string(name), st).Interface()
+ } else {
+ val[i] = arg
+ }
+ }
+ for i, fmt := range v.fmts[:len(v.fmts)-1] {
+ b := &st.buf[i&1]
+ b.Reset()
+ t.format(b, fmt, val, v, st)
+ val = val[0:1]
+ val[0] = b.Bytes()
+ }
+ t.format(st.wr, v.fmts[len(v.fmts)-1], val, v, st)
+}
+
+// Execute element i. Return next index to execute.
+func (t *Template) executeElement(i int, st *state) int {
+ switch elem := t.elems[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[i]
+ t.execError(st, 0, "internal error: bad directive in execute: %v %T\n", reflect.ValueOf(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.IsValid() {
+ t.execError(st, s.linenum, ".section: cannot find field %s in %s", s.field, 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.Value {
+ for j := 0; j < v.Type().NumMethod(); j++ {
+ mth := v.Type().Method(j)
+ fv := v.Method(j)
+ ft := fv.Type()
+ // 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 := ft.Out(0)
+ if ct.Kind() != reflect.Chan ||
+ ct.ChanDir()&reflect.RecvDir == 0 {
+ continue
+ }
+ return fv.Call(nil)[0]
+ }
+ return reflect.Value{}
+}
+
+// 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.IsValid() {
+ t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, st.data.Type())
+ }
+ field = indirect(field)
+
+ start, end := r.start, r.or
+ if end < 0 {
+ end = r.end
+ }
+ if r.altstart >= 0 {
+ end = r.altstart
+ }
+ first := true
+
+ // Code common to all the loops.
+ loopBody := func(newst *state) {
+ // .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)
+ }
+ }
+
+ if array := field; array.Kind() == reflect.Array || array.Kind() == reflect.Slice {
+ for j := 0; j < array.Len(); j++ {
+ loopBody(st.clone(array.Index(j)))
+ }
+ } else if m := field; m.Kind() == reflect.Map {
+ for _, key := range m.MapKeys() {
+ loopBody(st.clone(m.MapIndex(key)))
+ }
+ } else if ch := iter(field); ch.IsValid() {
+ for {
+ e, ok := ch.Recv()
+ if !ok {
+ break
+ }
+ loopBody(st.clone(e))
+ }
+ } 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 space and be non-empty.
+func validDelim(d []byte) bool {
+ if len(d) == 0 {
+ return false
+ }
+ for _, c := range d {
+ if isSpace(c) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/src/pkg/template/template.go b/src/pkg/template/parse.go
index 253207852..b4aa5fcd2 100644
--- a/src/pkg/template/template.go
+++ b/src/pkg/template/parse.go
@@ -2,95 +2,17 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-/*
- Package template implements data-driven templates for generating textual
- output such as HTML.
+// Code to parse a template.
- 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.
-
- In the following, 'field' is one of several things, according to the data.
-
- - The name of a field of a struct (result = data.field),
- - The value stored in a map under that key (result = data[field]), or
- - The result of invoking a niladic single-valued method with that name
- (result = data.field())
-
- Major constructs ({} are the default delimiters for template actions;
- [] are the notation in this comment for 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}
- {field1 field2 ...}
- {field|formatter}
- {field1 field2...|formatter}
- {field|formatter1|formatter2}
-
- Insert the value of the fields into the output. Each 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 the field value is a pointer, leading asterisks indicate
- that the value to be inserted should be evaluated through the
- pointer. For example, if x.p is of type *int, {x.p} will
- insert the value of the pointer but {*x.p} will insert the
- value of the underlying integer. If the value is nil or not a
- pointer, asterisks have no effect.
-
- 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.Writer, formatter string, data ...interface{})
- where wr is the destination for output, data holds the field
- values at the instantiation, and formatter is its name at
- the invocation site. The default formatter just concatenates
- the string representations of the fields.
-
- Multiple formatters separated by the pipeline character | are
- executed sequentially, with each formatter receiving the bytes
- emitted by the one to its left.
-
- The delimiter strings get their default value, "{" and "}", from
- JSON-template. They may be set to any non-empty, space-free
- string using the SetDelims method. Their value can be printed
- in the output using {.meta-left} and {.meta-right}.
-*/
package template
import (
- "bytes"
- "container/vector"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
+ "strconv"
"strings"
"unicode"
"utf8"
@@ -105,6 +27,19 @@ type Error struct {
func (e *Error) String() string { return fmt.Sprintf("line %d: %s", e.Line, e.Msg) }
+// checkError is a deferred function to turn a panic with type *Error into a plain error return.
+// Other panics are unexpected and so are re-enabled.
+func checkError(error *os.Error) {
+ if v := recover(); v != nil {
+ if e, ok := v.(*Error); ok {
+ *error = e
+ } else {
+ // runtime errors should crash
+ panic(v)
+ }
+ }
+}
+
// Most of the literals are aces.
var lbrace = []byte{'{'}
var rbrace = []byte{'}'}
@@ -151,10 +86,13 @@ type literalElement struct {
// A variable invocation to be evaluated
type variableElement struct {
linenum int
- word []string // The fields in the invocation.
- fmts []string // Names of formatters to apply. len(fmts) > 0
+ args []interface{} // The fields and literals in the invocation.
+ fmts []string // Names of formatters to apply. len(fmts) > 0
}
+// A variableElement arg to be evaluated as a field name
+type fieldName string
+
// A .section block, possibly with a .or
type sectionElement struct {
linenum int // of .section itself
@@ -181,21 +119,7 @@ type Template struct {
p int // position in buf
linenum int // position in input
// 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
- buf [2]bytes.Buffer // alternating buffers used when chaining formatters
-}
-
-func (parent *state) clone(data reflect.Value) *state {
- return &state{parent: parent, data: data, wr: parent.wr}
+ elems []interface{}
}
// New creates a new template with the specified formatter map (which
@@ -205,7 +129,7 @@ func New(fmap FormatterMap) *Template {
t.fmap = fmap
t.ldelim = lbrace
t.rdelim = rbrace
- t.elems = new(vector.Vector)
+ t.elems = make([]interface{}, 0, 16)
return t
}
@@ -228,8 +152,8 @@ func isExported(name string) bool {
// -- Lexical analysis
-// Is c a white space character?
-func white(c uint8) bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n' }
+// Is c a space character?
+func isSpace(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 {
@@ -245,6 +169,31 @@ func equal(s []byte, n int, t []byte) bool {
return true
}
+// isQuote returns true if c is a string- or character-delimiting quote character.
+func isQuote(c byte) bool {
+ return c == '"' || c == '`' || c == '\''
+}
+
+// endQuote returns the end quote index for the quoted string that
+// starts at n, or -1 if no matching end quote is found before the end
+// of the line.
+func endQuote(s []byte, n int) int {
+ quote := s[n]
+ for n++; n < len(s); n++ {
+ switch s[n] {
+ case '\\':
+ if quote == '"' || quote == '\'' {
+ n++
+ }
+ case '\n':
+ return -1
+ case quote:
+ return n
+ }
+ }
+ return -1
+}
+
// 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
@@ -259,9 +208,9 @@ func (t *Template) nextItem() []byte {
t.linenum++
i++
}
- // Leading white space up to but not including newline
+ // Leading space up to but not including newline
for i = start; i < len(t.buf); i++ {
- if t.buf[i] == '\n' || !white(t.buf[i]) {
+ if t.buf[i] == '\n' || !isSpace(t.buf[i]) {
break
}
}
@@ -282,6 +231,14 @@ func (t *Template) nextItem() []byte {
if t.buf[i] == '\n' {
break
}
+ if isQuote(t.buf[i]) {
+ i = endQuote(t.buf, i)
+ if i == -1 {
+ t.parseError("unmatched quote")
+ return nil
+ }
+ continue
+ }
if equal(t.buf, i, t.rdelim) {
i += len(t.rdelim)
right = i
@@ -298,7 +255,7 @@ func (t *Template) nextItem() []byte {
firstChar := t.buf[left+len(t.ldelim)]
if firstChar == '.' || firstChar == '#' {
// It's special and the first thing on the line. Is it the last?
- for j := right; j < len(t.buf) && white(t.buf[j]); j++ {
+ for j := right; j < len(t.buf) && isSpace(t.buf[j]); j++ {
if t.buf[j] == '\n' {
// Yes it is. Drop the surrounding space and return the {.foo}
t.linenum++
@@ -310,7 +267,7 @@ func (t *Template) nextItem() []byte {
}
// No it's not. If there's leading space, return that.
if leadingSpace {
- // not trimming space: return leading white space if there is some.
+ // not trimming space: return leading space if there is some.
t.p = left
return t.buf[start:left]
}
@@ -333,23 +290,34 @@ func (t *Template) nextItem() []byte {
return item
}
-// Turn a byte array into a white-space-split array of strings.
+// Turn a byte array into a space-split array of strings,
+// taking into account quoted 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++ {
+ for i := 0; i < len(buf); {
+ // One word per loop
+ for i < len(buf) && isSpace(buf[i]) {
+ i++
}
- if start == p { // no text left
+ if i == len(buf) {
break
}
- s = append(s, string(buf[start:p]))
+ // Got a word
+ start := i
+ if isQuote(buf[i]) {
+ i = endQuote(buf, i)
+ if i < 0 {
+ i = len(buf)
+ } else {
+ i++
+ }
+ }
+ // Even with quotes, break on space only. This handles input
+ // such as {""|} and catches quoting mistakes.
+ for i < len(buf) && !isSpace(buf[i]) {
+ i++
+ }
+ s = append(s, string(buf[start:i]))
}
return s
}
@@ -381,11 +349,17 @@ func (t *Template) analyze(item []byte) (tok int, w []string) {
t.parseError("empty directive")
return
}
- if len(w) > 0 && w[0][0] != '.' {
+ first := w[0]
+ if first[0] != '.' {
+ tok = tokVariable
+ return
+ }
+ if len(first) > 1 && first[1] >= '0' && first[1] <= '9' {
+ // Must be a float.
tok = tokVariable
return
}
- switch w[0] {
+ switch first {
case ".meta-left", ".meta-right", ".space", ".tab":
tok = tokLiteral
return
@@ -433,18 +407,38 @@ func (t *Template) formatter(name string) func(io.Writer, string, ...interface{}
// -- Parsing
-// Allocate a new variable-evaluation element.
+// newVariable allocates a new variable-evaluation element.
func (t *Template) newVariable(words []string) *variableElement {
- // After the final space-separated argument, formatters may be specified separated
- // by pipe symbols, for example: {a b c|d|e}
+ formatters := extractFormatters(words)
+ args := make([]interface{}, len(words))
+
+ // Build argument list, processing any literals
+ for i, word := range words {
+ var lerr os.Error
+ switch word[0] {
+ case '"', '`', '\'':
+ v, err := strconv.Unquote(word)
+ if err == nil && word[0] == '\'' {
+ args[i] = []int(v)[0]
+ } else {
+ args[i], lerr = v, err
+ }
- // Until we learn otherwise, formatters contains a single name: "", the default formatter.
- formatters := []string{""}
- lastWord := words[len(words)-1]
- bar := strings.IndexRune(lastWord, '|')
- if bar >= 0 {
- words[len(words)-1] = lastWord[0:bar]
- formatters = strings.Split(lastWord[bar+1:], "|", -1)
+ case '.', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ v, err := strconv.Btoi64(word, 0)
+ if err == nil {
+ args[i] = v
+ } else {
+ v, err := strconv.Atof64(word)
+ args[i], lerr = v, err
+ }
+
+ default:
+ args[i] = fieldName(word)
+ }
+ if lerr != nil {
+ t.parseError("invalid literal: %q: %s", word, lerr)
+ }
}
// We could remember the function address here and avoid the lookup later,
@@ -457,7 +451,40 @@ func (t *Template) newVariable(words []string) *variableElement {
t.parseError("unknown formatter: %q", f)
}
}
- return &variableElement{t.linenum, words, formatters}
+
+ return &variableElement{t.linenum, args, formatters}
+}
+
+// extractFormatters extracts a list of formatters from words.
+// After the final space-separated argument in a variable, formatters may be
+// specified separated by pipe symbols. For example: {a b c|d|e}
+// The words parameter still has the formatters joined by '|' in the last word.
+// extractFormatters splits formatters, replaces the last word with the content
+// found before the first '|' within it, and returns the formatters obtained.
+// If no formatters are found in words, the default formatter is returned.
+func extractFormatters(words []string) (formatters []string) {
+ // "" is the default formatter.
+ formatters = []string{""}
+ if len(words) == 0 {
+ return
+ }
+ var bar int
+ lastWord := words[len(words)-1]
+ if isQuote(lastWord[0]) {
+ end := endQuote([]byte(lastWord), 0)
+ if end < 0 || end+1 == len(lastWord) || lastWord[end+1] != '|' {
+ return
+ }
+ bar = end + 1
+ } else {
+ bar = strings.IndexRune(lastWord, '|')
+ if bar < 0 {
+ return
+ }
+ }
+ words[len(words)-1] = lastWord[0:bar]
+ formatters = strings.Split(lastWord[bar+1:], "|", -1)
+ return
}
// Grab the next item. If it's simple, just append it to the template.
@@ -469,24 +496,24 @@ func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
case tokComment:
return
case tokText:
- t.elems.Push(&textElement{item})
+ t.elems = append(t.elems, &textElement{item})
return
case tokLiteral:
switch w[0] {
case ".meta-left":
- t.elems.Push(&literalElement{t.ldelim})
+ t.elems = append(t.elems, &literalElement{t.ldelim})
case ".meta-right":
- t.elems.Push(&literalElement{t.rdelim})
+ t.elems = append(t.elems, &literalElement{t.rdelim})
case ".space":
- t.elems.Push(&literalElement{space})
+ t.elems = append(t.elems, &literalElement{space})
case ".tab":
- t.elems.Push(&literalElement{tab})
+ t.elems = append(t.elems, &literalElement{tab})
default:
t.parseError("internal error: unknown literal: %s", w[0])
}
return
case tokVariable:
- t.elems.Push(t.newVariable(w))
+ t.elems = append(t.elems, t.newVariable(w))
return
}
return false, tok, w
@@ -496,11 +523,11 @@ func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
func (t *Template) parseRepeated(words []string) *repeatedElement {
r := new(repeatedElement)
- t.elems.Push(r)
+ t.elems = append(t.elems, r)
r.linenum = t.linenum
r.field = words[2]
// Scan section, collecting true and false (.or) blocks.
- r.start = t.elems.Len()
+ r.start = len(t.elems)
r.or = -1
r.altstart = -1
r.altend = -1
@@ -523,8 +550,8 @@ Loop:
t.parseError("extra .or in .repeated section")
break Loop
}
- r.altend = t.elems.Len()
- r.or = t.elems.Len()
+ r.altend = len(t.elems)
+ r.or = len(t.elems)
case tokSection:
t.parseSection(w)
case tokRepeated:
@@ -538,26 +565,26 @@ Loop:
t.parseError(".alternates inside .or block in .repeated section")
break Loop
}
- r.altstart = t.elems.Len()
+ r.altstart = len(t.elems)
default:
t.parseError("internal error: unknown repeated section item: %s", item)
break Loop
}
}
if r.altend < 0 {
- r.altend = t.elems.Len()
+ r.altend = len(t.elems)
}
- r.end = t.elems.Len()
+ r.end = len(t.elems)
return r
}
func (t *Template) parseSection(words []string) *sectionElement {
s := new(sectionElement)
- t.elems.Push(s)
+ t.elems = append(t.elems, s)
s.linenum = t.linenum
s.field = words[1]
// Scan section, collecting true and false (.or) blocks.
- s.start = t.elems.Len()
+ s.start = len(t.elems)
s.or = -1
Loop:
for {
@@ -578,7 +605,7 @@ Loop:
t.parseError("extra .or in .section")
break Loop
}
- s.or = t.elems.Len()
+ s.or = len(t.elems)
case tokSection:
t.parseSection(w)
case tokRepeated:
@@ -589,7 +616,7 @@ Loop:
t.parseError("internal error: unknown section item: %s", item)
}
}
- s.end = t.elems.Len()
+ s.end = len(t.elems)
return s
}
@@ -618,334 +645,6 @@ func (t *Template) parse() {
// -- Execution
-// 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 (t *Template) lookup(st *state, v reflect.Value, name string) 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 && mtyp.NumOut() == 1 {
- if !isExported(name) {
- t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
- }
- 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:
- if !isExported(name) {
- t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
- }
- return av.FieldByName(name)
- case reflect.Map:
- if v := av.MapIndex(reflect.ValueOf(name)); v.IsValid() {
- return v
- }
- return reflect.Zero(typ.Elem())
- default:
- return reflect.Value{}
- }
- }
- return v
-}
-
-// indirectPtr returns the item numLevels levels of indirection below the value.
-// It is forgiving: if the value is not a pointer, it returns it rather than giving
-// an error. If the pointer is nil, it is returned as is.
-func indirectPtr(v reflect.Value, numLevels int) reflect.Value {
- for i := numLevels; v.IsValid() && i > 0; i++ {
- if p := v; p.Kind() == reflect.Ptr {
- if p.IsNil() {
- return v
- }
- v = p.Elem()
- } else {
- break
- }
- }
- return v
-}
-
-// Walk v through pointers and interfaces, extracting the elements within.
-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
-}
-
-// 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. Leading stars indicate
-// levels of indirection to be applied to the value.
-func (t *Template) findVar(st *state, s string) reflect.Value {
- data := st.data
- flattenedName := strings.TrimLeft(s, "*")
- numStars := len(s) - len(flattenedName)
- s = flattenedName
- if s == "@" {
- return indirectPtr(data, numStars)
- }
- for _, elem := range strings.Split(s, ".", -1) {
- // Look up field; data must be a struct or map.
- data = t.lookup(st, data, elem)
- if !data.IsValid() {
- return reflect.Value{}
- }
- }
- return indirectPtr(data, numStars)
-}
-
-// Is there no data to look at?
-func empty(v reflect.Value) bool {
- v = indirect(v)
- if !v.IsValid() {
- return true
- }
- switch v.Kind() {
- case reflect.Bool:
- return v.Bool() == false
- case reflect.String:
- return v.String() == ""
- case reflect.Struct:
- return false
- case reflect.Map:
- return false
- case reflect.Array:
- return v.Len() == 0
- case reflect.Slice:
- return v.Len() == 0
- }
- return false
-}
-
-// Look up a variable or method, up through the parent if necessary.
-func (t *Template) varValue(name string, st *state) reflect.Value {
- field := t.findVar(st, name)
- if !field.IsValid() {
- if st.parent == nil {
- t.execError(st, t.linenum, "name not found: %s in type %s", name, st.data.Type())
- }
- return t.varValue(name, st.parent)
- }
- return field
-}
-
-func (t *Template) format(wr io.Writer, fmt string, val []interface{}, v *variableElement, st *state) {
- fn := t.formatter(fmt)
- if fn == nil {
- t.execError(st, v.linenum, "missing formatter %s for variable %s", fmt, v.word[0])
- }
- fn(wr, fmt, val...)
-}
-
-// 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) {
- // Turn the words of the invocation into values.
- val := make([]interface{}, len(v.word))
- for i, word := range v.word {
- val[i] = t.varValue(word, st).Interface()
- }
-
- for i, fmt := range v.fmts[:len(v.fmts)-1] {
- b := &st.buf[i&1]
- b.Reset()
- t.format(b, fmt, val, v, st)
- val = val[0:1]
- val[0] = b.Bytes()
- }
- t.format(st.wr, v.fmts[len(v.fmts)-1], val, v, st)
-}
-
-// 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.ValueOf(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.IsValid() {
- t.execError(st, s.linenum, ".section: cannot find field %s in %s", s.field, 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.Value {
- for j := 0; j < v.Type().NumMethod(); j++ {
- mth := v.Type().Method(j)
- fv := v.Method(j)
- ft := fv.Type()
- // 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 := ft.Out(0)
- if ct.Kind() != reflect.Chan ||
- ct.ChanDir()&reflect.RecvDir == 0 {
- continue
- }
- return fv.Call(nil)[0]
- }
- return reflect.Value{}
-}
-
-// 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.IsValid() {
- t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, st.data.Type())
- }
- field = indirect(field)
-
- start, end := r.start, r.or
- if end < 0 {
- end = r.end
- }
- if r.altstart >= 0 {
- end = r.altstart
- }
- first := true
-
- // Code common to all the loops.
- loopBody := func(newst *state) {
- // .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)
- }
- }
-
- if array := field; array.Kind() == reflect.Array || array.Kind() == reflect.Slice {
- for j := 0; j < array.Len(); j++ {
- loopBody(st.clone(array.Index(j)))
- }
- } else if m := field; m.Kind() == reflect.Map {
- for _, key := range m.MapKeys() {
- loopBody(st.clone(m.MapIndex(key)))
- }
- } else if ch := iter(field); ch.IsValid() {
- for {
- e, ok := ch.Recv()
- if !ok {
- break
- }
- loopBody(st.clone(e))
- }
- } 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
-}
-
-// checkError is a deferred function to turn a panic with type *Error into a plain error return.
-// Other panics are unexpected and so are re-enabled.
-func checkError(error *os.Error) {
- if v := recover(); v != nil {
- if e, ok := v.(*Error); ok {
- *error = e
- } else {
- // runtime errors should crash
- panic(v)
- }
- }
-}
-
// -- Public interface
// Parse initializes a Template by parsing its definition. The string
@@ -983,7 +682,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
val := reflect.ValueOf(data)
defer checkError(&err)
t.p = 0
- t.execute(0, t.elems.Len(), &state{parent: nil, data: val, wr: wr})
+ t.execute(0, len(t.elems), &state{parent: nil, data: val, wr: wr})
return nil
}
diff --git a/src/pkg/template/template_test.go b/src/pkg/template/template_test.go
index d21a5397a..99b23c288 100644
--- a/src/pkg/template/template_test.go
+++ b/src/pkg/template/template_test.go
@@ -94,10 +94,15 @@ func multiword(w io.Writer, format string, value ...interface{}) {
}
}
+func printf(w io.Writer, format string, v ...interface{}) {
+ io.WriteString(w, fmt.Sprintf(v[0].(string), v[1:]...))
+}
+
var formatters = FormatterMap{
"uppercase": writer(uppercase),
"+1": writer(plus1),
"multiword": multiword,
+ "printf": printf,
}
var tests = []*Test{
@@ -138,6 +143,36 @@ var tests = []*Test{
out: "nil pointer: <nil>=77\n",
},
+ &Test{
+ in: `{"Strings" ":"} {""} {"|"} {"\t\u0123 \x23\\"} {"\"}{\\"}`,
+
+ out: "Strings: | \t\u0123 \x23\\ \"}{\\",
+ },
+
+ &Test{
+ in: "{`Raw strings` `:`} {``} {`|`} {`\\t\\u0123 \\x23\\`} {`}{\\`}",
+
+ out: "Raw strings: | \\t\\u0123 \\x23\\ }{\\",
+ },
+
+ &Test{
+ in: "Characters: {'a'} {'\\u0123'} {' '} {'{'} {'|'} {'}'}",
+
+ out: "Characters: 97 291 32 123 124 125",
+ },
+
+ &Test{
+ in: "Integers: {1} {-2} {+42} {0777} {0x0a}",
+
+ out: "Integers: 1 -2 42 511 10",
+ },
+
+ &Test{
+ in: "Floats: {.5} {-.5} {1.1} {-2.2} {+42.1} {1e10} {1.2e-3} {1.2e3} {-1.2e3}",
+
+ out: "Floats: 0.5 -0.5 1.1 -2.2 42.1 1e+10 0.0012 1200 -1200",
+ },
+
// Method at top level
&Test{
in: "ptrmethod={PointerMethod}\n",
@@ -723,6 +758,14 @@ var formatterTests = []Test{
in: "{Integer|||||}", // empty string is a valid formatter
out: "77",
},
+ {
+ in: `{"%.02f 0x%02X" 1.1 10|printf}`,
+ out: "1.10 0x0A",
+ },
+ {
+ in: `{""|}{""||}{""|printf}`, // Issue #1896.
+ out: "",
+ },
}
func TestFormatters(t *testing.T) {