1
0
mirror of https://github.com/golang/go synced 2024-11-12 07:10:22 -07:00

template: add method Delims to allow alternate action delimiters.

R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5209045
This commit is contained in:
Rob Pike 2011-10-06 13:30:50 -07:00
parent edacc863d0
commit dcf5318990
8 changed files with 145 additions and 38 deletions

View File

@ -493,6 +493,43 @@ func TestExecute(t *testing.T) {
testExecute(execTests, nil, 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. // Check that an error from a method flows back to the top.
func TestExecuteError(t *testing.T) { func TestExecuteError(t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
@ -538,18 +575,19 @@ type Tree struct {
Left, Right *Tree Left, Right *Tree
} }
// Use different delimiters to test Set.Delims.
const treeTemplate = ` const treeTemplate = `
{{define "tree"}} (define "tree")
[ [
{{.Val}} (.Val)
{{with .Left}} (with .Left)
{{template "tree" .}} (template "tree" .)
{{end}} (end)
{{with .Right}} (with .Right)
{{template "tree" .}} (template "tree" .)
{{end}} (end)
] ]
{{end}} (end)
` `
func TestTree(t *testing.T) { func TestTree(t *testing.T) {
@ -590,7 +628,7 @@ func TestTree(t *testing.T) {
}, },
} }
set := new(Set) set := new(Set)
_, err := set.Parse(treeTemplate) _, err := set.Delims("(", ")").Parse(treeTemplate)
if err != nil { if err != nil {
t.Fatal("parse error:", err) t.Fatal("parse error:", err)
} }

View File

@ -14,6 +14,8 @@ import (
type Template struct { type Template struct {
name string name string
*parse.Tree *parse.Tree
leftDelim string
rightDelim string
// We use two maps, one for parsing and one for execution. // We use two maps, one for parsing and one for execution.
// This separation makes the API cleaner since it doesn't // This separation makes the API cleaner since it doesn't
// expose reflection to the client. // 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 // 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 // map. It panics if a value in the map is not a function with appropriate
// return type. // return type.
@ -51,7 +63,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
// Parse parses the template definition string to construct an internal // Parse parses the template definition string to construct an internal
// representation of the template for execution. // representation of the template for execution.
func (t *Template) Parse(s string) (tmpl *Template, err os.Error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -67,7 +79,7 @@ func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err os.Error)
if set != nil { if set != nil {
setFuncs = set.parseFuncs 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -119,13 +119,15 @@ type stateFn func(*lexer) stateFn
// lexer holds the state of the scanner. // lexer holds the state of the scanner.
type lexer struct { type lexer struct {
name string // the name of the input; used only for error reports. name string // the name of the input; used only for error reports.
input string // the string being scanned. input string // the string being scanned.
state stateFn // the next lexing function to enter leftDelim string // start of action.
pos int // current position in the input. rightDelim string // end of action.
start int // start position of this item. state stateFn // the next lexing function to enter.
width int // width of last rune read from input. pos int // current position in the input.
items chan item // channel of scanned items. 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. // 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. // 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{ l := &lexer{
name: name, name: name,
input: input, input: input,
state: lexText, leftDelim: left,
items: make(chan item, 2), // Two items of buffering is sufficient for all state functions rightDelim: right,
state: lexText,
items: make(chan item, 2), // Two items of buffering is sufficient for all state functions
} }
return l return l
} }
@ -227,7 +237,7 @@ const (
// lexText scans until an opening action delimiter, "{{". // lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn { func lexText(l *lexer) stateFn {
for { for {
if strings.HasPrefix(l.input[l.pos:], leftDelim) { if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
if l.pos > l.start { if l.pos > l.start {
l.emit(itemText) l.emit(itemText)
} }
@ -250,7 +260,7 @@ func lexLeftDelim(l *lexer) stateFn {
if strings.HasPrefix(l.input[l.pos:], leftComment) { if strings.HasPrefix(l.input[l.pos:], leftComment) {
return lexComment return lexComment
} }
l.pos += len(leftDelim) l.pos += len(l.leftDelim)
l.emit(itemLeftDelim) l.emit(itemLeftDelim)
return lexInsideAction return lexInsideAction
} }
@ -268,7 +278,7 @@ func lexComment(l *lexer) stateFn {
// lexRightDelim scans the right delimiter, which is known to be present. // lexRightDelim scans the right delimiter, which is known to be present.
func lexRightDelim(l *lexer) stateFn { func lexRightDelim(l *lexer) stateFn {
l.pos += len(rightDelim) l.pos += len(l.rightDelim)
l.emit(itemRightDelim) l.emit(itemRightDelim)
return lexText return lexText
} }
@ -278,7 +288,7 @@ func lexInsideAction(l *lexer) stateFn {
// Either number, quoted string, or identifier. // Either number, quoted string, or identifier.
// Spaces separate and are ignored. // Spaces separate and are ignored.
// Pipe symbols separate and are emitted. // 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 return lexRightDelim
} }
switch r := l.next(); { switch r := l.next(); {

View File

@ -201,8 +201,8 @@ var lexTests = []lexTest{
} }
// collect gathers the emitted items into a slice. // collect gathers the emitted items into a slice.
func collect(t *lexTest) (items []item) { func collect(t *lexTest, left, right string) (items []item) {
l := lex(t.name, t.input) l := lex(t.name, t.input, left, right)
for { for {
item := l.nextItem() item := l.nextItem()
items = append(items, item) items = append(items, item)
@ -215,7 +215,41 @@ func collect(t *lexTest) (items []item) {
func TestLex(t *testing.T) { func TestLex(t *testing.T) {
for _, test := range lexTests { 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) { if !reflect.DeepEqual(items, test.items) {
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
} }

View File

@ -145,10 +145,11 @@ func (t *Tree) atEOF() bool {
} }
// Parse parses the template definition string to construct an internal // Parse parses the template definition string to construct an internal
// representation of the template for execution. // representation of the template for execution. If either action delimiter
func (t *Tree) Parse(s string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) { // 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) defer t.recover(&err)
t.startParse(funcs, lex(t.Name, s)) t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
t.parse(true) t.parse(true)
t.stopParse() t.stopParse()
return t, nil return t, nil

View File

@ -236,7 +236,7 @@ var builtins = map[string]interface{}{
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
for _, test := range parseTests { for _, test := range parseTests {
tmpl, err := New(test.name).Parse(test.input, builtins) tmpl, err := New(test.name).Parse(test.input, "", "", builtins)
switch { switch {
case err == nil && !test.ok: case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name) t.Errorf("%q: expected error; got none", test.name)

View File

@ -13,10 +13,10 @@ import (
// Set returns a slice of Trees created by parsing the template set // Set returns a slice of Trees created by parsing the template set
// definition in the argument string. If an error is encountered, // definition in the argument string. If an error is encountered,
// parsing stops and an empty slice is returned with the error. // 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) tree = make(map[string]*Tree)
defer (*Tree)(nil).recover(&err) defer (*Tree)(nil).recover(&err)
lex := lex("set", text) lex := lex("set", text, leftDelim, rightDelim)
const context = "define clause" const context = "define clause"
for { for {
t := New("set") // name will be updated once we know it. t := New("set") // name will be updated once we know it.

View File

@ -17,6 +17,8 @@ import (
// A template may be a member of multiple sets. // A template may be a member of multiple sets.
type Set struct { type Set struct {
tmpl map[string]*Template tmpl map[string]*Template
leftDelim string
rightDelim string
parseFuncs FuncMap parseFuncs FuncMap
execFuncs map[string]reflect.Value 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 // 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 // panics if a value in the map is not a function with appropriate return
// type. // 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 // to the set. If a template is redefined, the element in the set is
// overwritten with the new definition. // overwritten with the new definition.
func (s *Set) Parse(text string) (*Set, os.Error) { 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 { if err != nil {
return nil, err return nil, err
} }