Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
G
golang
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
golang
Commits
42973ddd
Commit
42973ddd
authored
Feb 05, 2011
by
Kyle Consalus
Committed by
Rob Pike
Feb 05, 2011
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
template: Add simple formatter chaining.
Fixes #676. R=r, rsc, r2 CC=golang-dev
https://golang.org/cl/4127043
parent
eeafc065
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
132 additions
and
75 deletions
+132
-75
template.go
src/pkg/template/template.go
+48
-33
template_test.go
src/pkg/template/template_test.go
+84
-42
No files found.
src/pkg/template/template.go
View file @
42973ddd
...
@@ -47,6 +47,7 @@
...
@@ -47,6 +47,7 @@
{field1 field2 ...}
{field1 field2 ...}
{field|formatter}
{field|formatter}
{field1 field2...|formatter}
{field1 field2...|formatter}
{field|formatter1|formatter2}
Insert the value of the fields into the output. Each field is
Insert the value of the fields into the output. Each field is
first looked for in the cursor, as in .section and .repeated.
first looked for in the cursor, as in .section and .repeated.
...
@@ -69,10 +70,15 @@
...
@@ -69,10 +70,15 @@
values at the instantiation, and formatter is its name at
values at the instantiation, and formatter is its name at
the invocation site. The default formatter just concatenates
the invocation site. The default formatter just concatenates
the string representations of the fields.
the string representations of the fields.
Multiple formatters separated by the pipeline character | are
executed sequentially, with each formatter receiving the bytes
emitted by the one to its left.
*/
*/
package
template
package
template
import
(
import
(
"bytes"
"container/vector"
"container/vector"
"fmt"
"fmt"
"io"
"io"
...
@@ -140,7 +146,7 @@ type literalElement struct {
...
@@ -140,7 +146,7 @@ type literalElement struct {
type
variableElement
struct
{
type
variableElement
struct
{
linenum
int
linenum
int
word
[]
string
// The fields in the invocation.
word
[]
string
// The fields in the invocation.
f
ormatter
string
// TODO(r): implement pipelines
f
mts
[]
string
// Names of formatters to apply. len(fmts) > 0
}
}
// A .section block, possibly with a .or
// A .section block, possibly with a .or
...
@@ -179,10 +185,11 @@ type state struct {
...
@@ -179,10 +185,11 @@ type state struct {
parent
*
state
// parent in hierarchy
parent
*
state
// parent in hierarchy
data
reflect
.
Value
// the driver data for this section etc.
data
reflect
.
Value
// the driver data for this section etc.
wr
io
.
Writer
// where to send output
wr
io
.
Writer
// where to send output
buf
[
2
]
bytes
.
Buffer
// alternating buffers used when chaining formatters
}
}
func
(
parent
*
state
)
clone
(
data
reflect
.
Value
)
*
state
{
func
(
parent
*
state
)
clone
(
data
reflect
.
Value
)
*
state
{
return
&
state
{
parent
,
data
,
parent
.
wr
}
return
&
state
{
parent
:
parent
,
data
:
data
,
wr
:
parent
.
wr
}
}
}
// New creates a new template with the specified formatter map (which
// New creates a new template with the specified formatter map (which
...
@@ -409,38 +416,43 @@ func (t *Template) analyze(item []byte) (tok int, w []string) {
...
@@ -409,38 +416,43 @@ func (t *Template) analyze(item []byte) (tok int, w []string) {
return
return
}
}
// formatter returns the Formatter with the given name in the Template, or nil if none exists.
func
(
t
*
Template
)
formatter
(
name
string
)
func
(
io
.
Writer
,
string
,
...
interface
{})
{
if
t
.
fmap
!=
nil
{
if
fn
:=
t
.
fmap
[
name
];
fn
!=
nil
{
return
fn
}
}
return
builtins
[
name
]
}
// -- Parsing
// -- Parsing
// Allocate a new variable-evaluation element.
// Allocate a new variable-evaluation element.
func
(
t
*
Template
)
newVariable
(
words
[]
string
)
(
v
*
variableElement
)
{
func
(
t
*
Template
)
newVariable
(
words
[]
string
)
*
variableElement
{
// The words are tokenized elements from the {item}. The last one may be of
// After the final space-separated argument, formatters may be specified separated
// the form "|fmt". For example: {a b c|d}
// by pipe symbols, for example: {a b c|d|e}
formatter
:=
""
// Until we learn otherwise, formatters contains a single name: "", the default formatter.
formatters
:=
[]
string
{
""
}
lastWord
:=
words
[
len
(
words
)
-
1
]
lastWord
:=
words
[
len
(
words
)
-
1
]
bar
:=
strings
.
Index
(
lastWord
,
"|"
)
bar
:=
strings
.
Index
Rune
(
lastWord
,
'|'
)
if
bar
>=
0
{
if
bar
>=
0
{
words
[
len
(
words
)
-
1
]
=
lastWord
[
0
:
bar
]
words
[
len
(
words
)
-
1
]
=
lastWord
[
0
:
bar
]
formatter
=
lastWord
[
bar
+
1
:
]
formatter
s
=
strings
.
Split
(
lastWord
[
bar
+
1
:
],
"|"
,
-
1
)
}
}
// Probably ok, so let's build it.
v
=
&
variableElement
{
t
.
linenum
,
words
,
formatter
}
// We could remember the function address here and avoid the lookup later,
// We could remember the function address here and avoid the lookup later,
// but it's more dynamic to let the user change the map contents underfoot.
// but it's more dynamic to let the user change the map contents underfoot.
// We do require the name to be present, though.
// We do require the name to be present, though.
// Is it in user-supplied map?
// Is it in user-supplied map?
if
t
.
fmap
!=
nil
{
for
_
,
f
:=
range
formatters
{
if
_
,
ok
:=
t
.
fmap
[
formatter
];
ok
{
if
t
.
formatter
(
f
)
==
nil
{
return
t
.
parseError
(
"unknown formatter: %q"
,
f
)
}
}
}
}
// Is it in builtin map?
return
&
variableElement
{
t
.
linenum
,
words
,
formatters
}
if
_
,
ok
:=
builtins
[
formatter
];
ok
{
return
}
t
.
parseError
(
"unknown formatter: %s"
,
formatter
)
return
}
}
// Grab the next item. If it's simple, just append it to the template.
// Grab the next item. If it's simple, just append it to the template.
...
@@ -733,28 +745,31 @@ func (t *Template) varValue(name string, st *state) reflect.Value {
...
@@ -733,28 +745,31 @@ func (t *Template) varValue(name string, st *state) reflect.Value {
return
field
return
field
}
}
func
(
t
*
Template
)
format
(
wr
io
.
Writer
,
fmt
string
,
val
[]
interface
{},
v
*
variableElement
,
st
*
state
)
{
fn
:=
t
.
formatter
(
fmt
)
if
fn
==
nil
{
t
.
execError
(
st
,
v
.
linenum
,
"missing formatter %s for variable %s"
,
fmt
,
v
.
word
[
0
])
}
fn
(
wr
,
fmt
,
val
...
)
}
// Evaluate a variable, looking up through the parent if necessary.
// Evaluate a variable, looking up through the parent if necessary.
// If it has a formatter attached ({var|formatter}) run that too.
// If it has a formatter attached ({var|formatter}) run that too.
func
(
t
*
Template
)
writeVariable
(
v
*
variableElement
,
st
*
state
)
{
func
(
t
*
Template
)
writeVariable
(
v
*
variableElement
,
st
*
state
)
{
formatter
:=
v
.
formatter
// Turn the words of the invocation into values.
// Turn the words of the invocation into values.
val
:=
make
([]
interface
{},
len
(
v
.
word
))
val
:=
make
([]
interface
{},
len
(
v
.
word
))
for
i
,
word
:=
range
v
.
word
{
for
i
,
word
:=
range
v
.
word
{
val
[
i
]
=
t
.
varValue
(
word
,
st
)
.
Interface
()
val
[
i
]
=
t
.
varValue
(
word
,
st
)
.
Interface
()
}
}
// is it in user-supplied map?
if
t
.
fmap
!=
nil
{
for
i
,
fmt
:=
range
v
.
fmts
[
:
len
(
v
.
fmts
)
-
1
]
{
if
fn
,
ok
:=
t
.
fmap
[
formatter
];
ok
{
b
:=
&
st
.
buf
[
i
&
1
]
fn
(
st
.
wr
,
formatter
,
val
...
)
b
.
Reset
()
return
t
.
format
(
b
,
fmt
,
val
,
v
,
st
)
}
val
=
val
[
0
:
1
]
}
val
[
0
]
=
b
.
Bytes
()
// is it in builtin map?
if
fn
,
ok
:=
builtins
[
formatter
];
ok
{
fn
(
st
.
wr
,
formatter
,
val
...
)
return
}
}
t
.
execError
(
st
,
v
.
linenum
,
"missing formatter %s for variable %s"
,
formatter
,
v
.
word
[
0
]
)
t
.
format
(
st
.
wr
,
v
.
fmts
[
len
(
v
.
fmts
)
-
1
],
val
,
v
,
st
)
}
}
// Execute element i. Return next index to execute.
// Execute element i. Return next index to execute.
...
@@ -962,7 +977,7 @@ func (t *Template) Execute(data interface{}, wr io.Writer) (err os.Error) {
...
@@ -962,7 +977,7 @@ func (t *Template) Execute(data interface{}, wr io.Writer) (err os.Error) {
val
:=
reflect
.
NewValue
(
data
)
val
:=
reflect
.
NewValue
(
data
)
defer
checkError
(
&
err
)
defer
checkError
(
&
err
)
t
.
p
=
0
t
.
p
=
0
t
.
execute
(
0
,
t
.
elems
.
Len
(),
&
state
{
nil
,
val
,
wr
})
t
.
execute
(
0
,
t
.
elems
.
Len
(),
&
state
{
parent
:
nil
,
data
:
val
,
wr
:
wr
})
return
nil
return
nil
}
}
...
...
src/pkg/template/template_test.go
View file @
42973ddd
...
@@ -35,7 +35,6 @@ type S struct {
...
@@ -35,7 +35,6 @@ type S struct {
Integer
int
Integer
int
IntegerPtr
*
int
IntegerPtr
*
int
NilPtr
*
int
NilPtr
*
int
Raw
string
InnerT
T
InnerT
T
InnerPointerT
*
T
InnerPointerT
*
T
Data
[]
T
Data
[]
T
...
@@ -51,7 +50,6 @@ type S struct {
...
@@ -51,7 +50,6 @@ type S struct {
Innermap
U
Innermap
U
Stringmap
map
[
string
]
string
Stringmap
map
[
string
]
string
Ptrmap
map
[
string
]
*
string
Ptrmap
map
[
string
]
*
string
Bytes
[]
byte
Iface
interface
{}
Iface
interface
{}
Ifaceptr
interface
{}
Ifaceptr
interface
{}
}
}
...
@@ -334,38 +332,6 @@ var tests = []*Test{
...
@@ -334,38 +332,6 @@ var tests = []*Test{
out
:
"ItemNumber1=ValueNumber1
\n
"
,
out
:
"ItemNumber1=ValueNumber1
\n
"
,
},
},
// Formatters
&
Test
{
in
:
"{.section Pdata }
\n
"
+
"{Header|uppercase}={Integer|+1}
\n
"
+
"{Header|html}={Integer|str}
\n
"
+
"{.end}
\n
"
,
out
:
"HEADER=78
\n
"
+
"Header=77
\n
"
,
},
&
Test
{
in
:
"{.section Pdata }
\n
"
+
"{Header|uppercase}={Integer Header|multiword}
\n
"
+
"{Header|html}={Header Integer|multiword}
\n
"
+
"{Header|html}={Header Integer}
\n
"
+
"{.end}
\n
"
,
out
:
"HEADER=<77><Header>
\n
"
+
"Header=<Header><77>
\n
"
+
"Header=Header77
\n
"
,
},
&
Test
{
in
:
"{Raw}
\n
"
+
"{Raw|html}
\n
"
,
out
:
"&<>!@ #$%^
\n
"
+
"&<>!@ #$%^
\n
"
,
},
&
Test
{
&
Test
{
in
:
"{.section Emptystring}emptystring{.end}
\n
"
+
in
:
"{.section Emptystring}emptystring{.end}
\n
"
+
"{.section Header}header{.end}
\n
"
,
"{.section Header}header{.end}
\n
"
,
...
@@ -380,12 +346,6 @@ var tests = []*Test{
...
@@ -380,12 +346,6 @@ var tests = []*Test{
out
:
"1
\n
4
\n
"
,
out
:
"1
\n
4
\n
"
,
},
},
&
Test
{
in
:
"{Bytes}"
,
out
:
"hello"
,
},
// Maps
// Maps
&
Test
{
&
Test
{
...
@@ -499,7 +459,6 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) {
...
@@ -499,7 +459,6 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) {
s
.
HeaderPtr
=
&
s
.
Header
s
.
HeaderPtr
=
&
s
.
Header
s
.
Integer
=
77
s
.
Integer
=
77
s
.
IntegerPtr
=
&
s
.
Integer
s
.
IntegerPtr
=
&
s
.
Integer
s
.
Raw
=
"&<>!@ #$%^"
s
.
InnerT
=
t1
s
.
InnerT
=
t1
s
.
Data
=
[]
T
{
t1
,
t2
}
s
.
Data
=
[]
T
{
t1
,
t2
}
s
.
Pdata
=
[]
*
T
{
&
t1
,
&
t2
}
s
.
Pdata
=
[]
*
T
{
&
t1
,
&
t2
}
...
@@ -522,7 +481,6 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) {
...
@@ -522,7 +481,6 @@ func testAll(t *testing.T, parseFunc func(*Test) (*Template, os.Error)) {
x
:=
"pointedToString"
x
:=
"pointedToString"
s
.
Ptrmap
[
"stringkey1"
]
=
&
x
// the same value so repeated section is order-independent
s
.
Ptrmap
[
"stringkey1"
]
=
&
x
// the same value so repeated section is order-independent
s
.
Ptrmap
[
"stringkey2"
]
=
&
x
s
.
Ptrmap
[
"stringkey2"
]
=
&
x
s
.
Bytes
=
[]
byte
(
"hello"
)
s
.
Iface
=
[]
int
{
1
,
2
,
3
}
s
.
Iface
=
[]
int
{
1
,
2
,
3
}
s
.
Ifaceptr
=
&
T
{
"Item"
,
"Value"
}
s
.
Ifaceptr
=
&
T
{
"Item"
,
"Value"
}
...
@@ -719,3 +677,87 @@ func TestReferenceToUnexported(t *testing.T) {
...
@@ -719,3 +677,87 @@ func TestReferenceToUnexported(t *testing.T) {
t
.
Fatal
(
"expected unexported error; got"
,
err
)
t
.
Fatal
(
"expected unexported error; got"
,
err
)
}
}
}
}
var
formatterTests
=
[]
Test
{
{
in
:
"{Header|uppercase}={Integer|+1}
\n
"
+
"{Header|html}={Integer|str}
\n
"
,
out
:
"HEADER=78
\n
"
+
"Header=77
\n
"
,
},
{
in
:
"{Header|uppercase}={Integer Header|multiword}
\n
"
+
"{Header|html}={Header Integer|multiword}
\n
"
+
"{Header|html}={Header Integer}
\n
"
,
out
:
"HEADER=<77><Header>
\n
"
+
"Header=<Header><77>
\n
"
+
"Header=Header77
\n
"
,
},
{
in
:
"{Raw}
\n
"
+
"{Raw|html}
\n
"
,
out
:
"a <&> b
\n
"
+
"a <&> b
\n
"
,
},
{
in
:
"{Bytes}"
,
out
:
"hello"
,
},
{
in
:
"{Raw|uppercase|html|html}"
,
out
:
"A &lt;&amp;&gt; B"
,
},
{
in
:
"{Header Integer|multiword|html}"
,
out
:
"<Header><77>"
,
},
{
in
:
"{Integer|no_formatter|html}"
,
err
:
`unknown formatter: "no_formatter"`
,
},
{
in
:
"{Integer|||||}"
,
// empty string is a valid formatter
out
:
"77"
,
},
}
func
TestFormatters
(
t
*
testing
.
T
)
{
data
:=
map
[
string
]
interface
{}{
"Header"
:
"Header"
,
"Integer"
:
77
,
"Raw"
:
"a <&> b"
,
"Bytes"
:
[]
byte
(
"hello"
),
}
for
_
,
c
:=
range
formatterTests
{
tmpl
,
err
:=
Parse
(
c
.
in
,
formatters
)
if
err
!=
nil
{
if
c
.
err
==
""
{
t
.
Error
(
"unexpected parse error:"
,
err
)
continue
}
if
strings
.
Index
(
err
.
String
(),
c
.
err
)
<
0
{
t
.
Error
(
"unexpected error: expected %q, got %q"
,
c
.
err
,
err
.
String
())
continue
}
}
else
{
if
c
.
err
!=
""
{
t
.
Errorf
(
"For %q, expected error, got none."
,
c
.
in
)
continue
}
buf
:=
bytes
.
NewBuffer
(
nil
)
err
=
tmpl
.
Execute
(
data
,
buf
)
if
err
!=
nil
{
t
.
Error
(
"unexpected Execute error: "
,
err
)
continue
}
actual
:=
buf
.
String
()
if
actual
!=
c
.
out
{
t
.
Errorf
(
"for %q: expected %q but got %q."
,
c
.
in
,
c
.
out
,
actual
)
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment