Commit 7aa1a1a6 authored by Rob Pike's avatar Rob Pike

exp/template: doc and API changes suggested by rsc.

- 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
parent 2e9388e3
......@@ -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.
......
......@@ -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)
......
......@@ -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)
}
......
......@@ -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 {
......
......@@ -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)
}
......@@ -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")
}
......
......@@ -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) {
......
......@@ -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"
......
......@@ -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)
......
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