Commit 13f88977 authored by Rob Pike's avatar Rob Pike

exp/template: add template sets, allowing templates to reference one another

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4673042
parent 72efdea2
...@@ -9,5 +9,6 @@ GOFILES=\ ...@@ -9,5 +9,6 @@ GOFILES=\
exec.go\ exec.go\
lex.go\ lex.go\
parse.go\ parse.go\
set.go\
include ../../../Make.pkg include ../../../Make.pkg
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"io" "io"
"os" "os"
"reflect" "reflect"
"strings"
) )
// state represents the state of an execution. It's not part of the // state represents the state of an execution. It's not part of the
...@@ -17,6 +18,7 @@ import ( ...@@ -17,6 +18,7 @@ import (
type state struct { type state struct {
tmpl *Template tmpl *Template
wr io.Writer wr io.Writer
set *Set
line int // line number for errors line int // line number for errors
} }
...@@ -33,11 +35,19 @@ func (s *state) error(err os.Error) { ...@@ -33,11 +35,19 @@ func (s *state) error(err os.Error) {
// Execute applies a parsed template to the specified data object, // Execute applies a parsed template to the specified data object,
// writing the output to wr. // writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) { func (t *Template) Execute(wr io.Writer, data interface{}) os.Error {
return t.ExecuteInSet(wr, data, nil)
}
// ExecuteInSet applies a parsed template to the specified data object,
// writing the output to wr. Nested template invocations will be resolved
// from the specified set.
func (t *Template) ExecuteInSet(wr io.Writer, data interface{}, set *Set) (err os.Error) {
defer t.recover(&err) defer t.recover(&err)
state := &state{ state := &state{
tmpl: t, tmpl: t,
wr: wr, wr: wr,
set: set,
line: 1, line: 1,
} }
if t.root == nil { if t.root == nil {
...@@ -49,7 +59,6 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) { ...@@ -49,7 +59,6 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
// Walk functions step through the major pieces of the template structure, // Walk functions step through the major pieces of the template structure,
// generating output as they go. // generating output as they go.
func (s *state) walk(data reflect.Value, n node) { func (s *state) walk(data reflect.Value, n node) {
switch n := n.(type) { switch n := n.(type) {
case *actionNode: case *actionNode:
...@@ -59,17 +68,56 @@ func (s *state) walk(data reflect.Value, n node) { ...@@ -59,17 +68,56 @@ func (s *state) walk(data reflect.Value, n node) {
for _, node := range n.nodes { for _, node := range n.nodes {
s.walk(data, node) s.walk(data, node)
} }
case *ifNode:
s.walkIfOrWith(nodeIf, data, n.pipeline, n.list, n.elseList)
case *rangeNode: case *rangeNode:
s.walkRange(data, n) s.walkRange(data, n)
case *textNode: case *textNode:
if _, err := s.wr.Write(n.text); err != nil { if _, err := s.wr.Write(n.text); err != nil {
s.error(err) s.error(err)
} }
case *templateNode:
s.walkTemplate(data, n)
case *withNode:
s.walkIfOrWith(nodeWith, data, n.pipeline, n.list, n.elseList)
default: default:
s.errorf("unknown node: %s", n) s.errorf("unknown node: %s", n)
} }
} }
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
// are identical in behavior except that 'with' sets dot.
func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNode, list, elseList *listNode) {
val := s.evalPipeline(data, pipe)
truth := false
switch val.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
truth = val.Len() > 0
case reflect.Bool:
truth = val.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
truth = val.Uint() != 0
case reflect.Float32, reflect.Float64:
truth = val.Float() != 0
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
case reflect.Chan, reflect.Func, reflect.Ptr:
truth = !val.IsNil()
default:
s.errorf("if/with can't use value of type %T", val.Interface())
}
if truth {
if typ == nodeWith {
data = val
}
s.walk(data, list)
} else if elseList != nil {
s.walk(data, elseList)
}
}
func (s *state) walkRange(data reflect.Value, r *rangeNode) { func (s *state) walkRange(data reflect.Value, r *rangeNode) {
val := s.evalPipeline(data, r.pipeline) val := s.evalPipeline(data, r.pipeline)
switch val.Kind() { switch val.Kind() {
...@@ -97,6 +145,21 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) { ...@@ -97,6 +145,21 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
} }
} }
func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
name := s.evalArg(data, reflect.TypeOf("string"), t.name).String()
if s.set == nil {
s.errorf("no set defined in which to invoke template named %q", name)
}
tmpl := s.set.tmpl[name]
if tmpl == nil {
s.errorf("template %q not in set", name)
}
data = s.evalPipeline(data, t.pipeline)
newState := *s
newState.tmpl = tmpl
newState.walk(data, tmpl.root)
}
// Eval functions evaluate pipelines, commands, and their elements and extract // Eval functions evaluate pipelines, commands, and their elements and extract
// values from the data structure by examining fields, calling methods, and so on. // values from the data structure by examining fields, calling methods, and so on.
// The printing of those values happens only through walk functions. // The printing of those values happens only through walk functions.
...@@ -110,16 +173,38 @@ func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Va ...@@ -110,16 +173,38 @@ func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Va
} }
func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value { func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
switch field := cmd.args[0].(type) { firstWord := cmd.args[0]
if field, ok := firstWord.(*fieldNode); ok {
return s.evalFieldNode(data, field, cmd.args, final)
}
if len(cmd.args) > 1 || final.IsValid() {
// TODO: functions
s.errorf("can't give argument to non-method %s", cmd.args[0])
}
switch word := cmd.args[0].(type) {
case *dotNode: case *dotNode:
if final.IsValid() {
s.errorf("can't give argument to dot")
}
return data return data
case *fieldNode: case *boolNode:
return s.evalFieldNode(data, field, cmd.args, final) return reflect.ValueOf(word.true)
case *numberNode:
// These are ideal constants but we don't know the type
// and we have no context. (If it was a method argument,
// we'd know what we need.) The syntax guides us to some extent.
switch {
case word.isComplex:
return reflect.ValueOf(word.complex128) // incontrovertible.
case word.isFloat && strings.IndexAny(word.text, ".eE") >= 0:
return reflect.ValueOf(word.float64)
case word.isInt:
return reflect.ValueOf(word.int64)
case word.isUint:
return reflect.ValueOf(word.uint64)
}
case *stringNode:
return reflect.ValueOf(word.text)
default:
s.errorf("can't handle command %q", firstWord)
} }
s.errorf("%s not a field", cmd.args[0])
panic("not reached") panic("not reached")
} }
...@@ -148,7 +233,7 @@ func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value { ...@@ -148,7 +233,7 @@ func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
} }
s.errorf("%s has no field %s", data.Type(), fieldName) s.errorf("%s has no field %s", data.Type(), fieldName)
default: default:
s.errorf("can't evaluate field %s of type %s", fieldName, data.Type()) s.errorf("can't evaluate field %s of type %s", fieldName, data.Type())
} }
panic("not reached") panic("not reached")
} }
...@@ -282,7 +367,7 @@ func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) r ...@@ -282,7 +367,7 @@ func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) r
} }
func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Value { func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isFloat && !n.imaginary { if n, ok := n.(*numberNode); ok && n.isFloat {
value := reflect.New(typ).Elem() value := reflect.New(typ).Elem()
value.SetFloat(n.float64) value.SetFloat(n.float64)
return value return value
...@@ -292,9 +377,9 @@ func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Val ...@@ -292,9 +377,9 @@ func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Val
} }
func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.Value { func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.Value {
if n, ok := n.(*numberNode); ok && n.isFloat && n.imaginary { if n, ok := n.(*numberNode); ok && n.isComplex {
value := reflect.New(typ).Elem() value := reflect.New(typ).Elem()
value.SetComplex(complex(0, n.float64)) value.SetComplex(n.complex128)
return value return value
} }
s.errorf("expected complex; found %s", n) s.errorf("expected complex; found %s", n)
......
...@@ -16,17 +16,20 @@ import ( ...@@ -16,17 +16,20 @@ import (
// T has lots of interesting pieces to use to test execution. // T has lots of interesting pieces to use to test execution.
type T struct { type T struct {
// Basics // Basics
I int I int
U16 uint16 U16 uint16
X string X string
FloatZero float64
ComplexZero float64
// Nested structs. // Nested structs.
U *U U *U
// Slices // Slices
SI []int SI []int
SEmpty []int SIEmpty []int
SB []bool SB []bool
// Maps // Maps
MSI map[string]int MSI map[string]int
MSIone map[string]int // one element, for deterministic output
MSIEmpty map[string]int MSIEmpty map[string]int
} }
...@@ -76,13 +79,14 @@ type U struct { ...@@ -76,13 +79,14 @@ type U struct {
} }
var tVal = &T{ var tVal = &T{
I: 17, I: 17,
U16: 16, U16: 16,
X: "x", X: "x",
U: &U{"v"}, U: &U{"v"},
SI: []int{3, 4, 5}, SI: []int{3, 4, 5},
SB: []bool{true, false}, SB: []bool{true, false},
MSI: map[string]int{"one": 1, "two": 2, "three": 3}, MSI: map[string]int{"one": 1, "two": 2, "three": 3},
MSIone: map[string]int{"one": 1},
} }
type execTest struct { type execTest struct {
...@@ -94,31 +98,80 @@ type execTest struct { ...@@ -94,31 +98,80 @@ type execTest struct {
} }
var execTests = []execTest{ var execTests = []execTest{
// Trivial cases.
{"empty", "", "", nil, true}, {"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true}, {"text", "some text", "some text", nil, true},
// Fields of structs.
{".X", "-{{.X}}-", "-x-", tVal, true}, {".X", "-{{.X}}-", "-x-", tVal, true},
{".U.V", "-{{.U.V}}-", "-v-", tVal, true}, {".U.V", "-{{.U.V}}-", "-v-", tVal, true},
// Dots of all kinds to test basic evaluation.
{"dot int", "<{{.}}>", "<13>", 13, true},
{"dot uint", "<{{.}}>", "<14>", uint(14), true},
{"dot float", "<{{.}}>", "<15.1>", 15.1, true},
{"dot bool", "<{{.}}>", "<true>", true, true},
{"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true},
{"dot string", "<{{.}}>", "<hello>", "hello", true},
{"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true},
{"dot map", "<{{.}}>", "<map[two:22 one:11]>", map[string]int{"one": 11, "two": 22}, true},
{"dot struct", "<{{.}}>", "<{7 seven}>", struct {
a int
b string
}{7, "seven"}, true},
// Method calls.
{".Method0", "-{{.Method0}}-", "-resultOfMethod0-", tVal, true}, {".Method0", "-{{.Method0}}-", "-resultOfMethod0-", tVal, true},
{".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true}, {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true},
{".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true}, {".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true},
{".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true}, {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true},
{".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true}, {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
// Pipelines.
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 resultOfMethod0-", tVal, true}, {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 resultOfMethod0-", tVal, true},
// If.
{"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true},
{"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true},
{"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
{"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
{"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
{"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
{"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
// With.
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
{"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true},
{"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true},
{"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true},
{"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
{"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true},
{"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true},
{"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
// Range.
{"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true}, {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
{"range empty no else", "{{range .SEmpty}}-{{.}}-{{end}}", "", tVal, true}, {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
{"range empty else", "{{range .SEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true}, {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true}, {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
{"range map", "{{range .MSI | .MSort}}-{{.}}-{{end}}", "-one--three--two-", tVal, true}, {"range map", "{{range .MSI | .MSort}}-{{.}}-{{end}}", "-one--three--two-", tVal, true},
{"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true}, {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true},
{"range map else", "{{range .MSI | .MSort}}-{{.}}-{{else}}EMPTY{{end}}", "-one--three--two-", tVal, true}, {"range map else", "{{range .MSI | .MSort}}-{{.}}-{{else}}EMPTY{{end}}", "-one--three--two-", tVal, true},
{"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
// Error handling.
{"error method, error", "{{.EPERM true}}", "", tVal, false}, {"error method, error", "{{.EPERM true}}", "", tVal, false},
{"error method, no error", "{{.EPERM false}}", "false", tVal, true}, {"error method, no error", "{{.EPERM false}}", "false", tVal, true},
} }
func TestExecute(t *testing.T) { func testExecute(execTests []execTest, set *Set, t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
for _, test := range execTests { for _, test := range execTests {
tmpl := New(test.name) tmpl := New(test.name)
...@@ -128,7 +181,7 @@ func TestExecute(t *testing.T) { ...@@ -128,7 +181,7 @@ func TestExecute(t *testing.T) {
continue continue
} }
b.Reset() b.Reset()
err = tmpl.Execute(b, test.data) err = tmpl.ExecuteInSet(b, test.data, set)
switch { switch {
case !test.ok && err == nil: case !test.ok && err == nil:
t.Errorf("%s: expected error; got none", test.name) t.Errorf("%s: expected error; got none", test.name)
...@@ -146,6 +199,10 @@ func TestExecute(t *testing.T) { ...@@ -146,6 +199,10 @@ func TestExecute(t *testing.T) {
} }
} }
func TestExecute(t *testing.T) {
testExecute(execTests, nil, t)
}
// Check that an error from a method flows back to the top. // Check that an error from a method flows back to the top.
func TestExecuteError(t *testing.T) { func TestExecuteError(t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
......
This diff is collapsed.
...@@ -16,41 +16,53 @@ type numberTest struct { ...@@ -16,41 +16,53 @@ type numberTest struct {
isInt bool isInt bool
isUint bool isUint bool
isFloat bool isFloat bool
imaginary bool isComplex bool
int64 int64
uint64 uint64
float64 float64
complex128
} }
var numberTests = []numberTest{ var numberTests = []numberTest{
// basics // basics
{"0", true, true, true, false, 0, 0, 0}, {"0", true, true, true, false, 0, 0, 0, 0},
{"-0", true, true, true, false, 0, 0, 0}, // check that -0 is a uint. {"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
{"73", true, true, true, false, 73, 73, 73}, {"73", true, true, true, false, 73, 73, 73, 0},
{"-73", true, false, true, false, -73, 0, -73}, {"-73", true, false, true, false, -73, 0, -73, 0},
{"+73", true, false, true, false, 73, 0, 73}, {"+73", true, false, true, false, 73, 0, 73, 0},
{"100", true, true, true, false, 100, 100, 100}, {"100", true, true, true, false, 100, 100, 100, 0},
{"1e9", true, true, true, false, 1e9, 1e9, 1e9}, {"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0},
{"-1e9", true, false, true, false, -1e9, 0, -1e9}, {"-1e9", true, false, true, false, -1e9, 0, -1e9, 0},
{"-1.2", false, false, true, false, 0, 0, -1.2}, {"-1.2", false, false, true, false, 0, 0, -1.2, 0},
{"1e19", false, true, true, false, 0, 1e19, 1e19}, {"1e19", false, true, true, false, 0, 1e19, 1e19, 0},
{"-1e19", false, false, true, false, 0, 0, -1e19}, {"-1e19", false, false, true, false, 0, 0, -1e19, 0},
{"4i", false, false, true, true, 0, 0, 4}, {"4i", false, false, false, true, 0, 0, 0, 4i},
{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i},
// complex with 0 imaginary are float (and maybe integer)
{"0i", true, true, true, true, 0, 0, 0, 0},
{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
{"-12+0i", true, false, true, true, -12, 0, -12, -12},
{"13+0i", true, true, true, true, 13, 13, 13, 13},
// funny bases // funny bases
{"0123", true, true, true, false, 0123, 0123, 0123}, {"0123", true, true, true, false, 0123, 0123, 0123, 0},
{"-0x0", true, true, true, false, 0, 0, 0}, {"-0x0", true, true, true, false, 0, 0, 0, 0},
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef}, {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
// some broken syntax // some broken syntax
{text: "+-2"}, {text: "+-2"},
{text: "0x123."}, {text: "0x123."},
{text: "1e."}, {text: "1e."},
{text: "0xi."}, {text: "0xi."},
{text: "1+2."},
} }
func TestNumberParse(t *testing.T) { func TestNumberParse(t *testing.T) {
for _, test := range numberTests { for _, test := range numberTests {
n, err := newNumber(test.text) // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
ok := test.isInt || test.isUint || test.isFloat // because imaginary comes out as a number.
var c complex128
_, err := fmt.Sscan(test.text, &c)
n, err := newNumber(test.text, err == nil)
ok := test.isInt || test.isUint || test.isFloat || test.isComplex
if ok && err != nil { if ok && err != nil {
t.Errorf("unexpected error for %q", test.text) t.Errorf("unexpected error for %q", test.text)
continue continue
...@@ -62,8 +74,8 @@ func TestNumberParse(t *testing.T) { ...@@ -62,8 +74,8 @@ func TestNumberParse(t *testing.T) {
if !ok { if !ok {
continue continue
} }
if n.imaginary != test.imaginary { if n.isComplex != test.isComplex {
t.Errorf("imaginary incorrect for %q; should be %t", test.text, test.imaginary) t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
} }
if test.isInt { if test.isInt {
if !n.isInt { if !n.isInt {
...@@ -95,17 +107,19 @@ func TestNumberParse(t *testing.T) { ...@@ -95,17 +107,19 @@ func TestNumberParse(t *testing.T) {
} else if n.isFloat { } else if n.isFloat {
t.Errorf("did not expect float for %q", test.text) t.Errorf("did not expect float for %q", test.text)
} }
if test.isComplex {
if !n.isComplex {
t.Errorf("expected complex for %q", test.text)
}
if n.complex128 != test.complex128 {
t.Errorf("complex128 for %q should be %g is %g", test.text, test.complex128, n.complex128)
}
} else if n.isComplex {
t.Errorf("did not expect complex for %q", test.text)
}
} }
} }
func num(s string) *numberNode {
n, err := newNumber(s)
if err != nil {
panic(err)
}
return n
}
type parseTest struct { type parseTest struct {
name string name string
input string input string
...@@ -125,7 +139,7 @@ var parseTests = []parseTest{ ...@@ -125,7 +139,7 @@ var parseTests = []parseTest{
`[(text: " \t\n")]`}, `[(text: " \t\n")]`},
{"text", "some text", noError, {"text", "some text", noError,
`[(text: "some text")]`}, `[(text: "some text")]`},
{"emptyMeta", "{{}}", noError, {"emptyMeta", "{{}}", hasError,
`[(action: [])]`}, `[(action: [])]`},
{"field", "{{.X}}", noError, {"field", "{{.X}}", noError,
`[(action: [(command: [F=[X]])])]`}, `[(action: [(command: [F=[X]])])]`},
...@@ -139,6 +153,10 @@ var parseTests = []parseTest{ ...@@ -139,6 +153,10 @@ var parseTests = []parseTest{
"[(action: [(command: [I=hello S=`quoted text`])])]"}, "[(action: [(command: [I=hello S=`quoted text`])])]"},
{"pipeline", "{{hello|world}}", noError, {"pipeline", "{{hello|world}}", noError,
`[(action: [(command: [I=hello]) (command: [I=world])])]`}, `[(action: [(command: [I=hello]) (command: [I=world])])]`},
{"simple if", "{{if .X}}hello{{end}}", noError,
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
`[({{if [(command: [F=[X]])]}} [(text: "true")] {{else}} [(text: "false")])]`},
{"simple range", "{{range .X}}hello{{end}}", noError, {"simple range", "{{range .X}}hello{{end}}", noError,
`[({{range [(command: [F=[X]])]}} [(text: "hello")])]`}, `[({{range [(command: [F=[X]])]}} [(text: "hello")])]`},
{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError, {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
...@@ -153,6 +171,12 @@ var parseTests = []parseTest{ ...@@ -153,6 +171,12 @@ var parseTests = []parseTest{
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`}, `[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError, {"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`}, `[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
{"template", "{{template foo .X}}", noError,
"[{{template I=foo [(command: [F=[X]])]}}]"},
{"with", "{{with .X}}hello{{end}}", noError,
`[({{with [(command: [F=[X]])]}} [(text: "hello")])]`},
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
`[({{with [(command: [F=[X]])]}} [(text: "hello")] {{else}} [(text: "goodbye")])]`},
// Errors. // Errors.
{"unclosed action", "hello{{range", hasError, ""}, {"unclosed action", "hello{{range", hasError, ""},
{"missing end", "hello{{range .x}}", hasError, ""}, {"missing end", "hello{{range .x}}", hasError, ""},
......
// Copyright 2011 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 (
"os"
"runtime"
"strconv"
)
// Set holds a set of related templates that can refer to one another by name.
// A template may be a member of multiple sets.
type Set struct {
tmpl map[string]*Template
}
// NewSet allocates a new, empty template set.
func NewSet() *Set {
return &Set{
tmpl: make(map[string]*Template),
}
}
// recover is the handler that turns panics into returns from the top
// level of Parse.
func (s *Set) recover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
s.tmpl = nil
*errp = e.(os.Error)
}
return
}
// Parse parses the file into a set of named templates.
func (s *Set) Parse(text string) (err os.Error) {
defer s.recover(&err)
lex, tokens := lex("set", text)
const context = "define clause"
for {
t := New("set") // name will be updated once we know it.
t.startParse(lex, tokens)
// Expect EOF or "{{ define name }}".
if t.atEOF() {
return
}
t.expect(itemLeftMeta, context)
t.expect(itemDefine, context)
name := t.expect(itemString, context)
t.name, err = strconv.Unquote(name.val)
if err != nil {
t.error(err)
}
t.expect(itemRightMeta, context)
end := t.parse(false)
if end == nil {
t.errorf("unexpected EOF in %s", context)
}
if end.typ() != nodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
s.tmpl[t.name] = t
}
return nil
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
import (
"fmt"
"testing"
)
type setParseTest struct {
name string
input string
ok bool
names []string
results []string
}
var setParseTests = []setParseTest{
{"empty", "", noError,
nil,
nil},
{"one", `{{define "foo"}} FOO {{end}}`, noError,
[]string{"foo"},
[]string{`[(text: " FOO ")]`}},
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
[]string{"foo", "bar"},
[]string{`[(text: " FOO ")]`, `[(text: " BAR ")]`}},
// errors
{"missing end", `{{define "foo"}} FOO `, hasError,
nil,
nil},
{"malformed name", `{{define "foo}} FOO `, hasError,
nil,
nil},
}
func TestSetParse(t *testing.T) {
for _, test := range setParseTests {
set := NewSet()
err := set.Parse(test.input)
switch {
case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name)
continue
case err != nil && test.ok:
t.Errorf("%q: unexpected error: %v", test.name, err)
continue
case err != nil && !test.ok:
// expected error, got one
if dumpErrors {
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
}
continue
}
if len(set.tmpl) != len(test.names) {
t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(set.tmpl))
continue
}
for i, name := range test.names {
tmpl, ok := set.tmpl[name]
if !ok {
t.Errorf("%s: can't find template %q", test.name, name)
continue
}
result := tmpl.root.String()
if result != test.results[i] {
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
}
}
}
}
var setExecTests = []execTest{
{"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true},
{"invoke text", `{{template "text" .SI}}`, "TEXT", tVal, true},
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
}
const setText = `
{{define "text"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}}
{{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}}
`
func TestSetExecute(t *testing.T) {
// Declare a set with a couple of templates first.
set := NewSet()
err := set.Parse(setText)
if err != nil {
t.Fatalf("error parsing set: %s", err)
}
testExecute(setExecTests, set, t)
}
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