]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template/html: New package with a toy template transformation.
authorMike Samuel <mikesamuel@gmail.com>
Fri, 12 Aug 2011 04:34:29 +0000 (14:34 +1000)
committerRob Pike <r@golang.org>
Fri, 12 Aug 2011 04:34:29 +0000 (14:34 +1000)
func Reverse(*Template) *Template
returns a template that produces the reverse of the original
for any input.

Changes outside exp/template/html include:
- Adding a getter for a template's FuncMap so that derived templates
  can inherit function definitions.
- Exported one node factory function, newIdentifier.
  Deriving tempaltes requires constructing new nodes, but I didn't
  export all of them because I think shallow copy functions might
  be more useful for this kind of work.
- Bugfix: Template's Name() method ignores the name field so
  template.New("foo") is a nil dereference instead of "foo".

Caveats: Reverse is a toy.  It is not UTF-8 safe, and does not
preserve order of calls to funcs in FuncMap.

For context, see http://groups.google.com/group/golang-nuts/browse_thread/thread/e8bc7c771aae3f20/b1ac41dc6f609b6e?lnk=gst

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

src/pkg/Makefile
src/pkg/exp/template/html/Makefile [new file with mode: 0644]
src/pkg/exp/template/html/reverse.go [new file with mode: 0644]
src/pkg/exp/template/html/reverse_test.go [new file with mode: 0644]
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse/node.go
src/pkg/exp/template/parse/parse.go

index c824f508cfdbce24ce8e2ffdc90c912c02dfcb4e..ec9a070bd1f3eec08088df8ba3f5e106e8fe2dd1 100644 (file)
@@ -83,6 +83,7 @@ DIRS=\
        exp/norm\
        exp/regexp/syntax\
        exp/template\
+       exp/template/html\
        exp/template/parse\
        expvar\
        flag\
