}
}
+// Return the result of calling the Iter method on v, or nil.
+func iter(v reflect.Value) *reflect.ChanValue {
+ for j := 0; j < v.Type().NumMethod(); j++ {
+ mth := v.Type().Method(j);
+ fv := v.Method(j);
+ ft := fv.Type().(*reflect.FuncType);
+ // TODO(rsc): NumIn() should return 0 here, because ft is from a curried FuncValue.
+ if mth.Name != "Iter" || ft.NumIn() != 1 || ft.NumOut() != 1 {
+ continue
+ }
+ ct, ok := ft.Out(0).(*reflect.ChanType);
+ if !ok || ct.Dir() & reflect.RecvDir == 0 {
+ continue
+ }
+ return fv.Call(nil)[0].(*reflect.ChanValue)
+ }
+ return nil
+}
+
// Execute a .repeated section
func (t *Template) executeRepeated(r *repeatedElement, st *state) {
// Find driver data for this section. It must be in the current struct.
if field == nil {
t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, reflect.Indirect(st.data).Type());
}
- field = reflect.Indirect(field);
- // Must be an array/slice
- array, ok := field.(reflect.ArrayOrSliceValue);
- if !ok {
- t.execError(st, r.linenum, ".repeated: %s has bad type %s", r.field, field.Type());
- }
- if empty(field) {
- // Execute the .or block, once. If it's missing, do nothing.
- start, end := r.or, r.end;
- if start >= 0 {
- newst := st.clone(field);
- for i := start; i < end; {
- i = t.executeElement(i, newst)
- }
- }
- return
- }
- // Execute the normal block.
start, end := r.start, r.or;
if end < 0 {
end = r.end
if r.altstart >= 0 {
end = r.altstart
}
- if field != nil {
+ first := true;
+
+ if array, ok := field.(reflect.ArrayOrSliceValue); ok {
for j := 0; j < array.Len(); j++ {
newst := st.clone(array.Elem(j));
+
+ // .alternates between elements
+ if !first && r.altstart >= 0 {
+ for i := r.altstart; i < r.altend; i++ {
+ i = t.executeElement(i, newst)
+ }
+ }
+ first = false;
+
for i := start; i < end; {
i = t.executeElement(i, newst)
}
- // If appropriate, do .alternates between elements
- if j < array.Len() - 1 && r.altstart >= 0 {
+ }
+ } else if ch := iter(field); ch != nil {
+ for {
+ e := ch.Recv();
+ if ch.Closed() {
+ break
+ }
+ newst := st.clone(e);
+
+ // .alternates between elements
+ if !first && r.altstart >= 0 {
for i := r.altstart; i < r.altend; i++ {
i = t.executeElement(i, newst)
}
}
+ first = false;
+
+ for i := start; i < end; {
+ i = t.executeElement(i, newst)
+ }
}
+ } else {
+ t.execError(st, r.linenum, ".repeated: cannot repeat %s (type %s)",
+ r.field, field.Type());
+ }
+
+ if first {
+ // Empty. Execute the .or block, once. If it's missing, do nothing.
+ start, end := r.or, r.end;
+ if start >= 0 {
+ newst := st.clone(field);
+ for i := start; i < end; {
+ i = t.executeElement(i, newst)
+ }
+ }
+ return
}
}
import (
"bytes";
+ "container/vector";
"fmt";
"io";
"os";
)
type Test struct {
- in, out string
+ in, out, err string
}
type T struct {
empty []*T;
emptystring string;
null []*T;
+ vec *vector.Vector;
}
var t1 = T{ "ItemNumber1", "ValueNumber1" }
var tests = []*Test {
// Simple
- &Test{ "", "" },
- &Test{ "abc\ndef\n", "abc\ndef\n" },
- &Test{ " {.meta-left} \n", "{" },
- &Test{ " {.meta-right} \n", "}" },
- &Test{ " {.space} \n", " " },
- &Test{ " {.tab} \n", "\t" },
- &Test{ " {#comment} \n", "" },
+ &Test{ "", "", "" },
+ &Test{ "abc\ndef\n", "abc\ndef\n", "" },
+ &Test{ " {.meta-left} \n", "{", "" },
+ &Test{ " {.meta-right} \n", "}", "" },
+ &Test{ " {.space} \n", " ", "" },
+ &Test{ " {.tab} \n", "\t", "" },
+ &Test{ " {#comment} \n", "", "" },
// Variables at top level
&Test{
- "{header}={integer}\n",
+ in: "{header}={integer}\n",
- "Header=77\n"
+ out: "Header=77\n"
},
// Section
&Test{
- "{.section data }\n"
+ in: "{.section data }\n"
"some text for the section\n"
"{.end}\n",
- "some text for the section\n"
+ out: "some text for the section\n"
},
&Test{
- "{.section data }\n"
+ in: "{.section data }\n"
"{header}={integer}\n"
"{.end}\n",
- "Header=77\n"
+ out: "Header=77\n"
},
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{header}={integer}\n"
"{.end}\n",
- "Header=77\n"
+ out: "Header=77\n"
},
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
- "data present\n"
+ out: "data present\n"
},
&Test{
- "{.section empty }\n"
+ in: "{.section empty }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
- "data not present\n"
+ out: "data not present\n"
},
&Test{
- "{.section null }\n"
+ in: "{.section null }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
- "data not present\n"
+ out: "data not present\n"
},
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{header}={integer}\n"
"{.section @ }\n"
"{header}={integer}\n"
"{.end}\n"
"{.end}\n",
- "Header=77\n"
+ out: "Header=77\n"
"Header=77\n"
},
&Test{
- "{.section data}{.end} {header}\n",
+ in: "{.section data}{.end} {header}\n",
- " Header\n"
+ out: " Header\n"
},
// Repeated
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.end}\n"
"{.end}\n",
- "ItemNumber1=ValueNumber1\n"
+ out: "ItemNumber1=ValueNumber1\n"
"ItemNumber2=ValueNumber2\n"
},
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.or}\n"
"{.end}\n"
"{.end}\n",
- "ItemNumber1=ValueNumber1\n"
+ out: "ItemNumber1=ValueNumber1\n"
"ItemNumber2=ValueNumber2\n"
},
&Test{
- "{.section @ }\n"
+ in: "{.section @ }\n"
"{.repeated section empty }\n"
"{item}={value}\n"
"{.or}\n"
"{.end}\n"
"{.end}\n",
- "this should appear: empty field\n"
+ out: "this should appear: empty field\n"
},
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.alternates with}DIVIDER\n"
"{.end}\n"
"{.end}\n",
- "ItemNumber1=ValueNumber1\n"
+ out: "ItemNumber1=ValueNumber1\n"
"DIVIDER\n"
"ItemNumber2=ValueNumber2\n"
},
+ &Test{
+ in: "{.repeated section vec }\n"
+ "{@}\n"
+ "{.end}\n",
+
+ out: "elt1\n"
+ "elt2\n"
+ },
+ &Test{
+ in: "{.repeated section integer}{.end}",
+
+ err: "line 0: .repeated: cannot repeat integer (type int)",
+ },
// Nested names
&Test{
- "{.section @ }\n"
+ in: "{.section @ }\n"
"{innerT.item}={innerT.value}\n"
"{.end}",
- "ItemNumber1=ValueNumber1\n"
+ out: "ItemNumber1=ValueNumber1\n"
},
// Formatters
&Test{
- "{.section pdata }\n"
+ in: "{.section pdata }\n"
"{header|uppercase}={integer|+1}\n"
"{header|html}={integer|str}\n"
"{.end}\n",
- "HEADER=78\n"
+ out: "HEADER=78\n"
"Header=77\n"
},
&Test{
- "{raw}\n"
+ in: "{raw}\n"
"{raw|html}\n",
- "&<>!@ #$%^\n"
+ out: "&<>!@ #$%^\n"
"&<>!@ #$%^\n"
},
&Test{
- "{.section emptystring}emptystring{.end}\n"
+ in: "{.section emptystring}emptystring{.end}\n"
"{.section header}header{.end}\n",
- "\nheader\n"
+ out: "\nheader\n"
},
}
s.pdata = []*T{ &t1, &t2 };
s.empty = []*T{ };
s.null = nil;
+ s.vec = vector.New(0);
+ s.vec.Push("elt1");
+ s.vec.Push("elt2");
var buf bytes.Buffer;
for i, test := range tests {
continue;
}
err = tmpl.Execute(s, &buf);
- if err != nil {
- t.Error("unexpected execute error:", err)
+ if test.err == "" {
+ if err != nil {
+ t.Error("unexpected execute error:", err);
+ }
+ } else {
+ if err == nil || err.String() != test.err {
+ t.Errorf("expected execute error %q, got %q", test.err, err.String());
+ }
}
if string(buf.Data()) != test.out {
t.Errorf("for %q: expected %q got %q", test.in, test.out, string(buf.Data()));