2009-04-08 23:08:55 -06:00
|
|
|
// 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 (
|
|
|
|
"fmt";
|
|
|
|
"io";
|
|
|
|
"os";
|
|
|
|
"reflect";
|
|
|
|
"strings";
|
2009-04-09 01:10:46 -06:00
|
|
|
"template";
|
2009-04-08 23:08:55 -06:00
|
|
|
)
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
var ErrUnmatchedRDelim = os.NewError("unmatched closing delimiter")
|
|
|
|
var ErrUnmatchedLDelim = os.NewError("unmatched opening delimiter")
|
2009-04-08 23:08:55 -06:00
|
|
|
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")
|
2009-04-09 00:33:31 -06:00
|
|
|
var ErrNoFormatter = os.NewError("unknown formatter")
|
2009-04-14 23:35:18 -06:00
|
|
|
var ErrEmptyDelims = os.NewError("empty delimiter strings")
|
2009-04-08 23:08:55 -06:00
|
|
|
|
|
|
|
// All the literals are aces.
|
|
|
|
var lbrace = []byte{ '{' }
|
|
|
|
var rbrace = []byte{ '}' }
|
|
|
|
var space = []byte{ ' ' }
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
// The various types of "tokens", which are plain text or (usually) brace-delimited descriptors
|
2009-04-08 23:08:55 -06:00
|
|
|
const (
|
|
|
|
Alternates = iota;
|
|
|
|
Comment;
|
|
|
|
End;
|
|
|
|
Literal;
|
|
|
|
Or;
|
|
|
|
Repeated;
|
|
|
|
Section;
|
|
|
|
Text;
|
|
|
|
Variable;
|
|
|
|
)
|
|
|
|
|
2009-04-09 00:33:31 -06:00
|
|
|
// FormatterMap is the type describing the mapping from formatter
|
|
|
|
// names to the functions that implement them.
|
2009-04-13 20:29:23 -06:00
|
|
|
type FormatterMap map[string] func(io.Write, interface{}, string)
|
2009-04-09 00:33:31 -06:00
|
|
|
|
2009-04-09 01:10:46 -06:00
|
|
|
// Built-in formatters.
|
|
|
|
var builtins = FormatterMap {
|
|
|
|
"html" : HtmlFormatter,
|
|
|
|
"str" : StringFormatter,
|
|
|
|
"" : StringFormatter,
|
|
|
|
}
|
|
|
|
|
2009-04-14 01:06:49 -06:00
|
|
|
// State for executing a Template
|
|
|
|
type state struct {
|
|
|
|
parent *state; // parent in hierarchy
|
2009-04-08 23:08:55 -06:00
|
|
|
errorchan chan *os.Error; // for erroring out
|
|
|
|
data reflect.Value; // the driver data for this section etc.
|
2009-04-14 01:06:49 -06:00
|
|
|
wr io.Write; // where to send output
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report error and stop generation.
|
|
|
|
func (st *state) error(err *os.Error, args ...) {
|
|
|
|
st.errorchan <- err;
|
|
|
|
sys.Goexit();
|
|
|
|
}
|
|
|
|
|
|
|
|
type Template struct {
|
2009-04-09 00:33:31 -06:00
|
|
|
fmap FormatterMap; // formatters for variables
|
2009-04-14 23:35:18 -06:00
|
|
|
ldelim, rdelim []byte; // delimiters; default {}
|
2009-04-08 23:08:55 -06:00
|
|
|
buf []byte; // input text to process
|
|
|
|
p int; // position in buf
|
2009-04-14 01:06:49 -06:00
|
|
|
linenum *int; // position in input
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
// Initialize a top-level template in prepratation for parsing.
|
|
|
|
// The formatter map and delimiters are already set.
|
|
|
|
func (t *Template) init(buf []byte) *Template {
|
2009-04-08 23:08:55 -06:00
|
|
|
t.buf = buf;
|
|
|
|
t.p = 0;
|
2009-04-14 01:06:49 -06:00
|
|
|
t.linenum = new(int);
|
2009-04-08 23:08:55 -06:00
|
|
|
return t;
|
|
|
|
}
|
|
|
|
// Create a template deriving from its parent
|
2009-04-14 01:06:49 -06:00
|
|
|
func childTemplate(parent *Template, buf []byte) *Template {
|
|
|
|
t := new(Template);
|
2009-04-14 23:35:18 -06:00
|
|
|
t.ldelim = parent.ldelim;
|
|
|
|
t.rdelim = parent.rdelim;
|
2009-04-14 01:06:49 -06:00
|
|
|
t.buf = buf;
|
|
|
|
t.p = 0;
|
|
|
|
t.fmap = parent.fmap;
|
|
|
|
t.linenum = parent.linenum;
|
2009-04-08 23:08:55 -06:00
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
func white(c uint8) bool {
|
2009-04-13 20:27:35 -06:00
|
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
// safely, does s[n:n+len(t)] == t?
|
|
|
|
func equal(s []byte, n int, t []byte) bool {
|
|
|
|
b := s[n:len(s)];
|
|
|
|
if len(t) > len(b) { // not enough space left for a match.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i , c := range t {
|
|
|
|
if c != b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) execute(st *state)
|
|
|
|
func (t *Template) executeSection(w []string, st *state)
|
2009-04-08 23:08:55 -06:00
|
|
|
|
|
|
|
// nextItem returns the next item from the input buffer. If the returned
|
2009-04-14 23:35:18 -06:00
|
|
|
// item is empty, we are at EOF. The item will be either a
|
|
|
|
// delimited string or a non-empty string between delimited
|
2009-04-08 23:08:55 -06:00
|
|
|
// 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.
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) nextItem(st *state) []byte {
|
2009-04-14 23:35:18 -06:00
|
|
|
sawLeft := false; // are we waiting for an opening delimiter?
|
2009-04-08 23:08:55 -06:00
|
|
|
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++ {
|
2009-04-14 23:35:18 -06:00
|
|
|
switch {
|
|
|
|
case t.buf[i] == '\n':
|
2009-04-08 23:08:55 -06:00
|
|
|
*t.linenum++;
|
|
|
|
i++;
|
|
|
|
break Loop;
|
2009-04-14 23:35:18 -06:00
|
|
|
case white(t.buf[i]):
|
2009-04-08 23:08:55 -06:00
|
|
|
// white space, do nothing
|
2009-04-14 23:35:18 -06:00
|
|
|
case !sawLeft && equal(t.buf, i, t.ldelim): // sawLeft checked because delims may be equal
|
2009-04-08 23:08:55 -06:00
|
|
|
// anything interesting already on the line?
|
|
|
|
if !only_white {
|
|
|
|
break Loop;
|
|
|
|
}
|
|
|
|
// is it a directive or comment?
|
2009-04-14 23:35:18 -06:00
|
|
|
j := i + len(t.ldelim); // position after delimiter
|
|
|
|
if j+1 < len(t.buf) && (t.buf[j] == '.' || t.buf[j] == '#') {
|
2009-04-08 23:08:55 -06:00
|
|
|
special = true;
|
|
|
|
if trim_white && only_white {
|
|
|
|
start = i;
|
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
} else if i > t.p { // have some text accumulated so stop before delimiter
|
2009-04-08 23:08:55 -06:00
|
|
|
break Loop;
|
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
sawLeft = true;
|
|
|
|
i = j - 1;
|
|
|
|
case equal(t.buf, i, t.rdelim):
|
|
|
|
if !sawLeft {
|
|
|
|
st.error(ErrUnmatchedRDelim)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
sawLeft = false;
|
|
|
|
i += len(t.rdelim);
|
2009-04-08 23:08:55 -06:00
|
|
|
break Loop;
|
|
|
|
default:
|
|
|
|
only_white = false;
|
|
|
|
}
|
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
if sawLeft {
|
|
|
|
st.error(ErrUnmatchedLDelim)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
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.
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) analyze(item []byte, st *state) (tok int, w []string) {
|
2009-04-08 23:08:55 -06:00
|
|
|
// item is known to be non-empty
|
2009-04-14 23:35:18 -06:00
|
|
|
if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter
|
2009-04-08 23:08:55 -06:00
|
|
|
tok = Text;
|
|
|
|
return
|
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
|
|
|
|
st.error(ErrUnmatchedLDelim) // should not happen anyway
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrEmptyDirective)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
// Comment
|
2009-04-14 23:35:18 -06:00
|
|
|
if item[len(t.ldelim)] == '#' {
|
2009-04-08 23:08:55 -06:00
|
|
|
tok = Comment;
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Split into words
|
2009-04-14 23:35:18 -06:00
|
|
|
w = words(item[len(t.ldelim): len(item)-len(t.rdelim)]); // drop final delimiter
|
2009-04-08 23:08:55 -06:00
|
|
|
if len(w) == 0 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrBadDirective)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
if len(w[0]) == 0 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrEmptyDirective)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
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 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrFields, ": ", string(item))
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
tok = Section;
|
|
|
|
return;
|
|
|
|
case ".repeated":
|
|
|
|
if len(w) != 3 || w[1] != "section" {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrFields, ": ", string(item))
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
tok = Repeated;
|
|
|
|
return;
|
|
|
|
case ".alternates":
|
|
|
|
if len(w) != 2 || w[1] != "with" {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrFields, ": ", string(item))
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
tok = Alternates;
|
|
|
|
return;
|
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrBadDirective, ": ", string(item));
|
2009-04-08 23:08:55 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the data for this template is a struct, find the named variable.
|
2009-04-14 02:12:20 -06:00
|
|
|
// The special name "@" denotes the current data.
|
|
|
|
func (st *state) findVar(s string) reflect.Value {
|
|
|
|
if s == "@" {
|
|
|
|
return st.data
|
|
|
|
}
|
|
|
|
data := reflect.Indirect(st.data);
|
|
|
|
typ, ok := data.Type().(reflect.StructType);
|
2009-04-08 23:08:55 -06:00
|
|
|
if ok {
|
|
|
|
for i := 0; i < typ.Len(); i++ {
|
|
|
|
name, ftyp, tag, offset := typ.Field(i);
|
|
|
|
if name == s {
|
2009-04-14 02:12:20 -06:00
|
|
|
return data.(reflect.StructValue).Field(i)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-04-14 02:12:20 -06:00
|
|
|
return nil
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is there no data to look at?
|
|
|
|
func empty(v reflect.Value, indirect_ok bool) bool {
|
2009-04-09 00:33:31 -06:00
|
|
|
v = reflect.Indirect(v);
|
2009-04-08 23:08:55 -06:00
|
|
|
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
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) executeRepeated(w []string, st *state) {
|
2009-04-08 23:08:55 -06:00
|
|
|
if w[1] != "section" {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrSyntax, `: .repeated must have "section"`)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 02:12:20 -06:00
|
|
|
|
2009-04-08 23:08:55 -06:00
|
|
|
// Find driver array/struct for this section. It must be in the current struct.
|
2009-04-14 02:12:20 -06:00
|
|
|
field := st.findVar(w[2]);
|
|
|
|
if field == nil {
|
|
|
|
st.error(ErrNoVar, ": .repeated ", w[2], " in ", reflect.Indirect(st.data).Type());
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 02:12:20 -06:00
|
|
|
|
2009-04-08 23:08:55 -06:00
|
|
|
// Must be an array/slice
|
|
|
|
if field != nil && field.Kind() != reflect.ArrayKind {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrBadType, " in .repeated: ", w[2], " ", field.Type().String());
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
// Scan repeated section, remembering slice of text we must execute.
|
|
|
|
nesting := 0;
|
|
|
|
start := t.p;
|
|
|
|
end := t.p;
|
|
|
|
Loop:
|
|
|
|
for {
|
2009-04-14 01:06:49 -06:00
|
|
|
item := t.nextItem(st);
|
2009-04-08 23:08:55 -06:00
|
|
|
if len(item) == 0 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrNoEnd)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
tok, s := t.analyze(item, st);
|
2009-04-08 23:08:55 -06:00
|
|
|
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++ {
|
2009-04-14 01:06:49 -06:00
|
|
|
tmp := childTemplate(t, t.buf[start:end]);
|
2009-04-14 02:12:20 -06:00
|
|
|
tmp.execute(&state{st, st.errorchan, array.Elem(i), st.wr});
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute a ".section"
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) executeSection(w []string, st *state) {
|
2009-04-14 02:12:20 -06:00
|
|
|
// Find driver data for this section. It must be in the current struct.
|
|
|
|
field := st.findVar(w[1]);
|
|
|
|
if field == nil {
|
|
|
|
st.error(ErrNoVar, ": .section ", w[1], " in ", reflect.Indirect(st.data).Type());
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
// 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 {
|
2009-04-14 01:06:49 -06:00
|
|
|
item := t.nextItem(st);
|
2009-04-08 23:08:55 -06:00
|
|
|
if len(item) == 0 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrNoEnd)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
tok, s := t.analyze(item, st);
|
2009-04-08 23:08:55 -06:00
|
|
|
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 {
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrSyntax, ": .or");
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
tmp := childTemplate(t, t.buf[start:end]);
|
|
|
|
tmp.execute(&state{st, st.errorchan, field, st.wr});
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-09 00:33:31 -06:00
|
|
|
// Look up a variable, up through the parent if necessary.
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) varValue(name string, st *state) reflect.Value {
|
2009-04-14 02:12:20 -06:00
|
|
|
field := st.findVar(name);
|
|
|
|
if field == nil {
|
2009-04-14 01:06:49 -06:00
|
|
|
if st.parent == nil {
|
|
|
|
st.error(ErrNoVar, ": ", name)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
return t.varValue(name, st.parent);
|
2009-04-09 00:33:31 -06:00
|
|
|
}
|
2009-04-14 02:12:20 -06:00
|
|
|
return field;
|
2009-04-09 00:33:31 -06:00
|
|
|
}
|
|
|
|
|
2009-04-14 01:06:49 -06:00
|
|
|
// Evaluate a variable, looking up through the parent if necessary.
|
2009-04-09 00:33:31 -06:00
|
|
|
// If it has a formatter attached ({var|formatter}) run that too.
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) writeVariable(st *state, name_formatter string) {
|
2009-04-09 00:33:31 -06:00
|
|
|
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)];
|
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
val := t.varValue(name, st).Interface();
|
2009-04-09 01:10:46 -06:00
|
|
|
// is it in user-supplied map?
|
2009-04-13 16:23:57 -06:00
|
|
|
if t.fmap != nil {
|
|
|
|
if fn, ok := t.fmap[formatter]; ok {
|
2009-04-14 01:06:49 -06:00
|
|
|
fn(st.wr, val, formatter);
|
2009-04-13 20:29:23 -06:00
|
|
|
return;
|
2009-04-13 16:23:57 -06:00
|
|
|
}
|
2009-04-09 00:33:31 -06:00
|
|
|
}
|
2009-04-09 01:10:46 -06:00
|
|
|
// is it in builtin map?
|
|
|
|
if fn, ok := builtins[formatter]; ok {
|
2009-04-14 01:06:49 -06:00
|
|
|
fn(st.wr, val, formatter);
|
2009-04-13 20:29:23 -06:00
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrNoFormatter, ": ", formatter);
|
2009-04-09 00:33:31 -06:00
|
|
|
panic("notreached");
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) execute(st *state) {
|
2009-04-08 23:08:55 -06:00
|
|
|
for {
|
2009-04-14 01:06:49 -06:00
|
|
|
item := t.nextItem(st);
|
2009-04-08 23:08:55 -06:00
|
|
|
if len(item) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
tok, w := t.analyze(item, st);
|
2009-04-08 23:08:55 -06:00
|
|
|
switch tok {
|
|
|
|
case Comment:
|
|
|
|
break;
|
|
|
|
case Text:
|
2009-04-14 01:06:49 -06:00
|
|
|
st.wr.Write(item);
|
2009-04-08 23:08:55 -06:00
|
|
|
case Literal:
|
|
|
|
switch w[0] {
|
|
|
|
case ".meta-left":
|
2009-04-14 23:35:18 -06:00
|
|
|
st.wr.Write(t.ldelim);
|
2009-04-08 23:08:55 -06:00
|
|
|
case ".meta-right":
|
2009-04-14 23:35:18 -06:00
|
|
|
st.wr.Write(t.rdelim);
|
2009-04-08 23:08:55 -06:00
|
|
|
case ".space":
|
2009-04-14 01:06:49 -06:00
|
|
|
st.wr.Write(space);
|
2009-04-08 23:08:55 -06:00
|
|
|
default:
|
|
|
|
panic("unknown literal: ", w[0]);
|
|
|
|
}
|
|
|
|
case Variable:
|
2009-04-14 01:06:49 -06:00
|
|
|
t.writeVariable(st, w[0]);
|
2009-04-08 23:08:55 -06:00
|
|
|
case Or, End, Alternates:
|
2009-04-14 01:06:49 -06:00
|
|
|
st.error(ErrSyntax, ": ", string(item));
|
2009-04-08 23:08:55 -06:00
|
|
|
case Section:
|
2009-04-14 01:06:49 -06:00
|
|
|
t.executeSection(w, st);
|
2009-04-08 23:08:55 -06:00
|
|
|
case Repeated:
|
2009-04-14 01:06:49 -06:00
|
|
|
t.executeRepeated(w, st);
|
2009-04-08 23:08:55 -06:00
|
|
|
default:
|
|
|
|
panic("bad directive in execute:", string(item));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
func (t *Template) doParse() {
|
2009-04-14 01:06:49 -06:00
|
|
|
// stub for now
|
|
|
|
}
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
// Parse initializes a Template by parsing its definition. The string s contains
|
|
|
|
// the template text. If any errors occur, it returns the error and line number
|
|
|
|
// in the text of the erroneous construct.
|
|
|
|
func (t *Template) Parse(s string) (*os.Error, int) {
|
|
|
|
if len(t.ldelim) == 0 || len(t.rdelim) == 0 {
|
|
|
|
return ErrEmptyDelims, 0
|
|
|
|
}
|
|
|
|
t.init(io.StringBytes(s));
|
2009-04-14 01:06:49 -06:00
|
|
|
ch := make(chan *os.Error);
|
|
|
|
go func() {
|
2009-04-14 23:35:18 -06:00
|
|
|
t.doParse();
|
2009-04-14 01:06:49 -06:00
|
|
|
ch <- nil; // clean return;
|
|
|
|
}();
|
|
|
|
err := <-ch;
|
|
|
|
if err != nil {
|
2009-04-14 23:35:18 -06:00
|
|
|
return err, *t.linenum
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
return nil, 0
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
|
|
|
|
2009-04-14 23:35:18 -06:00
|
|
|
// Execute executes a parsed template on the specified data object,
|
|
|
|
// generating output to wr.
|
2009-04-14 01:06:49 -06:00
|
|
|
func (t *Template) Execute(data interface{}, wr io.Write) *os.Error {
|
2009-04-14 02:12:20 -06:00
|
|
|
// Extract the driver data.
|
|
|
|
val := reflect.NewValue(data);
|
2009-04-08 23:08:55 -06:00
|
|
|
ch := make(chan *os.Error);
|
|
|
|
go func() {
|
2009-04-14 22:25:33 -06:00
|
|
|
t.p = 0;
|
2009-04-14 01:06:49 -06:00
|
|
|
t.execute(&state{nil, ch, val, wr});
|
2009-04-08 23:08:55 -06:00
|
|
|
ch <- nil; // clean return;
|
|
|
|
}();
|
|
|
|
return <-ch;
|
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
|
|
|
|
// New creates a new template with the specified formatter map (which
|
|
|
|
// may be nil) defining auxiliary functions for formatting variables.
|
|
|
|
func New(fmap FormatterMap) *Template {
|
|
|
|
t := new(Template);
|
|
|
|
t.fmap = fmap;
|
|
|
|
t.ldelim = lbrace;
|
|
|
|
t.rdelim = rbrace;
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDelims sets the left and right delimiters for operations in the template.
|
|
|
|
func (t *Template) SetDelims(left, right string) {
|
|
|
|
t.ldelim = io.StringBytes(left);
|
|
|
|
t.rdelim = io.StringBytes(right);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse creates a Template with default parameters (such as {} for
|
|
|
|
// metacharacters). The string s contains the template text and the
|
|
|
|
// formatter map fmap (which may be nil) defines auxiliary functions
|
|
|
|
// for formatting variables. It returns the template, an error report
|
|
|
|
// (or nil), and the line number in the text of the erroneous construct.
|
|
|
|
func Parse(s string, fmap FormatterMap) (*Template, *os.Error, int) {
|
|
|
|
t := New(fmap);
|
|
|
|
err, line := t.Parse(s);
|
|
|
|
return t, err, line
|
|
|
|
}
|