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:
parent
edacc863d0
commit
dcf5318990
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(); {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user