mirror of https://github.com/golang/go.git
configurable delimiters.
R=rsc DELTA=139 (90 added, 7 deleted, 42 changed) OCL=27475 CL=27477
This commit is contained in:
parent
cf8b9ce580
commit
1cb1251436
|
|
@ -15,9 +15,8 @@ import (
|
|||
"template";
|
||||
)
|
||||
|
||||
var ErrLBrace = os.NewError("unexpected opening brace")
|
||||
var ErrUnmatchedRBrace = os.NewError("unmatched closing brace")
|
||||
var ErrUnmatchedLBrace = os.NewError("unmatched opening brace")
|
||||
var ErrUnmatchedRDelim = os.NewError("unmatched closing delimiter")
|
||||
var ErrUnmatchedLDelim = os.NewError("unmatched opening delimiter")
|
||||
var ErrBadDirective = os.NewError("unrecognized directive name")
|
||||
var ErrEmptyDirective = os.NewError("empty directive")
|
||||
var ErrFields = os.NewError("incorrect fields for directive")
|
||||
|
|
@ -27,13 +26,14 @@ 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")
|
||||
var ErrNoFormatter = os.NewError("unknown formatter")
|
||||
var ErrEmptyDelims = os.NewError("empty delimiter strings")
|
||||
|
||||
// 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
|
||||
// The various types of "tokens", which are plain text or (usually) brace-delimited descriptors
|
||||
const (
|
||||
Alternates = iota;
|
||||
Comment;
|
||||
|
|
@ -73,24 +73,25 @@ func (st *state) error(err *os.Error, args ...) {
|
|||
|
||||
type Template struct {
|
||||
fmap FormatterMap; // formatters for variables
|
||||
ldelim, rdelim []byte; // delimiters; default {}
|
||||
buf []byte; // input text to process
|
||||
p int; // position in buf
|
||||
linenum *int; // position in input
|
||||
}
|
||||
|
||||
// Create a top-level template
|
||||
func newTemplate(buf []byte, fmap FormatterMap) *Template {
|
||||
t := new(Template);
|
||||
// Initialize a top-level template in prepratation for parsing.
|
||||
// The formatter map and delimiters are already set.
|
||||
func (t *Template) init(buf []byte) *Template {
|
||||
t.buf = buf;
|
||||
t.p = 0;
|
||||
t.fmap = fmap;
|
||||
t.linenum = new(int);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Create a template deriving from its parent
|
||||
func childTemplate(parent *Template, buf []byte) *Template {
|
||||
t := new(Template);
|
||||
t.ldelim = parent.ldelim;
|
||||
t.rdelim = parent.rdelim;
|
||||
t.buf = buf;
|
||||
t.p = 0;
|
||||
t.fmap = parent.fmap;
|
||||
|
|
@ -102,17 +103,31 @@ func white(c uint8) bool {
|
|||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (t *Template) execute(st *state)
|
||||
func (t *Template) executeSection(w []string, st *state)
|
||||
|
||||
// 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
|
||||
// item is empty, we are at EOF. The item will be either a
|
||||
// delimited string or a non-empty string between 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(st *state) []byte {
|
||||
brace := false; // are we waiting for an opening brace?
|
||||
sawLeft := false; // are we waiting for an opening delimiter?
|
||||
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';
|
||||
|
|
@ -121,44 +136,43 @@ func (t *Template) nextItem(st *state) []byte {
|
|||
start := t.p;
|
||||
Loop:
|
||||
for i = t.p; i < len(t.buf); i++ {
|
||||
switch t.buf[i] {
|
||||
case '\n':
|
||||
switch {
|
||||
case t.buf[i] == '\n':
|
||||
*t.linenum++;
|
||||
i++;
|
||||
break Loop;
|
||||
case ' ', '\t', '\r':
|
||||
case white(t.buf[i]):
|
||||
// white space, do nothing
|
||||
case '{':
|
||||
if brace {
|
||||
st.error(ErrLBrace)
|
||||
}
|
||||
case !sawLeft && equal(t.buf, i, t.ldelim): // sawLeft checked because delims may be equal
|
||||
// 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] == '#') {
|
||||
j := i + len(t.ldelim); // position after delimiter
|
||||
if j+1 < len(t.buf) && (t.buf[j] == '.' || t.buf[j] == '#') {
|
||||
special = true;
|
||||
if trim_white && only_white {
|
||||
start = i;
|
||||
}
|
||||
} else if i > t.p { // have some text accumulated so stop before '{'
|
||||
} else if i > t.p { // have some text accumulated so stop before delimiter
|
||||
break Loop;
|
||||
}
|
||||
brace = true;
|
||||
case '}':
|
||||
if !brace {
|
||||
st.error(ErrUnmatchedRBrace)
|
||||
sawLeft = true;
|
||||
i = j - 1;
|
||||
case equal(t.buf, i, t.rdelim):
|
||||
if !sawLeft {
|
||||
st.error(ErrUnmatchedRDelim)
|
||||
}
|
||||
brace = false;
|
||||
i++;
|
||||
sawLeft = false;
|
||||
i += len(t.rdelim);
|
||||
break Loop;
|
||||
default:
|
||||
only_white = false;
|
||||
}
|
||||
}
|
||||
if brace {
|
||||
st.error(ErrUnmatchedLBrace)
|
||||
if sawLeft {
|
||||
st.error(ErrUnmatchedLDelim)
|
||||
}
|
||||
item := t.buf[start:i];
|
||||
if special && trim_white {
|
||||
|
|
@ -207,23 +221,23 @@ func words(buf []byte) []string {
|
|||
// its constituent words.
|
||||
func (t *Template) analyze(item []byte, st *state) (tok int, w []string) {
|
||||
// item is known to be non-empty
|
||||
if item[0] != '{' {
|
||||
if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter
|
||||
tok = Text;
|
||||
return
|
||||
}
|
||||
if item[len(item)-1] != '}' {
|
||||
st.error(ErrUnmatchedLBrace) // should not happen anyway
|
||||
if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
|
||||
st.error(ErrUnmatchedLDelim) // should not happen anyway
|
||||
}
|
||||
if len(item) <= 2 {
|
||||
if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
|
||||
st.error(ErrEmptyDirective)
|
||||
}
|
||||
// Comment
|
||||
if item[1] == '#' {
|
||||
if item[len(t.ldelim)] == '#' {
|
||||
tok = Comment;
|
||||
return
|
||||
}
|
||||
// Split into words
|
||||
w = words(item[1: len(item)-1]); // drop final brace
|
||||
w = words(item[len(t.ldelim): len(item)-len(t.rdelim)]); // drop final delimiter
|
||||
if len(w) == 0 {
|
||||
st.error(ErrBadDirective)
|
||||
}
|
||||
|
|
@ -469,9 +483,9 @@ func (t *Template) execute(st *state) {
|
|||
case Literal:
|
||||
switch w[0] {
|
||||
case ".meta-left":
|
||||
st.wr.Write(lbrace);
|
||||
st.wr.Write(t.ldelim);
|
||||
case ".meta-right":
|
||||
st.wr.Write(rbrace);
|
||||
st.wr.Write(t.rdelim);
|
||||
case ".space":
|
||||
st.wr.Write(space);
|
||||
default:
|
||||
|
|
@ -491,24 +505,32 @@ func (t *Template) execute(st *state) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *Template) parse() {
|
||||
func (t *Template) doParse() {
|
||||
// stub for now
|
||||
}
|
||||
|
||||
func Parse(s string, fmap FormatterMap) (*Template, *os.Error, int) {
|
||||
// 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));
|
||||
ch := make(chan *os.Error);
|
||||
t := newTemplate(io.StringBytes(s), fmap);
|
||||
go func() {
|
||||
t.parse();
|
||||
t.doParse();
|
||||
ch <- nil; // clean return;
|
||||
}();
|
||||
err := <-ch;
|
||||
if err != nil {
|
||||
return nil, err, *t.linenum
|
||||
return err, *t.linenum
|
||||
}
|
||||
return t, nil, 0
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// Execute executes a parsed template on the specified data object,
|
||||
// generating output to wr.
|
||||
func (t *Template) Execute(data interface{}, wr io.Write) *os.Error {
|
||||
// Extract the driver data.
|
||||
val := reflect.NewValue(data);
|
||||
|
|
@ -520,3 +542,30 @@ func (t *Template) Execute(data interface{}, wr io.Write) *os.Error {
|
|||
}();
|
||||
return <-ch;
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ func TestStringDriverType(t *testing.T) {
|
|||
var b io.ByteBuffer;
|
||||
err = tmpl.Execute("hello", &b);
|
||||
if err != nil {
|
||||
t.Error("unexpected parse error:", err)
|
||||
t.Error("unexpected execute error:", err)
|
||||
}
|
||||
s := string(b.Data());
|
||||
if s != "template: hello" {
|
||||
|
|
@ -233,3 +233,37 @@ func TestTwice(t *testing.T) {
|
|||
t.Errorf("failed passing string as data: expected %q got %q", text, s);
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDelims(t *testing.T) {
|
||||
// try various lengths. zero should catch error.
|
||||
for i := 0; i < 7; i++ {
|
||||
for j := 0; j < 7; j++ {
|
||||
tmpl := New(nil);
|
||||
// first two chars deliberately the same to test equal left and right delims
|
||||
ldelim := "$!#$%^&"[0:i];
|
||||
rdelim := "$*&^%$!"[0:j];
|
||||
tmpl.SetDelims(ldelim, rdelim);
|
||||
// if braces, this would be template: {@}{.meta-left}{.meta-right}
|
||||
text := "template: " +
|
||||
ldelim + "@" + rdelim +
|
||||
ldelim + ".meta-left" + rdelim +
|
||||
ldelim + ".meta-right" + rdelim;
|
||||
err, line := tmpl.Parse(text);
|
||||
if err != nil {
|
||||
if i == 0 || j == 0 { // expected
|
||||
continue
|
||||
}
|
||||
t.Error("unexpected parse error:", err)
|
||||
} else if i == 0 || j == 0 {
|
||||
t.Errorf("expected parse error for empty delimiter: %d %d %q %q", i, j, ldelim, rdelim);
|
||||
continue;
|
||||
}
|
||||
var b io.ByteBuffer;
|
||||
err = tmpl.Execute("hello", &b);
|
||||
s := string(b.Data());
|
||||
if s != "template: hello" + ldelim + rdelim {
|
||||
t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue