From 3a7df4dde0aaab2e93072a75213c528d91529e5e Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Wed, 8 Apr 2009 23:33:31 -0700 Subject: [PATCH] add support for variable formatters R=rsc DELTA=134 (75 added, 41 deleted, 18 changed) OCL=27245 CL=27247 --- src/lib/reflect/value.go | 14 ++++++++ src/lib/template.go | 69 ++++++++++++++++++++++++---------------- src/lib/template_test.go | 65 ++++++++++++++++++++----------------- 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/lib/reflect/value.go b/src/lib/reflect/value.go index ad0cd46556..af43de98aa 100644 --- a/src/lib/reflect/value.go +++ b/src/lib/reflect/value.go @@ -914,3 +914,17 @@ func NewValue(e interface {}) Value { *ap = value; return newValueAddr(typ, Addr(ap)); } + +// Indirect indirects one level through a value, if it is a pointer. +// If not a pointer, the value is returned unchanged. +// Useful when walking arbitrary data structures. +func Indirect(v Value) Value { + if v.Kind() == PtrKind { + p := v.(PtrValue); + if p.Get() == nil { + return nil + } + v = p.Sub() + } + return v +} diff --git a/src/lib/template.go b/src/lib/template.go index 2d36c59d66..fd6a863d25 100644 --- a/src/lib/template.go +++ b/src/lib/template.go @@ -27,6 +27,7 @@ var ErrNoEnd = os.NewError("section does not have .end") var ErrNoVar = os.NewError("variable name not in struct"); var ErrBadType = os.NewError("unsupported type for variable"); var ErrNotStruct = os.NewError("driver must be a struct") +var ErrNoFormatter = os.NewError("unknown formatter") // All the literals are aces. var lbrace = []byte{ '{' } @@ -46,18 +47,23 @@ const ( Variable; ) +// FormatterMap is the type describing the mapping from formatter +// names to the functions that implement them. +type FormatterMap map[string] func(reflect.Value) string + type template struct { errorchan chan *os.Error; // for erroring out linenum *int; // shared by all templates derived from this one parent *template; data reflect.Value; // the driver data for this section etc. + fmap FormatterMap; // formatters for variables buf []byte; // input text to process p int; // position in buf wr io.Write; // where to send output } // Create a top-level template -func newTemplate(ch chan *os.Error, linenum *int, buf []byte, data reflect.Value, wr io.Write) *template { +func newTemplate(ch chan *os.Error, linenum *int, buf []byte, data reflect.Value, fmap FormatterMap, wr io.Write) *template { t := new(template); t.errorchan = ch; t.linenum = linenum; @@ -66,13 +72,14 @@ func newTemplate(ch chan *os.Error, linenum *int, buf []byte, data reflect.Value t.data = data; t.buf = buf; t.p = 0; + t.fmap = fmap; t.wr = wr; return t; } // Create a template deriving from its parent func childTemplate(parent *template, buf []byte, data reflect.Value) *template { - t := newTemplate(parent.errorchan, parent.linenum, buf, data, parent.wr); + t := newTemplate(parent.errorchan, parent.linenum, buf, data, parent.fmap, parent.wr); t.parent = parent; return t; } @@ -88,18 +95,6 @@ func white(c uint8) bool { return c == ' ' || c == '\t' || c == '\n' } -// Data items can be values or pointers to values. This function hides the pointer. -func indirect(v reflect.Value) reflect.Value { - if v.Kind() == reflect.PtrKind { - p := v.(reflect.PtrValue); - if p.Get() == nil { - return nil - } - v = p.Sub() - } - return v -} - func (t *template) execute() func (t *template) executeSection(w []string) @@ -281,7 +276,7 @@ func (t *template) findVar(s string) (int, int) { // Is there no data to look at? func empty(v reflect.Value, indirect_ok bool) bool { - v = indirect(v); + v = reflect.Indirect(v); if v == nil { return true } @@ -309,7 +304,7 @@ func (t *template) executeRepeated(w []string) { if i < 0 { t.error(ErrNoVar, ": ", w[2]); } - field = indirect(t.data.(reflect.StructValue).Field(i)); + field = reflect.Indirect(t.data.(reflect.StructValue).Field(i)); } // Must be an array/slice if field != nil && field.Kind() != reflect.ArrayKind { @@ -346,7 +341,7 @@ Loop: if field != nil { array := field.(reflect.ArrayValue); for i := 0; i < array.Len(); i++ { - elem := indirect(array.Elem(i)); + elem := reflect.Indirect(array.Elem(i)); tmp := childTemplate(t, t.buf[start:end], elem); tmp.execute(); } @@ -421,17 +416,37 @@ Loop: tmp.execute(); } -// Evalute a variable, looking up through the parent if necessary. -// TODO: add formatting outputters -func (t *template) evalVariable(s string) string { - i, kind := t.findVar(s); +// Look up a variable, up through the parent if necessary. +func (t *template) varValue(name string) reflect.Value { + i, kind := t.findVar(name); if i < 0 { if t.parent == nil { - t.error(ErrNoVar, ": ", s) + t.error(ErrNoVar, ": ", name) } - return t.parent.evalVariable(s); + return t.parent.varValue(name); } - return fmt.Sprint(t.data.(reflect.StructValue).Field(i).Interface()); + return t.data.(reflect.StructValue).Field(i); +} + +// Evalute a variable, looking up through the parent if necessary. +// If it has a formatter attached ({var|formatter}) run that too. +func (t *template) evalVariable(name_formatter string) string { + name := name_formatter; + formatter := ""; + bar := strings.Index(name_formatter, "|"); + if bar >= 0 { + name = name_formatter[0:bar]; + formatter = name_formatter[bar+1:len(name_formatter)]; + } + val := t.varValue(name); + if fn, ok := t.fmap[formatter]; ok { + return fn(val) + } + if formatter == "" { + return fmt.Sprint(val.Interface()) + } + t.error(ErrNoFormatter, ": ", formatter); + panic("notreached"); } func (t *template) execute() { @@ -471,16 +486,16 @@ func (t *template) execute() { } } -func Execute(s string, data interface{}, wr io.Write) *os.Error { +func Execute(s string, data interface{}, fmap FormatterMap, wr io.Write) *os.Error { // Extract the driver struct. - val := indirect(reflect.NewValue(data)); + val := reflect.Indirect(reflect.NewValue(data)); sval, ok1 := val.(reflect.StructValue); if !ok1 { return ErrNotStruct } ch := make(chan *os.Error); var linenum int; - t := newTemplate(ch, &linenum, io.StringBytes(s), val, wr); + t := newTemplate(ch, &linenum, io.StringBytes(s), val, fmap, wr); go func() { t.execute(); ch <- nil; // clean return; diff --git a/src/lib/template_test.go b/src/lib/template_test.go index 2ddbcef69f..a67a888ea0 100644 --- a/src/lib/template_test.go +++ b/src/lib/template_test.go @@ -8,6 +8,7 @@ import ( "fmt"; "io"; "os"; + "reflect"; "template"; "testing"; ) @@ -33,6 +34,29 @@ type S struct { var t1 = T{ "ItemNumber1", "ValueNumber1" } var t2 = T{ "ItemNumber2", "ValueNumber2" } +func uppercase(v reflect.Value) string { + s := reflect.Indirect(v).(reflect.StringValue).Get(); + t := ""; + for i := 0; i < len(s); i++ { + c := s[i]; + if 'a' <= c && c <= 'z' { + c = c + 'A' - 'a' + } + t += string(c); + } + return t; +} + +func plus1(v reflect.Value) string { + i := reflect.Indirect(v).(reflect.IntValue).Get(); + return fmt.Sprint(i + 1); +} + +var formatters = FormatterMap { + "uppercase" : uppercase, + "+1" : plus1, +} + var tests = []*Test { // Simple &Test{ "", "" }, @@ -114,6 +138,15 @@ var tests = []*Test { "ItemNumber1=ValueNumber1\n" "ItemNumber2=ValueNumber2\n" }, + + // Formatters + &Test{ + "{.section pdata }\n" + "{header|uppercase}={integer|+1}\n" + "{.end}\n", + + "HEADER=78\n" + }, } func TestAll(t *testing.T) { @@ -129,7 +162,7 @@ func TestAll(t *testing.T) { var buf io.ByteBuffer; for i, test := range tests { buf.Reset(); - err := Execute(test.in, s, &buf); + err := Execute(test.in, s, formatters, &buf); if err != nil { t.Error("unexpected error:", err) } @@ -139,36 +172,8 @@ func TestAll(t *testing.T) { } } -/* -func TestParser(t *testing.T) { - t1 := &T{ "ItemNumber1", "ValueNumber1" }; - t2 := &T{ "ItemNumber2", "ValueNumber2" }; - a := []*T{ t1, t2 }; - s := &S{ "Header", 77, a }; - err := Execute( - "{#hello world}\n" - "some text: {.meta-left}{.space}{.meta-right}\n" - "{.meta-left}\n" - "{.meta-right}\n" - "{.section data }\n" - "some text for the section\n" - "{header} for iteration number {integer}\n" - " {.repeated section @}\n" - "repeated section: {value1}={value2}\n" - " {.end}\n" - "{.or}\n" - "This appears only if there is no data\n" - "{.end }\n" - "this is the end\n" - , s, os.Stdout); - if err != nil { - t.Error(err) - } -} -*/ - func TestBadDriverType(t *testing.T) { - err := Execute("hi", "hello", os.Stdout); + err := Execute("hi", "hello", nil, os.Stdout); if err == nil { t.Error("failed to detect string as driver type") }