1
0
mirror of https://github.com/golang/go synced 2024-11-25 03:07:56 -07:00

template: Add simple formatter chaining.

Fixes #676.

R=r, rsc, r2
CC=golang-dev
https://golang.org/cl/4127043
This commit is contained in:
Kyle Consalus 2011-02-04 16:37:30 -08:00 committed by Rob Pike
parent eeafc06538
commit 42973dddf4
2 changed files with 137 additions and 80 deletions

View File

@ -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 @@
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"
@ -138,9 +144,9 @@ type literalElement struct {
// A variable invocation to be evaluated // A variable invocation to be evaluated
type variableElement struct { type variableElement struct {
linenum int linenum int
word []string // The fields in the invocation. word []string // The fields in the invocation.
formatter string // TODO(r): implement pipelines fmts []string // Names of formatters to apply. len(fmts) > 0
} }
// A .section block, possibly with a .or // A .section block, possibly with a .or
@ -176,13 +182,14 @@ type Template struct {
// the data item descends into the fields associated with sections, etc. // the data item descends into the fields associated with sections, etc.
// Parent is used to walk upwards to find variables higher in the tree. // Parent is used to walk upwards to find variables higher in the tree.
type state struct { 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) {
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.IndexRune(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:] formatters = 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 {
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? t.format(st.wr, v.fmts[len(v.fmts)-1], val, v, st)
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])
} }
// 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) {
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
} }

View File

@ -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 {
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{
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" +
"&amp;&lt;&gt;!@ #$%^\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{
out: "1\n4\n", out: "1\n4\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)) {
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)) {
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) {
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 &lt;&amp;&gt; b\n",
},
{
in: "{Bytes}",
out: "hello",
},
{
in: "{Raw|uppercase|html|html}",
out: "A &amp;lt;&amp;amp;&amp;gt; B",
},
{
in: "{Header Integer|multiword|html}",
out: "&lt;Header&gt;&lt;77&gt;",
},
{
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)
}
}
}
}