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.
|
|
|
|
|
2009-04-16 18:44:23 -06:00
|
|
|
/*
|
|
|
|
Data-driven templates for generating textual output such as
|
|
|
|
HTML. See
|
|
|
|
http://code.google.com/p/json-template/wiki/Reference
|
|
|
|
for full documentation of the template language. A summary:
|
|
|
|
|
|
|
|
Templates are executed by applying them to a data structure.
|
|
|
|
Annotations in the template refer to elements of the data
|
|
|
|
structure (typically a field of a struct) to control execution
|
|
|
|
and derive values to be displayed. The template walks the
|
|
|
|
structure as it executes and the "cursor" @ represents the
|
|
|
|
value at the current location in the structure.
|
|
|
|
|
|
|
|
Data items may be values or pointers; the interface hides the
|
|
|
|
indirection.
|
|
|
|
|
|
|
|
Major constructs ({} are metacharacters; [] marks optional elements):
|
|
|
|
|
|
|
|
{# comment }
|
|
|
|
|
|
|
|
A one-line comment.
|
|
|
|
|
|
|
|
{.section field} XXX [ {.or} YYY ] {.end}
|
|
|
|
|
|
|
|
Set @ to the value of the field. It may be an explicit @
|
|
|
|
to stay at the same point in the data. If the field is nil
|
|
|
|
or empty, execute YYY; otherwise execute XXX.
|
|
|
|
|
|
|
|
{.repeated section field} XXX [ {.alternates with} ZZZ ] [ {.or} YYY ] {.end}
|
|
|
|
|
|
|
|
Like .section, but field must be an array or slice. XXX
|
|
|
|
is executed for each element. If the array is nil or empty,
|
|
|
|
YYY is executed instead. If the {.alternates with} marker
|
|
|
|
is present, ZZZ is executed between iterations of XXX.
|
|
|
|
|
|
|
|
{field}
|
|
|
|
{field|formatter}
|
|
|
|
|
|
|
|
Insert the value of the field into the output. Field is
|
|
|
|
first looked for in the cursor, as in .section and .repeated.
|
|
|
|
If it is not found, the search continues in outer sections
|
|
|
|
until the top level is reached.
|
2009-05-08 16:21:41 -06:00
|
|
|
|
2009-04-16 18:44:23 -06:00
|
|
|
If a formatter is specified, it must be named in the formatter
|
|
|
|
map passed to the template set up routines or in the default
|
|
|
|
set ("html","str","") and is used to process the data for
|
|
|
|
output. The formatter function has signature
|
|
|
|
func(wr io.Write, data interface{}, formatter string)
|
|
|
|
where wr is the destination for output, data is the field
|
|
|
|
value, and formatter is its name at the invocation site.
|
|
|
|
*/
|
2009-04-08 23:08:55 -06:00
|
|
|
package template
|
|
|
|
|
|
|
|
import (
|
2009-05-13 11:01:55 -06:00
|
|
|
"container/vector";
|
2009-04-08 23:08:55 -06:00
|
|
|
"fmt";
|
|
|
|
"io";
|
|
|
|
"os";
|
|
|
|
"reflect";
|
2009-05-08 16:21:41 -06:00
|
|
|
"runtime";
|
2009-04-08 23:08:55 -06:00
|
|
|
"strings";
|
|
|
|
)
|
|
|
|
|
2009-05-13 11:01:55 -06:00
|
|
|
// Errors returned during parsing and execution. Users may extract the information and reformat
|
|
|
|
// if they desire.
|
|
|
|
type Error struct {
|
2009-05-13 11:34:11 -06:00
|
|
|
Line int;
|
|
|
|
Msg string;
|
2009-05-13 11:01:55 -06:00
|
|
|
}
|
2009-04-17 01:08:24 -06:00
|
|
|
|
2009-05-13 11:01:55 -06:00
|
|
|
func (e *Error) String() string {
|
2009-05-13 11:34:11 -06:00
|
|
|
return fmt.Sprintf("line %d: %s", e.Line, e.Msg)
|
2009-04-18 17:44:13 -06:00
|
|
|
}
|
2009-04-08 23:08:55 -06:00
|
|
|
|
2009-04-22 01:53:35 -06:00
|
|
|
// Most of the literals are aces.
|
2009-04-08 23:08:55 -06:00
|
|
|
var lbrace = []byte{ '{' }
|
|
|
|
var rbrace = []byte{ '}' }
|
|
|
|
var space = []byte{ ' ' }
|
2009-04-22 01:53:35 -06:00
|
|
|
var tab = []byte{ '\t' }
|
2009-04-08 23:08:55 -06:00
|
|
|
|
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 (
|
2009-04-20 19:51:13 -06:00
|
|
|
tokAlternates = iota;
|
|
|
|
tokComment;
|
|
|
|
tokEnd;
|
|
|
|
tokLiteral;
|
|
|
|
tokOr;
|
|
|
|
tokRepeated;
|
|
|
|
tokSection;
|
|
|
|
tokText;
|
|
|
|
tokVariable;
|
2009-04-08 23:08:55 -06:00
|
|
|
)
|
|
|
|
|
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-05-08 12:22:57 -06:00
|
|
|
type FormatterMap map[string] func(io.Writer, 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-20 19:51:13 -06:00
|
|
|
// The parsed state of a template is a vector of xxxElement structs.
|
|
|
|
// Sections have line numbers so errors can be reported better during execution.
|
|
|
|
|
|
|
|
// Plain text.
|
|
|
|
type textElement struct {
|
|
|
|
text []byte;
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// A literal such as .meta-left or .meta-right
|
|
|
|
type literalElement struct {
|
|
|
|
text []byte;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A variable to be evaluated
|
|
|
|
type variableElement struct {
|
|
|
|
linenum int;
|
|
|
|
name string;
|
|
|
|
formatter string; // TODO(r): implement pipelines
|
|
|
|
}
|
|
|
|
|
|
|
|
// A .section block, possibly with a .or
|
|
|
|
type sectionElement struct {
|
|
|
|
linenum int; // of .section itself
|
|
|
|
field string; // cursor field for this block
|
|
|
|
start int; // first element
|
|
|
|
or int; // first element of .or block
|
|
|
|
end int; // one beyond last element
|
|
|
|
}
|
|
|
|
|
2009-04-27 22:04:46 -06:00
|
|
|
// A .repeated block, possibly with a .or and a .alternates
|
2009-04-20 19:51:13 -06:00
|
|
|
type repeatedElement struct {
|
2009-04-27 22:04:46 -06:00
|
|
|
sectionElement; // It has the same structure...
|
|
|
|
altstart int; // ... except for alternates
|
|
|
|
altend int;
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
|
|
|
|
2009-04-16 18:44:23 -06:00
|
|
|
// Template is the type that represents a template definition.
|
2009-04-20 19:51:13 -06:00
|
|
|
// It is unchanged after parsing.
|
2009-04-14 01:06:49 -06:00
|
|
|
type Template struct {
|
2009-04-09 00:33:31 -06:00
|
|
|
fmap FormatterMap; // formatters for variables
|
2009-04-20 19:51:13 -06:00
|
|
|
// Used during parsing:
|
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-20 19:51:13 -06:00
|
|
|
linenum int; // position in input
|
2009-11-02 14:09:31 -07:00
|
|
|
error os.Error; // error during parsing (only)
|
2009-05-06 14:42:59 -06:00
|
|
|
// Parsed results:
|
2009-04-20 19:51:13 -06:00
|
|
|
elems *vector.Vector;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// Internal state for executing a Template. As we evaluate the struct,
|
|
|
|
// the data item descends into the fields associated with sections, etc.
|
|
|
|
// Parent is used to walk upwards to find variables higher in the tree.
|
|
|
|
type state struct {
|
|
|
|
parent *state; // parent in hierarchy
|
|
|
|
data reflect.Value; // the driver data for this section etc.
|
2009-05-08 12:22:57 -06:00
|
|
|
wr io.Writer; // where to send output
|
2009-05-06 14:42:59 -06:00
|
|
|
errors chan os.Error; // for reporting errors during execute
|
|
|
|
}
|
|
|
|
|
|
|
|
func (parent *state) clone(data reflect.Value) *state {
|
|
|
|
return &state{parent, data, parent.wr, parent.errors}
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
|
|
|
|
// New creates a new template with the specified formatter map (which
|
|
|
|
// may be nil) to define auxiliary functions for formatting variables.
|
|
|
|
func New(fmap FormatterMap) *Template {
|
2009-04-14 01:06:49 -06:00
|
|
|
t := new(Template);
|
2009-04-20 19:51:13 -06:00
|
|
|
t.fmap = fmap;
|
|
|
|
t.ldelim = lbrace;
|
|
|
|
t.rdelim = rbrace;
|
|
|
|
t.elems = vector.New(0);
|
2009-04-08 23:08:55 -06:00
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2009-11-02 14:09:31 -07:00
|
|
|
// Report error and stop executing. The line number must be provided explicitly.
|
2009-05-06 14:42:59 -06:00
|
|
|
func (t *Template) execError(st *state, line int, err string, args ...) {
|
2009-11-02 14:09:31 -07:00
|
|
|
st.errors <- &Error{line, fmt.Sprintf(err, args)};
|
|
|
|
runtime.Goexit();
|
2009-05-06 14:42:59 -06:00
|
|
|
}
|
|
|
|
|
2009-11-02 14:09:31 -07:00
|
|
|
// Report error, save in Template to terminate parsing.
|
|
|
|
// The line number comes from the template state.
|
2009-05-06 14:42:59 -06:00
|
|
|
func (t *Template) parseError(err string, args ...) {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.error = &Error{t.linenum, fmt.Sprintf(err, args)};
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// -- Lexical analysis
|
|
|
|
|
2009-04-16 18:44:23 -06:00
|
|
|
// Is c a white space character?
|
2009-04-08 23:08:55 -06:00
|
|
|
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-16 18:44:23 -06:00
|
|
|
// Safely, does s[n:n+len(t)] == t?
|
2009-04-14 23:35:18 -06:00
|
|
|
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-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-16 18:44:23 -06:00
|
|
|
// strings. Tokens stop at (but include, if plain text) a newline.
|
2009-04-08 23:08:55 -06:00
|
|
|
// Action tokens on a line by themselves drop the white space on
|
|
|
|
// either side, up to and including the newline.
|
2009-04-20 19:51:13 -06:00
|
|
|
func (t *Template) nextItem() []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-20 19:51:13 -06:00
|
|
|
t.linenum++;
|
2009-04-08 23:08:55 -06:00
|
|
|
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 {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("unmatched closing delimiter");
|
|
|
|
return nil;
|
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 {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("unmatched opening delimiter");
|
|
|
|
return nil;
|
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' {
|
2009-11-02 21:35:52 -07:00
|
|
|
t.linenum++;
|
2009-04-08 23:08:55 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// Analyze an item and return its token type and, if it's an action item, an array of
|
2009-04-08 23:08:55 -06:00
|
|
|
// its constituent words.
|
2009-04-20 19:51:13 -06:00
|
|
|
func (t *Template) analyze(item []byte) (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-20 19:51:13 -06:00
|
|
|
tok = tokText;
|
2009-11-02 14:09:31 -07:00
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-14 23:35:18 -06:00
|
|
|
if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("internal error: unmatched opening delimiter"); // lexing should prevent this
|
|
|
|
return;
|
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-11-02 14:09:31 -07:00
|
|
|
t.parseError("empty directive");
|
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
// Comment
|
2009-04-14 23:35:18 -06:00
|
|
|
if item[len(t.ldelim)] == '#' {
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokComment;
|
2009-04-08 23:08:55 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
// Split into words
|
2009-04-20 19:51:13 -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-11-02 14:09:31 -07:00
|
|
|
t.parseError("empty directive");
|
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
if len(w) == 1 && w[0][0] != '.' {
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokVariable;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch w[0] {
|
2009-04-22 01:53:35 -06:00
|
|
|
case ".meta-left", ".meta-right", ".space", ".tab":
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokLiteral;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
case ".or":
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokOr;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
case ".end":
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokEnd;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
case ".section":
|
|
|
|
if len(w) != 2 {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("incorrect fields for .section: %s", item);
|
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokSection;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
case ".repeated":
|
|
|
|
if len(w) != 3 || w[1] != "section" {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("incorrect fields for .repeated: %s", item);
|
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokRepeated;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
case ".alternates":
|
|
|
|
if len(w) != 2 || w[1] != "with" {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("incorrect fields for .alternates: %s", item);
|
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
tok = tokAlternates;
|
2009-04-08 23:08:55 -06:00
|
|
|
return;
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
t.parseError("bad directive: %s", item);
|
2009-04-08 23:08:55 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// -- Parsing
|
|
|
|
|
|
|
|
// Allocate a new variable-evaluation element.
|
|
|
|
func (t *Template) newVariable(name_formatter string) (v *variableElement) {
|
|
|
|
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)];
|
|
|
|
}
|
|
|
|
// Probably ok, so let's build it.
|
|
|
|
v = &variableElement{t.linenum, name, formatter};
|
|
|
|
|
|
|
|
// We could remember the function address here and avoid the lookup later,
|
|
|
|
// but it's more dynamic to let the user change the map contents underfoot.
|
|
|
|
// We do require the name to be present, though.
|
|
|
|
|
|
|
|
// Is it in user-supplied map?
|
|
|
|
if t.fmap != nil {
|
2009-09-14 22:03:53 -06:00
|
|
|
if _, ok := t.fmap[formatter]; ok {
|
2009-04-20 19:51:13 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Is it in builtin map?
|
2009-09-14 22:03:53 -06:00
|
|
|
if _, ok := builtins[formatter]; ok {
|
2009-04-20 19:51:13 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
t.parseError("unknown formatter: %s", formatter);
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab the next item. If it's simple, just append it to the template.
|
|
|
|
// Otherwise return its details.
|
|
|
|
func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
|
|
|
|
tok, w = t.analyze(item);
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
return
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
done = true; // assume for simplicity
|
|
|
|
switch tok {
|
|
|
|
case tokComment:
|
|
|
|
return;
|
|
|
|
case tokText:
|
|
|
|
t.elems.Push(&textElement{item});
|
|
|
|
return;
|
|
|
|
case tokLiteral:
|
|
|
|
switch w[0] {
|
|
|
|
case ".meta-left":
|
|
|
|
t.elems.Push(&literalElement{t.ldelim});
|
|
|
|
case ".meta-right":
|
|
|
|
t.elems.Push(&literalElement{t.rdelim});
|
|
|
|
case ".space":
|
|
|
|
t.elems.Push(&literalElement{space});
|
2009-04-22 01:53:35 -06:00
|
|
|
case ".tab":
|
|
|
|
t.elems.Push(&literalElement{tab});
|
2009-04-20 19:51:13 -06:00
|
|
|
default:
|
|
|
|
t.parseError("internal error: unknown literal: %s", w[0]);
|
2009-11-02 14:09:31 -07:00
|
|
|
return;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
case tokVariable:
|
|
|
|
t.elems.Push(t.newVariable(w[0]));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return false, tok, w
|
|
|
|
}
|
|
|
|
|
2009-08-12 14:19:17 -06:00
|
|
|
// parseRepeated and parseSection are mutually recursive
|
2009-04-20 19:51:13 -06:00
|
|
|
|
|
|
|
func (t *Template) parseRepeated(words []string) *repeatedElement {
|
|
|
|
r := new(repeatedElement);
|
|
|
|
t.elems.Push(r);
|
|
|
|
r.linenum = t.linenum;
|
|
|
|
r.field = words[2];
|
|
|
|
// Scan section, collecting true and false (.or) blocks.
|
|
|
|
r.start = t.elems.Len();
|
|
|
|
r.or = -1;
|
2009-04-27 22:04:46 -06:00
|
|
|
r.altstart = -1;
|
|
|
|
r.altend = -1;
|
2009-04-20 19:51:13 -06:00
|
|
|
Loop:
|
2009-11-02 14:09:31 -07:00
|
|
|
for t.error == nil {
|
2009-04-20 19:51:13 -06:00
|
|
|
item := t.nextItem();
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
break;
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
if len(item) == 0 {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("missing .end for .repeated section");
|
|
|
|
break;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
done, tok, w := t.parseSimple(item);
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
break;
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
if done {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch tok {
|
|
|
|
case tokEnd:
|
|
|
|
break Loop;
|
|
|
|
case tokOr:
|
|
|
|
if r.or >= 0 {
|
|
|
|
t.parseError("extra .or in .repeated section");
|
2009-11-02 14:09:31 -07:00
|
|
|
break Loop;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
2009-04-27 22:04:46 -06:00
|
|
|
r.altend = t.elems.Len();
|
2009-04-20 19:51:13 -06:00
|
|
|
r.or = t.elems.Len();
|
|
|
|
case tokSection:
|
|
|
|
t.parseSection(w);
|
|
|
|
case tokRepeated:
|
|
|
|
t.parseRepeated(w);
|
|
|
|
case tokAlternates:
|
2009-04-27 22:04:46 -06:00
|
|
|
if r.altstart >= 0 {
|
|
|
|
t.parseError("extra .alternates in .repeated section");
|
2009-11-02 14:09:31 -07:00
|
|
|
break Loop;
|
2009-04-27 22:04:46 -06:00
|
|
|
}
|
|
|
|
if r.or >= 0 {
|
|
|
|
t.parseError(".alternates inside .or block in .repeated section");
|
2009-11-02 14:09:31 -07:00
|
|
|
break Loop;
|
2009-04-27 22:04:46 -06:00
|
|
|
}
|
|
|
|
r.altstart = t.elems.Len();
|
2009-04-20 19:51:13 -06:00
|
|
|
default:
|
|
|
|
t.parseError("internal error: unknown repeated section item: %s", item);
|
2009-11-02 14:09:31 -07:00
|
|
|
break Loop;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
}
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2009-04-27 22:04:46 -06:00
|
|
|
if r.altend < 0 {
|
|
|
|
r.altend = t.elems.Len()
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
r.end = t.elems.Len();
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) parseSection(words []string) *sectionElement {
|
|
|
|
s := new(sectionElement);
|
|
|
|
t.elems.Push(s);
|
|
|
|
s.linenum = t.linenum;
|
|
|
|
s.field = words[1];
|
|
|
|
// Scan section, collecting true and false (.or) blocks.
|
|
|
|
s.start = t.elems.Len();
|
|
|
|
s.or = -1;
|
|
|
|
Loop:
|
2009-11-02 14:09:31 -07:00
|
|
|
for t.error == nil {
|
2009-04-20 19:51:13 -06:00
|
|
|
item := t.nextItem();
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
break;
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
if len(item) == 0 {
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parseError("missing .end for .section");
|
|
|
|
break;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
done, tok, w := t.parseSimple(item);
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
break;
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
if done {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch tok {
|
|
|
|
case tokEnd:
|
|
|
|
break Loop;
|
|
|
|
case tokOr:
|
|
|
|
if s.or >= 0 {
|
|
|
|
t.parseError("extra .or in .section");
|
2009-11-02 14:09:31 -07:00
|
|
|
break Loop;
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
s.or = t.elems.Len();
|
|
|
|
case tokSection:
|
|
|
|
t.parseSection(w);
|
|
|
|
case tokRepeated:
|
|
|
|
t.parseRepeated(w);
|
|
|
|
case tokAlternates:
|
|
|
|
t.parseError(".alternates not in .repeated");
|
|
|
|
default:
|
|
|
|
t.parseError("internal error: unknown section item: %s", item);
|
|
|
|
}
|
|
|
|
}
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
s.end = t.elems.Len();
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) parse() {
|
2009-11-02 14:09:31 -07:00
|
|
|
for t.error == nil {
|
2009-04-20 19:51:13 -06:00
|
|
|
item := t.nextItem();
|
2009-11-02 14:09:31 -07:00
|
|
|
if t.error != nil {
|
|
|
|
break
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
if len(item) == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
done, tok, w := t.parseSimple(item);
|
|
|
|
if done {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch tok {
|
|
|
|
case tokOr, tokEnd, tokAlternates:
|
|
|
|
t.parseError("unexpected %s", w[0]);
|
|
|
|
case tokSection:
|
|
|
|
t.parseSection(w);
|
|
|
|
case tokRepeated:
|
|
|
|
t.parseRepeated(w);
|
|
|
|
default:
|
|
|
|
t.parseError("internal error: bad directive in parse: %s", item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// -- Execution
|
|
|
|
|
2009-04-08 23:08:55 -06:00
|
|
|
// If the data for this template is a struct, find the named variable.
|
2009-07-30 19:17:07 -06:00
|
|
|
// Names of the form a.b.c are walked down the data tree.
|
2009-04-20 19:51:13 -06:00
|
|
|
// The special name "@" (the "cursor") denotes the current data.
|
2009-07-31 13:42:29 -06:00
|
|
|
// The value coming in (st.data) might need indirecting to reach
|
|
|
|
// a struct while the return value is not indirected - that is,
|
|
|
|
// it represents the actual named field.
|
2009-04-14 02:12:20 -06:00
|
|
|
func (st *state) findVar(s string) reflect.Value {
|
|
|
|
if s == "@" {
|
|
|
|
return st.data
|
|
|
|
}
|
2009-07-30 20:29:31 -06:00
|
|
|
data := st.data;
|
2009-07-30 19:17:07 -06:00
|
|
|
elems := strings.Split(s, ".", 0);
|
|
|
|
for i := 0; i < len(elems); i++ {
|
|
|
|
// Look up field; data must be a struct.
|
2009-07-30 20:29:31 -06:00
|
|
|
data = reflect.Indirect(data);
|
2009-09-14 17:46:48 -06:00
|
|
|
if data == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2009-07-30 19:17:07 -06:00
|
|
|
typ, ok := data.Type().(*reflect.StructType);
|
|
|
|
if !ok {
|
|
|
|
return nil
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-07-30 19:17:07 -06:00
|
|
|
field, ok := typ.FieldByName(elems[i]);
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
2009-08-05 16:56:44 -06:00
|
|
|
data = data.(*reflect.StructValue).FieldByIndex(field.Index);
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-07-30 19:17:07 -06:00
|
|
|
return data
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is there no data to look at?
|
2009-07-31 13:42:29 -06:00
|
|
|
func empty(v reflect.Value) 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
|
|
|
|
}
|
2009-07-07 12:04:34 -06:00
|
|
|
switch v := v.(type) {
|
2009-09-01 17:31:49 -06:00
|
|
|
case *reflect.BoolValue:
|
|
|
|
return v.Get() == false;
|
2009-07-07 12:04:34 -06:00
|
|
|
case *reflect.StringValue:
|
|
|
|
return v.Get() == "";
|
|
|
|
case *reflect.StructValue:
|
2009-04-08 23:08:55 -06:00
|
|
|
return false;
|
2009-07-07 12:04:34 -06:00
|
|
|
case *reflect.ArrayValue:
|
|
|
|
return v.Len() == 0;
|
|
|
|
case *reflect.SliceValue:
|
|
|
|
return v.Len() == 0;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-04-09 00:33:31 -06:00
|
|
|
// Look up a variable, up through the parent if necessary.
|
2009-09-01 17:31:49 -06:00
|
|
|
func (t *Template) varValue(name string, st *state) reflect.Value {
|
|
|
|
field := st.findVar(name);
|
2009-04-14 02:12:20 -06:00
|
|
|
if field == nil {
|
2009-04-14 01:06:49 -06:00
|
|
|
if st.parent == nil {
|
2009-09-01 17:31:49 -06:00
|
|
|
t.execError(st, t.linenum, "name not found: %s", name)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-09-01 17:31: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-20 19:51:13 -06:00
|
|
|
func (t *Template) writeVariable(v *variableElement, st *state) {
|
|
|
|
formatter := v.formatter;
|
2009-09-01 17:31:49 -06:00
|
|
|
val := t.varValue(v.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 {
|
2009-09-14 22:03:53 -06:00
|
|
|
if fn, ok := t.fmap[formatter]; ok {
|
|
|
|
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?
|
2009-09-14 22:03:53 -06:00
|
|
|
if fn, ok := builtins[formatter]; ok {
|
|
|
|
fn(st.wr, val, formatter);
|
2009-04-13 20:29:23 -06:00
|
|
|
return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-09-14 22:03:53 -06:00
|
|
|
t.execError(st, v.linenum, "missing formatter %s for variable %s", formatter, v.name)
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// Execute element i. Return next index to execute.
|
|
|
|
func (t *Template) executeElement(i int, st *state) int {
|
|
|
|
switch elem := t.elems.At(i).(type) {
|
|
|
|
case *textElement:
|
|
|
|
st.wr.Write(elem.text);
|
|
|
|
return i+1;
|
|
|
|
case *literalElement:
|
|
|
|
st.wr.Write(elem.text);
|
|
|
|
return i+1;
|
|
|
|
case *variableElement:
|
|
|
|
t.writeVariable(elem, st);
|
|
|
|
return i+1;
|
|
|
|
case *sectionElement:
|
|
|
|
t.executeSection(elem, st);
|
|
|
|
return elem.end;
|
|
|
|
case *repeatedElement:
|
|
|
|
t.executeRepeated(elem, st);
|
|
|
|
return elem.end;
|
|
|
|
}
|
|
|
|
e := t.elems.At(i);
|
2009-05-06 14:42:59 -06:00
|
|
|
t.execError(st, 0, "internal error: bad directive in execute: %v %T\n", reflect.NewValue(e).Interface(), e);
|
2009-04-20 19:51:13 -06:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the template.
|
|
|
|
func (t *Template) execute(start, end int, st *state) {
|
|
|
|
for i := start; i < end; {
|
|
|
|
i = t.executeElement(i, st)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute a .section
|
|
|
|
func (t *Template) executeSection(s *sectionElement, st *state) {
|
|
|
|
// Find driver data for this section. It must be in the current struct.
|
2009-09-01 17:31:49 -06:00
|
|
|
field := t.varValue(s.field, st);
|
2009-04-20 19:51:13 -06:00
|
|
|
if field == nil {
|
2009-05-06 14:42:59 -06:00
|
|
|
t.execError(st, s.linenum, ".section: cannot find field %s in %s", s.field, reflect.Indirect(st.data).Type());
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
2009-05-06 14:42:59 -06:00
|
|
|
st = st.clone(field);
|
2009-04-20 19:51:13 -06:00
|
|
|
start, end := s.start, s.or;
|
2009-07-31 13:42:29 -06:00
|
|
|
if !empty(field) {
|
2009-04-20 19:51:13 -06:00
|
|
|
// Execute the normal block.
|
|
|
|
if end < 0 {
|
|
|
|
end = s.end
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
} else {
|
|
|
|
// Execute the .or block. If it's missing, do nothing.
|
|
|
|
start, end = s.or, s.end;
|
|
|
|
if start < 0 {
|
|
|
|
return
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
for i := start; i < end; {
|
|
|
|
i = t.executeElement(i, st)
|
|
|
|
}
|
2009-04-08 23:08:55 -06:00
|
|
|
}
|
|
|
|
|
2009-08-26 19:30:13 -06:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
// 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.
|
2009-09-01 17:31:49 -06:00
|
|
|
field := t.varValue(r.field, st);
|
2009-04-20 19:51:13 -06:00
|
|
|
if field == nil {
|
2009-05-06 14:42:59 -06:00
|
|
|
t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, reflect.Indirect(st.data).Type());
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
start, end := r.start, r.or;
|
|
|
|
if end < 0 {
|
|
|
|
end = r.end
|
|
|
|
}
|
2009-04-27 22:04:46 -06:00
|
|
|
if r.altstart >= 0 {
|
|
|
|
end = r.altstart
|
|
|
|
}
|
2009-08-26 19:30:13 -06:00
|
|
|
first := true;
|
|
|
|
|
|
|
|
if array, ok := field.(reflect.ArrayOrSliceValue); ok {
|
2009-04-20 19:51:13 -06:00
|
|
|
for j := 0; j < array.Len(); j++ {
|
2009-05-06 14:42:59 -06:00
|
|
|
newst := st.clone(array.Elem(j));
|
2009-08-26 19:30:13 -06:00
|
|
|
|
|
|
|
// .alternates between elements
|
|
|
|
if !first && r.altstart >= 0 {
|
2009-08-29 22:13:32 -06:00
|
|
|
for i := r.altstart; i < r.altend; {
|
2009-08-26 19:30:13 -06:00
|
|
|
i = t.executeElement(i, newst)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
|
2009-04-20 19:51:13 -06:00
|
|
|
for i := start; i < end; {
|
|
|
|
i = t.executeElement(i, newst)
|
|
|
|
}
|
2009-08-26 19:30:13 -06:00
|
|
|
}
|
|
|
|
} 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 {
|
2009-08-29 22:13:32 -06:00
|
|
|
for i := r.altstart; i < r.altend; {
|
2009-04-27 22:04:46 -06:00
|
|
|
i = t.executeElement(i, newst)
|
|
|
|
}
|
|
|
|
}
|
2009-08-26 19:30:13 -06:00
|
|
|
first = false;
|
|
|
|
|
|
|
|
for i := start; i < end; {
|
|
|
|
i = t.executeElement(i, newst)
|
|
|
|
}
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
2009-08-26 19:30:13 -06:00
|
|
|
} 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
|
2009-04-20 19:51:13 -06:00
|
|
|
}
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
|
|
|
|
2009-04-17 01:08:24 -06:00
|
|
|
// A valid delimiter must contain no white space and be non-empty.
|
2009-04-16 18:44:23 -06:00
|
|
|
func validDelim(d []byte) bool {
|
|
|
|
if len(d) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
2009-09-14 22:03:53 -06:00
|
|
|
for _, c := range d {
|
2009-04-16 18:44:23 -06:00
|
|
|
if white(c) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-04-22 01:53:35 -06:00
|
|
|
// -- Public interface
|
2009-04-20 19:51:13 -06:00
|
|
|
|
2009-04-17 01:08:24 -06:00
|
|
|
// Parse initializes a Template by parsing its definition. The string
|
|
|
|
// s contains the template text. If any errors occur, Parse returns
|
|
|
|
// the error.
|
2009-04-29 19:20:09 -06:00
|
|
|
func (t *Template) Parse(s string) os.Error {
|
2009-04-16 18:44:23 -06:00
|
|
|
if !validDelim(t.ldelim) || !validDelim(t.rdelim) {
|
2009-05-13 11:01:55 -06:00
|
|
|
return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)}
|
2009-04-14 23:35:18 -06:00
|
|
|
}
|
2009-06-29 16:24:23 -06:00
|
|
|
t.buf = strings.Bytes(s);
|
2009-04-20 19:51:13 -06:00
|
|
|
t.p = 0;
|
2009-11-02 21:35:52 -07:00
|
|
|
t.linenum = 1;
|
2009-11-02 14:09:31 -07:00
|
|
|
t.parse();
|
|
|
|
return t.error;
|
2009-04-14 01:06:49 -06:00
|
|
|
}
|
|
|
|
|
2009-04-22 01:53:35 -06:00
|
|
|
// Execute applies a parsed template to the specified data object,
|
2009-04-14 23:35:18 -06:00
|
|
|
// generating output to wr.
|
2009-05-08 12:22:57 -06:00
|
|
|
func (t *Template) Execute(data interface{}, wr io.Writer) os.Error {
|
2009-04-14 02:12:20 -06:00
|
|
|
// Extract the driver data.
|
|
|
|
val := reflect.NewValue(data);
|
2009-05-06 14:42:59 -06:00
|
|
|
errors := make(chan os.Error);
|
2009-04-08 23:08:55 -06:00
|
|
|
go func() {
|
2009-04-14 22:25:33 -06:00
|
|
|
t.p = 0;
|
2009-05-06 14:42:59 -06:00
|
|
|
t.execute(0, t.elems.Len(), &state{nil, val, wr, errors});
|
|
|
|
errors <- nil; // clean return;
|
2009-04-08 23:08:55 -06:00
|
|
|
}();
|
2009-05-06 14:42:59 -06:00
|
|
|
return <-errors;
|
2009-04-14 23:35:18 -06:00
|
|
|
}
|
|
|
|
|
2009-04-17 01:08:24 -06:00
|
|
|
// SetDelims sets the left and right delimiters for operations in the
|
|
|
|
// template. They are validated during parsing. They could be
|
|
|
|
// validated here but it's better to keep the routine simple. The
|
|
|
|
// delimiters are very rarely invalid and Parse has the necessary
|
|
|
|
// error-handling interface already.
|
2009-04-14 23:35:18 -06:00
|
|
|
func (t *Template) SetDelims(left, right string) {
|
2009-06-29 16:24:23 -06:00
|
|
|
t.ldelim = strings.Bytes(left);
|
|
|
|
t.rdelim = strings.Bytes(right);
|
2009-04-14 23:35:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse creates a Template with default parameters (such as {} for
|
2009-04-17 01:08:24 -06:00
|
|
|
// metacharacters). The string s contains the template text while
|
|
|
|
// the formatter map fmap, which may be nil, defines auxiliary functions
|
2009-04-16 18:44:23 -06:00
|
|
|
// for formatting variables. The template is returned. If any errors
|
2009-04-17 01:08:24 -06:00
|
|
|
// occur, err will be non-nil.
|
|
|
|
func Parse(s string, fmap FormatterMap) (t *Template, err os.Error) {
|
2009-04-16 18:44:23 -06:00
|
|
|
t = New(fmap);
|
2009-04-17 01:08:24 -06:00
|
|
|
err = t.Parse(s);
|
2009-11-02 14:09:31 -07:00
|
|
|
if err != nil {
|
|
|
|
t = nil
|
|
|
|
}
|
2009-04-16 18:44:23 -06:00
|
|
|
return
|
2009-04-14 23:35:18 -06:00
|
|
|
}
|
2009-11-02 14:09:31 -07:00
|
|
|
|
|
|
|
// MustParse is like Parse but panics if the template cannot be parsed.
|
|
|
|
func MustParse(s string, fmap FormatterMap) *Template {
|
|
|
|
t , err := Parse(s, fmap);
|
|
|
|
if err != nil {
|
2009-11-04 16:13:16 -07:00
|
|
|
panic("template parse error: ", err.String());
|
2009-11-02 14:09:31 -07:00
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|