Commit 5e4882a3 authored by Rob Pike's avatar Rob Pike

template: implement multi-word variable instantation for formatters.

Before one could say
	{field}
or
	{field|formatter}
Now one can also say
	{field1 field2 field3}
or
	{field1 field2 field3|formatter}
and the fields are passed as successive arguments to the formatter,
analogous to fmt.Print.

R=rsc, gri
CC=golang-dev
https://golang.org/cl/3385041
parent 4bafef81
...@@ -44,9 +44,11 @@ ...@@ -44,9 +44,11 @@
is present, ZZZ is executed between iterations of XXX. is present, ZZZ is executed between iterations of XXX.
{field} {field}
{field1 field2 ...}
{field|formatter} {field|formatter}
{field1 field2...|formatter}
Insert the value of the field into the output. 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.
If it is not found, the search continues in outer sections If it is not found, the search continues in outer sections
until the top level is reached. until the top level is reached.
...@@ -58,7 +60,8 @@ ...@@ -58,7 +60,8 @@
func(wr io.Writer, formatter string, data ...interface{}) func(wr io.Writer, formatter string, data ...interface{})
where wr is the destination for output, data holds the field where wr is the destination for output, data holds the field
values at the instantiation, and formatter is its name at values at the instantiation, and formatter is its name at
the invocation site. the invocation site. The default formatter just concatenates
the string representations of the fields.
*/ */
package template package template
...@@ -124,11 +127,11 @@ type literalElement struct { ...@@ -124,11 +127,11 @@ type literalElement struct {
text []byte text []byte
} }
// A variable to be evaluated // A variable invocation to be evaluated
type variableElement struct { type variableElement struct {
linenum int linenum int
name string word []string // The fields in the invocation.
formatter string // TODO(r): implement pipelines formatter string // TODO(r): implement pipelines
} }
// A .section block, possibly with a .or // A .section block, possibly with a .or
...@@ -351,7 +354,7 @@ func (t *Template) analyze(item []byte) (tok int, w []string) { ...@@ -351,7 +354,7 @@ func (t *Template) analyze(item []byte) (tok int, w []string) {
t.parseError("empty directive") t.parseError("empty directive")
return return
} }
if len(w) == 1 && w[0][0] != '.' { if len(w) > 0 && w[0][0] != '.' {
tok = tokVariable tok = tokVariable
return return
} }
...@@ -394,16 +397,18 @@ func (t *Template) analyze(item []byte) (tok int, w []string) { ...@@ -394,16 +397,18 @@ func (t *Template) analyze(item []byte) (tok int, w []string) {
// -- Parsing // -- Parsing
// Allocate a new variable-evaluation element. // Allocate a new variable-evaluation element.
func (t *Template) newVariable(name_formatter string) (v *variableElement) { func (t *Template) newVariable(words []string) (v *variableElement) {
name := name_formatter // The words are tokenized elements from the {item}. The last one may be of
// the form "|fmt". For example: {a b c|d}
formatter := "" formatter := ""
bar := strings.Index(name_formatter, "|") lastWord := words[len(words)-1]
bar := strings.Index(lastWord, "|")
if bar >= 0 { if bar >= 0 {
name = name_formatter[0:bar] words[len(words)-1] = lastWord[0:bar]
formatter = name_formatter[bar+1:] formatter = lastWord[bar+1:]
} }
// Probably ok, so let's build it. // Probably ok, so let's build it.
v = &variableElement{t.linenum, name, formatter} 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.
...@@ -449,7 +454,7 @@ func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) { ...@@ -449,7 +454,7 @@ func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
} }
return return
case tokVariable: case tokVariable:
t.elems.Push(t.newVariable(w[0])) t.elems.Push(t.newVariable(w))
return return
} }
return false, tok, w return false, tok, w
...@@ -687,20 +692,24 @@ func (t *Template) varValue(name string, st *state) reflect.Value { ...@@ -687,20 +692,24 @@ func (t *Template) varValue(name string, st *state) reflect.Value {
// 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 formatter := v.formatter
val := t.varValue(v.name, st).Interface() // Turn the words of the invocation into values.
val := make([]interface{}, len(v.word))
for i, word := range v.word {
val[i] = t.varValue(word, st).Interface()
}
// is it in user-supplied map? // is it in user-supplied map?
if t.fmap != nil { if t.fmap != nil {
if fn, ok := t.fmap[formatter]; ok { if fn, ok := t.fmap[formatter]; ok {
fn(st.wr, formatter, val) fn(st.wr, formatter, val...)
return return
} }
} }
// is it in builtin map? // is it in builtin map?
if fn, ok := builtins[formatter]; ok { if fn, ok := builtins[formatter]; ok {
fn(st.wr, formatter, val) fn(st.wr, formatter, val...)
return return
} }
t.execError(st, v.linenum, "missing formatter %s for variable %s", formatter, v.name) 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.
......
...@@ -85,10 +85,16 @@ func writer(f func(interface{}) string) func(io.Writer, string, ...interface{}) ...@@ -85,10 +85,16 @@ func writer(f func(interface{}) string) func(io.Writer, string, ...interface{})
} }
} }
func multiword(w io.Writer, format string, value ...interface{}) {
for _, v := range value {
fmt.Fprintf(w, "<%v>", v)
}
}
var formatters = FormatterMap{ var formatters = FormatterMap{
"uppercase": writer(uppercase), "uppercase": writer(uppercase),
"+1": writer(plus1), "+1": writer(plus1),
"multiword": multiword,
} }
var tests = []*Test{ var tests = []*Test{
...@@ -310,6 +316,18 @@ var tests = []*Test{ ...@@ -310,6 +316,18 @@ var tests = []*Test{
"Header=77\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{ &Test{
in: "{raw}\n" + in: "{raw}\n" +
"{raw|html}\n", "{raw|html}\n",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment