]> Cypherpunks repositories - gostls13.git/commitdiff
add support for variable formatters
authorRob Pike <r@golang.org>
Thu, 9 Apr 2009 06:33:31 +0000 (23:33 -0700)
committerRob Pike <r@golang.org>
Thu, 9 Apr 2009 06:33:31 +0000 (23:33 -0700)
R=rsc
DELTA=134  (75 added, 41 deleted, 18 changed)
OCL=27245
CL=27247

src/lib/reflect/value.go
src/lib/template.go
src/lib/template_test.go

index ad0cd465563236abf6b40a4cfbbc61a8f6454bfb..af43de98aaae7309800cb0907258ac0c0b3aecba 100644 (file)
@@ -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
+}
index 2d36c59d668105546fa8e687fd428130fa6e063a..fd6a863d25f94208ae31f5833e62726650c9adfd 100644 (file)
@@ -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 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())
        }
-       return fmt.Sprint(t.data.(reflect.StructValue).Field(i).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;
index 2ddbcef69fdba42c3f4d533e89572a56fb655f4d..a67a888ea011112ec996bbf04de87bcd40382bb0 100644 (file)
@@ -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")
        }