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:
parent
f95da9a639
commit
3a7df4dde0
@ -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
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user