diff --git a/src/pkg/exp/template/html/Makefile b/src/pkg/exp/template/html/Makefile
new file mode 100644 (file)
index 0000000..e532950
--- /dev/null
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../../Make.inc
+
+TARG=exp/template/html
+GOFILES=\
+       reverse.go
+
+include ../../../../Make.pkg
diff --git a/src/pkg/exp/template/html/reverse.go b/src/pkg/exp/template/html/reverse.go
new file mode 100644 (file)
index 0000000..446e0f7
--- /dev/null
@@ -0,0 +1,134 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package html is a specialization of exp/template that automates the
+// construction of safe HTML output.
+// At the moment it is just skeleton code that demonstrates how to derive
+// templates via AST -> AST transformations.
+package html
+
+import (
+       "exp/template"
+       "exp/template/parse"
+       "fmt"
+)
+
+// Reverse reverses a template.
+// After Reverse(t), t.Execute(wr, data) writes to wr the byte-wise reverse of
+// what would have been written otherwise.
+//
+// E.g.
+// Reverse(template.Parse("{{if .Coming}}Hello{{else}}Bye{{end}}, {{.World}}")
+// behaves like
+// template.Parse("{{.World | reverse}} ,{{if .Coming}}olleH{{else}}eyB{{end}}")
+func Reverse(t *template.Template) {
+       t.Funcs(supportFuncs)
+
+       // If the parser shares trees based on common-subexpression
+       // joining then we will need to avoid multiply reversing the same tree.
+       reverseListNode(t.Tree.Root)
+}
+
+// reverseNode dispatches to reverse<NodeType> helpers by type.
+func reverseNode(node parse.Node) {
+       switch n := node.(type) {
+       case *parse.ListNode:
+               reverseListNode(n)
+       case *parse.TextNode:
+               reverseTextNode(n)
+       case *parse.ActionNode:
+               reverseActionNode(n)
+       case *parse.IfNode:
+               reverseIfNode(n)
+       default:
+               panic("handling for " + node.String() + " not implemented")
+               // TODO: Handle other inner node types.
+       }
+}
+
+// reverseListNode recursively reverses its input's children and reverses their
+// order.
+func reverseListNode(node *parse.ListNode) {
+       if node == nil {
+               return
+       }
+       children := node.Nodes
+       for _, child := range children {
+               reverseNode(child)
+       }
+       for i, j := 0, len(children)-1; i < j; i, j = i+1, j-1 {
+               children[i], children[j] = children[j], children[i]
+       }
+}
+
+// reverseTextNode reverses the text UTF-8 sequence by UTF-8 sequence.
+func reverseTextNode(node *parse.TextNode) {
+       runes := []int(string(node.Text))
+       for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
+               runes[i], runes[j] = runes[j], runes[i]
+       }
+       node.Text = []byte(string(runes))
+}
+
+// reverseActionNode adds a pipeline call to the end that reverses the result
+// of the expression before it is interpolated into the template output.
+func reverseActionNode(node *parse.ActionNode) {
+       pipe := node.Pipe
+
+       cmds := pipe.Cmds
+       nCmds := len(cmds)
+
+       // If it's already been reversed, just slice out the reverse command.
+       // This makes (Reverse o Reverse) almost the identity function
+       // modulo changes to the templates FuncMap.
+       if nCmds != 0 {
+               if lastCmd := cmds[nCmds-1]; len(lastCmd.Args) != 0 {
+                       if arg, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok && arg.Ident == "reverse" {
+                               pipe.Cmds = pipe.Cmds[:nCmds-1]
+                               return
+                       }
+               }
+       }
+
+       reverseCommand := parse.CommandNode{
+               NodeType: parse.NodeCommand,
+               Args:     []parse.Node{parse.NewIdentifier("reverse")},
+       }
+
+       node.Pipe.Cmds = append(node.Pipe.Cmds, &reverseCommand)
+}
+
+// reverseIfNode recursively reverses the if and then clauses but leaves the
+// condition unchanged.
+func reverseIfNode(node *parse.IfNode) {
+       reverseListNode(node.List)
+       reverseListNode(node.ElseList)
+}
+
+// reverse writes the reverse of the given byte buffer to the given Writer.
+func reverse(x interface{}) string {
+       var s string
+       switch y := x.(type) {
+       case nil:
+               s = "<nil>"
+       case []byte:
+               // TODO: unnecessary buffer copy.
+               s = string(y)
+       case string:
+               s = y
+       case fmt.Stringer:
+               s = y.String()
+       default:
+               s = fmt.Sprintf("<inconvertible of type %T>", x)
+       }
+       n := len(s)
+       bytes := make([]byte, n)
+       for i := 0; i < n; i++ {
+               bytes[n-i-1] = s[i]
+       }
+       return string(bytes)
+}
+
+// supportFuncs contains functions required by reversed template nodes.
+var supportFuncs = template.FuncMap{"reverse": reverse}
diff --git a/src/pkg/exp/template/html/reverse_test.go b/src/pkg/exp/template/html/reverse_test.go
new file mode 100644 (file)
index 0000000..32d11c6
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package html
+
+import (
+       "bytes"
+       "exp/template"
+       "testing"
+)
+
+type data struct {
+       World  string
+       Coming bool
+}
+
+func TestReverse(t *testing.T) {
+       templateSource :=
+               "{{if .Coming}}Hello{{else}}Goodbye{{end}}, {{.World}}!"
+       templateData := data{
+               World:  "Cincinatti",
+               Coming: true,
+       }
+
+       tmpl := template.New("test")
+       tmpl, err := tmpl.Parse(templateSource)
+       if err != nil {
+               t.Errorf("failed to parse template: %s", err)
+               return
+       }
+
+       Reverse(tmpl)
+
+       buffer := new(bytes.Buffer)
+
+       err = tmpl.Execute(buffer, templateData)
+       if err != nil {
+               t.Errorf("failed to execute reversed template: %s", err)
+               return
+       }
+
+       golden := "!ittanicniC ,olleH"
+       actual := buffer.String()
+       if golden != actual {
+               t.Errorf("reversed output: %q != %q", golden, actual)
+       }
+}
index 6db00c1c117ea0a1383dc7fc3aec56acc88b726f..4b8a54e65ca8db55907cfd4dabed1e43d2d6e746 100644 (file)
@@ -24,7 +24,7 @@ type Template struct {
 
 // Name returns the name of the template.
 func (t *Template) Name() string {
-       return t.Tree.Name
+       return t.name
 }
 
 // Parsing.
index 0f77ad850e174fc5c9c8439fb24eff91627026f5..a917418dc39f2ce4f3f611706c363fa6ccf29df1 100644 (file)
@@ -31,14 +31,14 @@ func (t NodeType) Type() NodeType {
 
 const (
        NodeText       NodeType = iota // Plain text.
-       NodeAction                     // An simple action such as field evaluation.
+       NodeAction                     // A simple action such as field evaluation.
        NodeBool                       // A boolean constant.
        NodeCommand                    // An element of a pipeline.
        NodeDot                        // The cursor, dot.
        NodeElse                       // An else action.
        NodeEnd                        // An end action.
        NodeField                      // A field or method name.
-       NodeIdentifier                 // A identifier; always a function name.
+       NodeIdentifier                 // An identifier; always a function name.
        NodeIf                         // An if action.
        NodeList                       // A list of Nodes.
        NodeNumber                     // A numerical constant.
@@ -154,7 +154,8 @@ type IdentifierNode struct {
        Ident string // The identifier's name.
 }
 
-func newIdentifier(ident string) *IdentifierNode {
+// NewIdentifier returns a new IdentifierNode with the given identifier name.
+func NewIdentifier(ident string) *IdentifierNode {
        return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
 }
 
index f8f9023e54fb6939731fa0fd57dbc5ca1cf6b28c..691d85ef63d01439db89a31cc9ce708d78c96faa 100644 (file)
@@ -373,7 +373,7 @@ Loop:
                        if !t.hasFunction(token.val) {
                                t.errorf("function %q not defined", token.val)
                        }
-                       cmd.append(newIdentifier(token.val))
+                       cmd.append(NewIdentifier(token.val))
                case itemDot:
                        cmd.append(newDot())
                case itemVariable: