diff options
author | Rob Pike <r@golang.org> | 2011-10-06 13:30:50 -0700 |
---|---|---|
committer | Rob Pike <r@golang.org> | 2011-10-06 13:30:50 -0700 |
commit | dcf53189900b814925475b09770092d06f362ebc (patch) | |
tree | 733f47cc0f05c1d946a961983040f786c52293ba | |
parent | edacc863d0dd3806fd2caf869a99852d39c8ed08 (diff) | |
download | go-dcf53189900b814925475b09770092d06f362ebc.tar.gz go-dcf53189900b814925475b09770092d06f362ebc.zip |
template: add method Delims to allow alternate action delimiters.
R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5209045
-rw-r--r-- | src/pkg/template/exec_test.go | 58 | ||||
-rw-r--r-- | src/pkg/template/parse.go | 16 | ||||
-rw-r--r-- | src/pkg/template/parse/lex.go | 42 | ||||
-rw-r--r-- | src/pkg/template/parse/lex_test.go | 40 | ||||
-rw-r--r-- | src/pkg/template/parse/parse.go | 7 | ||||
-rw-r--r-- | src/pkg/template/parse/parse_test.go | 2 | ||||
-rw-r--r-- | src/pkg/template/parse/set.go | 4 | ||||
-rw-r--r-- | src/pkg/template/set.go | 14 |
8 files changed, 145 insertions, 38 deletions
diff --git a/src/pkg/template/exec_test.go b/src/pkg/template/exec_test.go index 8e1894ea03..57c63257c4 100644 --- a/src/pkg/template/exec_test.go +++ b/src/pkg/template/exec_test.go @@ -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) } diff --git a/src/pkg/template/parse.go b/src/pkg/template/parse.go index b089c599a4..3068a77bed 100644 --- a/src/pkg/template/parse.go +++ b/src/pkg/template/parse.go @@ -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 } diff --git a/src/pkg/template/parse/lex.go b/src/pkg/template/parse/lex.go index 83ad6c628b..07740d7911 100644 --- a/src/pkg/template/parse/lex.go +++ b/src/pkg/template/parse/lex.go @@ -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(); { diff --git a/src/pkg/template/parse/lex_test.go b/src/pkg/template/parse/lex_test.go index d71c8e66df..f2569b1576 100644 --- a/src/pkg/template/parse/lex_test.go +++ b/src/pkg/template/parse/lex_test.go @@ -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) } diff --git a/src/pkg/template/parse/parse.go b/src/pkg/template/parse/parse.go index 6918074664..9934d8221d 100644 --- a/src/pkg/template/parse/parse.go +++ b/src/pkg/template/parse/parse.go @@ -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 diff --git a/src/pkg/template/parse/parse_test.go b/src/pkg/template/parse/parse_test.go index 1928c319de..f05f6e3874 100644 --- a/src/pkg/template/parse/parse_test.go +++ b/src/pkg/template/parse/parse_test.go @@ -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) diff --git a/src/pkg/template/parse/set.go b/src/pkg/template/parse/set.go index dca41ea76c..b909f71cd7 100644 --- a/src/pkg/template/parse/set.go +++ b/src/pkg/template/parse/set.go @@ -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. diff --git a/src/pkg/template/set.go b/src/pkg/template/set.go index f778fd1693..712961b731 100644 --- a/src/pkg/template/set.go +++ b/src/pkg/template/set.go @@ -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 } |