]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: doc and API changes suggested by rsc.
authorRob Pike <r@golang.org>
Wed, 13 Jul 2011 05:58:31 +0000 (15:58 +1000)
committerRob Pike <r@golang.org>
Wed, 13 Jul 2011 05:58:31 +0000 (15:58 +1000)
- template invocation is by string constant only.
- NewSet is gone.
- no global Funcs
- writer is now first arg to Execute

R=rsc, r
CC=golang-dev
https://golang.org/cl/4700043

src/pkg/exp/template/doc.go
src/pkg/exp/template/exec.go
src/pkg/exp/template/exec_test.go
src/pkg/exp/template/funcs.go
src/pkg/exp/template/helper.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go
src/pkg/exp/template/set.go
src/pkg/exp/template/set_test.go

index ce8d3feb546eb0a01987018ead393e5893183eae..f6d2788eb0248d94d483bed21f6bed578c4ef8b8 100644 (file)
@@ -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.
 
index 12aa80ec7771de6c0120b93fb7afa10a28608a9e..7aab7f7de3a34f874bee22953a1335ea890e43fe 100644 (file)
@@ -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")
-       }
-       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)
-               }
+       if s.set == nil {
+               s.errorf("no set defined in which to invoke template named %q", t.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)
index 5b0a47fe1f366405ade904fcf4a6e1deecd1d37a..b154a90fd6d604dcf6313f81fc8a72d6e35a8a56 100644 (file)
@@ -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)
        }
index 32c4969f590722a0dd1509ce6a3d73acd48d41cb..3aa9d629a81e1c83c87296cc3a15fbda7d286d57 100644 (file)
@@ -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 {
index baf54fdad816350627379afeca98537bfcffe682..558938272c1c284b6cf9ee60ffcdd74482b75560 100644 (file)
@@ -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)
 }
index 91d19e5f673f46e93f2904279246710d5947575b..c416b3483333f88cd36581b53c9161ea503ddc7a 100644 (file)
@@ -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")
        }
index 10be3cbb236a5023e1b3922072aec0a25f9c46cc..de72aa9dde64f9874b16d5bac5e00734faf55655 100644 (file)
@@ -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) {
index e6a0ae4ed6395d0fb6a6262f5c35d4af9a47b293..0c12bfff49cb12d0e92ed88dfc10657ace2137fc 100644 (file)
@@ -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"
index ede924cc19f14598eeea2ee10eb70f88530dc4f5..056ba43831ab44dbf62f20bd4558687a446770f3 100644 (file)
@@ -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)