1
0
mirror of https://github.com/golang/go synced 2024-11-18 12:54:44 -07:00

add support for variable formatters

R=rsc
DELTA=134  (75 added, 41 deleted, 18 changed)
OCL=27245
CL=27247
This commit is contained in:
Rob Pike 2009-04-08 23:33:31 -07:00
parent f95da9a639
commit 3a7df4dde0
3 changed files with 91 additions and 57 deletions

View File

@ -914,3 +914,17 @@ func NewValue(e interface {}) Value {
*ap = value; *ap = value;
return newValueAddr(typ, Addr(ap)); 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
}

View File

@ -27,6 +27,7 @@ var ErrNoEnd = os.NewError("section does not have .end")
var ErrNoVar = os.NewError("variable name not in struct"); var ErrNoVar = os.NewError("variable name not in struct");
var ErrBadType = os.NewError("unsupported type for variable"); var ErrBadType = os.NewError("unsupported type for variable");
var ErrNotStruct = os.NewError("driver must be a struct") var ErrNotStruct = os.NewError("driver must be a struct")
var ErrNoFormatter = os.NewError("unknown formatter")
// All the literals are aces. // All the literals are aces.
var lbrace = []byte{ '{' } var lbrace = []byte{ '{' }
@ -46,18 +47,23 @@ const (
Variable; 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 { type template struct {
errorchan chan *os.Error; // for erroring out errorchan chan *os.Error; // for erroring out
linenum *int; // shared by all templates derived from this one linenum *int; // shared by all templates derived from this one
parent *template; parent *template;
data reflect.Value; // the driver data for this section etc. data reflect.Value; // the driver data for this section etc.
fmap FormatterMap; // formatters for variables
buf []byte; // input text to process buf []byte; // input text to process
p int; // position in buf p int; // position in buf
wr io.Write; // where to send output wr io.Write; // where to send output
} }
// Create a top-level template // 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 := new(template);
t.errorchan = ch; t.errorchan = ch;
t.linenum = linenum; t.linenum = linenum;
@ -66,13 +72,14 @@ func newTemplate(ch chan *os.Error, linenum *int, buf []byte, data reflect.Value
t.data = data; t.data = data;
t.buf = buf; t.buf = buf;
t.p = 0; t.p = 0;
t.fmap = fmap;
t.wr = wr; t.wr = wr;
return t; return t;
} }
// Create a template deriving from its parent // Create a template deriving from its parent
func childTemplate(parent *template, buf []byte, data reflect.Value) *template { 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; t.parent = parent;
return t; return t;
} }
@ -88,18 +95,6 @@ func white(c uint8) bool {
return c == ' ' || c == '\t' || c == '\n' 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) execute()
func (t *template) executeSection(w []string) 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? // Is there no data to look at?
func empty(v reflect.Value, indirect_ok bool) bool { func empty(v reflect.Value, indirect_ok bool) bool {
v = indirect(v); v = reflect.Indirect(v);
if v == nil { if v == nil {
return true return true
} }
@ -309,7 +304,7 @@ func (t *template) executeRepeated(w []string) {
if i < 0 { if i < 0 {
t.error(ErrNoVar, ": ", w[2]); 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 // Must be an array/slice
if field != nil && field.Kind() != reflect.ArrayKind { if field != nil && field.Kind() != reflect.ArrayKind {
@ -346,7 +341,7 @@ Loop:
if field != nil { if field != nil {
array := field.(reflect.ArrayValue); array := field.(reflect.ArrayValue);
for i := 0; i < array.Len(); i++ { 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 := childTemplate(t, t.buf[start:end], elem);
tmp.execute(); tmp.execute();
} }
@ -421,17 +416,37 @@ Loop:
tmp.execute(); tmp.execute();
} }
// Evalute a variable, looking up through the parent if necessary. // Look up a variable, up through the parent if necessary.
// TODO: add formatting outputters func (t *template) varValue(name string) reflect.Value {
func (t *template) evalVariable(s string) string { i, kind := t.findVar(name);
i, kind := t.findVar(s);
if i < 0 { if i < 0 {
if t.parent == nil { 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() { 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. // Extract the driver struct.
val := indirect(reflect.NewValue(data)); val := reflect.Indirect(reflect.NewValue(data));
sval, ok1 := val.(reflect.StructValue); sval, ok1 := val.(reflect.StructValue);
if !ok1 { if !ok1 {
return ErrNotStruct return ErrNotStruct
} }
ch := make(chan *os.Error); ch := make(chan *os.Error);
var linenum int; var linenum int;
t := newTemplate(ch, &linenum, io.StringBytes(s), val, wr); t := newTemplate(ch, &linenum, io.StringBytes(s), val, fmap, wr);
go func() { go func() {
t.execute(); t.execute();
ch <- nil; // clean return; ch <- nil; // clean return;

View File

@ -8,6 +8,7 @@ import (
"fmt"; "fmt";
"io"; "io";
"os"; "os";
"reflect";
"template"; "template";
"testing"; "testing";
) )
@ -33,6 +34,29 @@ type S struct {
var t1 = T{ "ItemNumber1", "ValueNumber1" } var t1 = T{ "ItemNumber1", "ValueNumber1" }
var t2 = T{ "ItemNumber2", "ValueNumber2" } 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 { var tests = []*Test {
// Simple // Simple
&Test{ "", "" }, &Test{ "", "" },
@ -114,6 +138,15 @@ var tests = []*Test {
"ItemNumber1=ValueNumber1\n" "ItemNumber1=ValueNumber1\n"
"ItemNumber2=ValueNumber2\n" "ItemNumber2=ValueNumber2\n"
}, },
// Formatters
&Test{
"{.section pdata }\n"
"{header|uppercase}={integer|+1}\n"
"{.end}\n",
"HEADER=78\n"
},
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
@ -129,7 +162,7 @@ func TestAll(t *testing.T) {
var buf io.ByteBuffer; var buf io.ByteBuffer;
for i, test := range tests { for i, test := range tests {
buf.Reset(); buf.Reset();
err := Execute(test.in, s, &buf); err := Execute(test.in, s, formatters, &buf);
if err != nil { if err != nil {
t.Error("unexpected error:", err) 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) { func TestBadDriverType(t *testing.T) {
err := Execute("hi", "hello", os.Stdout); err := Execute("hi", "hello", nil, os.Stdout);
if err == nil { if err == nil {
t.Error("failed to detect string as driver type") t.Error("failed to detect string as driver type")
} }