diff --git a/src/text/template/exampleparent_test.go b/src/text/template/exampleparent_test.go
new file mode 100644
index 00000000000000..89b2491183377f
--- /dev/null
+++ b/src/text/template/exampleparent_test.go
@@ -0,0 +1,76 @@
+package template_test
+
+import (
+ "log"
+ "os"
+ "text/template"
+)
+
+func ExampleTemplate_parent() {
+ const base_html = `
+
+
+ {{block "title" .}}My website{{end}}
+
+
+
+ {{- block "content" .}}{{end -}}
+
+
+
+
+
+`
+ base := template.Must(template.New("base.html").Parse(base_html))
+
+ const index_html = `{{define "content"}}Welcome!
{{end}}`
+ index := template.Must(template.Must(base.Clone()).New("index.html").Parse(index_html))
+ {
+ err := index.ExecuteTemplate(os.Stdout, "base.html", nil)
+ if err != nil {
+ log.Println("executing template:", err)
+ }
+ }
+
+ const about_html = `{{define "title"}}{{template parent .}} - About{{end}}
+{{define "content"}}About us
{{end}}`
+ about := template.Must(template.Must(base.Clone()).New("about.html").Parse(about_html))
+ {
+ err := about.ExecuteTemplate(os.Stdout, "base.html", nil)
+ if err != nil {
+ log.Println("executing template:", err)
+ }
+ }
+
+ // Output:
+ //
+ //
+ //
+ // My website
+ //
+ //
+ // Welcome!
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // My website - About
+ //
+ //
+ // About us
+ //
+ //
+ //
+
+}
diff --git a/src/text/template/exec.go b/src/text/template/exec.go
index 5ad3b4ec582c41..40cb8e4669f1d2 100644
--- a/src/text/template/exec.go
+++ b/src/text/template/exec.go
@@ -33,9 +33,10 @@ func initMaxExecDepth() int {
type state struct {
tmpl *Template
wr io.Writer
- node parse.Node // current node, for errors
- vars []variable // push-down stack of variable values.
- depth int // the height of the stack of executing templates.
+ node parse.Node // current node, for errors
+ vars []variable // push-down stack of variable values.
+ depth int // the height of the stack of executing templates.
+ level map[string]int // map from template name to current level.
}
// variable holds the dynamic value of a variable such as $, $x etc.
@@ -207,9 +208,10 @@ func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
value = reflect.ValueOf(data)
}
state := &state{
- tmpl: t,
- wr: wr,
- vars: []variable{{"$", value}},
+ tmpl: t,
+ wr: wr,
+ vars: []variable{{"$", value}},
+ level: make(map[string]int),
}
if t.Tree == nil || t.Root == nil {
state.errorf("%q is an incomplete or empty template", t.Name())
@@ -230,6 +232,7 @@ func (t *Template) DefinedTemplates() string {
t.muTmpl.RLock()
defer t.muTmpl.RUnlock()
for name, tmpl := range t.tmpl {
+ tmpl := tmpl[0] // TODO: invariant check - at least one
if tmpl.Tree == nil || tmpl.Root == nil {
continue
}
@@ -400,7 +403,12 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
s.at(t)
- tmpl := s.tmpl.Lookup(t.Name)
+ level := s.level[t.Name]
+ if t.Parent {
+ level++
+ }
+ s.level[t.Name] = level
+ tmpl := s.tmpl.LookupByLevel(t.Name, level)
if tmpl == nil {
s.errorf("template %q not defined", t.Name)
}
@@ -415,6 +423,11 @@ func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
// No dynamic scoping: template invocations inherit no variables.
newState.vars = []variable{{"$", dot}}
newState.walk(dot, tmpl.Root)
+ level--
+ if level < 0 {
+ level = 0 // TODO: this could be masking a bug
+ }
+ newState.level[t.Name] = level
}
// Eval functions evaluate pipelines, commands, and their elements and extract
diff --git a/src/text/template/multi_test.go b/src/text/template/multi_test.go
index b543ab5c47c6d3..f4d49cc0730589 100644
--- a/src/text/template/multi_test.go
+++ b/src/text/template/multi_test.go
@@ -71,12 +71,12 @@ func TestMultiParse(t *testing.T) {
continue
}
for i, name := range test.names {
- tmpl, ok := template.tmpl[name]
+ tmpl, ok := template.tmpl[name] // TODO making this pass tests for now, turn into an accessor method
if !ok {
t.Errorf("%s: can't find template %q", test.name, name)
continue
}
- result := tmpl.Root.String()
+ result := tmpl[0].Root.String() // TODO
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])
}
@@ -234,10 +234,10 @@ func TestClone(t *testing.T) {
}
// Verify that the clone is self-consistent.
for k, v := range clone.tmpl {
- if k == clone.name && v.tmpl[k] != clone {
+ if k == clone.name && v[0].tmpl[k][0] != clone { // TODO making this pass tests for now
t.Error("clone does not contain root")
}
- if v != v.tmpl[v.name] {
+ if v[0] != v[0].tmpl[v[0].name][0] { // TODO making this pass tests for now
t.Errorf("clone does not contain self for %q", k)
}
}
diff --git a/src/text/template/parent_test.go b/src/text/template/parent_test.go
new file mode 100644
index 00000000000000..cbfb6c2b6f2b9a
--- /dev/null
+++ b/src/text/template/parent_test.go
@@ -0,0 +1,84 @@
+package template_test
+
+import (
+ "bytes"
+ "testing"
+ "text/template"
+)
+
+func TestParent(t *testing.T) {
+ parent, err := template.New("parent").Parse(`{{block "content" .}}parent{{end}}`)
+ if err != nil {
+ t.Fatalf("parsing parent template: %v", err)
+ }
+
+ var b bytes.Buffer
+ if err := parent.Execute(&b, nil); err != nil {
+ t.Fatalf("executing parent template: %v", err)
+ }
+ if b.String() != "parent" {
+ t.Errorf("want %q, got %q", "parent", b.String())
+ }
+
+ var child *template.Template
+ {
+ clone, err := parent.Clone()
+ if err != nil {
+ t.Fatalf("cloning parent: %v", err)
+ }
+
+ child, err = clone.Parse(`{{define "content"}}{{template parent}}child{{end}}`)
+ if err != nil {
+ t.Fatalf("parsing child template: %v", err)
+ }
+
+ b.Reset()
+ if err := child.Execute(&b, nil); err != nil {
+ t.Fatalf("executing child template: %v", err)
+ }
+ if b.String() != "parentchild" {
+ t.Errorf("want %q, got %q", "child", b.String())
+ }
+ }
+
+ {
+ clone, err := parent.Clone()
+ if err != nil {
+ t.Fatalf("cloning parent: %v", err)
+ }
+
+ child, err := clone.Parse(`{{define "content"}}{{template parent}}cloned child{{end}}`)
+ if err != nil {
+ t.Fatalf("parsing child template: %v", err)
+ }
+
+ b.Reset()
+ if err := child.Execute(&b, nil); err != nil {
+ t.Fatalf("executing child template: %v", err)
+ }
+ if b.String() != "parentcloned child" {
+ t.Errorf("want %q, got %q", "parentcloned child", b.String())
+ }
+ }
+
+ {
+ clone, err := child.Clone()
+ if err != nil {
+ t.Fatalf("cloning child: %v", err)
+ }
+
+ gc, err := clone.Parse(`{{define "content"}}{{template parent}}grandchild{{end}}`)
+ if err != nil {
+ t.Fatalf("parsing grandchild template: %v", err)
+ }
+
+ b.Reset()
+ if err := gc.Execute(&b, nil); err != nil {
+ t.Fatalf("executing grandchild template: %v", err)
+ }
+ if b.String() != "parentchildgrandchild" {
+ t.Errorf("want %q, got %q", "parentchildgrandchild", b.String())
+ }
+ }
+
+}
diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go
index 6784071b1118d1..4a289ad1694b4e 100644
--- a/src/text/template/parse/lex.go
+++ b/src/text/template/parse/lex.go
@@ -68,6 +68,7 @@ const (
itemEnd // end keyword
itemIf // if keyword
itemNil // the untyped nil constant, easiest to treat as a keyword
+ itemParent // parent keyword
itemRange // range keyword
itemTemplate // template keyword
itemWith // with keyword
@@ -82,6 +83,7 @@ var key = map[string]itemType{
"if": itemIf,
"range": itemRange,
"nil": itemNil,
+ "parent": itemParent,
"template": itemTemplate,
"with": itemWith,
}
diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go
index 6510eed674dd9f..ae0e517ebf7bd0 100644
--- a/src/text/template/parse/lex_test.go
+++ b/src/text/template/parse/lex_test.go
@@ -40,6 +40,7 @@ var itemName = map[itemType]string{
itemIf: "if",
itemEnd: "end",
itemNil: "nil",
+ itemParent: "parent",
itemRange: "range",
itemTemplate: "template",
itemWith: "with",
diff --git a/src/text/template/parse/node.go b/src/text/template/parse/node.go
index 177482f9b26059..558a3a7ac2a9da 100644
--- a/src/text/template/parse/node.go
+++ b/src/text/template/parse/node.go
@@ -937,14 +937,15 @@ func (w *WithNode) Copy() Node {
type TemplateNode struct {
NodeType
Pos
- tr *Tree
- Line int // The line number in the input. Deprecated: Kept for compatibility.
- Name string // The name of the template (unquoted).
- Pipe *PipeNode // The command to evaluate as dot for the template.
+ tr *Tree
+ Line int // The line number in the input. Deprecated: Kept for compatibility.
+ Name string // The name of the template (unquoted).
+ Parent bool // Whether to lookup template of this name in parent scope.
+ Pipe *PipeNode // The command to evaluate as dot for the template.
}
-func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
- return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
+func (t *Tree) newTemplate(pos Pos, line int, name string, parent bool, pipe *PipeNode) *TemplateNode {
+ return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Parent: parent, Pipe: pipe}
}
func (t *TemplateNode) String() string {
@@ -955,7 +956,11 @@ func (t *TemplateNode) String() string {
func (t *TemplateNode) writeTo(sb *strings.Builder) {
sb.WriteString("{{template ")
- sb.WriteString(strconv.Quote(t.Name))
+ if t.Parent {
+ sb.WriteString("parent")
+ } else {
+ sb.WriteString(strconv.Quote(t.Name))
+ }
if t.Pipe != nil {
sb.WriteByte(' ')
t.Pipe.writeTo(sb)
@@ -968,5 +973,5 @@ func (t *TemplateNode) tree() *Tree {
}
func (t *TemplateNode) Copy() Node {
- return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
+ return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Parent, t.Pipe.CopyPipe())
}
diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go
index 1a63961c13ecce..19a1e09b61fd06 100644
--- a/src/text/template/parse/parse.go
+++ b/src/text/template/parse/parse.go
@@ -580,7 +580,7 @@ func (t *Tree) blockControl() Node {
block.add()
block.stopParse()
- return t.newTemplate(token.pos, token.line, name, pipe)
+ return t.newTemplate(token.pos, token.line, name, false, pipe)
}
// Template:
@@ -590,14 +590,32 @@ func (t *Tree) blockControl() Node {
func (t *Tree) templateControl() Node {
const context = "template clause"
token := t.nextNonSpace()
- name := t.parseTemplateName(token, context)
+ var (
+ name string
+ parent bool
+ )
+ if token.typ == itemParent {
+ // If we encounter the `parent` keyword, then we assume the template we're
+ // in the midst currently defining is overriding a previously defined
+ // template, and that the template author's intent is to execute that
+ // "parent" template as part of this "child" template.
+ //
+ // We know the name of the template currently being defined (i.e., the one
+ // overriding a previous definition), so we set a flag to indicate to
+ // template execution that it should lookup the "parent" template with the
+ // same name.
+ name = t.Name
+ parent = true
+ } else {
+ name = t.parseTemplateName(token, context)
+ }
var pipe *PipeNode
if t.nextNonSpace().typ != itemRightDelim {
t.backup()
// Do not pop variables; they persist until "end".
pipe = t.pipeline(context, itemRightDelim)
}
- return t.newTemplate(token.pos, token.line, name, pipe)
+ return t.newTemplate(token.pos, token.line, name, parent, pipe)
}
func (t *Tree) parseTemplateName(token item, context string) (name string) {
diff --git a/src/text/template/parse/parse_test.go b/src/text/template/parse/parse_test.go
index 9b1be272e573d9..2aaf9e4b58c317 100644
--- a/src/text/template/parse/parse_test.go
+++ b/src/text/template/parse/parse_test.go
@@ -236,6 +236,8 @@ var parseTests = []parseTest{
`{{template "x"}}`},
{"template with arg", "{{template `x` .Y}}", noError,
`{{template "x" .Y}}`},
+ {"template with parent", "{{template parent .}}", noError,
+ `{{template parent .}}`},
{"with", "{{with .X}}hello{{end}}", noError,
`{{with .X}}"hello"{{end}}`},
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
diff --git a/src/text/template/template.go b/src/text/template/template.go
index fd74d45e9b1c6d..59d49fb531c2bd 100644
--- a/src/text/template/template.go
+++ b/src/text/template/template.go
@@ -12,8 +12,11 @@ import (
// common holds the information shared by related templates.
type common struct {
- tmpl map[string]*Template // Map from name to defined templates.
- muTmpl sync.RWMutex // protects tmpl
+ // Map from name to defined templates. There is a slice of templates,
+ // because there are generations of previously defined templates with the
+ // same name.
+ tmpl map[string][]*Template
+ muTmpl sync.RWMutex // protects tmpl
option option
// We use two maps, one for parsing and one for execution.
// This separation makes the API cleaner since it doesn't
@@ -70,7 +73,7 @@ func (t *Template) New(name string) *Template {
func (t *Template) init() {
if t.common == nil {
c := new(common)
- c.tmpl = make(map[string]*Template)
+ c.tmpl = make(map[string][]*Template)
c.parseFuncs = make(FuncMap)
c.execFuncs = make(map[string]reflect.Value)
t.common = c
@@ -91,14 +94,16 @@ func (t *Template) Clone() (*Template, error) {
}
t.muTmpl.RLock()
defer t.muTmpl.RUnlock()
- for k, v := range t.tmpl {
+ for k, generations := range t.tmpl {
if k == t.name {
- nt.tmpl[t.name] = nt
+ nt.tmpl[t.name] = append(nt.tmpl[t.name], nt)
continue
}
// The associated templates share nt's common structure.
- tmpl := v.copy(nt.common)
- nt.tmpl[k] = tmpl
+ for i := range generations {
+ tmpl := generations[i].copy(nt.common)
+ nt.tmpl[k] = append(nt.tmpl[k], tmpl)
+ }
}
t.muFuncs.RLock()
defer t.muFuncs.RUnlock()
@@ -150,8 +155,8 @@ func (t *Template) Templates() []*Template {
t.muTmpl.RLock()
defer t.muTmpl.RUnlock()
m := make([]*Template, 0, len(t.tmpl))
- for _, v := range t.tmpl {
- m = append(m, v)
+ for _, generations := range t.tmpl {
+ m = append(m, generations[0])
}
return m
}
@@ -191,7 +196,23 @@ func (t *Template) Lookup(name string) *Template {
}
t.muTmpl.RLock()
defer t.muTmpl.RUnlock()
- return t.tmpl[name]
+ return t.lookup(name, 0)
+}
+
+func (t *Template) LookupByLevel(name string, level int) *Template {
+ if t.common == nil {
+ return nil
+ }
+ t.muTmpl.RLock()
+ defer t.muTmpl.RUnlock()
+ return t.lookup(name, level)
+}
+
+func (t *Template) lookup(name string, level int) *Template {
+ if level < 0 || level >= len(t.tmpl[name]) {
+ return nil
+ }
+ return t.tmpl[name][level]
}
// Parse parses text as a template body for t.
@@ -228,11 +249,22 @@ func (t *Template) associate(new *Template, tree *parse.Tree) bool {
if new.common != t.common {
panic("internal error: associate not common")
}
- if old := t.tmpl[new.name]; old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil {
+ if old := t.lookup(new.name, 0); old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil {
// If a template by that name exists,
// don't replace it with an empty template.
return false
}
- t.tmpl[new.name] = new
+
+ // associating a template with the name of an existing one retains a
+ // reference to the original, pushing the new one to the front of a slice
+ // of previously defined templates, so that previously defined templates by
+ // that name can be accessed by indexing into the slice, each successive
+ // level being an older generation.
+ generations := t.tmpl[new.name]
+ generations = append(generations, nil)
+ copy(generations[1:], generations[0:])
+ generations[0] = new
+ t.tmpl[new.name] = generations
+
return true
}