--- /dev/null
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Template library. See http://code.google.com/p/json-template/wiki/Reference
+// TODO: document this here as well.
+
+package template
+
+import (
+ "bufio";
+ "fmt";
+ "io";
+ "os";
+ "reflect";
+ "strings";
+)
+
+var ErrLBrace = os.NewError("unexpected opening brace")
+var ErrUnmatchedRBrace = os.NewError("unmatched closing brace")
+var ErrUnmatchedLBrace = os.NewError("unmatched opening brace")
+var ErrBadDirective = os.NewError("unrecognized directive name")
+var ErrEmptyDirective = os.NewError("empty directive")
+var ErrFields = os.NewError("incorrect fields for directive")
+var ErrSyntax = os.NewError("directive out of place")
+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")
+
+// All the literals are aces.
+var lbrace = []byte{ '{' }
+var rbrace = []byte{ '}' }
+var space = []byte{ ' ' }
+
+// The various types of "tokens", which are plain text or brace-delimited descriptors
+const (
+ Alternates = iota;
+ Comment;
+ End;
+ Literal;
+ Or;
+ Repeated;
+ Section;
+ Text;
+ Variable;
+)
+
+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.
+ 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 {
+ t := new(template);
+ t.errorchan = ch;
+ t.linenum = linenum;
+ *linenum = 1;
+ t.parent = nil;
+ t.data = data;
+ t.buf = buf;
+ t.p = 0;
+ 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.parent = parent;
+ return t;
+}
+
+// Report error and stop generation.
+func (t *template) error(err *os.Error, args ...) {
+ fmt.Fprintf(os.Stderr, "template error: line %d: %s%s\n", *t.linenum, err, fmt.Sprint(args)); // TODO: drop this? (only way to get line number)
+ t.errorchan <- err;
+ sys.Goexit();
+}
+
+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)
+
+// nextItem returns the next item from the input buffer. If the returned
+// item is empty, we are at EOF. The item will be either a brace-
+// delimited string or a non-empty string between brace-delimited
+// strings. Most tokens stop at (but include, if plain text) a newline.
+// Action tokens on a line by themselves drop the white space on
+// either side, up to and including the newline.
+func (t *template) nextItem() []byte {
+ brace := false; // are we waiting for an opening brace?
+ special := false; // is this a {.foo} directive, which means trim white space?
+ // Delete surrounding white space if this {.foo} is the only thing on the line.
+ trim_white := t.p == 0 || t.buf[t.p-1] == '\n';
+ only_white := true; // we have seen only white space so far
+ var i int;
+ start := t.p;
+Loop:
+ for i = t.p; i < len(t.buf); i++ {
+ switch t.buf[i] {
+ case '\n':
+ *t.linenum++;
+ i++;
+ break Loop;
+ case ' ', '\t':
+ // white space, do nothing
+ case '{':
+ if brace {
+ t.error(ErrLBrace)
+ }
+ // anything interesting already on the line?
+ if !only_white {
+ break Loop;
+ }
+ // is it a directive or comment?
+ if i+2 < len(t.buf) && (t.buf[i+1] == '.' || t.buf[i+1] == '#') {
+ special = true;
+ if trim_white && only_white {
+ start = i;
+ }
+ } else if i > t.p+1 { // have some text accumulated so stop before '{'
+ break Loop;
+ }
+ brace = true;
+ case '}':
+ if !brace {
+ t.error(ErrUnmatchedRBrace)
+ }
+ brace = false;
+ i++;
+ break Loop;
+ default:
+ only_white = false;
+ }
+ }
+ if brace {
+ t.error(ErrUnmatchedLBrace)
+ }
+ item := t.buf[start:i];
+ if special && trim_white {
+ // consume trailing white space
+ for ; i < len(t.buf) && white(t.buf[i]); i++ {
+ if t.buf[i] == '\n' {
+ i++;
+ break // stop after newline
+ }
+ }
+ }
+ t.p = i;
+ return item
+}
+
+// Turn a byte array into a white-space-split array of strings.
+func words(buf []byte) []string {
+ s := make([]string, 0, 5);
+ p := 0; // position in buf
+ // one word per loop
+ for i := 0; ; i++ {
+ // skip white space
+ for ; p < len(buf) && white(buf[p]); p++ {
+ }
+ // grab word
+ start := p;
+ for ; p < len(buf) && !white(buf[p]); p++ {
+ }
+ if start == p { // no text left
+ break
+ }
+ if i == cap(s) {
+ ns := make([]string, 2*cap(s));
+ for j := range s {
+ ns[j] = s[j]
+ }
+ s = ns;
+ }
+ s = s[0:i+1];
+ s[i] = string(buf[start:p])
+ }
+ return s
+}
+
+// Analyze an item and return its type and, if it's an action item, an array of
+// its constituent words.
+func (t *template) analyze(item []byte) (tok int, w []string) {
+ // item is known to be non-empty
+ if item[0] != '{' {
+ tok = Text;
+ return
+ }
+ if item[len(item)-1] != '}' {
+ t.error(ErrUnmatchedLBrace) // should not happen anyway
+ }
+ if len(item) <= 2 {
+ t.error(ErrEmptyDirective)
+ }
+ // Comment
+ if item[1] == '#' {
+ tok = Comment;
+ return
+ }
+ // Split into words
+ w = words(item[1: len(item)-1]); // drop final brace
+ if len(w) == 0 {
+ t.error(ErrBadDirective)
+ }
+ if len(w[0]) == 0 {
+ t.error(ErrEmptyDirective)
+ }
+ if len(w) == 1 && w[0][0] != '.' {
+ tok = Variable;
+ return;
+ }
+ switch w[0] {
+ case ".meta-left", ".meta-right", ".space":
+ tok = Literal;
+ return;
+ case ".or":
+ tok = Or;
+ return;
+ case ".end":
+ tok = End;
+ return;
+ case ".section":
+ if len(w) != 2 {
+ t.error(ErrFields, ": ", string(item))
+ }
+ tok = Section;
+ return;
+ case ".repeated":
+ if len(w) != 3 || w[1] != "section" {
+ t.error(ErrFields, ": ", string(item))
+ }
+ tok = Repeated;
+ return;
+ case ".alternates":
+ if len(w) != 2 || w[1] != "with" {
+ t.error(ErrFields, ": ", string(item))
+ }
+ tok = Alternates;
+ return;
+ }
+ t.error(ErrBadDirective, ": ", string(item));
+ return
+}
+
+// If the data for this template is a struct, find the named variable.
+func (t *template) findVar(s string) (int, int) {
+ typ, ok := t.data.Type().(reflect.StructType);
+ if ok {
+ for i := 0; i < typ.Len(); i++ {
+ name, ftyp, tag, offset := typ.Field(i);
+ if name == s {
+ return i, ftyp.Kind()
+ }
+ }
+ }
+ return -1, -1
+}
+
+// Is there no data to look at?
+func empty(v reflect.Value, indirect_ok bool) bool {
+ v = indirect(v);
+ if v == nil {
+ return true
+ }
+ switch v.Type().Kind() {
+ case reflect.StructKind:
+ return false;
+ case reflect.ArrayKind:
+ return v.(reflect.ArrayValue).Len() == 0;
+ }
+ return true;
+}
+
+// Execute a ".repeated" section
+func (t *template) executeRepeated(w []string) {
+ if w[1] != "section" {
+ t.error(ErrSyntax, `: .repeated must have "section"`)
+ }
+ // Find driver array/struct for this section. It must be in the current struct.
+ // The special name "@" leaves us at this level.
+ var field reflect.Value;
+ if w[2] == "@" {
+ field = t.data
+ } else {
+ i, kind := t.findVar(w[1]);
+ if i < 0 {
+ t.error(ErrNoVar, ": ", w[2]);
+ }
+ field = indirect(t.data.(reflect.StructValue).Field(i));
+ }
+ // Must be an array/slice
+ if field != nil && field.Kind() != reflect.ArrayKind {
+ t.error(ErrBadType, " in .repeated: ", w[2], " ", field.Type().String());
+ }
+ // Scan repeated section, remembering slice of text we must execute.
+ nesting := 0;
+ start := t.p;
+ end := t.p;
+Loop:
+ for {
+ item := t.nextItem();
+ if len(item) == 0 {
+ t.error(ErrNoEnd)
+ }
+ tok, s := t.analyze(item);
+ switch tok {
+ case Comment:
+ continue; // just ignore it
+ case End:
+ if nesting == 0 {
+ break Loop
+ }
+ nesting--;
+ case Repeated, Section:
+ nesting++;
+ case Literal, Or, Text, Variable:
+ // just accumulate
+ default:
+ panic("unknown section item", string(item));
+ }
+ end = t.p
+ }
+ if field != nil {
+ array := field.(reflect.ArrayValue);
+ for i := 0; i < array.Len(); i++ {
+ elem := indirect(array.Elem(i));
+ tmp := childTemplate(t, t.buf[start:end], elem);
+ tmp.execute();
+ }
+ }
+}
+
+// Execute a ".section"
+func (t *template) executeSection(w []string) {
+ // Find driver array/struct for this section. It must be in the current struct.
+ // The special name "@" leaves us at this level.
+ var field reflect.Value;
+ if w[1] == "@" {
+ field = t.data
+ } else {
+ i, kind := t.findVar(w[1]);
+ if i < 0 {
+ t.error(ErrNoVar, ": ", w[1]);
+ }
+ field = t.data.(reflect.StructValue).Field(i);
+ }
+ // Scan section, remembering slice of text we must execute.
+ orFound := false;
+ nesting := 0; // How deeply are .section and .repeated nested?
+ start := t.p;
+ end := t.p;
+ accumulate := !empty(field, true); // Keep this section if there's data
+Loop:
+ for {
+ item := t.nextItem();
+ if len(item) == 0 {
+ t.error(ErrNoEnd)
+ }
+ tok, s := t.analyze(item);
+ switch tok {
+ case Comment:
+ continue; // just ignore it
+ case End:
+ if nesting == 0 {
+ break Loop
+ }
+ nesting--;
+ case Or:
+ if nesting > 0 { // just accumulate
+ break
+ }
+ if orFound {
+ t.error(ErrSyntax, ": .or");
+ }
+ orFound = true;
+ if !accumulate {
+ // No data; execute the .or instead
+ start = t.p;
+ end = t.p;
+ accumulate = true;
+ continue;
+ } else {
+ // Data present so disregard the .or section
+ accumulate = false
+ }
+ case Repeated, Section:
+ nesting++;
+ case Literal, Text, Variable:
+ // just accumulate
+ default:
+ panic("unknown section item", string(item));
+ }
+ if accumulate {
+ end = t.p
+ }
+ }
+ tmp := childTemplate(t, t.buf[start:end], field);
+ 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);
+ if i < 0 {
+ if t.parent == nil {
+ t.error(ErrNoVar, ": ", s)
+ }
+ return t.parent.evalVariable(s);
+ }
+ return fmt.Sprint(t.data.(reflect.StructValue).Field(i).Interface());
+}
+
+func (t *template) execute() {
+ for {
+ item := t.nextItem();
+ if len(item) == 0 {
+ return
+ }
+ tok, w := t.analyze(item);
+ switch tok {
+ case Comment:
+ break;
+ case Text:
+ t.wr.Write(item);
+ case Literal:
+ switch w[0] {
+ case ".meta-left":
+ t.wr.Write(lbrace);
+ case ".meta-right":
+ t.wr.Write(rbrace);
+ case ".space":
+ t.wr.Write(space);
+ default:
+ panic("unknown literal: ", w[0]);
+ }
+ case Variable:
+ t.wr.Write(io.StringBytes(t.evalVariable(w[0])));
+ case Or, End, Alternates:
+ t.error(ErrSyntax, ": ", string(item));
+ case Section:
+ t.executeSection(w);
+ case Repeated:
+ t.executeRepeated(w);
+ default:
+ panic("bad directive in execute:", string(item));
+ }
+ }
+}
+
+func Execute(s string, data interface{}, wr io.Write) *os.Error {
+ // Extract the driver struct.
+ val := 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);
+ go func() {
+ t.execute();
+ ch <- nil; // clean return;
+ }();
+ return <-ch;
+}
--- /dev/null
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+ "fmt";
+ "io";
+ "os";
+ "template";
+ "testing";
+)
+
+type Test struct {
+ in, out string
+}
+
+type T struct {
+ item string;
+ value string;
+}
+
+type S struct {
+ header string;
+ integer int;
+ data []T;
+ pdata []*T;
+ empty []*T;
+ null []*T;
+}
+
+var t1 = T{ "ItemNumber1", "ValueNumber1" }
+var t2 = T{ "ItemNumber2", "ValueNumber2" }
+
+var tests = []*Test {
+ // Simple
+ &Test{ "", "" },
+ &Test{ "abc\ndef\n", "abc\ndef\n" },
+ &Test{ " {.meta-left} \n", "{" },
+ &Test{ " {.meta-right} \n", "}" },
+ &Test{ " {.space} \n", " " },
+ &Test{ " {#comment} \n", "" },
+
+ // Section
+ &Test{
+ "{.section data }\n"
+ "some text for the section\n"
+ "{.end}\n",
+
+ "some text for the section\n"
+ },
+ &Test{
+ "{.section data }\n"
+ "{header}={integer}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{header}={integer}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data present\n"
+ },
+ &Test{
+ "{.section empty }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data not present\n"
+ },
+ &Test{
+ "{.section null }\n"
+ "data present\n"
+ "{.or}\n"
+ "data not present\n"
+ "{.end}\n",
+
+ "data not present\n"
+ },
+ &Test{
+ "{.section pdata }\n"
+ "{header}={integer}\n"
+ "{.section @ }\n"
+ "{header}={integer}\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "Header=77\n"
+ "Header=77\n"
+ },
+
+ // Repeated
+ &Test{
+ "{.section pdata }\n"
+ "{.repeated section @ }\n"
+ "{item}={value}\n"
+ "{.end}\n"
+ "{.end}\n",
+
+ "ItemNumber1=ValueNumber1\n"
+ "ItemNumber2=ValueNumber2\n"
+ },
+}
+
+func TestAll(t *testing.T) {
+ s := new(S);
+ // initialized by hand for clarity.
+ s.header = "Header";
+ s.integer = 77;
+ s.data = []T{ t1, t2 };
+ s.pdata = []*T{ &t1, &t2 };
+ s.empty = []*T{ };
+ s.null = nil;
+
+ var buf io.ByteBuffer;
+ for i, test := range tests {
+ buf.Reset();
+ err := Execute(test.in, s, &buf);
+ if err != nil {
+ t.Error("unexpected error:", err)
+ }
+ if string(buf.Data()) != test.out {
+ t.Errorf("for %q: expected %q got %q", test.in, test.out, string(buf.Data()));
+ }
+ }
+}
+
+/*
+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);
+ if err == nil {
+ t.Error("failed to detect string as driver type")
+ }
+ var s S;
+}