diff --git a/src/pkg/exp/template/doc.go b/src/pkg/exp/template/doc.go index ce8d3feb54..f6d2788eb0 100644 --- a/src/pkg/exp/template/doc.go +++ b/src/pkg/exp/template/doc.go @@ -33,13 +33,15 @@ data, defined in detail below. is copied to the output. {{if pipeline}} T1 {{end}} - If the value of the pipeline is the "zero value" (see below) for - its type, no output is generated; otherwise, T1 is executed. + If the value of the pipeline is empty, no output is generated; + otherwise, T1 is executed. The empty values are false, 0, any + nil pointer or interface value, and any array, slice, map, or + string of length zero. Dot is unaffected. {{if pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is the zero value for its type, T0 - is executed; otherwise, T1 is executed. Dot is unaffected. + If the value of the pipeline is empty, T0 is executed; + otherwise, T1 is executed. Dot is unaffected. {{range pipeline}} T1 {{end}} The value of the pipeline must be an array, slice, or map. If @@ -53,29 +55,22 @@ data, defined in detail below. T0 is executed; otherwise, dot is set to the successive elements of the array, slice, or map and T1 is executed. - {{template argument}} - If the value of the argument is a string, the template with that - name is executed with nil data. If the value of arg is of type - *Template, that template is executed. + {{template "name"}} + The template with the specified name is executed with nil data. - {{template argument pipeline}} - If the value of the argument is a string, the template with that - name is executed with data set to the value of the pipeline. If - the value of arg is of type *Template, that template is - executed. + {{template "name" pipeline}} + The template with the specified name is executed with dot set + to the value of the pipeline. {{with pipeline}} T1 {{end}} - If the value of the pipeline is the zero value for its type, no - output is generated; otherwise, dot is set to the value of the - pipeline and T1 is executed. + If the value of the pipeline is empty, no output is generated; + otherwise, dot is set to the value of the pipeline and T1 is + executed. {{with pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is the zero value for its type, dot - is unaffected and T0 is executed; otherwise, dot is set to the - value of the pipeline and T1 is executed. - -"Zero value" means the true zero value in Go terms. Also, for arrays, slices, -maps, and strings, any value v with len(v)==0 counts as a zero value. + If the value of the pipeline is empty, dot is unaffected and T0 + is executed; otherwise, dot is set to the value of the pipeline + and T1 is executed. Arguments @@ -106,12 +101,12 @@ An argument is a simple value, denoted by one of the following. such as .Method The result is the value of invoking the method with dot as the - receiver, dot.Method(). Such methods must have one return value (of + receiver, dot.Method(). Such a method must have one return value (of any type) or two return values, the second of which is an os.Error. If it has two and the returned error is non-nil, execution terminates - and that error is returned to the caller as the value of Execute. + and an error is returned to the caller as the value of Execute. Method invocations may be chained, but only the last element of - the chain may be a method; other others must be struct fields: + the chain may be a method; others must be struct fields: .Field1.Field2.Method Methods can also be evaluated on variables, including chaining: $x.Field1.Method @@ -173,7 +168,7 @@ All produce the quoted word "output": A string constant. {{`"output"`}} A raw string constant. - {{printf "%q" output}} + {{printf "%q" "output"}} A function call. {{"output" | printf "%q"}} A function call whose final argument comes from the previous @@ -182,14 +177,12 @@ All produce the quoted word "output": A more elaborate call. {{"output" | printf "%s" | printf "%q"}} A longer chain. - {{$x := "output" | printf "%s" | printf "%q"}} - An unused variables captures the output. {{with "output"}}{{printf "%q" .}}{{end}} A with action using dot. {{with $x := "output" | printf "%q"}}{{$x}}{{end}} - A with action creates and uses a variable. + A with action that creates and uses a variable. {{with $x := "output"}}{{printf "%q" $x}}{{end}} - A with action uses the variable in another action. + A with action that uses the variable in another action. {{with $x := "output"}}{{$x | printf "%q"}}{{end}} The same, but pipelined. @@ -230,10 +223,10 @@ be true. Template sets -All templates are named by a string specified when they are created. A template -may use a template invocation to instantiate another template directly or by its -name; see the explanation of the template action above. The name of a template -is looked up in the template set active during the invocation. +Each template is named by a string specified when it is created. A template may +use a template invocation to instantiate another template directly or by its +name; see the explanation of the template action above. The name is looked up +in the template set active during the invocation. If no template invocation actions occur in the template, the issue of template sets can be ignored. If it does contain invocations, though, a set must be @@ -241,10 +234,9 @@ defined in which to look up the names. There are two ways to construct template sets. -The first is to use the Parse method of Set to create a set of named templates -by reading a single string defining multiple templates. The syntax of the -definitions is to surround each template declaration with a define and end -action; those actions are discarded after parsing. +The first is to use a Set's Parse method to create a set of named templates from +a single input defining multiple templates. The syntax of the definitions is to +surround each template declaration with a define and end action. The define action names the template being created by providing a string constant. Here is a simple example of input to Set.Parse: @@ -256,14 +248,14 @@ constant. Here is a simple example of input to Set.Parse: This defines two templates, T1 and T2, and a third T3 that invokes the other two when it is executed. -The second way to build a template set is to use the Add method of Set to bind +The second way to build a template set is to use Set's Add method to add a template to a set. A template may be bound to multiple sets. Set.Parse may be called multiple times on different inputs to construct the set. Two sets may therefore be constructed with a common base set of templates plus, through a second Parse call each, specializations for some elements. -When templates are executed via Template.Execute, no set is defined and so no +When a template is executed via Template.Execute, no set is defined and so no template invocations are possible. The method Template.ExecuteInSet provides a way to specify a template set when executing a template directly. diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index 12aa80ec77..7aab7f7de3 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -220,27 +220,12 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) { } func (s *state) walkTemplate(dot reflect.Value, t *templateNode) { - // Can't use evalArg because there are two types we expect. - arg := s.evalEmptyInterface(dot, t.name) - if !arg.IsValid() { - s.errorf("invalid value in template invocation; expected string or *Template") + if s.set == nil { + s.errorf("no set defined in which to invoke template named %q", t.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) - } + tmpl := s.set.tmpl[t.name] + if tmpl == nil { + s.errorf("template %q not in set", t.name) } defer s.pop(s.mark()) dot = s.evalPipeline(dot, t.pipe) diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index 5b0a47fe1f..b154a90fd6 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -448,13 +448,13 @@ func TestTree(t *testing.T) { }, }, } - set := NewSet() + set := new(Set) err := set.Parse(treeTemplate) if err != nil { t.Fatal("parse error:", err) } var b bytes.Buffer - err = set.Execute("tree", &b, tree) + err = set.Execute(&b, "tree", tree) if err != nil { t.Fatal("exec error:", err) } diff --git a/src/pkg/exp/template/funcs.go b/src/pkg/exp/template/funcs.go index 32c4969f59..3aa9d629a8 100644 --- a/src/pkg/exp/template/funcs.go +++ b/src/pkg/exp/template/funcs.go @@ -18,7 +18,7 @@ import ( // FuncMap is the type of the map defining the mapping from names to functions. // Each function must have either a single return value, or two return values of // which the second has type os.Error. If the second argument evaluates to non-nil -// during execution, execution terminates and the error is returned by Execute. +// during execution, execution terminates and Execute returns an error. type FuncMap map[string]interface{} var funcs = map[string]reflect.Value{ @@ -33,13 +33,6 @@ var funcs = map[string]reflect.Value{ "println": reflect.ValueOf(fmt.Sprintln), } -// Funcs adds to the global function map the elements of the -// argument map. It panics if a value in the map is not a function -// with appropriate return type. -func Funcs(funcMap FuncMap) { - addFuncs(funcs, funcMap) -} - // addFuncs adds to values the functions in funcs, converting them to reflect.Values. func addFuncs(values map[string]reflect.Value, funcMap FuncMap) { for name, fn := range funcMap { diff --git a/src/pkg/exp/template/helper.go b/src/pkg/exp/template/helper.go index baf54fdad8..558938272c 100644 --- a/src/pkg/exp/template/helper.go +++ b/src/pkg/exp/template/helper.go @@ -42,17 +42,15 @@ func (t *Template) MustParseFile(filename string) *Template { return t } -// ParseFile is a helper function that creates a new Template and parses -// the template definition from the named file. -// The template name is the base name of the file. +// ParseFile creates a new Template and parses the template definition from +// the named file. The template name is the base name of the file. func ParseFile(filename string) (*Template, os.Error) { t := New(filepath.Base(filename)) return t, t.ParseFile(filename) } -// MustParseFile is a helper function that creates a new Template and parses -// the template definition from the named file. -// The template name is the base name of the file. +// MustParseFile creates a new Template and parses the template definition +// from the named file. The template name is the base name of the file. // It panics if the file cannot be read or the template cannot be parsed. func MustParseFile(filename string) *Template { return New(filepath.Base(filename)).MustParseFile(filename) @@ -85,16 +83,16 @@ func (s *Set) MustParseFile(filename string) *Set { return s } -// ParseSetFile is a helper function that creates a new Set and parses -// the set definition from the named file. +// ParseSetFile creates a new Set and parses the set definition from the +// named file. func ParseSetFile(filename string) (*Set, os.Error) { - s := NewSet() + s := new(Set) return s, s.ParseFile(filename) } -// MustParseSetFile is a helper function that creates a new Set and parses -// the set definition from the named file. +// MustParseSetFile creates a new Set and parses the set definition from the +// named file. // It panics if the file cannot be read or the set cannot be parsed. func MustParseSetFile(filename string) *Set { - return NewSet().MustParseFile(filename) + return new(Set).MustParseFile(filename) } diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index 91d19e5f67..c416b34833 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -477,19 +477,19 @@ func (r *rangeNode) String() string { type templateNode struct { nodeType line int - name node + name string pipe *pipeNode } -func newTemplate(line int, name node, pipe *pipeNode) *templateNode { +func newTemplate(line int, name string, pipe *pipeNode) *templateNode { return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipe: pipe} } func (t *templateNode) String() string { if t.pipe == nil { - return fmt.Sprintf("{{template %s}}", t.name) + return fmt.Sprintf("{{template %q}}", t.name) } - return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe) + return fmt.Sprintf("{{template %q %s}}", t.name, t.pipe) } // withNode represents a {{with}} action and its commands. @@ -523,9 +523,9 @@ func New(name string) *Template { } } -// Funcs adds to the template's function map the elements of the -// argument map. It panics if a value in the map is not a function -// with appropriate return type. +// Funcs adds the elements of the argument map to the template's function +// map. It panics if a value in the map is not a function with appropriate +// return type. // The return value is the template, so calls can be chained. func (t *Template) Funcs(funcMap FuncMap) *Template { addFuncs(t.funcs, funcMap) @@ -800,25 +800,14 @@ func (t *Template) elseControl() node { // Template keyword is past. The name must be something that can evaluate // to a string. func (t *Template) templateControl() node { - var name node + var name string switch token := t.next(); token.typ { - case itemIdentifier: - if _, ok := findFunction(token.val, t, t.set); !ok { - t.errorf("function %q not defined", token.val) - } - name = newIdentifier(token.val) - case itemDot: - name = newDot() - case itemVariable: - name = t.useVar(token.val) - case itemField: - name = newField(token.val) case itemString, itemRawString: s, err := strconv.Unquote(token.val) if err != nil { t.error(err) } - name = newString(s) + name = s default: t.unexpected(token, "template invocation") } diff --git a/src/pkg/exp/template/parse_test.go b/src/pkg/exp/template/parse_test.go index 10be3cbb23..de72aa9dde 100644 --- a/src/pkg/exp/template/parse_test.go +++ b/src/pkg/exp/template/parse_test.go @@ -204,15 +204,13 @@ var parseTests = []parseTest{ {"constants", "{{range .SI 1 -3.2i true false 'a'}}{{end}}", noError, `[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false N='a'])]}} [])]`}, {"template", "{{template `x`}}", noError, - "[{{template S=`x`}}]"}, - {"template", "{{template `x` .Y}}", noError, - "[{{template S=`x` [(command: [F=[Y]])]}}]"}, + `[{{template "x"}}]`}, + {"template with arg", "{{template `x` .Y}}", noError, + `[{{template "x" [(command: [F=[Y]])]}}]`}, {"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")])]`}, - {"variable in template", "{{with $v := `hi`}}{{template $v}}{{end}}", noError, - "[({{with [$v] := [(command: [S=`hi`])]}} [{{template V=[$v]}}])]"}, // Errors. {"unclosed action", "hello{{range", hasError, ""}, {"unmatched end", "{{end}}", hasError, ""}, @@ -223,6 +221,8 @@ var parseTests = []parseTest{ {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""}, {"variable undefined in template", "{{template $v}}", hasError, ""}, {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""}, + {"template with field ref", "{{template .X}}", hasError, ""}, + {"template with var", "{{template $v}}", hasError, ""}, } func TestParse(t *testing.T) { diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go index e6a0ae4ed6..0c12bfff49 100644 --- a/src/pkg/exp/template/set.go +++ b/src/pkg/exp/template/set.go @@ -14,33 +14,35 @@ import ( ) // Set holds a set of related templates that can refer to one another by name. +// The zero value represents an empty set. // A template may be a member of multiple sets. type Set struct { tmpl map[string]*Template funcs map[string]reflect.Value } -// NewSet allocates a new, empty template set. -func NewSet() *Set { - return &Set{ - tmpl: make(map[string]*Template), - funcs: make(map[string]reflect.Value), +func (s *Set) init() { + if s.tmpl == nil { + s.tmpl = make(map[string]*Template) + s.funcs = make(map[string]reflect.Value) } } -// Funcs adds to the set's function map the elements of the -// argument map. It panics if a value in the map is not a function -// with appropriate return type. +// Funcs adds the elements of the argument map to the set's function map. It +// panics if a value in the map is not a function with appropriate return +// type. // The return value is the set, so calls can be chained. func (s *Set) Funcs(funcMap FuncMap) *Set { + s.init() addFuncs(s.funcs, funcMap) return s } -// Add adds the argument templates to the set. It panics if the call -// attempts to reuse a name defined in the set. +// Add adds the argument templates to the set. It panics if two templates +// with the same name are added. // The return value is the set, so calls can be chained. func (s *Set) Add(templates ...*Template) *Set { + s.init() for _, t := range templates { if _, ok := s.tmpl[t.name]; ok { panic(fmt.Errorf("template: %q already defined in set", t.name)) @@ -54,6 +56,7 @@ func (s *Set) Add(templates ...*Template) *Set { // It panics if the call attempts to reuse a name defined in the set. // The return value is the set, so calls can be chained. func (s *Set) AddSet(set *Set) *Set { + s.init() for _, t := range set.tmpl { if _, ok := s.tmpl[t.name]; ok { panic(fmt.Errorf("template: %q already defined in set", t.name)) @@ -68,6 +71,7 @@ func (s *Set) AddSet(set *Set) *Set { // template is replaced. // The return value is the set, so calls can be chained. func (s *Set) Union(set *Set) *Set { + s.init() for _, t := range set.tmpl { s.tmpl[t.name] = t } @@ -80,10 +84,9 @@ func (s *Set) Template(name string) *Template { return s.tmpl[name] } -// Execute looks for the named template in the set and then applies that -// template to the specified data object, writing the output to wr. Nested -// template invocations will be resolved from the set. -func (s *Set) Execute(name string, wr io.Writer, data interface{}) os.Error { +// Execute applies the named template to the specified data object, writing +// the output to wr. Nested template invocations will be resolved from the set. +func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error { tmpl := s.tmpl[name] if tmpl == nil { return fmt.Errorf("template: no template %q in set", name) @@ -110,6 +113,7 @@ func (s *Set) recover(errp *os.Error) { // to the set. If a template is redefined, the element in the set is // overwritten with the new definition. func (s *Set) Parse(text string) (err os.Error) { + s.init() defer s.recover(&err) lex := lex("set", text) const context = "define clause" diff --git a/src/pkg/exp/template/set_test.go b/src/pkg/exp/template/set_test.go index ede924cc19..056ba43831 100644 --- a/src/pkg/exp/template/set_test.go +++ b/src/pkg/exp/template/set_test.go @@ -38,7 +38,7 @@ var setParseTests = []setParseTest{ func TestSetParse(t *testing.T) { for _, test := range setParseTests { - set := NewSet() + set := new(Set) err := set.Parse(test.input) switch { case err == nil && !test.ok: @@ -82,10 +82,6 @@ var setExecTests = []execTest{ {"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}, - {"invoke template by variable", `{{with $t := "x"}}{{template $t}}{{end}}`, "TEXT", 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}, @@ -104,7 +100,7 @@ const setText2 = ` func TestSetExecute(t *testing.T) { // Declare a set with a couple of templates first. - set := NewSet() + set := new(Set) err := set.Parse(setText1) if err != nil { t.Fatalf("error parsing set: %s", err)