mirror of
https://github.com/golang/go
synced 2024-11-19 13:04:45 -07:00
exp/template: better template invocation
1) Make the value optional ({{template "foo"}}) 2) Allow the template identifier to be a thing of type *Template. The second makes it easy to drop templates in to a set dynamically during invocation. R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/4671056
This commit is contained in:
parent
c17347eea9
commit
3987b91213
@ -220,13 +220,27 @@ 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)
|
||||
// Can't use evalArg because there are two types we expect.
|
||||
arg := s.evalEmptyInterface(data, t.name)
|
||||
if !arg.IsValid() {
|
||||
s.errorf("invalid value in template invocation; expected string or *Template")
|
||||
}
|
||||
tmpl := s.set.tmpl[name]
|
||||
if tmpl == nil {
|
||||
s.errorf("template %q not in set", name)
|
||||
var tmpl *Template
|
||||
if arg.Type() == reflect.TypeOf((*Template)(nil)) {
|
||||
tmpl = arg.Interface().(*Template)
|
||||
if tmpl == nil {
|
||||
s.errorf("nil template")
|
||||
}
|
||||
} else {
|
||||
s.validateType(arg, reflect.TypeOf(""))
|
||||
name := arg.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)
|
||||
}
|
||||
}
|
||||
defer s.pop(s.mark())
|
||||
data = s.evalPipeline(data, t.pipe)
|
||||
@ -245,8 +259,10 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
|
||||
// pipeline has a variable declaration, the variable will be pushed on the
|
||||
// stack. Callers should therefore pop the stack after they are finished
|
||||
// executing commands depending on the pipeline value.
|
||||
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
|
||||
value := zero
|
||||
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) (value reflect.Value) {
|
||||
if pipe == nil {
|
||||
return
|
||||
}
|
||||
for _, cmd := range pipe.cmds {
|
||||
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg.
|
||||
// If the object has type interface{}, dig down one level to the thing inside.
|
||||
@ -434,8 +450,11 @@ func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// validateType guarantees that the value is assignable to the type.
|
||||
// validateType guarantees that the value is valid and assignable to the type.
|
||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||
if !value.IsValid() {
|
||||
s.errorf("invalid value; expected %s", typ)
|
||||
}
|
||||
if !value.Type().AssignableTo(typ) {
|
||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||
}
|
||||
@ -462,7 +481,7 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
|
||||
return s.evalInteger(typ, n)
|
||||
case reflect.Interface:
|
||||
if typ.NumMethod() == 0 {
|
||||
return s.evalEmptyInterface(data, typ, n)
|
||||
return s.evalEmptyInterface(data, n)
|
||||
}
|
||||
case reflect.String:
|
||||
return s.evalString(typ, n)
|
||||
@ -533,7 +552,7 @@ func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalEmptyInterface(data reflect.Value, typ reflect.Type, n node) reflect.Value {
|
||||
func (s *state) evalEmptyInterface(data reflect.Value, n node) reflect.Value {
|
||||
switch n := n.(type) {
|
||||
case *boolNode:
|
||||
return reflect.ValueOf(n.true)
|
||||
|
@ -42,6 +42,8 @@ type T struct {
|
||||
PI *int
|
||||
PSI *[]int
|
||||
NIL *int
|
||||
// Template to test evaluation of templates.
|
||||
Tmpl *Template
|
||||
}
|
||||
|
||||
type U struct {
|
||||
@ -67,6 +69,7 @@ var tVal = &T{
|
||||
Empty4: &U{"v"},
|
||||
PI: newInt(23),
|
||||
PSI: newIntSlice(21, 22, 23),
|
||||
Tmpl: New("x").MustParse("test template"), // "x" is the value of .X
|
||||
}
|
||||
|
||||
// Helpers for creation.
|
||||
|
@ -469,6 +469,9 @@ func newTemplate(line int, name node, pipe *pipeNode) *templateNode {
|
||||
}
|
||||
|
||||
func (t *templateNode) String() string {
|
||||
if t.pipe == nil {
|
||||
return fmt.Sprintf("{{template %s}}", t.name)
|
||||
}
|
||||
return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe)
|
||||
}
|
||||
|
||||
@ -748,7 +751,6 @@ func (t *Template) withControl() node {
|
||||
return newWith(t.parseControl("with"))
|
||||
}
|
||||
|
||||
|
||||
// End:
|
||||
// {{end}}
|
||||
// End keyword is past.
|
||||
@ -790,7 +792,12 @@ func (t *Template) templateControl() node {
|
||||
default:
|
||||
t.unexpected(token, "template invocation")
|
||||
}
|
||||
return newTemplate(t.lex.lineNumber(), name, t.pipeline("template"))
|
||||
var pipe *pipeNode
|
||||
if t.next().typ != itemRightDelim {
|
||||
t.backup()
|
||||
pipe = t.pipeline("template")
|
||||
}
|
||||
return newTemplate(t.lex.lineNumber(), name, pipe)
|
||||
}
|
||||
|
||||
// command:
|
||||
|
@ -174,6 +174,8 @@ var parseTests = []parseTest{
|
||||
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
|
||||
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
|
||||
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
|
||||
{"template", "{{template `x`}}", noError,
|
||||
"[{{template S=`x`}}]"},
|
||||
{"template", "{{template `x` .Y}}", noError,
|
||||
"[{{template S=`x` [(command: [F=[Y]])]}}]"},
|
||||
{"with", "{{with .X}}hello{{end}}", noError,
|
||||
|
@ -76,11 +76,15 @@ func TestSetParse(t *testing.T) {
|
||||
var setExecTests = []execTest{
|
||||
{"empty", "", "", nil, true},
|
||||
{"text", "some text", "some text", nil, true},
|
||||
{"invoke text", `{{template "text" .SI}}`, "TEXT", tVal, true},
|
||||
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
|
||||
{"invoke x no args", `{{template "x"}}`, "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},
|
||||
{"invoke template by field", `{{template .X}}`, "TEXT", tVal, true},
|
||||
{"invoke template by template", `{{template .Tmpl}}`, "test template", tVal, true},
|
||||
{"invalid: invoke template by []int", `{{template .SI}}`, "", tVal, false},
|
||||
|
||||
// User-defined function: test argument evaluator.
|
||||
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
|
||||
@ -88,7 +92,7 @@ var setExecTests = []execTest{
|
||||
}
|
||||
|
||||
const setText = `
|
||||
{{define "text"}}TEXT{{end}}
|
||||
{{define "x"}}TEXT{{end}}
|
||||
{{define "dotV"}}{{.V}}{{end}}
|
||||
{{define "dot"}}{{.}}{{end}}
|
||||
{{define "nested"}}{{template "dot" .}}{{end}}
|
||||
|
Loading…
Reference in New Issue
Block a user