]> Cypherpunks repositories - gostls13.git/commitdiff
text/template/parse: deep Copy method for nodes
authorRob Pike <r@golang.org>
Sat, 11 Feb 2012 03:21:16 +0000 (14:21 +1100)
committerRob Pike <r@golang.org>
Sat, 11 Feb 2012 03:21:16 +0000 (14:21 +1100)
This will help html/template copy templates.

R=golang-dev, gri, nigeltao, r
CC=golang-dev
https://golang.org/cl/5653062

src/pkg/text/template/parse/node.go
src/pkg/text/template/parse/parse_test.go

index 0d030b8b4be6d07abb042c2b1f5c2a837009a792..db645624c562064725d2189146d941e2adfe9a72 100644 (file)
@@ -17,6 +17,10 @@ import (
 type Node interface {
        Type() NodeType
        String() string
+       // Copy does a deep copy of the Node and all its components.
+       // To avoid type assertions, some XxxNodes also have specialized
+       // CopyXxx methods that return *XxxNode.
+       Copy() Node
 }
 
 // NodeType identifies the type of a parse tree node.
@@ -73,6 +77,21 @@ func (l *ListNode) String() string {
        return b.String()
 }
 
+func (l *ListNode) CopyList() *ListNode {
+       if l == nil {
+               return l
+       }
+       n := newList()
+       for _, elem := range l.Nodes {
+               n.append(elem.Copy())
+       }
+       return n
+}
+
+func (l *ListNode) Copy() Node {
+       return l.CopyList()
+}
+
 // TextNode holds plain text.
 type TextNode struct {
        NodeType
@@ -87,6 +106,10 @@ func (t *TextNode) String() string {
        return fmt.Sprintf("%q", t.Text)
 }
 
+func (t *TextNode) Copy() Node {
+       return &TextNode{NodeType: NodeText, Text: append([]byte{}, t.Text...)}
+}
+
 // PipeNode holds a pipeline with optional declaration
 type PipeNode struct {
        NodeType
@@ -123,6 +146,25 @@ func (p *PipeNode) String() string {
        return s
 }
 
+func (p *PipeNode) CopyPipe() *PipeNode {
+       if p == nil {
+               return p
+       }
+       var decl []*VariableNode
+       for _, d := range p.Decl {
+               decl = append(decl, d.Copy().(*VariableNode))
+       }
+       n := newPipeline(p.Line, decl)
+       for _, c := range p.Cmds {
+               n.append(c.Copy().(*CommandNode))
+       }
+       return n
+}
+
+func (p *PipeNode) Copy() Node {
+       return p.CopyPipe()
+}
+
 // ActionNode holds an action (something bounded by delimiters).
 // Control actions have their own nodes; ActionNode represents simple
 // ones such as field evaluations.
@@ -141,6 +183,11 @@ func (a *ActionNode) String() string {
 
 }
 
+func (a *ActionNode) Copy() Node {
+       return newAction(a.Line, a.Pipe.CopyPipe())
+
+}
+
 // CommandNode holds a command (a pipeline inside an evaluating action).
 type CommandNode struct {
        NodeType
@@ -166,6 +213,17 @@ func (c *CommandNode) String() string {
        return s
 }
 
+func (c *CommandNode) Copy() Node {
+       if c == nil {
+               return c
+       }
+       n := newCommand()
+       for _, c := range c.Args {
+               n.append(c.Copy())
+       }
+       return n
+}
+
 // IdentifierNode holds an identifier.
 type IdentifierNode struct {
        NodeType
@@ -181,6 +239,10 @@ func (i *IdentifierNode) String() string {
        return i.Ident
 }
 
+func (i *IdentifierNode) Copy() Node {
+       return NewIdentifier(i.Ident)
+}
+
 // VariableNode holds a list of variable names. The dollar sign is
 // part of the name.
 type VariableNode struct {
@@ -203,6 +265,10 @@ func (v *VariableNode) String() string {
        return s
 }
 
+func (v *VariableNode) Copy() Node {
+       return &VariableNode{NodeType: NodeVariable, Ident: append([]string{}, v.Ident...)}
+}
+
 // DotNode holds the special identifier '.'. It is represented by a nil pointer.
 type DotNode bool
 
@@ -218,6 +284,10 @@ func (d *DotNode) String() string {
        return "."
 }
 
+func (d *DotNode) Copy() Node {
+       return newDot()
+}
+
 // FieldNode holds a field (identifier starting with '.').
 // The names may be chained ('.x.y').
 // The period is dropped from each ident.
@@ -238,6 +308,10 @@ func (f *FieldNode) String() string {
        return s
 }
 
+func (f *FieldNode) Copy() Node {
+       return &FieldNode{NodeType: NodeField, Ident: append([]string{}, f.Ident...)}
+}
+
 // BoolNode holds a boolean constant.
 type BoolNode struct {
        NodeType
@@ -255,6 +329,10 @@ func (b *BoolNode) String() string {
        return "false"
 }
 
+func (b *BoolNode) Copy() Node {
+       return newBool(b.True)
+}
+
 // NumberNode holds a number: signed or unsigned integer, float, or complex.
 // The value is parsed and stored under all the types that can represent the value.
 // This simulates in a small amount of code the behavior of Go's ideal constants.
@@ -373,6 +451,12 @@ func (n *NumberNode) String() string {
        return n.Text
 }
 
+func (n *NumberNode) Copy() Node {
+       nn := new(NumberNode)
+       *nn = *n // Easy, fast, correct.
+       return nn
+}
+
 // StringNode holds a string constant. The value has been "unquoted".
 type StringNode struct {
        NodeType
@@ -388,6 +472,10 @@ func (s *StringNode) String() string {
        return s.Quoted
 }
 
+func (s *StringNode) Copy() Node {
+       return newString(s.Quoted, s.Text)
+}
+
 // endNode represents an {{end}} action. It is represented by a nil pointer.
 // It does not appear in the final parse tree.
 type endNode bool
@@ -404,6 +492,10 @@ func (e *endNode) String() string {
        return "{{end}}"
 }
 
+func (e *endNode) Copy() Node {
+       return newEnd()
+}
+
 // elseNode represents an {{else}} action. Does not appear in the final tree.
 type elseNode struct {
        NodeType
@@ -422,6 +514,10 @@ func (e *elseNode) String() string {
        return "{{else}}"
 }
 
+func (e *elseNode) Copy() Node {
+       return newElse(e.Line)
+}
+
 // BranchNode is the common representation of if, range, and with.
 type BranchNode struct {
        NodeType
@@ -458,6 +554,10 @@ func newIf(line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
        return &IfNode{BranchNode{NodeType: NodeIf, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
+func (i *IfNode) Copy() Node {
+       return newIf(i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
+}
+
 // RangeNode represents a {{range}} action and its commands.
 type RangeNode struct {
        BranchNode
@@ -467,6 +567,10 @@ func newRange(line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
        return &RangeNode{BranchNode{NodeType: NodeRange, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
+func (r *RangeNode) Copy() Node {
+       return newRange(r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
+}
+
 // WithNode represents a {{with}} action and its commands.
 type WithNode struct {
        BranchNode
@@ -476,6 +580,10 @@ func newWith(line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
        return &WithNode{BranchNode{NodeType: NodeWith, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
 }
 
+func (w *WithNode) Copy() Node {
+       return newWith(w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
+}
+
 // TemplateNode represents a {{template}} action.
 type TemplateNode struct {
        NodeType
@@ -494,3 +602,7 @@ func (t *TemplateNode) String() string {
        }
        return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
 }
+
+func (t *TemplateNode) Copy() Node {
+       return newTemplate(t.Line, t.Name, t.Pipe.CopyPipe())
+}
index 13c5548abbf5616cb37f2baffa8e5fe324a6e26e..efa7d8be7424170b11a77ea8997e26f220d38004 100644 (file)
@@ -232,7 +232,7 @@ var builtins = map[string]interface{}{
        "printf": fmt.Sprintf,
 }
 
-func TestParse(t *testing.T) {
+func testParse(doCopy bool, t *testing.T) {
        for _, test := range parseTests {
                tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
                switch {
@@ -249,13 +249,27 @@ func TestParse(t *testing.T) {
                        }
                        continue
                }
-               result := tmpl.Root.String()
+               var result string
+               if doCopy {
+                       result = tmpl.Root.Copy().String()
+               } else {
+                       result = tmpl.Root.String()
+               }
                if result != test.result {
                        t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
                }
        }
 }
 
+func TestParse(t *testing.T) {
+       testParse(false, t)
+}
+
+// Same as TestParse, but we copy the node first
+func TestParseCopy(t *testing.T) {
+       testParse(true, t)
+}
+
 type isEmptyTest struct {
        name  string
        input string