blob: 5e73bc9f45beca822dac7b4a79bc0c98306d05cf [file] [log] [blame]
// 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 (
"bytes";
"container/vector";
"fmt";
"io";
"testing";
)
type Test struct {
in, out, err string
}
type T struct {
item string;
value string;
}
type S struct {
header string;
integer int;
raw string;
innerT T;
innerPointerT *T;
data []T;
pdata []*T;
empty []*T;
emptystring string;
null []*T;
vec *vector.Vector;
true bool;
false bool;
}
var t1 = T{ "ItemNumber1", "ValueNumber1" }
var t2 = T{ "ItemNumber2", "ValueNumber2" }
func uppercase(v interface{}) string {
s := v.(string);
t := "";
for i := 0; i < len(s); i++ {
c := s[i];
if 'a' <= c && c <= 'z' {
c = c + 'A' - 'a'
}
t += string(c);
}
return t;
}
func plus1(v interface{}) string {
i := v.(int);
return fmt.Sprint(i + 1);
}
func writer(f func(interface{}) string) (func(io.Writer, interface{}, string)) {
return func(w io.Writer, v interface{}, format string) {
io.WriteString(w, f(v));
}
}
var formatters = FormatterMap {
"uppercase" : writer(uppercase),
"+1" : writer(plus1),
}
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", "", "" },
// Variables at top level
&Test{
in: "{header}={integer}\n",
out: "Header=77\n"
},
// Section
&Test{
in: "{.section data }\n"
"some text for the section\n"
"{.end}\n",
out: "some text for the section\n"
},
&Test{
in: "{.section data }\n"
"{header}={integer}\n"
"{.end}\n",
out: "Header=77\n"
},
&Test{
in: "{.section pdata }\n"
"{header}={integer}\n"
"{.end}\n",
out: "Header=77\n"
},
&Test{
in: "{.section pdata }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
out: "data present\n"
},
&Test{
in: "{.section empty }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
out: "data not present\n"
},
&Test{
in: "{.section null }\n"
"data present\n"
"{.or}\n"
"data not present\n"
"{.end}\n",
out: "data not present\n"
},
&Test{
in: "{.section pdata }\n"
"{header}={integer}\n"
"{.section @ }\n"
"{header}={integer}\n"
"{.end}\n"
"{.end}\n",
out: "Header=77\n"
"Header=77\n"
},
&Test{
in: "{.section data}{.end} {header}\n",
out: " Header\n"
},
// Repeated
&Test{
in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.end}\n"
"{.end}\n",
out: "ItemNumber1=ValueNumber1\n"
"ItemNumber2=ValueNumber2\n"
},
&Test{
in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.or}\n"
"this should not appear\n"
"{.end}\n"
"{.end}\n",
out: "ItemNumber1=ValueNumber1\n"
"ItemNumber2=ValueNumber2\n"
},
&Test{
in: "{.section @ }\n"
"{.repeated section empty }\n"
"{item}={value}\n"
"{.or}\n"
"this should appear: empty field\n"
"{.end}\n"
"{.end}\n",
out: "this should appear: empty field\n"
},
&Test{
in: "{.repeated section pdata }\n"
"{item}\n"
"{.alternates with}\n"
"is\nover\nmultiple\nlines\n"
"{.end}\n",
out: "ItemNumber1\n"
"is\nover\nmultiple\nlines\n"
"ItemNumber2\n"
},
&Test{
in: "{.section pdata }\n"
"{.repeated section @ }\n"
"{item}={value}\n"
"{.alternates with}DIVIDER\n"
"{.or}\n"
"this should not appear\n"
"{.end}\n"
"{.end}\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{
in: "{.section @ }\n"
"{innerT.item}={innerT.value}\n"
"{.end}",
out: "ItemNumber1=ValueNumber1\n"
},
&Test{
in: "{.section @ }\n"
"{innerT.item}={.section innerT}{.section value}{@}{.end}{.end}\n"
"{.end}",
out: "ItemNumber1=ValueNumber1\n"
},
// Formatters
&Test{
in: "{.section pdata }\n"
"{header|uppercase}={integer|+1}\n"
"{header|html}={integer|str}\n"
"{.end}\n",
out: "HEADER=78\n"
"Header=77\n"
},
&Test{
in: "{raw}\n"
"{raw|html}\n",
out: "&<>!@ #$%^\n"
"&amp;&lt;&gt;!@ #$%^\n"
},
&Test{
in: "{.section emptystring}emptystring{.end}\n"
"{.section header}header{.end}\n",
out: "\nheader\n"
},
&Test {
in: "{.section true}1{.or}2{.end}\n"
"{.section false}3{.or}4{.end}\n",
out: "1\n4\n"
},
}
func TestAll(t *testing.T) {
s := new(S);
// initialized by hand for clarity.
s.header = "Header";
s.integer = 77;
s.raw = "&<>!@ #$%^";
s.innerT = t1;
s.data = []T{ t1, t2 };
s.pdata = []*T{ &t1, &t2 };
s.empty = []*T{ };
s.null = nil;
s.vec = vector.New(0);
s.vec.Push("elt1");
s.vec.Push("elt2");
s.true = true;
s.false = false;
var buf bytes.Buffer;
for _, test := range tests {
buf.Reset();
tmpl, err := Parse(test.in, formatters);
if err != nil {
t.Error("unexpected parse error:", err);
continue;
}
err = tmpl.Execute(s, &buf);
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 buf.String() != test.out {
t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String());
}
}
}
func TestStringDriverType(t *testing.T) {
tmpl, err := Parse("template: {@}", nil);
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer;
err = tmpl.Execute("hello", &b);
if err != nil {
t.Error("unexpected execute error:", err)
}
s := b.String();
if s != "template: hello" {
t.Errorf("failed passing string as data: expected %q got %q", "template: hello", s)
}
}
func TestTwice(t *testing.T) {
tmpl, err := Parse("template: {@}", nil);
if err != nil {
t.Error("unexpected parse error:", err)
}
var b bytes.Buffer;
err = tmpl.Execute("hello", &b);
if err != nil {
t.Error("unexpected parse error:", err)
}
s := b.String();
text := "template: hello";
if s != text {
t.Errorf("failed passing string as data: expected %q got %q", text, s);
}
err = tmpl.Execute("hello", &b);
if err != nil {
t.Error("unexpected parse error:", err)
}
s = b.String();
text += text;
if s != text {
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 := 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 bytes.Buffer;
err = tmpl.Execute("hello", &b);
s := b.String();
if s != "template: hello" + ldelim + rdelim {
t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s)
}
}
}
}
// Test that a variable evaluates to the field itself and does not further indirection
func TestVarIndirection(t *testing.T) {
s := new(S);
// initialized by hand for clarity.
s.innerPointerT = &t1;
var buf bytes.Buffer;
input := "{.section @}{innerPointerT}{.end}";
tmpl, err := Parse(input, nil);
if err != nil {
t.Fatal("unexpected parse error:", err);
}
err = tmpl.Execute(s, &buf);
if err != nil {
t.Fatal("unexpected execute error:", err)
}
expect := fmt.Sprintf("%v", &t1); // output should be hex address of t1
if buf.String() != expect {
t.Errorf("for %q: expected %q got %q", input, expect, buf.String());
}
}