Commit 73b7afbe authored by Rob Pike's avatar Rob Pike

template: delete old template code.

It's already in old/template; make that build.
Update a couple of references to point to the old template.
They can be updated later.
Update goplay to use exp/template.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4902046
parent ff0240da
......@@ -3,9 +3,9 @@ package main
import (
"http"
"io/ioutil"
"old/template"
"os"
"regexp"
"template"
)
type Page struct {
......
......@@ -3,8 +3,8 @@ package main
import (
"http"
"io/ioutil"
"old/template"
"os"
"template"
)
type Page struct {
......
......@@ -3,9 +3,9 @@ package main
import (
"http"
"io/ioutil"
"old/template"
"os"
"regexp"
"template"
)
type Page struct {
......
package main
import (
"old/template"
"os"
"template"
"io/ioutil"
)
......
......@@ -7,7 +7,7 @@ Covered in this codelab:
<ul>
<li>Creating a data structure with load and save methods</li>
<li>Using the <code>http</code> package to build web applications
<li>Using the <code>template</code> package to process HTML templates</li>
<li>Using the <code>old/template</code> package to process HTML templates</li>
<li>Using the <code>regexp</code> package to validate user input</li>
<li>Using closures</li>
</ul>
......@@ -426,25 +426,27 @@ This function will work fine, but all that hard-coded HTML is ugly.
Of course, there is a better way.
</p>
<h2>The <code>template</code> package</h2>
<h2>The <code>old/template</code> package</h2>
<p>
The <code>template</code> package is part of the Go standard library. We can
use <code>template</code> to keep the HTML in a separate file, allowing
The <code>old/template</code> package is part of the Go standard library.
(A new template package is coming; this code lab will be updated soon.)
We can
use <code>old/template</code> to keep the HTML in a separate file, allowing
us to change the layout of our edit page without modifying the underlying Go
code.
</p>
<p>
First, we must add <code>template</code> to the list of imports:
First, we must add <code>old/template</code> to the list of imports:
</p>
<pre>
import (
"http"
"io/ioutil"
<b>"old/template"</b>
"os"
<b>"template"</b>
)
</pre>
......
......@@ -8,8 +8,8 @@ import (
"go/ast"
"go/token"
"log"
"old/template"
"os"
"template"
)
var (
......
......@@ -7,7 +7,7 @@ Covered in this codelab:
<ul>
<li>Creating a data structure with load and save methods</li>
<li>Using the <code>http</code> package to build web applications
<li>Using the <code>template</code> package to process HTML templates</li>
<li>Using the <code>old/template</code> package to process HTML templates</li>
<li>Using the <code>regexp</code> package to validate user input</li>
<li>Using closures</li>
</ul>
......@@ -366,25 +366,27 @@ This function will work fine, but all that hard-coded HTML is ugly.
Of course, there is a better way.
</p>
<h2>The <code>template</code> package</h2>
<h2>The <code>old/template</code> package</h2>
<p>
The <code>template</code> package is part of the Go standard library. We can
use <code>template</code> to keep the HTML in a separate file, allowing
The <code>old/template</code> package is part of the Go standard library.
(A new template package is coming; this code lab will be updated soon.)
We can
use <code>old/template</code> to keep the HTML in a separate file, allowing
us to change the layout of our edit page without modifying the underlying Go
code.
</p>
<p>
First, we must add <code>template</code> to the list of imports:
First, we must add <code>old/template</code> to the list of imports:
</p>
<pre>
import (
"http"
"io/ioutil"
<b>"old/template"</b>
"os"
<b>"template"</b>
)
</pre>
......
......@@ -2926,7 +2926,7 @@ import (
"http"
"io"
"log"
"template"
"old/template" // New template package coming soon...
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
......
......@@ -14,7 +14,7 @@ import (
"os"
"runtime"
"strconv"
"template"
"exp/template"
)
var (
......@@ -142,18 +142,10 @@ func run(cmd ...string) ([]byte, os.Error) {
return exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
}
var frontPage, output *template.Template // HTML templates
var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template
var output = template.Must(template.New("output").Parse(outputText)) // HTML template
func init() {
frontPage = template.New(nil)
frontPage.SetDelims("«", "»")
if err := frontPage.Parse(frontPageText); err != nil {
panic(err)
}
output = template.MustParse(outputText, nil)
}
var outputText = `<pre>{@|html}</pre>`
var outputText = `<pre>{{html .}}</pre>`
var frontPageText = `<!doctype html>
<html>
......@@ -264,7 +256,7 @@ function compileUpdate() {
</head>
<body>
<table width="100%"><tr><td width="60%" valign="top">
<textarea autofocus="true" id="edit" spellcheck="false" onkeydown="keyHandler(event);" onkeyup="autocompile();">«@|html»</textarea>
<textarea autofocus="true" id="edit" spellcheck="false" onkeydown="keyHandler(event);" onkeyup="autocompile();">{{html .}}</textarea>
<div class="hints">
(Shift-Enter to compile and run.)&nbsp;&nbsp;&nbsp;&nbsp;
<input type="checkbox" id="autocompile" value="checked" /> Compile and run after each keystroke
......
......@@ -131,6 +131,7 @@ DIRS=\
net/dict\
net/textproto\
netchan\
old/template\
os\
os/signal\
os/user\
......@@ -156,7 +157,6 @@ DIRS=\
syscall\
syslog\
tabwriter\
template\
testing\
testing/iotest\
testing/quick\
......
......@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include ../../Make.inc
include ../../../Make.inc
TARG=old/template
GOFILES=\
......@@ -11,4 +11,4 @@ GOFILES=\
format.go\
parse.go\
include ../../Make.pkg
include ../../../Make.pkg
# 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.
include ../../Make.inc
TARG=template
GOFILES=\
doc.go\
execute.go\
format.go\
parse.go\
include ../../Make.pkg
// 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
// 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, ".") {
// 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
}
// 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 (
"bytes"
"fmt"
"io"
)
// 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, format string, value ...interface{}) {
if len(value) == 1 {
if b, ok := value[0].([]byte); ok {
w.Write(b)
return
}
}
fmt.Fprint(w, value...)
}
var (
esc_quot = []byte("&#34;") // shorter than "&quot;"
esc_apos = []byte("&#39;") // shorter than "&apos;"
esc_amp = []byte("&amp;")
esc_lt = []byte("&lt;")
esc_gt = []byte("&gt;")
)
// HTMLEscape writes to w the properly escaped HTML equivalent
// of the plain text data s.
func HTMLEscape(w io.Writer, s []byte) {
var esc []byte
last := 0
for i, c := range s {
switch c {
case '"':
esc = esc_quot
case '\'':
esc = esc_apos
case '&':
esc = esc_amp
case '<':
esc = esc_lt
case '>':
esc = esc_gt
default:
continue
}
w.Write(s[last:i])
w.Write(esc)
last = i + 1
}
w.Write(s[last:])
}
// HTMLFormatter formats arbitrary values for HTML
func HTMLFormatter(w io.Writer, format string, value ...interface{}) {
ok := false
var b []byte
if len(value) == 1 {
b, ok = value[0].([]byte)
}
if !ok {
var buf bytes.Buffer
fmt.Fprint(&buf, value...)
b = buf.Bytes()
}
HTMLEscape(w, b)
}
// 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 parse a template.
package template
import (
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"unicode"
"utf8"
)
// 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) }
// 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{'}'}
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, string, ...interface{})
// 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 invocation to be evaluated
type variableElement struct {
linenum int
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
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
// Parsed results:
elems []interface{}
}
// 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 = make([]interface{}, 0, 16)
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 ...interface{}) {
panic(&Error{line, fmt.Sprintf(err, args...)})
}
// Report error, panic to terminate parsing.
// The line number comes from the template state.
func (t *Template) parseError(err string, args ...interface{}) {
panic(&Error{t.linenum, fmt.Sprintf(err, args...)})
}
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// -- Lexical analysis
// 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 {
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
}
// 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
// strings. Tokens stop at (but include, if plain text) a newline.
// Action tokens on a line by themselves drop any space on
// either side, up to and including the newline.
func (t *Template) nextItem() []byte {
startOfLine := t.p == 0 || t.buf[t.p-1] == '\n'
start := t.p
var i int
newline := func() {
t.linenum++
i++
}
// Leading space up to but not including newline
for i = start; i < len(t.buf); i++ {
if t.buf[i] == '\n' || !isSpace(t.buf[i]) {
break
}
}
leadingSpace := i > start
// What's left is nothing, newline, delimited string, or plain text
switch {
case i == len(t.buf):
// EOF; nothing to do
case t.buf[i] == '\n':
newline()
case equal(t.buf, i, t.ldelim):
left := i // Start of left delimiter.
right := -1 // Will be (immediately after) right delimiter.
haveText := false // Delimiters contain text.
i += len(t.ldelim)
// Find the end of the action.
for ; i < len(t.buf); i++ {
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
break
}
haveText = true
}
if right < 0 {
t.parseError("unmatched opening delimiter")
return nil
}
// Is this a special action (starts with '.' or '#') and the only thing on the line?
if startOfLine && haveText {
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) && isSpace(t.buf[j]); j++ {
if t.buf[j] == '\n' {
// Yes it is. Drop the surrounding space and return the {.foo}
t.linenum++
t.p = j + 1
return t.buf[left:right]
}
}
}
}
// No it's not. If there's leading space, return that.
if leadingSpace {
// not trimming space: return leading space if there is some.
t.p = left
return t.buf[start:left]
}
// Return the word, leave the trailing space.
start = left
break
default:
for ; i < len(t.buf); i++ {
if t.buf[i] == '\n' {
newline()
break
}
if equal(t.buf, i, t.ldelim) {
break
}
}
}
item := t.buf[start:i]
t.p = i
return item
}
// 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)
for i := 0; i < len(buf); {
// One word per loop
for i < len(buf) && isSpace(buf[i]) {
i++
}
if i == len(buf) {
break
}
// 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
}
// 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
}
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 first {
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
}
// formatter returns the Formatter with the given name in the Template, or nil if none exists.
func (t *Template) formatter(name string) func(io.Writer, string, ...interface{}) {
if t.fmap != nil {
if fn := t.fmap[name]; fn != nil {
return fn
}
}
return builtins[name]
}
// -- Parsing
// newVariable allocates a new variable-evaluation element.
func (t *Template) newVariable(words []string) *variableElement {
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
}
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,
// 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?
for _, f := range formatters {
if t.formatter(f) == nil {
t.parseError("unknown formatter: %q", f)
}
}
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:], "|")
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 = append(t.elems, &textElement{item})
return
case tokLiteral:
switch w[0] {
case ".meta-left":
t.elems = append(t.elems, &literalElement{t.ldelim})
case ".meta-right":
t.elems = append(t.elems, &literalElement{t.rdelim})
case ".space":
t.elems = append(t.elems, &literalElement{space})
case ".tab":
t.elems = append(t.elems, &literalElement{tab})
default:
t.parseError("internal error: unknown literal: %s", w[0])
}
return
case tokVariable:
t.elems = append(t.elems, t.newVariable(w))
return
}
return false, tok, w
}
// parseRepeated and parseSection are mutually recursive
func (t *Template) parseRepeated(words []string) *repeatedElement {
r := new(repeatedElement)
t.elems = append(t.elems, r)
r.linenum = t.linenum
r.field = words[2]
// Scan section, collecting true and false (.or) blocks.
r.start = len(t.elems)
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")
break
}
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")
break Loop
}
r.altend = len(t.elems)
r.or = len(t.elems)
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 = len(t.elems)
default:
t.parseError("internal error: unknown repeated section item: %s", item)
break Loop
}
}
if r.altend < 0 {
r.altend = len(t.elems)
}
r.end = len(t.elems)
return r
}
func (t *Template) parseSection(words []string) *sectionElement {
s := new(sectionElement)
t.elems = append(t.elems, s)
s.linenum = t.linenum
s.field = words[1]
// Scan section, collecting true and false (.or) blocks.
s.start = len(t.elems)
s.or = -1
Loop:
for {
item := t.nextItem()
if len(item) == 0 {
t.parseError("missing .end for .section")
break
}
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")
break Loop
}
s.or = len(t.elems)
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 = len(t.elems)
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
// -- 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) (err os.Error) {
if t.elems == nil {
return &Error{1, "template not allocated with New"}
}
if !validDelim(t.ldelim) || !validDelim(t.rdelim) {
return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)}
}
defer checkError(&err)
t.buf = []byte(s)
t.p = 0
t.linenum = 1
t.parse()
return nil
}
// ParseFile is like Parse but reads the template definition from the
// named file.
func (t *Template) ParseFile(filename string) (err os.Error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
return t.Parse(string(b))
}
// Execute applies a parsed template to the specified data object,
// generating output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
// Extract the driver data.
val := reflect.ValueOf(data)
defer checkError(&err)
t.p = 0
t.execute(0, len(t.elems), &state{parent: nil, data: val, wr: wr})
return nil
}
// 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 = []byte(left)
t.rdelim = []byte(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
}
// ParseFile is a wrapper function that creates a Template with default
// parameters (such as {} for metacharacters). The filename identifies
// a file containing 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 ParseFile(filename string, fmap FormatterMap) (t *Template, err os.Error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return Parse(string(b), fmap)
}
// 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.MustParse error: " + err.String())
}
return t
}
// MustParseFile is like ParseFile but panics if the file cannot be read
// or the template cannot be parsed.
func MustParseFile(filename string, fmap FormatterMap) *Template {
b, err := ioutil.ReadFile(filename)
if err != nil {
panic("template.MustParseFile error: " + err.String())
}
return MustParse(string(b), fmap)
}
// 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 (
"bytes"
"container/vector"
"fmt"
"io"
"io/ioutil"
"json"
"os"
"strings"
"testing"
)
type Test struct {
in, out, err string
}
type T struct {
Item string
Value string
}
type U struct {
Mp map[string]int
}
type S struct {
Header string
HeaderPtr *string
Integer int
IntegerPtr *int
NilPtr *int
InnerT T
InnerPointerT *T
Data []T
Pdata []*T
Empty []*T
Emptystring string
Null []*T
Vec *vector.Vector
True bool
False bool
Mp map[string]string
JSON interface{}
Innermap U
Stringmap map[string]string
Ptrmap map[string]*string
Iface interface{}
Ifaceptr interface{}
}
func (s *S) PointerMethod() string { return "ptrmethod!" }
func (s S) ValueMethod() string { return "valmethod!" }
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, string, ...interface{}) {
return func(w io.Writer, format string, v ...interface{}) {
if len(v) != 1 {
panic("test writer expected one arg")
}
io.WriteString(w, f(v[0]))
}
}
func multiword(w io.Writer, format string, value ...interface{}) {
for _, v := range value {
fmt.Fprintf(w, "<%v>", v)
}
}
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{
// Simple
&Test{"", "", ""},
&Test{"abc", "abc", ""},
&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", "", ""},
&Test{"\tSome Text\t\n", "\tSome Text\t\n", ""},
&Test{" {.meta-right} {.meta-right} {.meta-right} \n", " } } } \n", ""},
// Variables at top level
&Test{
in: "{Header}={Integer}\n",
out: "Header=77\n",
},
&Test{
in: "Pointers: {*HeaderPtr}={*IntegerPtr}\n",
out: "Pointers: Header=77\n",
},
&Test{
in: "Stars but not pointers: {*Header}={*Integer}\n",
out: "Stars but not pointers: Header=77\n",
},
&Test{
in: "nil pointer: {*NilPtr}={*Integer}\n",
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",
out: "ptrmethod=ptrmethod!\n",
},
&Test{
in: "valmethod={ValueMethod}\n",
out: "valmethod=valmethod!\n",
},
// Section
&Test{
in: "{.section Data }\n" +
"some text for the section\n" +
"{.end}\n",
out: "some text for the section\n",
},
&Test{
in: "{.section Data }\n" +
"{Header}={Integer}\n" +
"{.end}\n",
out: "Header=77\n",
},
&Test{
in: "{.section Pdata }\n" +
"{Header}={Integer}\n" +
"{.end}\n",
out: "Header=77\n",
},
&Test{
in: "{.section Pdata }\n" +
"data present\n" +
"{.or}\n" +
"data not present\n" +
"{.end}\n",
out: "data present\n",
},
&Test{
in: "{.section Empty }\n" +
"data present\n" +
"{.or}\n" +
"data not present\n" +
"{.end}\n",
out: "data not present\n",
},
&Test{
in: "{.section Null }\n" +
"data present\n" +
"{.or}\n" +
"data not present\n" +
"{.end}\n",
out: "data not present\n",
},
&Test{
in: "{.section Pdata }\n" +
"{Header}={Integer}\n" +
"{.section @ }\n" +
"{Header}={Integer}\n" +
"{.end}\n" +
"{.end}\n",
out: "Header=77\n" +
"Header=77\n",
},
&Test{
in: "{.section Data}{.end} {Header}\n",
out: " Header\n",
},
&Test{
in: "{.section Integer}{@}{.end}",
out: "77",
},
// Repeated
&Test{
in: "{.section Pdata }\n" +
"{.repeated section @ }\n" +
"{Item}={Value}\n" +
"{.end}\n" +
"{.end}\n",
out: "ItemNumber1=ValueNumber1\n" +
"ItemNumber2=ValueNumber2\n",
},
&Test{
in: "{.section Pdata }\n" +
"{.repeated section @ }\n" +
"{Item}={Value}\n" +
"{.or}\n" +
"this should not appear\n" +
"{.end}\n" +
"{.end}\n",
out: "ItemNumber1=ValueNumber1\n" +
"ItemNumber2=ValueNumber2\n",
},
&Test{
in: "{.section @ }\n" +
"{.repeated section Empty }\n" +
"{Item}={Value}\n" +
"{.or}\n" +
"this should appear: empty field\n" +
"{.end}\n" +
"{.end}\n",
out: "this should appear: empty field\n",
},
&Test{
in: "{.repeated section Pdata }\n" +
"{Item}\n" +
"{.alternates with}\n" +
"is\nover\nmultiple\nlines\n" +
"{.end}\n",
out: "ItemNumber1\n" +
"is\nover\nmultiple\nlines\n" +
"ItemNumber2\n",
},
&Test{
in: "{.repeated section Pdata }\n" +
"{Item}\n" +
"{.alternates with}\n" +
"is\nover\nmultiple\nlines\n" +
" {.end}\n",
out: "ItemNumber1\n" +
"is\nover\nmultiple\nlines\n" +
"ItemNumber2\n",
},
&Test{
in: "{.section Pdata }\n" +
"{.repeated section @ }\n" +
"{Item}={Value}\n" +
"{.alternates with}DIVIDER\n" +
"{.or}\n" +
"this should not appear\n" +
"{.end}\n" +
"{.end}\n",
out: "ItemNumber1=ValueNumber1\n" +
"DIVIDER\n" +
"ItemNumber2=ValueNumber2\n",
},
&Test{
in: "{.repeated section Vec }\n" +
"{@}\n" +
"{.end}\n",
out: "elt1\n" +
"elt2\n",
},
// Same but with a space before {.end}: was a bug.
&Test{
in: "{.repeated section Vec }\n" +
"{@} {.end}\n",
out: "elt1 elt2 \n",
},
&Test{
in: "{.repeated section Integer}{.end}",
err: "line 1: .repeated: cannot repeat Integer (type int)",
},
// Nested names
&Test{
in: "{.section @ }\n" +
"{InnerT.Item}={InnerT.Value}\n" +
"{.end}",
out: "ItemNumber1=ValueNumber1\n",
},
&Test{
in: "{.section @ }\n" +
"{InnerT.Item}={.section InnerT}{.section Value}{@}{.end}{.end}\n" +
"{.end}",
out: "ItemNumber1=ValueNumber1\n",
},
&Test{
in: "{.section Emptystring}emptystring{.end}\n" +
"{.section Header}header{.end}\n",
out: "\nheader\n",
},
&Test{
in: "{.section True}1{.or}2{.end}\n" +
"{.section False}3{.or}4{.end}\n",
out: "1\n4\n",
},
// Maps
&Test{
in: "{Mp.mapkey}\n",
out: "Ahoy!\n",
},
&Test{
in: "{Innermap.Mp.innerkey}\n",
out: "55\n",
},
&Test{
in: "{.section Innermap}{.section Mp}{innerkey}{.end}{.end}\n",
out: "55\n",
},
&Test{
in: "{.section JSON}{.repeated section maps}{a}{b}{.end}{.end}\n",
out: "1234\n",
},
&Test{
in: "{Stringmap.stringkey1}\n",
out: "stringresult\n",
},
&Test{
in: "{.repeated section Stringmap}\n" +
"{@}\n" +
"{.end}",
out: "stringresult\n" +
"stringresult\n",
},
&Test{
in: "{.repeated section Stringmap}\n" +
"\t{@}\n" +
"{.end}",
out: "\tstringresult\n" +
"\tstringresult\n",
},
&Test{
in: "{*Ptrmap.stringkey1}\n",
out: "pointedToString\n",
},
&Test{
in: "{.repeated section Ptrmap}\n" +
"{*@}\n" +
"{.end}",
out: "pointedToString\n" +
"pointedToString\n",
},
// Interface values
&Test{
in: "{Iface}",
out: "[1 2 3]",
},
&Test{
in: "{.repeated section Iface}{@}{.alternates with} {.end}",
out: "1 2 3",
},
&Test{
in: "{.section Iface}{@}{.end}",
out: "[1 2 3]",
},
&Test{
in: "{.section Ifaceptr}{Item} {Value}{.end}",
out: "Item Value",
},
}
func TestAll(t *testing.T) {
// Parse
testAll(t, func(test *Test) (*Template, os.Error) { return Parse(test.in, formatters) })
// ParseFile
testAll(t, func(test *Test) (*Template, os.Error) {
err := ioutil.WriteFile("_test/test.tmpl", []byte(test.in), 0600)
if err != nil {
t.Error("unexpected write error:", err)
return nil, err
}
return ParseFile("_test/test.tmpl", formatters)
})
// tmpl.ParseFile
testAll(t, func(test *Test) (*Template, os.Error) {
err := ioutil.WriteFile("_test/test.tmpl", []byte(test.in), 0600)
if err != nil {
t.Error("unexpected write error:", err)
return nil, err
}
tmpl := New(formatters)
return tmpl, tmpl.ParseFile("_test/test.tmpl")
})
}
func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) {
s := new(S)
// initialized by hand for clarity.
s.Header = "Header"
s.HeaderPtr = &s.Header
s.Integer = 77
s.IntegerPtr = &s.Integer
s.InnerT = t1
s.Data = []T{t1, t2}
s.Pdata = []*T{&t1, &t2}
s.Empty = []*T{}
s.Null = nil
s.Vec = new(vector.Vector)
s.Vec.Push("elt1")
s.Vec.Push("elt2")
s.True = true
s.False = false
s.Mp = make(map[string]string)
s.Mp["mapkey"] = "Ahoy!"
json.Unmarshal([]byte(`{"maps":[{"a":1,"b":2},{"a":3,"b":4}]}`), &s.JSON)
s.Innermap.Mp = make(map[string]int)
s.Innermap.Mp["innerkey"] = 55
s.Stringmap = make(map[string]string)
s.Stringmap["stringkey1"] = "stringresult" // the same value so repeated section is order-independent
s.Stringmap["stringkey2"] = "stringresult"
s.Ptrmap = make(map[string]*string)
x := "pointedToString"
s.Ptrmap["stringkey1"] = &x // the same value so repeated section is order-independent
s.Ptrmap["stringkey2"] = &x
s.Iface = []int{1, 2, 3}
s.Ifaceptr = &T{"Item", "Value"}
var buf bytes.Buffer
for _, test := range tests {
buf.Reset()
tmpl, err := parseFunc(test)
if err != nil {
t.Error("unexpected parse error: ", err)
continue
}
err = tmpl.Execute(&buf, s)
if test.err == "" {
if err != nil {
t.Error("unexpected execute error:", err)
}
} else {
if err == nil {
t.Errorf("expected execute error %q, got nil", test.err)
} else if err.String() != test.err {
t.Errorf("expected execute error %q, got %q", test.err, err.String())
}
}
if buf.String() != test.out {
t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String())
}
}
}
func TestMapDriverType(t *testing.T) {
mp := map[string]string{"footer": "Ahoy!"}
tmpl, err := Parse("template: {footer}", nil)
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer
err = tmpl.Execute(&b, mp)
if err != nil {
t.Error("unexpected execute error:", err)
}
s := b.String()
expect := "template: Ahoy!"
if s != expect {
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
}
}
func TestMapNoEntry(t *testing.T) {
mp := make(map[string]int)
tmpl, err := Parse("template: {notthere}!", nil)
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer
err = tmpl.Execute(&b, mp)
if err != nil {
t.Error("unexpected execute error:", err)
}
s := b.String()
expect := "template: 0!"
if s != expect {
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
}
}
func TestStringDriverType(t *testing.T) {
tmpl, err := Parse("template: {@}", nil)
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer
err = tmpl.Execute(&b, "hello")
if err != nil {
t.Error("unexpected execute error:", err)
}
s := b.String()
expect := "template: hello"
if s != expect {
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
}
}
func TestTwice(t *testing.T) {
tmpl, err := Parse("template: {@}", nil)
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer
err = tmpl.Execute(&b, "hello")
if err != nil {
t.Error("unexpected parse error:", err)
}
s := b.String()
expect := "template: hello"
if s != expect {
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
}
err = tmpl.Execute(&b, "hello")
if err != nil {
t.Error("unexpected parse error:", err)
}
s = b.String()
expect += expect
if s != expect {
t.Errorf("failed passing string as data: expected %q got %q", expect, 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 bytes.Buffer
err = tmpl.Execute(&b, "hello")
s := b.String()
if s != "template: hello"+ldelim+rdelim {
t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s)
}
}
}
}
// Test that a variable evaluates to the field itself and does not further indirection
func TestVarIndirection(t *testing.T) {
s := new(S)
// initialized by hand for clarity.
s.InnerPointerT = &t1
var buf bytes.Buffer
input := "{.section @}{InnerPointerT}{.end}"
tmpl, err := Parse(input, nil)
if err != nil {
t.Fatal("unexpected parse error:", err)
}
err = tmpl.Execute(&buf, s)
if err != nil {
t.Fatal("unexpected execute error:", err)
}
expect := fmt.Sprintf("%v", &t1) // output should be hex address of t1
if buf.String() != expect {
t.Errorf("for %q: expected %q got %q", input, expect, buf.String())
}
}
func TestHTMLFormatterWithByte(t *testing.T) {
s := "Test string."
b := []byte(s)
var buf bytes.Buffer
HTMLFormatter(&buf, "", b)
bs := buf.String()
if bs != s {
t.Errorf("munged []byte, expected: %s got: %s", s, bs)
}
}
type UF struct {
I int
s string
}
func TestReferenceToUnexported(t *testing.T) {
u := &UF{3, "hello"}
var buf bytes.Buffer
input := "{.section @}{I}{s}{.end}"
tmpl, err := Parse(input, nil)
if err != nil {
t.Fatal("unexpected parse error:", err)
}
err = tmpl.Execute(&buf, u)
if err == nil {
t.Fatal("expected execute error, got none")
}
if strings.Index(err.String(), "not exported") < 0 {
t.Fatal("expected unexported error; got", err)
}
}
var formatterTests = []Test{
{
in: "{Header|uppercase}={Integer|+1}\n" +
"{Header|html}={Integer|str}\n",
out: "HEADER=78\n" +
"Header=77\n",
},
{
in: "{Header|uppercase}={Integer Header|multiword}\n" +
"{Header|html}={Header Integer|multiword}\n" +
"{Header|html}={Header Integer}\n",
out: "HEADER=<77><Header>\n" +
"Header=<Header><77>\n" +
"Header=Header77\n",
},
{
in: "{Raw}\n" +
"{Raw|html}\n",
out: "a <&> b\n" +
"a &lt;&amp;&gt; b\n",
},
{
in: "{Bytes}",
out: "hello",
},
{
in: "{Raw|uppercase|html|html}",
out: "A &amp;lt;&amp;amp;&amp;gt; B",
},
{
in: "{Header Integer|multiword|html}",
out: "&lt;Header&gt;&lt;77&gt;",
},
{
in: "{Integer|no_formatter|html}",
err: `unknown formatter: "no_formatter"`,
},
{
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) {
data := map[string]interface{}{
"Header": "Header",
"Integer": 77,
"Raw": "a <&> b",
"Bytes": []byte("hello"),
}
for _, c := range formatterTests {
tmpl, err := Parse(c.in, formatters)
if err != nil {
if c.err == "" {
t.Error("unexpected parse error:", err)
continue
}
if strings.Index(err.String(), c.err) < 0 {
t.Errorf("unexpected error: expected %q, got %q", c.err, err.String())
continue
}
} else {
if c.err != "" {
t.Errorf("For %q, expected error, got none.", c.in)
continue
}
buf := bytes.NewBuffer(nil)
err = tmpl.Execute(buf, data)
if err != nil {
t.Error("unexpected Execute error: ", err)
continue
}
actual := buf.String()
if actual != c.out {
t.Errorf("for %q: expected %q but got %q.", c.in, c.out, actual)
}
}
}
}
......@@ -17,8 +17,8 @@ import (
"bufio"
"fmt"
"io"
"old/template"
"os"
"template"
)
func main() {
......@@ -37,7 +37,7 @@ func main() {
}
fmt.Fprintln(out, `}`)
}
do(recv)
do(send)
do(recvOrder)
......@@ -54,8 +54,8 @@ func run(t *template.Template, a interface{}, out io.Writer) {
}
}
type arg struct{
def bool
type arg struct {
def bool
nreset int
}
......@@ -466,7 +466,7 @@ func next() bool {
}
// increment last choice sequence
cp = len(choices)-1
cp = len(choices) - 1
for cp >= 0 && choices[cp].i == choices[cp].n-1 {
cp--
}
......@@ -479,4 +479,3 @@ func next() bool {
cp = 0
return true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment