]> Cypherpunks repositories - gostls13.git/commitdiff
template: add method Delims to allow alternate action delimiters.
authorRob Pike <r@golang.org>
Thu, 6 Oct 2011 20:30:50 +0000 (13:30 -0700)
committerRob Pike <r@golang.org>
Thu, 6 Oct 2011 20:30:50 +0000 (13:30 -0700)
R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5209045

src/pkg/template/exec_test.go
src/pkg/template/parse.go
src/pkg/template/parse/lex.go
src/pkg/template/parse/lex_test.go
src/pkg/template/parse/parse.go
src/pkg/template/parse/parse_test.go
src/pkg/template/parse/set.go
src/pkg/template/set.go

index 8e1894ea03be5c02da4bde35bdc2718f74f7357d..57c63257c4317297249ae3be7ebd3b0c9f74e5b9 100644 (file)
@@ -493,6 +493,43 @@ func TestExecute(t *testing.T) {
        testExecute(execTests, nil, t)
 }
 
+var delimPairs = []string{
+       "", "", // default
+       "{{", "}}", // same as default
+       "<<", ">>", // distinct
+       "|", "|", // same
+       "(日)", "(本)", // peculiar
+}
+
+func TestDelims(t *testing.T) {
+       const hello = "Hello, world"
+       var value = struct{ Str string }{hello}
+       for i := 0; i < len(delimPairs); i += 2 {
+               text := ".Str"
+               left := delimPairs[i+0]
+               right := delimPairs[i+1]
+               if left == "" { // default case
+                       text = "{{" + text
+               }
+               if right == "" { // default case
+                       text = text + "}}"
+               }
+               text = left + text + right
+               tmpl, err := New("delims").Delims(left, right).Parse(text)
+               if err != nil {
+                       t.Fatalf("delim %q text %q parse err %s", left, text, err)
+               }
+               var b = new(bytes.Buffer)
+               err = tmpl.Execute(b, value)
+               if err != nil {
+                       t.Fatalf("delim %q exec err %s", left, err)
+               }
+               if b.String() != hello {
+                       t.Error("expected %q got %q", hello, b.String())
+               }
+       }
+}
+
 // Check that an error from a method flows back to the top.
 func TestExecuteError(t *testing.T) {
        b := new(bytes.Buffer)
@@ -538,18 +575,19 @@ type Tree struct {
        Left, Right *Tree
 }
 
+// Use different delimiters to test Set.Delims.
 const treeTemplate = `
-       {{define "tree"}}
+       (define "tree")
        [
-               {{.Val}}
-               {{with .Left}}
-                       {{template "tree" .}}
-               {{end}}
-               {{with .Right}}
-                       {{template "tree" .}}
-               {{end}}
+               (.Val)
+               (with .Left)
+                       (template "tree" .)
+               (end)
+               (with .Right)
+                       (template "tree" .)
+               (end)
        ]
-       {{end}}
+       (end)
 `
 
 func TestTree(t *testing.T) {
@@ -590,7 +628,7 @@ func TestTree(t *testing.T) {
                },
        }
        set := new(Set)
-       _, err := set.Parse(treeTemplate)
+       _, err := set.Delims("(", ")").Parse(treeTemplate)
        if err != nil {
                t.Fatal("parse error:", err)
        }
index b089c599a4769ee26b334cb3e3959293cfe2952c..3068a77bed929295948f71b532d253b3e487b68e 100644 (file)
@@ -14,6 +14,8 @@ import (
 type Template struct {
        name string
        *parse.Tree
+       leftDelim  string
+       rightDelim string
        // We use two maps, one for parsing and one for execution.
        // This separation makes the API cleaner since it doesn't
        // expose reflection to the client.
@@ -38,6 +40,16 @@ func New(name string) *Template {
        }
 }
 
+// Delims sets the action delimiters, to be used in a subsequent
+// parse, to the specified strings.
+// An empty delimiter stands for the corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+       t.leftDelim = left
+       t.rightDelim = right
+       return t
+}
+
 // 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.
@@ -51,7 +63,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
 // Parse parses the template definition string to construct an internal
 // representation of the template for execution.
 func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
-       t.Tree, err = parse.New(t.name).Parse(s, t.parseFuncs, builtins)
+       t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
        if err != nil {
                return nil, err
        }
@@ -67,7 +79,7 @@ func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err os.Error)
        if set != nil {
                setFuncs = set.parseFuncs
        }
-       t.Tree, err = parse.New(t.name).Parse(s, t.parseFuncs, setFuncs, builtins)
+       t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, setFuncs, builtins)
        if err != nil {
                return nil, err
        }
index 83ad6c628bfa3c98ee7676d75fe849a8bbf5282e..07740d79117403e8caf85d72515daf15ca6fbdf0 100644 (file)
@@ -119,13 +119,15 @@ type stateFn func(*lexer) stateFn
 
 // lexer holds the state of the scanner.
 type lexer struct {
-       name  string    // the name of the input; used only for error reports.
-       input string    // the string being scanned.
-       state stateFn   // the next lexing function to enter
-       pos   int       // current position in the input.
-       start int       // start position of this item.
-       width int       // width of last rune read from input.
-       items chan item // channel of scanned items.
+       name       string    // the name of the input; used only for error reports.
+       input      string    // the string being scanned.
+       leftDelim  string    // start of action.
+       rightDelim string    // end of action.
+       state      stateFn   // the next lexing function to enter.
+       pos        int       // current position in the input.
+       start      int       // start position of this item.
+       width      int       // width of last rune read from input.
+       items      chan item // channel of scanned items.
 }
 
 // next returns the next rune in the input.
@@ -205,12 +207,20 @@ func (l *lexer) nextItem() item {
 }
 
 // lex creates a new scanner for the input string.
-func lex(name, input string) *lexer {
+func lex(name, input, left, right string) *lexer {
+       if left == "" {
+               left = leftDelim
+       }
+       if right == "" {
+               right = rightDelim
+       }
        l := &lexer{
-               name:  name,
-               input: input,
-               state: lexText,
-               items: make(chan item, 2), // Two items of buffering is sufficient for all state functions
+               name:       name,
+               input:      input,
+               leftDelim:  left,
+               rightDelim: right,
+               state:      lexText,
+               items:      make(chan item, 2), // Two items of buffering is sufficient for all state functions
        }
        return l
 }
@@ -227,7 +237,7 @@ const (
 // lexText scans until an opening action delimiter, "{{".
 func lexText(l *lexer) stateFn {
        for {
-               if strings.HasPrefix(l.input[l.pos:], leftDelim) {
+               if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
                        if l.pos > l.start {
                                l.emit(itemText)
                        }
@@ -250,7 +260,7 @@ func lexLeftDelim(l *lexer) stateFn {
        if strings.HasPrefix(l.input[l.pos:], leftComment) {
                return lexComment
        }
-       l.pos += len(leftDelim)
+       l.pos += len(l.leftDelim)
        l.emit(itemLeftDelim)
        return lexInsideAction
 }
@@ -268,7 +278,7 @@ func lexComment(l *lexer) stateFn {
 
 // lexRightDelim scans the right delimiter, which is known to be present.
 func lexRightDelim(l *lexer) stateFn {
-       l.pos += len(rightDelim)
+       l.pos += len(l.rightDelim)
        l.emit(itemRightDelim)
        return lexText
 }
@@ -278,7 +288,7 @@ func lexInsideAction(l *lexer) stateFn {
        // Either number, quoted string, or identifier.
        // Spaces separate and are ignored.
        // Pipe symbols separate and are emitted.
-       if strings.HasPrefix(l.input[l.pos:], rightDelim) {
+       if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
                return lexRightDelim
        }
        switch r := l.next(); {
index d71c8e66df29afbf844430639807b587e4fb68fc..f2569b1576478ce54eb9109bd85bafa95a2bc06d 100644 (file)
@@ -201,8 +201,8 @@ var lexTests = []lexTest{
 }
 
 // collect gathers the emitted items into a slice.
-func collect(t *lexTest) (items []item) {
-       l := lex(t.name, t.input)
+func collect(t *lexTest, left, right string) (items []item) {
+       l := lex(t.name, t.input, left, right)
        for {
                item := l.nextItem()
                items = append(items, item)
@@ -215,7 +215,41 @@ func collect(t *lexTest) (items []item) {
 
 func TestLex(t *testing.T) {
        for _, test := range lexTests {
-               items := collect(&test)
+               items := collect(&test, "", "")
+               if !reflect.DeepEqual(items, test.items) {
+                       t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
+               }
+       }
+}
+
+// Some easy cases from above, but with delimiters are $$ and @@
+var lexDelimTests = []lexTest{
+       {"punctuation", "$$,@%{{}}@@", []item{
+               tLeftDelim,
+               {itemChar, ","},
+               {itemChar, "@"},
+               {itemChar, "%"},
+               {itemChar, "{"},
+               {itemChar, "{"},
+               {itemChar, "}"},
+               {itemChar, "}"},
+               tRightDelim,
+               tEOF,
+       }},
+       {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
+       {"for", `$$for @@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
+       {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
+       {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
+}
+
+var (
+       tLeftDelim  = item{itemLeftDelim, "$$"}
+       tRightDelim = item{itemRightDelim, "@@"}
+)
+
+func TestDelims(t *testing.T) {
+       for _, test := range lexDelimTests {
+               items := collect(&test, "$$", "@@")
                if !reflect.DeepEqual(items, test.items) {
                        t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
                }
index 6918074664e9e9e0f146ef90034e45d5ab836159..9934d8221dbc4d0d380eb358c128ab13bc23fd3e 100644 (file)
@@ -145,10 +145,11 @@ func (t *Tree) atEOF() bool {
 }
 
 // Parse parses the template definition string to construct an internal
-// representation of the template for execution.
-func (t *Tree) Parse(s string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) {
+// representation of the template for execution. If either action delimiter
+// string is empty, the default ("{{" or "}}") is used.
+func (t *Tree) Parse(s, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) {
        defer t.recover(&err)
-       t.startParse(funcs, lex(t.Name, s))
+       t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
        t.parse(true)
        t.stopParse()
        return t, nil
index 1928c319dec6df6dcdb1a722c09ad84b0315a883..f05f6e3874cc86c69ba5707fbba6105a3d7f22d6 100644 (file)
@@ -236,7 +236,7 @@ var builtins = map[string]interface{}{
 
 func TestParse(t *testing.T) {
        for _, test := range parseTests {
-               tmpl, err := New(test.name).Parse(test.input, builtins)
+               tmpl, err := New(test.name).Parse(test.input, "", "", builtins)
                switch {
                case err == nil && !test.ok:
                        t.Errorf("%q: expected error; got none", test.name)
index dca41ea76cdac3d3a07d313e106d89f83faed208..b909f71cd76852044963745eaaf7eead1898c77e 100644 (file)
@@ -13,10 +13,10 @@ import (
 // Set returns a slice of Trees created by parsing the template set
 // definition in the argument string. If an error is encountered,
 // parsing stops and an empty slice is returned with the error.
-func Set(text string, funcs ...map[string]interface{}) (tree map[string]*Tree, err os.Error) {
+func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err os.Error) {
        tree = make(map[string]*Tree)
        defer (*Tree)(nil).recover(&err)
-       lex := lex("set", text)
+       lex := lex("set", text, leftDelim, rightDelim)
        const context = "define clause"
        for {
                t := New("set") // name will be updated once we know it.
index f778fd1693f32c1222ca3295748dde710323009b..712961b7314f6066fc9d704978be42e8e2e371ff 100644 (file)
@@ -17,6 +17,8 @@ import (
 // A template may be a member of multiple sets.
 type Set struct {
        tmpl       map[string]*Template
+       leftDelim  string
+       rightDelim string
        parseFuncs FuncMap
        execFuncs  map[string]reflect.Value
 }
@@ -29,6 +31,16 @@ func (s *Set) init() {
        }
 }
 
+// Delims sets the action delimiters, to be used in a subsequent
+// parse, to the specified strings.
+// An empty delimiter stands for the corresponding default: {{ or }}.
+// The return value is the set, so calls can be chained.
+func (s *Set) Delims(left, right string) *Set {
+       s.leftDelim = left
+       s.rightDelim = right
+       return s
+}
+
 // 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.
@@ -93,7 +105,7 @@ func (s *Set) Execute(wr io.Writer, name string, data interface{}) 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) (*Set, os.Error) {
-       trees, err := parse.Set(text, s.parseFuncs, builtins)
+       trees, err := parse.Set(text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins)
        if err != nil {
                return nil, err
        }