mirror of
https://github.com/golang/go
synced 2024-11-21 14:14:40 -07:00
old/regexp, old/template: delete.
Both of these have replacements. R=golang-dev, r, rsc, r, adg CC=golang-dev https://golang.org/cl/5979046
This commit is contained in:
parent
0bad08cbbb
commit
9ce770afad
@ -618,8 +618,6 @@ The packages in their new locations are:
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>old/netchan</code></li>
|
<li><code>old/netchan</code></li>
|
||||||
<li><code>old/regexp</code></li>
|
|
||||||
<li><code>old/template</code></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -639,6 +637,8 @@ Go 1 deletes several packages outright:
|
|||||||
<li><code>container/vector</code></li>
|
<li><code>container/vector</code></li>
|
||||||
<li><code>exp/datafmt</code></li>
|
<li><code>exp/datafmt</code></li>
|
||||||
<li><code>go/typechecker</code></li>
|
<li><code>go/typechecker</code></li>
|
||||||
|
<li><code>old/regexp</code></li>
|
||||||
|
<li><code>old/template</code></li>
|
||||||
<li><code>try</code></li>
|
<li><code>try</code></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -1,421 +0,0 @@
|
|||||||
// 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 regexp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var good_re = []string{
|
|
||||||
``,
|
|
||||||
`.`,
|
|
||||||
`^.$`,
|
|
||||||
`a`,
|
|
||||||
`a*`,
|
|
||||||
`a+`,
|
|
||||||
`a?`,
|
|
||||||
`a|b`,
|
|
||||||
`a*|b*`,
|
|
||||||
`(a*|b)(c*|d)`,
|
|
||||||
`[a-z]`,
|
|
||||||
`[a-abc-c\-\]\[]`,
|
|
||||||
`[a-z]+`,
|
|
||||||
`[]`,
|
|
||||||
`[abc]`,
|
|
||||||
`[^1234]`,
|
|
||||||
`[^\n]`,
|
|
||||||
`\!\\`,
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringError struct {
|
|
||||||
re string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var bad_re = []stringError{
|
|
||||||
{`*`, ErrBareClosure},
|
|
||||||
{`+`, ErrBareClosure},
|
|
||||||
{`?`, ErrBareClosure},
|
|
||||||
{`(abc`, ErrUnmatchedLpar},
|
|
||||||
{`abc)`, ErrUnmatchedRpar},
|
|
||||||
{`x[a-z`, ErrUnmatchedLbkt},
|
|
||||||
{`abc]`, ErrUnmatchedRbkt},
|
|
||||||
{`[z-a]`, ErrBadRange},
|
|
||||||
{`abc\`, ErrExtraneousBackslash},
|
|
||||||
{`a**`, ErrBadClosure},
|
|
||||||
{`a*+`, ErrBadClosure},
|
|
||||||
{`a??`, ErrBadClosure},
|
|
||||||
{`\x`, ErrBadBackslash},
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileTest(t *testing.T, expr string, error error) *Regexp {
|
|
||||||
re, err := Compile(expr)
|
|
||||||
if err != error {
|
|
||||||
t.Error("compiling `", expr, "`; unexpected error: ", err.Error())
|
|
||||||
}
|
|
||||||
return re
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGoodCompile(t *testing.T) {
|
|
||||||
for i := 0; i < len(good_re); i++ {
|
|
||||||
compileTest(t, good_re[i], nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadCompile(t *testing.T) {
|
|
||||||
for i := 0; i < len(bad_re); i++ {
|
|
||||||
compileTest(t, bad_re[i].re, bad_re[i].err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTest(t *testing.T, test *FindTest) {
|
|
||||||
re := compileTest(t, test.pat, nil)
|
|
||||||
if re == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m := re.MatchString(test.text)
|
|
||||||
if m != (len(test.matches) > 0) {
|
|
||||||
t.Errorf("MatchString failure on %s: %t should be %t", test, m, len(test.matches) > 0)
|
|
||||||
}
|
|
||||||
// now try bytes
|
|
||||||
m = re.Match([]byte(test.text))
|
|
||||||
if m != (len(test.matches) > 0) {
|
|
||||||
t.Errorf("Match failure on %s: %t should be %t", test, m, len(test.matches) > 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
matchTest(t, &test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchFunctionTest(t *testing.T, test *FindTest) {
|
|
||||||
m, err := MatchString(test.pat, test.text)
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m != (len(test.matches) > 0) {
|
|
||||||
t.Errorf("Match failure on %s: %t should be %t", test, m, len(test.matches) > 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatchFunction(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
matchFunctionTest(t, &test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplaceTest struct {
|
|
||||||
pattern, replacement, input, output string
|
|
||||||
}
|
|
||||||
|
|
||||||
var replaceTests = []ReplaceTest{
|
|
||||||
// Test empty input and/or replacement, with pattern that matches the empty string.
|
|
||||||
{"", "", "", ""},
|
|
||||||
{"", "x", "", "x"},
|
|
||||||
{"", "", "abc", "abc"},
|
|
||||||
{"", "x", "abc", "xaxbxcx"},
|
|
||||||
|
|
||||||
// Test empty input and/or replacement, with pattern that does not match the empty string.
|
|
||||||
{"b", "", "", ""},
|
|
||||||
{"b", "x", "", ""},
|
|
||||||
{"b", "", "abc", "ac"},
|
|
||||||
{"b", "x", "abc", "axc"},
|
|
||||||
{"y", "", "", ""},
|
|
||||||
{"y", "x", "", ""},
|
|
||||||
{"y", "", "abc", "abc"},
|
|
||||||
{"y", "x", "abc", "abc"},
|
|
||||||
|
|
||||||
// Multibyte characters -- verify that we don't try to match in the middle
|
|
||||||
// of a character.
|
|
||||||
{"[a-c]*", "x", "\u65e5", "x\u65e5x"},
|
|
||||||
{"[^\u65e5]", "x", "abc\u65e5def", "xxx\u65e5xxx"},
|
|
||||||
|
|
||||||
// Start and end of a string.
|
|
||||||
{"^[a-c]*", "x", "abcdabc", "xdabc"},
|
|
||||||
{"[a-c]*$", "x", "abcdabc", "abcdx"},
|
|
||||||
{"^[a-c]*$", "x", "abcdabc", "abcdabc"},
|
|
||||||
{"^[a-c]*", "x", "abc", "x"},
|
|
||||||
{"[a-c]*$", "x", "abc", "x"},
|
|
||||||
{"^[a-c]*$", "x", "abc", "x"},
|
|
||||||
{"^[a-c]*", "x", "dabce", "xdabce"},
|
|
||||||
{"[a-c]*$", "x", "dabce", "dabcex"},
|
|
||||||
{"^[a-c]*$", "x", "dabce", "dabce"},
|
|
||||||
{"^[a-c]*", "x", "", "x"},
|
|
||||||
{"[a-c]*$", "x", "", "x"},
|
|
||||||
{"^[a-c]*$", "x", "", "x"},
|
|
||||||
|
|
||||||
{"^[a-c]+", "x", "abcdabc", "xdabc"},
|
|
||||||
{"[a-c]+$", "x", "abcdabc", "abcdx"},
|
|
||||||
{"^[a-c]+$", "x", "abcdabc", "abcdabc"},
|
|
||||||
{"^[a-c]+", "x", "abc", "x"},
|
|
||||||
{"[a-c]+$", "x", "abc", "x"},
|
|
||||||
{"^[a-c]+$", "x", "abc", "x"},
|
|
||||||
{"^[a-c]+", "x", "dabce", "dabce"},
|
|
||||||
{"[a-c]+$", "x", "dabce", "dabce"},
|
|
||||||
{"^[a-c]+$", "x", "dabce", "dabce"},
|
|
||||||
{"^[a-c]+", "x", "", ""},
|
|
||||||
{"[a-c]+$", "x", "", ""},
|
|
||||||
{"^[a-c]+$", "x", "", ""},
|
|
||||||
|
|
||||||
// Other cases.
|
|
||||||
{"abc", "def", "abcdefg", "defdefg"},
|
|
||||||
{"bc", "BC", "abcbcdcdedef", "aBCBCdcdedef"},
|
|
||||||
{"abc", "", "abcdabc", "d"},
|
|
||||||
{"x", "xXx", "xxxXxxx", "xXxxXxxXxXxXxxXxxXx"},
|
|
||||||
{"abc", "d", "", ""},
|
|
||||||
{"abc", "d", "abc", "d"},
|
|
||||||
{".+", "x", "abc", "x"},
|
|
||||||
{"[a-c]*", "x", "def", "xdxexfx"},
|
|
||||||
{"[a-c]+", "x", "abcbcdcdedef", "xdxdedef"},
|
|
||||||
{"[a-c]*", "x", "abcbcdcdedef", "xdxdxexdxexfx"},
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplaceFuncTest struct {
|
|
||||||
pattern string
|
|
||||||
replacement func(string) string
|
|
||||||
input, output string
|
|
||||||
}
|
|
||||||
|
|
||||||
var replaceFuncTests = []ReplaceFuncTest{
|
|
||||||
{"[a-c]", func(s string) string { return "x" + s + "y" }, "defabcdef", "defxayxbyxcydef"},
|
|
||||||
{"[a-c]+", func(s string) string { return "x" + s + "y" }, "defabcdef", "defxabcydef"},
|
|
||||||
{"[a-c]*", func(s string) string { return "x" + s + "y" }, "defabcdef", "xydxyexyfxabcydxyexyfxy"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceAll(t *testing.T) {
|
|
||||||
for _, tc := range replaceTests {
|
|
||||||
re, err := Compile(tc.pattern)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error compiling %q: %v", tc.pattern, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
actual := re.ReplaceAllString(tc.input, tc.replacement)
|
|
||||||
if actual != tc.output {
|
|
||||||
t.Errorf("%q.Replace(%q,%q) = %q; want %q",
|
|
||||||
tc.pattern, tc.input, tc.replacement, actual, tc.output)
|
|
||||||
}
|
|
||||||
// now try bytes
|
|
||||||
actual = string(re.ReplaceAll([]byte(tc.input), []byte(tc.replacement)))
|
|
||||||
if actual != tc.output {
|
|
||||||
t.Errorf("%q.Replace(%q,%q) = %q; want %q",
|
|
||||||
tc.pattern, tc.input, tc.replacement, actual, tc.output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceAllFunc(t *testing.T) {
|
|
||||||
for _, tc := range replaceFuncTests {
|
|
||||||
re, err := Compile(tc.pattern)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error compiling %q: %v", tc.pattern, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
actual := re.ReplaceAllStringFunc(tc.input, tc.replacement)
|
|
||||||
if actual != tc.output {
|
|
||||||
t.Errorf("%q.ReplaceFunc(%q,%q) = %q; want %q",
|
|
||||||
tc.pattern, tc.input, tc.replacement, actual, tc.output)
|
|
||||||
}
|
|
||||||
// now try bytes
|
|
||||||
actual = string(re.ReplaceAllFunc([]byte(tc.input), func(s []byte) []byte { return []byte(tc.replacement(string(s))) }))
|
|
||||||
if actual != tc.output {
|
|
||||||
t.Errorf("%q.ReplaceFunc(%q,%q) = %q; want %q",
|
|
||||||
tc.pattern, tc.input, tc.replacement, actual, tc.output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetaTest struct {
|
|
||||||
pattern, output, literal string
|
|
||||||
isLiteral bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaTests = []MetaTest{
|
|
||||||
{``, ``, ``, true},
|
|
||||||
{`foo`, `foo`, `foo`, true},
|
|
||||||
{`foo\.\$`, `foo\\\.\\\$`, `foo.$`, true}, // has meta but no operator
|
|
||||||
{`foo.\$`, `foo\.\\\$`, `foo`, false}, // has escaped operators and real operators
|
|
||||||
{`!@#$%^&*()_+-=[{]}\|,<.>/?~`, `!@#\$%\^&\*\(\)_\+-=\[{\]}\\\|,<\.>/\?~`, `!@#`, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQuoteMeta(t *testing.T) {
|
|
||||||
for _, tc := range metaTests {
|
|
||||||
// Verify that QuoteMeta returns the expected string.
|
|
||||||
quoted := QuoteMeta(tc.pattern)
|
|
||||||
if quoted != tc.output {
|
|
||||||
t.Errorf("QuoteMeta(`%s`) = `%s`; want `%s`",
|
|
||||||
tc.pattern, quoted, tc.output)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the quoted string is in fact treated as expected
|
|
||||||
// by Compile -- i.e. that it matches the original, unquoted string.
|
|
||||||
if tc.pattern != "" {
|
|
||||||
re, err := Compile(quoted)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error compiling QuoteMeta(`%s`): %v", tc.pattern, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
src := "abc" + tc.pattern + "def"
|
|
||||||
repl := "xyz"
|
|
||||||
replaced := re.ReplaceAllString(src, repl)
|
|
||||||
expected := "abcxyzdef"
|
|
||||||
if replaced != expected {
|
|
||||||
t.Errorf("QuoteMeta(`%s`).Replace(`%s`,`%s`) = `%s`; want `%s`",
|
|
||||||
tc.pattern, src, repl, replaced, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLiteralPrefix(t *testing.T) {
|
|
||||||
for _, tc := range metaTests {
|
|
||||||
// Literal method needs to scan the pattern.
|
|
||||||
re := MustCompile(tc.pattern)
|
|
||||||
str, complete := re.LiteralPrefix()
|
|
||||||
if complete != tc.isLiteral {
|
|
||||||
t.Errorf("LiteralPrefix(`%s`) = %t; want %t", tc.pattern, complete, tc.isLiteral)
|
|
||||||
}
|
|
||||||
if str != tc.literal {
|
|
||||||
t.Errorf("LiteralPrefix(`%s`) = `%s`; want `%s`", tc.pattern, str, tc.literal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type numSubexpCase struct {
|
|
||||||
input string
|
|
||||||
expected int
|
|
||||||
}
|
|
||||||
|
|
||||||
var numSubexpCases = []numSubexpCase{
|
|
||||||
{``, 0},
|
|
||||||
{`.*`, 0},
|
|
||||||
{`abba`, 0},
|
|
||||||
{`ab(b)a`, 1},
|
|
||||||
{`ab(.*)a`, 1},
|
|
||||||
{`(.*)ab(.*)a`, 2},
|
|
||||||
{`(.*)(ab)(.*)a`, 3},
|
|
||||||
{`(.*)((a)b)(.*)a`, 4},
|
|
||||||
{`(.*)(\(ab)(.*)a`, 3},
|
|
||||||
{`(.*)(\(a\)b)(.*)a`, 3},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNumSubexp(t *testing.T) {
|
|
||||||
for _, c := range numSubexpCases {
|
|
||||||
re := MustCompile(c.input)
|
|
||||||
n := re.NumSubexp()
|
|
||||||
if n != c.expected {
|
|
||||||
t.Errorf("NumSubexp for %q returned %d, expected %d", c.input, n, c.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLiteral(b *testing.B) {
|
|
||||||
x := strings.Repeat("x", 50) + "y"
|
|
||||||
b.StopTimer()
|
|
||||||
re := MustCompile("y")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if !re.MatchString(x) {
|
|
||||||
b.Fatal("no match!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkNotLiteral(b *testing.B) {
|
|
||||||
x := strings.Repeat("x", 50) + "y"
|
|
||||||
b.StopTimer()
|
|
||||||
re := MustCompile(".y")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if !re.MatchString(x) {
|
|
||||||
b.Fatal("no match!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMatchClass(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
x := strings.Repeat("xxxx", 20) + "w"
|
|
||||||
re := MustCompile("[abcdw]")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if !re.MatchString(x) {
|
|
||||||
b.Fatal("no match!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMatchClass_InRange(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
// 'b' is between 'a' and 'c', so the charclass
|
|
||||||
// range checking is no help here.
|
|
||||||
x := strings.Repeat("bbbb", 20) + "c"
|
|
||||||
re := MustCompile("[ac]")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if !re.MatchString(x) {
|
|
||||||
b.Fatal("no match!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkReplaceAll(b *testing.B) {
|
|
||||||
x := "abcdefghijklmnopqrstuvwxyz"
|
|
||||||
b.StopTimer()
|
|
||||||
re := MustCompile("[cjrw]")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
re.ReplaceAllString(x, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAnchoredLiteralShortNonMatch(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
x := []byte("abcdefghijklmnopqrstuvwxyz")
|
|
||||||
re := MustCompile("^zbc(d|e)")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
re.Match(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAnchoredLiteralLongNonMatch(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
x := []byte("abcdefghijklmnopqrstuvwxyz")
|
|
||||||
for i := 0; i < 15; i++ {
|
|
||||||
x = append(x, x...)
|
|
||||||
}
|
|
||||||
re := MustCompile("^zbc(d|e)")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
re.Match(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAnchoredShortMatch(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
x := []byte("abcdefghijklmnopqrstuvwxyz")
|
|
||||||
re := MustCompile("^.bc(d|e)")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
re.Match(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAnchoredLongMatch(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
x := []byte("abcdefghijklmnopqrstuvwxyz")
|
|
||||||
for i := 0; i < 15; i++ {
|
|
||||||
x = append(x, x...)
|
|
||||||
}
|
|
||||||
re := MustCompile("^.bc(d|e)")
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
re.Match(x)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,472 +0,0 @@
|
|||||||
// Copyright 2010 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 regexp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For each pattern/text pair, what is the expected output of each function?
|
|
||||||
// We can derive the textual results from the indexed results, the non-submatch
|
|
||||||
// results from the submatched results, the single results from the 'all' results,
|
|
||||||
// and the byte results from the string results. Therefore the table includes
|
|
||||||
// only the FindAllStringSubmatchIndex result.
|
|
||||||
type FindTest struct {
|
|
||||||
pat string
|
|
||||||
text string
|
|
||||||
matches [][]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t FindTest) String() string {
|
|
||||||
return fmt.Sprintf("pat: %#q text: %#q", t.pat, t.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
var findTests = []FindTest{
|
|
||||||
{``, ``, build(1, 0, 0)},
|
|
||||||
{`^abcdefg`, "abcdefg", build(1, 0, 7)},
|
|
||||||
{`a+`, "baaab", build(1, 1, 4)},
|
|
||||||
{"abcd..", "abcdef", build(1, 0, 6)},
|
|
||||||
{`a`, "a", build(1, 0, 1)},
|
|
||||||
{`x`, "y", nil},
|
|
||||||
{`b`, "abc", build(1, 1, 2)},
|
|
||||||
{`.`, "a", build(1, 0, 1)},
|
|
||||||
{`.*`, "abcdef", build(1, 0, 6)},
|
|
||||||
{`^`, "abcde", build(1, 0, 0)},
|
|
||||||
{`$`, "abcde", build(1, 5, 5)},
|
|
||||||
{`^abcd$`, "abcd", build(1, 0, 4)},
|
|
||||||
{`^bcd'`, "abcdef", nil},
|
|
||||||
{`^abcd$`, "abcde", nil},
|
|
||||||
{`a+`, "baaab", build(1, 1, 4)},
|
|
||||||
{`a*`, "baaab", build(3, 0, 0, 1, 4, 5, 5)},
|
|
||||||
{`[a-z]+`, "abcd", build(1, 0, 4)},
|
|
||||||
{`[^a-z]+`, "ab1234cd", build(1, 2, 6)},
|
|
||||||
{`[a\-\]z]+`, "az]-bcz", build(2, 0, 4, 6, 7)},
|
|
||||||
{`[^\n]+`, "abcd\n", build(1, 0, 4)},
|
|
||||||
{`[日本語]+`, "日本語日本語", build(1, 0, 18)},
|
|
||||||
{`日本語+`, "日本語", build(1, 0, 9)},
|
|
||||||
{`日本語+`, "日本語語語語", build(1, 0, 18)},
|
|
||||||
{`()`, "", build(1, 0, 0, 0, 0)},
|
|
||||||
{`(a)`, "a", build(1, 0, 1, 0, 1)},
|
|
||||||
{`(.)(.)`, "日a", build(1, 0, 4, 0, 3, 3, 4)},
|
|
||||||
{`(.*)`, "", build(1, 0, 0, 0, 0)},
|
|
||||||
{`(.*)`, "abcd", build(1, 0, 4, 0, 4)},
|
|
||||||
{`(..)(..)`, "abcd", build(1, 0, 4, 0, 2, 2, 4)},
|
|
||||||
{`(([^xyz]*)(d))`, "abcd", build(1, 0, 4, 0, 4, 0, 3, 3, 4)},
|
|
||||||
{`((a|b|c)*(d))`, "abcd", build(1, 0, 4, 0, 4, 2, 3, 3, 4)},
|
|
||||||
{`(((a|b|c)*)(d))`, "abcd", build(1, 0, 4, 0, 4, 0, 3, 2, 3, 3, 4)},
|
|
||||||
{`\a\b\f\n\r\t\v`, "\a\b\f\n\r\t\v", build(1, 0, 7)},
|
|
||||||
{`[\a\b\f\n\r\t\v]+`, "\a\b\f\n\r\t\v", build(1, 0, 7)},
|
|
||||||
|
|
||||||
{`a*(|(b))c*`, "aacc", build(1, 0, 4, 2, 2, -1, -1)},
|
|
||||||
{`(.*).*`, "ab", build(1, 0, 2, 0, 2)},
|
|
||||||
{`[.]`, ".", build(1, 0, 1)},
|
|
||||||
{`/$`, "/abc/", build(1, 4, 5)},
|
|
||||||
{`/$`, "/abc", nil},
|
|
||||||
|
|
||||||
// multiple matches
|
|
||||||
{`.`, "abc", build(3, 0, 1, 1, 2, 2, 3)},
|
|
||||||
{`(.)`, "abc", build(3, 0, 1, 0, 1, 1, 2, 1, 2, 2, 3, 2, 3)},
|
|
||||||
{`.(.)`, "abcd", build(2, 0, 2, 1, 2, 2, 4, 3, 4)},
|
|
||||||
{`ab*`, "abbaab", build(3, 0, 3, 3, 4, 4, 6)},
|
|
||||||
{`a(b*)`, "abbaab", build(3, 0, 3, 1, 3, 3, 4, 4, 4, 4, 6, 5, 6)},
|
|
||||||
|
|
||||||
// fixed bugs
|
|
||||||
{`ab$`, "cab", build(1, 1, 3)},
|
|
||||||
{`axxb$`, "axxcb", nil},
|
|
||||||
{`data`, "daXY data", build(1, 5, 9)},
|
|
||||||
{`da(.)a$`, "daXY data", build(1, 5, 9, 7, 8)},
|
|
||||||
{`zx+`, "zzx", build(1, 1, 3)},
|
|
||||||
|
|
||||||
// can backslash-escape any punctuation
|
|
||||||
{`\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\{\|\}\~`,
|
|
||||||
`!"#$%&'()*+,-./:;<=>?@[\]^_{|}~`, build(1, 0, 31)},
|
|
||||||
{`[\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\{\|\}\~]+`,
|
|
||||||
`!"#$%&'()*+,-./:;<=>?@[\]^_{|}~`, build(1, 0, 31)},
|
|
||||||
{"\\`", "`", build(1, 0, 1)},
|
|
||||||
{"[\\`]+", "`", build(1, 0, 1)},
|
|
||||||
|
|
||||||
// long set of matches (longer than startSize)
|
|
||||||
{
|
|
||||||
".",
|
|
||||||
"qwertyuiopasdfghjklzxcvbnm1234567890",
|
|
||||||
build(36, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
|
|
||||||
10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
|
|
||||||
20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30,
|
|
||||||
30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// build is a helper to construct a [][]int by extracting n sequences from x.
|
|
||||||
// This represents n matches with len(x)/n submatches each.
|
|
||||||
func build(n int, x ...int) [][]int {
|
|
||||||
ret := make([][]int, n)
|
|
||||||
runLength := len(x) / n
|
|
||||||
j := 0
|
|
||||||
for i := range ret {
|
|
||||||
ret[i] = make([]int, runLength)
|
|
||||||
copy(ret[i], x[j:])
|
|
||||||
j += runLength
|
|
||||||
if j > len(x) {
|
|
||||||
panic("invalid build entry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// First the simple cases.
|
|
||||||
|
|
||||||
func TestFind(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
re := MustCompile(test.pat)
|
|
||||||
if re.String() != test.pat {
|
|
||||||
t.Errorf("String() = `%s`; should be `%s`", re.String(), test.pat)
|
|
||||||
}
|
|
||||||
result := re.Find([]byte(test.text))
|
|
||||||
switch {
|
|
||||||
case len(test.matches) == 0 && len(result) == 0:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
expect := test.text[test.matches[0][0]:test.matches[0][1]]
|
|
||||||
if expect != string(result) {
|
|
||||||
t.Errorf("expected %q got %q: %s", expect, result, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindString(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindString(test.text)
|
|
||||||
switch {
|
|
||||||
case len(test.matches) == 0 && len(result) == 0:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != "":
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == "":
|
|
||||||
// Tricky because an empty result has two meanings: no match or empty match.
|
|
||||||
if test.matches[0][0] != test.matches[0][1] {
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
}
|
|
||||||
case test.matches != nil && result != "":
|
|
||||||
expect := test.text[test.matches[0][0]:test.matches[0][1]]
|
|
||||||
if expect != result {
|
|
||||||
t.Errorf("expected %q got %q: %s", expect, result, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFindIndex(test *FindTest, result []int, t *testing.T) {
|
|
||||||
switch {
|
|
||||||
case len(test.matches) == 0 && len(result) == 0:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
expect := test.matches[0]
|
|
||||||
if expect[0] != result[0] || expect[1] != result[1] {
|
|
||||||
t.Errorf("expected %v got %v: %s", expect, result, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindIndex(&test, MustCompile(test.pat).FindIndex([]byte(test.text)), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindStringIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindIndex(&test, MustCompile(test.pat).FindStringIndex(test.text), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindReaderIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindIndex(&test, MustCompile(test.pat).FindReaderIndex(strings.NewReader(test.text)), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now come the simple All cases.
|
|
||||||
|
|
||||||
func TestFindAll(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindAll([]byte(test.text), -1)
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
if len(test.matches) != len(result) {
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for k, e := range test.matches {
|
|
||||||
expect := test.text[e[0]:e[1]]
|
|
||||||
if expect != string(result[k]) {
|
|
||||||
t.Errorf("match %d: expected %q got %q: %s", k, expect, result[k], test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllString(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindAllString(test.text, -1)
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
if len(test.matches) != len(result) {
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for k, e := range test.matches {
|
|
||||||
expect := test.text[e[0]:e[1]]
|
|
||||||
if expect != result[k] {
|
|
||||||
t.Errorf("expected %q got %q: %s", expect, result, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFindAllIndex(test *FindTest, result [][]int, t *testing.T) {
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
if len(test.matches) != len(result) {
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k, e := range test.matches {
|
|
||||||
if e[0] != result[k][0] || e[1] != result[k][1] {
|
|
||||||
t.Errorf("match %d: expected %v got %v: %s", k, e, result[k], test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindAllIndex(&test, MustCompile(test.pat).FindAllIndex([]byte(test.text), -1), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllStringIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindAllIndex(&test, MustCompile(test.pat).FindAllStringIndex(test.text, -1), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now come the Submatch cases.
|
|
||||||
|
|
||||||
func testSubmatchBytes(test *FindTest, n int, submatches []int, result [][]byte, t *testing.T) {
|
|
||||||
if len(submatches) != len(result)*2 {
|
|
||||||
t.Errorf("match %d: expected %d submatches; got %d: %s", n, len(submatches)/2, len(result), test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k := 0; k < len(submatches); k += 2 {
|
|
||||||
if submatches[k] == -1 {
|
|
||||||
if result[k/2] != nil {
|
|
||||||
t.Errorf("match %d: expected nil got %q: %s", n, result, test)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expect := test.text[submatches[k]:submatches[k+1]]
|
|
||||||
if expect != string(result[k/2]) {
|
|
||||||
t.Errorf("match %d: expected %q got %q: %s", n, expect, result, test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindSubmatch(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindSubmatch([]byte(test.text))
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
testSubmatchBytes(&test, 0, test.matches[0], result, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSubmatchString(test *FindTest, n int, submatches []int, result []string, t *testing.T) {
|
|
||||||
if len(submatches) != len(result)*2 {
|
|
||||||
t.Errorf("match %d: expected %d submatches; got %d: %s", n, len(submatches)/2, len(result), test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k := 0; k < len(submatches); k += 2 {
|
|
||||||
if submatches[k] == -1 {
|
|
||||||
if result[k/2] != "" {
|
|
||||||
t.Errorf("match %d: expected nil got %q: %s", n, result, test)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expect := test.text[submatches[k]:submatches[k+1]]
|
|
||||||
if expect != result[k/2] {
|
|
||||||
t.Errorf("match %d: expected %q got %q: %s", n, expect, result, test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindStringSubmatch(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindStringSubmatch(test.text)
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
testSubmatchString(&test, 0, test.matches[0], result, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSubmatchIndices(test *FindTest, n int, expect, result []int, t *testing.T) {
|
|
||||||
if len(expect) != len(result) {
|
|
||||||
t.Errorf("match %d: expected %d matches; got %d: %s", n, len(expect)/2, len(result)/2, test)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k, e := range expect {
|
|
||||||
if e != result[k] {
|
|
||||||
t.Errorf("match %d: submatch error: expected %v got %v: %s", n, expect, result, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFindSubmatchIndex(test *FindTest, result []int, t *testing.T) {
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
testSubmatchIndices(test, 0, test.matches[0], result, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindSubmatchIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindSubmatchIndex(&test, MustCompile(test.pat).FindSubmatchIndex([]byte(test.text)), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindStringSubmatchIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindSubmatchIndex(&test, MustCompile(test.pat).FindStringSubmatchIndex(test.text), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindReaderSubmatchIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindSubmatchIndex(&test, MustCompile(test.pat).FindReaderSubmatchIndex(strings.NewReader(test.text)), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now come the monster AllSubmatch cases.
|
|
||||||
|
|
||||||
func TestFindAllSubmatch(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindAllSubmatch([]byte(test.text), -1)
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case len(test.matches) != len(result):
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
for k, match := range test.matches {
|
|
||||||
testSubmatchBytes(&test, k, match, result[k], t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllStringSubmatch(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
result := MustCompile(test.pat).FindAllStringSubmatch(test.text, -1)
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case len(test.matches) != len(result):
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
for k, match := range test.matches {
|
|
||||||
testSubmatchString(&test, k, match, result[k], t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFindAllSubmatchIndex(test *FindTest, result [][]int, t *testing.T) {
|
|
||||||
switch {
|
|
||||||
case test.matches == nil && result == nil:
|
|
||||||
// ok
|
|
||||||
case test.matches == nil && result != nil:
|
|
||||||
t.Errorf("expected no match; got one: %s", test)
|
|
||||||
case test.matches != nil && result == nil:
|
|
||||||
t.Errorf("expected match; got none: %s", test)
|
|
||||||
case len(test.matches) != len(result):
|
|
||||||
t.Errorf("expected %d matches; got %d: %s", len(test.matches), len(result), test)
|
|
||||||
case test.matches != nil && result != nil:
|
|
||||||
for k, match := range test.matches {
|
|
||||||
testSubmatchIndices(test, k, match, result[k], t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllSubmatchIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindAllSubmatchIndex(&test, MustCompile(test.pat).FindAllSubmatchIndex([]byte(test.text), -1), t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllStringSubmatchIndex(t *testing.T) {
|
|
||||||
for _, test := range findTests {
|
|
||||||
testFindAllSubmatchIndex(&test, MustCompile(test.pat).FindAllStringSubmatchIndex(test.text, -1), t)
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,91 +0,0 @@
|
|||||||
// 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 implements data-driven templates for generating textual
|
|
||||||
output such as HTML.
|
|
||||||
|
|
||||||
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 or a key in a map)
|
|
||||||
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.
|
|
||||||
|
|
||||||
In the following, 'Field' is one of several things, according to the data.
|
|
||||||
|
|
||||||
- The name of a field of a struct (result = data.Field),
|
|
||||||
- The value stored in a map under that key (result = data["Field"]), or
|
|
||||||
- The result of invoking a niladic single-valued method with that name
|
|
||||||
(result = data.Field())
|
|
||||||
|
|
||||||
If Field is a struct field or method name, it must be an exported
|
|
||||||
(capitalized) name.
|
|
||||||
|
|
||||||
Major constructs ({} are the default delimiters for template actions;
|
|
||||||
[] are the notation in this comment for 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}
|
|
||||||
{field1 field2 ...}
|
|
||||||
{field|formatter}
|
|
||||||
{field1 field2...|formatter}
|
|
||||||
{field|formatter1|formatter2}
|
|
||||||
|
|
||||||
Insert the value of the fields into the output. Each 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.
|
|
||||||
|
|
||||||
If the field value is a pointer, leading asterisks indicate
|
|
||||||
that the value to be inserted should be evaluated through the
|
|
||||||
pointer. For example, if x.p is of type *int, {x.p} will
|
|
||||||
insert the value of the pointer but {*x.p} will insert the
|
|
||||||
value of the underlying integer. If the value is nil or not a
|
|
||||||
pointer, asterisks have no effect.
|
|
||||||
|
|
||||||
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.Writer, formatter string, data ...interface{})
|
|
||||||
where wr is the destination for output, data holds the field
|
|
||||||
values at the instantiation, and formatter is its name at
|
|
||||||
the invocation site. The default formatter just concatenates
|
|
||||||
the string representations of the fields.
|
|
||||||
|
|
||||||
Multiple formatters separated by the pipeline character | are
|
|
||||||
executed sequentially, with each formatter receiving the bytes
|
|
||||||
emitted by the one to its left.
|
|
||||||
|
|
||||||
As well as field names, one may use literals with Go syntax.
|
|
||||||
Integer, floating-point, and string literals are supported.
|
|
||||||
Raw strings may not span newlines.
|
|
||||||
|
|
||||||
The delimiter strings get their default value, "{" and "}", from
|
|
||||||
JSON-template. They may be set to any non-empty, space-free
|
|
||||||
string using the SetDelims method. Their value can be printed
|
|
||||||
in the output using {.meta-left} and {.meta-right}.
|
|
||||||
*/
|
|
||||||
package template
|
|
@ -1,346 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
// Code to execute a parsed template.
|
|
||||||
|
|
||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
wr io.Writer // where to send output
|
|
||||||
buf [2]bytes.Buffer // alternating buffers used when chaining formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parent *state) clone(data reflect.Value) *state {
|
|
||||||
return &state{parent: parent, data: data, wr: parent.wr}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate interfaces and pointers looking for a value that can look up the name, via a
|
|
||||||
// struct field, method, or map key, and return the result of the lookup.
|
|
||||||
func (t *Template) lookup(st *state, v reflect.Value, name string) reflect.Value {
|
|
||||||
for v.IsValid() {
|
|
||||||
typ := v.Type()
|
|
||||||
if n := v.Type().NumMethod(); n > 0 {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
m := typ.Method(i)
|
|
||||||
mtyp := m.Type
|
|
||||||
if m.Name == name && mtyp.NumIn() == 1 && mtyp.NumOut() == 1 {
|
|
||||||
if !isExported(name) {
|
|
||||||
t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
|
|
||||||
}
|
|
||||||
return v.Method(i).Call(nil)[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch av := v; av.Kind() {
|
|
||||||
case reflect.Ptr:
|
|
||||||
v = av.Elem()
|
|
||||||
case reflect.Interface:
|
|
||||||
v = av.Elem()
|
|
||||||
case reflect.Struct:
|
|
||||||
if !isExported(name) {
|
|
||||||
t.execError(st, t.linenum, "name not exported: %s in type %s", name, st.data.Type())
|
|
||||||
}
|
|
||||||
return av.FieldByName(name)
|
|
||||||
case reflect.Map:
|
|
||||||
if v := av.MapIndex(reflect.ValueOf(name)); v.IsValid() {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return reflect.Zero(typ.Elem())
|
|
||||||
default:
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// indirectPtr returns the item numLevels levels of indirection below the value.
|
|
||||||
// It is forgiving: if the value is not a pointer, it returns it rather than giving
|
|
||||||
// an error. If the pointer is nil, it is returned as is.
|
|
||||||
func indirectPtr(v reflect.Value, numLevels int) reflect.Value {
|
|
||||||
for i := numLevels; v.IsValid() && i > 0; i++ {
|
|
||||||
if p := v; p.Kind() == reflect.Ptr {
|
|
||||||
if p.IsNil() {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
v = p.Elem()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk v through pointers and interfaces, extracting the elements within.
|
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
|
||||||
loop:
|
|
||||||
for v.IsValid() {
|
|
||||||
switch av := v; av.Kind() {
|
|
||||||
case reflect.Ptr:
|
|
||||||
v = av.Elem()
|
|
||||||
case reflect.Interface:
|
|
||||||
v = av.Elem()
|
|
||||||
default:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the data for this template is a struct, find the named variable.
|
|
||||||
// Names of the form a.b.c are walked down the data tree.
|
|
||||||
// The special name "@" (the "cursor") denotes the current data.
|
|
||||||
// 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. Leading stars indicate
|
|
||||||
// levels of indirection to be applied to the value.
|
|
||||||
func (t *Template) findVar(st *state, s string) reflect.Value {
|
|
||||||
data := st.data
|
|
||||||
flattenedName := strings.TrimLeft(s, "*")
|
|
||||||
numStars := len(s) - len(flattenedName)
|
|
||||||
s = flattenedName
|
|
||||||
if s == "@" {
|
|
||||||
return indirectPtr(data, numStars)
|
|
||||||
}
|
|
||||||
for _, elem := range strings.Split(s, ".") {
|
|
||||||
// Look up field; data must be a struct or map.
|
|
||||||
data = t.lookup(st, data, elem)
|
|
||||||
if !data.IsValid() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return indirectPtr(data, numStars)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is there no data to look at?
|
|
||||||
func empty(v reflect.Value) bool {
|
|
||||||
v = indirect(v)
|
|
||||||
if !v.IsValid() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return v.Bool() == false
|
|
||||||
case reflect.String:
|
|
||||||
return v.String() == ""
|
|
||||||
case reflect.Struct:
|
|
||||||
return false
|
|
||||||
case reflect.Map:
|
|
||||||
return false
|
|
||||||
case reflect.Array:
|
|
||||||
return v.Len() == 0
|
|
||||||
case reflect.Slice:
|
|
||||||
return v.Len() == 0
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up a variable or method, up through the parent if necessary.
|
|
||||||
func (t *Template) varValue(name string, st *state) reflect.Value {
|
|
||||||
field := t.findVar(st, name)
|
|
||||||
if !field.IsValid() {
|
|
||||||
if st.parent == nil {
|
|
||||||
t.execError(st, t.linenum, "name not found: %s in type %s", name, st.data.Type())
|
|
||||||
}
|
|
||||||
return t.varValue(name, st.parent)
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) format(wr io.Writer, fmt string, val []interface{}, v *variableElement, st *state) {
|
|
||||||
fn := t.formatter(fmt)
|
|
||||||
if fn == nil {
|
|
||||||
t.execError(st, v.linenum, "missing formatter %s for variable", fmt)
|
|
||||||
}
|
|
||||||
fn(wr, fmt, val...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate a variable, looking up through the parent if necessary.
|
|
||||||
// If it has a formatter attached ({var|formatter}) run that too.
|
|
||||||
func (t *Template) writeVariable(v *variableElement, st *state) {
|
|
||||||
// Resolve field names
|
|
||||||
val := make([]interface{}, len(v.args))
|
|
||||||
for i, arg := range v.args {
|
|
||||||
if name, ok := arg.(fieldName); ok {
|
|
||||||
val[i] = t.varValue(string(name), st).Interface()
|
|
||||||
} else {
|
|
||||||
val[i] = arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, fmt := range v.fmts[:len(v.fmts)-1] {
|
|
||||||
b := &st.buf[i&1]
|
|
||||||
b.Reset()
|
|
||||||
t.format(b, fmt, val, v, st)
|
|
||||||
val = val[0:1]
|
|
||||||
val[0] = b.Bytes()
|
|
||||||
}
|
|
||||||
t.format(st.wr, v.fmts[len(v.fmts)-1], val, v, st)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute element i. Return next index to execute.
|
|
||||||
func (t *Template) executeElement(i int, st *state) int {
|
|
||||||
switch elem := t.elems[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[i]
|
|
||||||
t.execError(st, 0, "internal error: bad directive in execute: %v %T\n", reflect.ValueOf(e).Interface(), e)
|
|
||||||
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.
|
|
||||||
field := t.varValue(s.field, st)
|
|
||||||
if !field.IsValid() {
|
|
||||||
t.execError(st, s.linenum, ".section: cannot find field %s in %s", s.field, st.data.Type())
|
|
||||||
}
|
|
||||||
st = st.clone(field)
|
|
||||||
start, end := s.start, s.or
|
|
||||||
if !empty(field) {
|
|
||||||
// Execute the normal block.
|
|
||||||
if end < 0 {
|
|
||||||
end = s.end
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Execute the .or block. If it's missing, do nothing.
|
|
||||||
start, end = s.or, s.end
|
|
||||||
if start < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := start; i < end; {
|
|
||||||
i = t.executeElement(i, st)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the result of calling the Iter method on v, or nil.
|
|
||||||
func iter(v reflect.Value) reflect.Value {
|
|
||||||
for j := 0; j < v.Type().NumMethod(); j++ {
|
|
||||||
mth := v.Type().Method(j)
|
|
||||||
fv := v.Method(j)
|
|
||||||
ft := fv.Type()
|
|
||||||
// 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 := ft.Out(0)
|
|
||||||
if ct.Kind() != reflect.Chan ||
|
|
||||||
ct.ChanDir()&reflect.RecvDir == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fv.Call(nil)[0]
|
|
||||||
}
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
field := t.varValue(r.field, st)
|
|
||||||
if !field.IsValid() {
|
|
||||||
t.execError(st, r.linenum, ".repeated: cannot find field %s in %s", r.field, st.data.Type())
|
|
||||||
}
|
|
||||||
field = indirect(field)
|
|
||||||
|
|
||||||
start, end := r.start, r.or
|
|
||||||
if end < 0 {
|
|
||||||
end = r.end
|
|
||||||
}
|
|
||||||
if r.altstart >= 0 {
|
|
||||||
end = r.altstart
|
|
||||||
}
|
|
||||||
first := true
|
|
||||||
|
|
||||||
// Code common to all the loops.
|
|
||||||
loopBody := func(newst *state) {
|
|
||||||
// .alternates between elements
|
|
||||||
if !first && r.altstart >= 0 {
|
|
||||||
for i := r.altstart; i < r.altend; {
|
|
||||||
i = t.executeElement(i, newst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first = false
|
|
||||||
for i := start; i < end; {
|
|
||||||
i = t.executeElement(i, newst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if array := field; array.Kind() == reflect.Array || array.Kind() == reflect.Slice {
|
|
||||||
for j := 0; j < array.Len(); j++ {
|
|
||||||
loopBody(st.clone(array.Index(j)))
|
|
||||||
}
|
|
||||||
} else if m := field; m.Kind() == reflect.Map {
|
|
||||||
for _, key := range m.MapKeys() {
|
|
||||||
loopBody(st.clone(m.MapIndex(key)))
|
|
||||||
}
|
|
||||||
} else if ch := iter(field); ch.IsValid() {
|
|
||||||
for {
|
|
||||||
e, ok := ch.Recv()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
loopBody(st.clone(e))
|
|
||||||
}
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valid delimiter must contain no space and be non-empty.
|
|
||||||
func validDelim(d []byte) bool {
|
|
||||||
if len(d) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, c := range d {
|
|
||||||
if isSpace(c) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
// 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: default formatters
|
|
||||||
|
|
||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StringFormatter formats into the default string representation.
|
|
||||||
// It is stored under the name "str" and is the default formatter.
|
|
||||||
// You can override the default formatter by storing your default
|
|
||||||
// under the name "" in your custom formatter map.
|
|
||||||
func StringFormatter(w io.Writer, format string, value ...interface{}) {
|
|
||||||
if len(value) == 1 {
|
|
||||||
if b, ok := value[0].([]byte); ok {
|
|
||||||
w.Write(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, value...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
esc_quot = []byte(""") // shorter than """
|
|
||||||
esc_apos = []byte("'") // shorter than "'"
|
|
||||||
esc_amp = []byte("&")
|
|
||||||
esc_lt = []byte("<")
|
|
||||||
esc_gt = []byte(">")
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTMLEscape writes to w the properly escaped HTML equivalent
|
|
||||||
// of the plain text data s.
|
|
||||||
func HTMLEscape(w io.Writer, s []byte) {
|
|
||||||
var esc []byte
|
|
||||||
last := 0
|
|
||||||
for i, c := range s {
|
|
||||||
switch c {
|
|
||||||
case '"':
|
|
||||||
esc = esc_quot
|
|
||||||
case '\'':
|
|
||||||
esc = esc_apos
|
|
||||||
case '&':
|
|
||||||
esc = esc_amp
|
|
||||||
case '<':
|
|
||||||
esc = esc_lt
|
|
||||||
case '>':
|
|
||||||
esc = esc_gt
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
w.Write(s[last:i])
|
|
||||||
w.Write(esc)
|
|
||||||
last = i + 1
|
|
||||||
}
|
|
||||||
w.Write(s[last:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLFormatter formats arbitrary values for HTML
|
|
||||||
func HTMLFormatter(w io.Writer, format string, value ...interface{}) {
|
|
||||||
ok := false
|
|
||||||
var b []byte
|
|
||||||
if len(value) == 1 {
|
|
||||||
b, ok = value[0].([]byte)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, value...)
|
|
||||||
b = buf.Bytes()
|
|
||||||
}
|
|
||||||
HTMLEscape(w, b)
|
|
||||||
}
|
|
@ -1,742 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
// Code to parse a template.
|
|
||||||
|
|
||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors returned during parsing and execution. Users may extract the information and reformat
|
|
||||||
// if they desire.
|
|
||||||
type Error struct {
|
|
||||||
Line int
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string { return fmt.Sprintf("line %d: %s", e.Line, e.Msg) }
|
|
||||||
|
|
||||||
// checkError is a deferred function to turn a panic with type *Error into a plain error return.
|
|
||||||
// Other panics are unexpected and so are re-enabled.
|
|
||||||
func checkError(error *error) {
|
|
||||||
if v := recover(); v != nil {
|
|
||||||
if e, ok := v.(*Error); ok {
|
|
||||||
*error = e
|
|
||||||
} else {
|
|
||||||
// runtime errors should crash
|
|
||||||
panic(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most of the literals are aces.
|
|
||||||
var lbrace = []byte{'{'}
|
|
||||||
var rbrace = []byte{'}'}
|
|
||||||
var space = []byte{' '}
|
|
||||||
var tab = []byte{'\t'}
|
|
||||||
|
|
||||||
// The various types of "tokens", which are plain text or (usually) brace-delimited descriptors
|
|
||||||
const (
|
|
||||||
tokAlternates = iota
|
|
||||||
tokComment
|
|
||||||
tokEnd
|
|
||||||
tokLiteral
|
|
||||||
tokOr
|
|
||||||
tokRepeated
|
|
||||||
tokSection
|
|
||||||
tokText
|
|
||||||
tokVariable
|
|
||||||
)
|
|
||||||
|
|
||||||
// FormatterMap is the type describing the mapping from formatter
|
|
||||||
// names to the functions that implement them.
|
|
||||||
type FormatterMap map[string]func(io.Writer, string, ...interface{})
|
|
||||||
|
|
||||||
// Built-in formatters.
|
|
||||||
var builtins = FormatterMap{
|
|
||||||
"html": HTMLFormatter,
|
|
||||||
"str": StringFormatter,
|
|
||||||
"": StringFormatter,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// A literal such as .meta-left or .meta-right
|
|
||||||
type literalElement struct {
|
|
||||||
text []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// A variable invocation to be evaluated
|
|
||||||
type variableElement struct {
|
|
||||||
linenum int
|
|
||||||
args []interface{} // The fields and literals in the invocation.
|
|
||||||
fmts []string // Names of formatters to apply. len(fmts) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// A variableElement arg to be evaluated as a field name
|
|
||||||
type fieldName string
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// A .repeated block, possibly with a .or and a .alternates
|
|
||||||
type repeatedElement struct {
|
|
||||||
sectionElement // It has the same structure...
|
|
||||||
altstart int // ... except for alternates
|
|
||||||
altend int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Template is the type that represents a template definition.
|
|
||||||
// It is unchanged after parsing.
|
|
||||||
type Template struct {
|
|
||||||
fmap FormatterMap // formatters for variables
|
|
||||||
// Used during parsing:
|
|
||||||
ldelim, rdelim []byte // delimiters; default {}
|
|
||||||
buf []byte // input text to process
|
|
||||||
p int // position in buf
|
|
||||||
linenum int // position in input
|
|
||||||
// Parsed results:
|
|
||||||
elems []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
t := new(Template)
|
|
||||||
t.fmap = fmap
|
|
||||||
t.ldelim = lbrace
|
|
||||||
t.rdelim = rbrace
|
|
||||||
t.elems = make([]interface{}, 0, 16)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report error and stop executing. The line number must be provided explicitly.
|
|
||||||
func (t *Template) execError(st *state, line int, err string, args ...interface{}) {
|
|
||||||
panic(&Error{line, fmt.Sprintf(err, args...)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report error, panic to terminate parsing.
|
|
||||||
// The line number comes from the template state.
|
|
||||||
func (t *Template) parseError(err string, args ...interface{}) {
|
|
||||||
panic(&Error{t.linenum, fmt.Sprintf(err, args...)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this an exported - upper case - name?
|
|
||||||
func isExported(name string) bool {
|
|
||||||
r, _ := utf8.DecodeRuneInString(name)
|
|
||||||
return unicode.IsUpper(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Lexical analysis
|
|
||||||
|
|
||||||
// Is c a space character?
|
|
||||||
func isSpace(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:]
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// isQuote returns true if c is a string- or character-delimiting quote character.
|
|
||||||
func isQuote(c byte) bool {
|
|
||||||
return c == '"' || c == '`' || c == '\''
|
|
||||||
}
|
|
||||||
|
|
||||||
// endQuote returns the end quote index for the quoted string that
|
|
||||||
// starts at n, or -1 if no matching end quote is found before the end
|
|
||||||
// of the line.
|
|
||||||
func endQuote(s []byte, n int) int {
|
|
||||||
quote := s[n]
|
|
||||||
for n++; n < len(s); n++ {
|
|
||||||
switch s[n] {
|
|
||||||
case '\\':
|
|
||||||
if quote == '"' || quote == '\'' {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
case '\n':
|
|
||||||
return -1
|
|
||||||
case quote:
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// delimited string or a non-empty string between delimited
|
|
||||||
// strings. Tokens stop at (but include, if plain text) a newline.
|
|
||||||
// Action tokens on a line by themselves drop any space on
|
|
||||||
// either side, up to and including the newline.
|
|
||||||
func (t *Template) nextItem() []byte {
|
|
||||||
startOfLine := t.p == 0 || t.buf[t.p-1] == '\n'
|
|
||||||
start := t.p
|
|
||||||
var i int
|
|
||||||
newline := func() {
|
|
||||||
t.linenum++
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
// Leading space up to but not including newline
|
|
||||||
for i = start; i < len(t.buf); i++ {
|
|
||||||
if t.buf[i] == '\n' || !isSpace(t.buf[i]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leadingSpace := i > start
|
|
||||||
// What's left is nothing, newline, delimited string, or plain text
|
|
||||||
switch {
|
|
||||||
case i == len(t.buf):
|
|
||||||
// EOF; nothing to do
|
|
||||||
case t.buf[i] == '\n':
|
|
||||||
newline()
|
|
||||||
case equal(t.buf, i, t.ldelim):
|
|
||||||
left := i // Start of left delimiter.
|
|
||||||
right := -1 // Will be (immediately after) right delimiter.
|
|
||||||
haveText := false // Delimiters contain text.
|
|
||||||
i += len(t.ldelim)
|
|
||||||
// Find the end of the action.
|
|
||||||
for ; i < len(t.buf); i++ {
|
|
||||||
if t.buf[i] == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if isQuote(t.buf[i]) {
|
|
||||||
i = endQuote(t.buf, i)
|
|
||||||
if i == -1 {
|
|
||||||
t.parseError("unmatched quote")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if equal(t.buf, i, t.rdelim) {
|
|
||||||
i += len(t.rdelim)
|
|
||||||
right = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
haveText = true
|
|
||||||
}
|
|
||||||
if right < 0 {
|
|
||||||
t.parseError("unmatched opening delimiter")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Is this a special action (starts with '.' or '#') and the only thing on the line?
|
|
||||||
if startOfLine && haveText {
|
|
||||||
firstChar := t.buf[left+len(t.ldelim)]
|
|
||||||
if firstChar == '.' || firstChar == '#' {
|
|
||||||
// It's special and the first thing on the line. Is it the last?
|
|
||||||
for j := right; j < len(t.buf) && isSpace(t.buf[j]); j++ {
|
|
||||||
if t.buf[j] == '\n' {
|
|
||||||
// Yes it is. Drop the surrounding space and return the {.foo}
|
|
||||||
t.linenum++
|
|
||||||
t.p = j + 1
|
|
||||||
return t.buf[left:right]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No it's not. If there's leading space, return that.
|
|
||||||
if leadingSpace {
|
|
||||||
// not trimming space: return leading space if there is some.
|
|
||||||
t.p = left
|
|
||||||
return t.buf[start:left]
|
|
||||||
}
|
|
||||||
// Return the word, leave the trailing space.
|
|
||||||
start = left
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
for ; i < len(t.buf); i++ {
|
|
||||||
if t.buf[i] == '\n' {
|
|
||||||
newline()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if equal(t.buf, i, t.ldelim) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item := t.buf[start:i]
|
|
||||||
t.p = i
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn a byte array into a space-split array of strings,
|
|
||||||
// taking into account quoted strings.
|
|
||||||
func words(buf []byte) []string {
|
|
||||||
s := make([]string, 0, 5)
|
|
||||||
for i := 0; i < len(buf); {
|
|
||||||
// One word per loop
|
|
||||||
for i < len(buf) && isSpace(buf[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i == len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Got a word
|
|
||||||
start := i
|
|
||||||
if isQuote(buf[i]) {
|
|
||||||
i = endQuote(buf, i)
|
|
||||||
if i < 0 {
|
|
||||||
i = len(buf)
|
|
||||||
} else {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Even with quotes, break on space only. This handles input
|
|
||||||
// such as {""|} and catches quoting mistakes.
|
|
||||||
for i < len(buf) && !isSpace(buf[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
s = append(s, string(buf[start:i]))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Analyze an item and return its token 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 !equal(item, 0, t.ldelim) { // doesn't start with left delimiter
|
|
||||||
tok = tokText
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
|
|
||||||
t.parseError("internal error: unmatched opening delimiter") // lexing should prevent this
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
|
|
||||||
t.parseError("empty directive")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Comment
|
|
||||||
if item[len(t.ldelim)] == '#' {
|
|
||||||
tok = tokComment
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Split into words
|
|
||||||
w = words(item[len(t.ldelim) : len(item)-len(t.rdelim)]) // drop final delimiter
|
|
||||||
if len(w) == 0 {
|
|
||||||
t.parseError("empty directive")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
first := w[0]
|
|
||||||
if first[0] != '.' {
|
|
||||||
tok = tokVariable
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(first) > 1 && first[1] >= '0' && first[1] <= '9' {
|
|
||||||
// Must be a float.
|
|
||||||
tok = tokVariable
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch first {
|
|
||||||
case ".meta-left", ".meta-right", ".space", ".tab":
|
|
||||||
tok = tokLiteral
|
|
||||||
return
|
|
||||||
case ".or":
|
|
||||||
tok = tokOr
|
|
||||||
return
|
|
||||||
case ".end":
|
|
||||||
tok = tokEnd
|
|
||||||
return
|
|
||||||
case ".section":
|
|
||||||
if len(w) != 2 {
|
|
||||||
t.parseError("incorrect fields for .section: %s", item)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tok = tokSection
|
|
||||||
return
|
|
||||||
case ".repeated":
|
|
||||||
if len(w) != 3 || w[1] != "section" {
|
|
||||||
t.parseError("incorrect fields for .repeated: %s", item)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tok = tokRepeated
|
|
||||||
return
|
|
||||||
case ".alternates":
|
|
||||||
if len(w) != 2 || w[1] != "with" {
|
|
||||||
t.parseError("incorrect fields for .alternates: %s", item)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tok = tokAlternates
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.parseError("bad directive: %s", item)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatter returns the Formatter with the given name in the Template, or nil if none exists.
|
|
||||||
func (t *Template) formatter(name string) func(io.Writer, string, ...interface{}) {
|
|
||||||
if t.fmap != nil {
|
|
||||||
if fn := t.fmap[name]; fn != nil {
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builtins[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Parsing
|
|
||||||
|
|
||||||
// newVariable allocates a new variable-evaluation element.
|
|
||||||
func (t *Template) newVariable(words []string) *variableElement {
|
|
||||||
formatters := extractFormatters(words)
|
|
||||||
args := make([]interface{}, len(words))
|
|
||||||
|
|
||||||
// Build argument list, processing any literals
|
|
||||||
for i, word := range words {
|
|
||||||
var lerr error
|
|
||||||
switch word[0] {
|
|
||||||
case '"', '`', '\'':
|
|
||||||
v, err := strconv.Unquote(word)
|
|
||||||
if err == nil && word[0] == '\'' {
|
|
||||||
args[i], _ = utf8.DecodeRuneInString(v)
|
|
||||||
} else {
|
|
||||||
args[i], lerr = v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
case '.', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
||||||
v, err := strconv.ParseInt(word, 0, 64)
|
|
||||||
if err == nil {
|
|
||||||
args[i] = v
|
|
||||||
} else {
|
|
||||||
v, err := strconv.ParseFloat(word, 64)
|
|
||||||
args[i], lerr = v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
args[i] = fieldName(word)
|
|
||||||
}
|
|
||||||
if lerr != nil {
|
|
||||||
t.parseError("invalid literal: %q: %s", word, lerr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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?
|
|
||||||
for _, f := range formatters {
|
|
||||||
if t.formatter(f) == nil {
|
|
||||||
t.parseError("unknown formatter: %q", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &variableElement{t.linenum, args, formatters}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractFormatters extracts a list of formatters from words.
|
|
||||||
// After the final space-separated argument in a variable, formatters may be
|
|
||||||
// specified separated by pipe symbols. For example: {a b c|d|e}
|
|
||||||
// The words parameter still has the formatters joined by '|' in the last word.
|
|
||||||
// extractFormatters splits formatters, replaces the last word with the content
|
|
||||||
// found before the first '|' within it, and returns the formatters obtained.
|
|
||||||
// If no formatters are found in words, the default formatter is returned.
|
|
||||||
func extractFormatters(words []string) (formatters []string) {
|
|
||||||
// "" is the default formatter.
|
|
||||||
formatters = []string{""}
|
|
||||||
if len(words) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var bar int
|
|
||||||
lastWord := words[len(words)-1]
|
|
||||||
if isQuote(lastWord[0]) {
|
|
||||||
end := endQuote([]byte(lastWord), 0)
|
|
||||||
if end < 0 || end+1 == len(lastWord) || lastWord[end+1] != '|' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bar = end + 1
|
|
||||||
} else {
|
|
||||||
bar = strings.IndexRune(lastWord, '|')
|
|
||||||
if bar < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
words[len(words)-1] = lastWord[0:bar]
|
|
||||||
formatters = strings.Split(lastWord[bar+1:], "|")
|
|
||||||
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)
|
|
||||||
done = true // assume for simplicity
|
|
||||||
switch tok {
|
|
||||||
case tokComment:
|
|
||||||
return
|
|
||||||
case tokText:
|
|
||||||
t.elems = append(t.elems, &textElement{item})
|
|
||||||
return
|
|
||||||
case tokLiteral:
|
|
||||||
switch w[0] {
|
|
||||||
case ".meta-left":
|
|
||||||
t.elems = append(t.elems, &literalElement{t.ldelim})
|
|
||||||
case ".meta-right":
|
|
||||||
t.elems = append(t.elems, &literalElement{t.rdelim})
|
|
||||||
case ".space":
|
|
||||||
t.elems = append(t.elems, &literalElement{space})
|
|
||||||
case ".tab":
|
|
||||||
t.elems = append(t.elems, &literalElement{tab})
|
|
||||||
default:
|
|
||||||
t.parseError("internal error: unknown literal: %s", w[0])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case tokVariable:
|
|
||||||
t.elems = append(t.elems, t.newVariable(w))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return false, tok, w
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRepeated and parseSection are mutually recursive
|
|
||||||
|
|
||||||
func (t *Template) parseRepeated(words []string) *repeatedElement {
|
|
||||||
r := new(repeatedElement)
|
|
||||||
t.elems = append(t.elems, r)
|
|
||||||
r.linenum = t.linenum
|
|
||||||
r.field = words[2]
|
|
||||||
// Scan section, collecting true and false (.or) blocks.
|
|
||||||
r.start = len(t.elems)
|
|
||||||
r.or = -1
|
|
||||||
r.altstart = -1
|
|
||||||
r.altend = -1
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
item := t.nextItem()
|
|
||||||
if len(item) == 0 {
|
|
||||||
t.parseError("missing .end for .repeated section")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
done, tok, w := t.parseSimple(item)
|
|
||||||
if done {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch tok {
|
|
||||||
case tokEnd:
|
|
||||||
break Loop
|
|
||||||
case tokOr:
|
|
||||||
if r.or >= 0 {
|
|
||||||
t.parseError("extra .or in .repeated section")
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
r.altend = len(t.elems)
|
|
||||||
r.or = len(t.elems)
|
|
||||||
case tokSection:
|
|
||||||
t.parseSection(w)
|
|
||||||
case tokRepeated:
|
|
||||||
t.parseRepeated(w)
|
|
||||||
case tokAlternates:
|
|
||||||
if r.altstart >= 0 {
|
|
||||||
t.parseError("extra .alternates in .repeated section")
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
if r.or >= 0 {
|
|
||||||
t.parseError(".alternates inside .or block in .repeated section")
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
r.altstart = len(t.elems)
|
|
||||||
default:
|
|
||||||
t.parseError("internal error: unknown repeated section item: %s", item)
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.altend < 0 {
|
|
||||||
r.altend = len(t.elems)
|
|
||||||
}
|
|
||||||
r.end = len(t.elems)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) parseSection(words []string) *sectionElement {
|
|
||||||
s := new(sectionElement)
|
|
||||||
t.elems = append(t.elems, s)
|
|
||||||
s.linenum = t.linenum
|
|
||||||
s.field = words[1]
|
|
||||||
// Scan section, collecting true and false (.or) blocks.
|
|
||||||
s.start = len(t.elems)
|
|
||||||
s.or = -1
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
item := t.nextItem()
|
|
||||||
if len(item) == 0 {
|
|
||||||
t.parseError("missing .end for .section")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
done, tok, w := t.parseSimple(item)
|
|
||||||
if done {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch tok {
|
|
||||||
case tokEnd:
|
|
||||||
break Loop
|
|
||||||
case tokOr:
|
|
||||||
if s.or >= 0 {
|
|
||||||
t.parseError("extra .or in .section")
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
s.or = len(t.elems)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.end = len(t.elems)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) parse() {
|
|
||||||
for {
|
|
||||||
item := t.nextItem()
|
|
||||||
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
|
|
||||||
|
|
||||||
// -- Public interface
|
|
||||||
|
|
||||||
// Parse initializes a Template by parsing its definition. The string
|
|
||||||
// s contains the template text. If any errors occur, Parse returns
|
|
||||||
// the error.
|
|
||||||
func (t *Template) Parse(s string) (err error) {
|
|
||||||
if t.elems == nil {
|
|
||||||
return &Error{1, "template not allocated with New"}
|
|
||||||
}
|
|
||||||
if !validDelim(t.ldelim) || !validDelim(t.rdelim) {
|
|
||||||
return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)}
|
|
||||||
}
|
|
||||||
defer checkError(&err)
|
|
||||||
t.buf = []byte(s)
|
|
||||||
t.p = 0
|
|
||||||
t.linenum = 1
|
|
||||||
t.parse()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFile is like Parse but reads the template definition from the
|
|
||||||
// named file.
|
|
||||||
func (t *Template) ParseFile(filename string) (err error) {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.Parse(string(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute applies a parsed template to the specified data object,
|
|
||||||
// generating output to wr.
|
|
||||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
|
||||||
// Extract the driver data.
|
|
||||||
val := reflect.ValueOf(data)
|
|
||||||
defer checkError(&err)
|
|
||||||
t.p = 0
|
|
||||||
t.execute(0, len(t.elems), &state{parent: nil, data: val, wr: wr})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (t *Template) SetDelims(left, right string) {
|
|
||||||
t.ldelim = []byte(left)
|
|
||||||
t.rdelim = []byte(right)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse creates a Template with default parameters (such as {} for
|
|
||||||
// metacharacters). The string s contains the template text while
|
|
||||||
// the formatter map fmap, which may be nil, defines auxiliary functions
|
|
||||||
// for formatting variables. The template is returned. If any errors
|
|
||||||
// occur, err will be non-nil.
|
|
||||||
func Parse(s string, fmap FormatterMap) (t *Template, err error) {
|
|
||||||
t = New(fmap)
|
|
||||||
err = t.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
t = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFile is a wrapper function that creates a Template with default
|
|
||||||
// parameters (such as {} for metacharacters). The filename identifies
|
|
||||||
// a file containing the template text, while the formatter map fmap, which
|
|
||||||
// may be nil, defines auxiliary functions for formatting variables.
|
|
||||||
// The template is returned. If any errors occur, err will be non-nil.
|
|
||||||
func ParseFile(filename string, fmap FormatterMap) (t *Template, err error) {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Parse(string(b), fmap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
panic("template.MustParse error: " + err.Error())
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseFile is like ParseFile but panics if the file cannot be read
|
|
||||||
// or the template cannot be parsed.
|
|
||||||
func MustParseFile(filename string, fmap FormatterMap) *Template {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
panic("template.MustParseFile error: " + err.Error())
|
|
||||||
}
|
|
||||||
return MustParse(string(b), fmap)
|
|
||||||
}
|
|
@ -1,810 +0,0 @@
|
|||||||
// 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"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Test struct {
|
|
||||||
in, out, err string
|
|
||||||
}
|
|
||||||
|
|
||||||
type T struct {
|
|
||||||
Item string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type U struct {
|
|
||||||
Mp map[string]int
|
|
||||||
}
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
Header string
|
|
||||||
HeaderPtr *string
|
|
||||||
Integer int
|
|
||||||
IntegerPtr *int
|
|
||||||
NilPtr *int
|
|
||||||
InnerT T
|
|
||||||
InnerPointerT *T
|
|
||||||
Data []T
|
|
||||||
Pdata []*T
|
|
||||||
Empty []*T
|
|
||||||
Emptystring string
|
|
||||||
Null []*T
|
|
||||||
Vec []interface{}
|
|
||||||
True bool
|
|
||||||
False bool
|
|
||||||
Mp map[string]string
|
|
||||||
JSON interface{}
|
|
||||||
Innermap U
|
|
||||||
Stringmap map[string]string
|
|
||||||
Ptrmap map[string]*string
|
|
||||||
Iface interface{}
|
|
||||||
Ifaceptr interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) PointerMethod() string { return "ptrmethod!" }
|
|
||||||
|
|
||||||
func (s S) ValueMethod() string { return "valmethod!" }
|
|
||||||
|
|
||||||
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, string, ...interface{}) {
|
|
||||||
return func(w io.Writer, format string, v ...interface{}) {
|
|
||||||
if len(v) != 1 {
|
|
||||||
panic("test writer expected one arg")
|
|
||||||
}
|
|
||||||
io.WriteString(w, f(v[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multiword(w io.Writer, format string, value ...interface{}) {
|
|
||||||
for _, v := range value {
|
|
||||||
fmt.Fprintf(w, "<%v>", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printf(w io.Writer, format string, v ...interface{}) {
|
|
||||||
io.WriteString(w, fmt.Sprintf(v[0].(string), v[1:]...))
|
|
||||||
}
|
|
||||||
|
|
||||||
var formatters = FormatterMap{
|
|
||||||
"uppercase": writer(uppercase),
|
|
||||||
"+1": writer(plus1),
|
|
||||||
"multiword": multiword,
|
|
||||||
"printf": printf,
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = []*Test{
|
|
||||||
// Simple
|
|
||||||
{"", "", ""},
|
|
||||||
{"abc", "abc", ""},
|
|
||||||
{"abc\ndef\n", "abc\ndef\n", ""},
|
|
||||||
{" {.meta-left} \n", "{", ""},
|
|
||||||
{" {.meta-right} \n", "}", ""},
|
|
||||||
{" {.space} \n", " ", ""},
|
|
||||||
{" {.tab} \n", "\t", ""},
|
|
||||||
{" {#comment} \n", "", ""},
|
|
||||||
{"\tSome Text\t\n", "\tSome Text\t\n", ""},
|
|
||||||
{" {.meta-right} {.meta-right} {.meta-right} \n", " } } } \n", ""},
|
|
||||||
|
|
||||||
// Variables at top level
|
|
||||||
{
|
|
||||||
in: "{Header}={Integer}\n",
|
|
||||||
|
|
||||||
out: "Header=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "Pointers: {*HeaderPtr}={*IntegerPtr}\n",
|
|
||||||
|
|
||||||
out: "Pointers: Header=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "Stars but not pointers: {*Header}={*Integer}\n",
|
|
||||||
|
|
||||||
out: "Stars but not pointers: Header=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "nil pointer: {*NilPtr}={*Integer}\n",
|
|
||||||
|
|
||||||
out: "nil pointer: <nil>=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: `{"Strings" ":"} {""} {"|"} {"\t\u0123 \x23\\"} {"\"}{\\"}`,
|
|
||||||
|
|
||||||
out: "Strings: | \t\u0123 \x23\\ \"}{\\",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{`Raw strings` `:`} {``} {`|`} {`\\t\\u0123 \\x23\\`} {`}{\\`}",
|
|
||||||
|
|
||||||
out: "Raw strings: | \\t\\u0123 \\x23\\ }{\\",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "Characters: {'a'} {'\\u0123'} {' '} {'{'} {'|'} {'}'}",
|
|
||||||
|
|
||||||
out: "Characters: 97 291 32 123 124 125",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "Integers: {1} {-2} {+42} {0777} {0x0a}",
|
|
||||||
|
|
||||||
out: "Integers: 1 -2 42 511 10",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "Floats: {.5} {-.5} {1.1} {-2.2} {+42.1} {1e10} {1.2e-3} {1.2e3} {-1.2e3}",
|
|
||||||
|
|
||||||
out: "Floats: 0.5 -0.5 1.1 -2.2 42.1 1e+10 0.0012 1200 -1200",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Method at top level
|
|
||||||
{
|
|
||||||
in: "ptrmethod={PointerMethod}\n",
|
|
||||||
|
|
||||||
out: "ptrmethod=ptrmethod!\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "valmethod={ValueMethod}\n",
|
|
||||||
|
|
||||||
out: "valmethod=valmethod!\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Section
|
|
||||||
{
|
|
||||||
in: "{.section Data }\n" +
|
|
||||||
"some text for the section\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "some text for the section\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Data }\n" +
|
|
||||||
"{Header}={Integer}\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "Header=77\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Pdata }\n" +
|
|
||||||
"{Header}={Integer}\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "Header=77\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Pdata }\n" +
|
|
||||||
"data present\n" +
|
|
||||||
"{.or}\n" +
|
|
||||||
"data not present\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "data present\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Empty }\n" +
|
|
||||||
"data present\n" +
|
|
||||||
"{.or}\n" +
|
|
||||||
"data not present\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "data not present\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Null }\n" +
|
|
||||||
"data present\n" +
|
|
||||||
"{.or}\n" +
|
|
||||||
"data not present\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "data not present\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Pdata }\n" +
|
|
||||||
"{Header}={Integer}\n" +
|
|
||||||
"{.section @ }\n" +
|
|
||||||
"{Header}={Integer}\n" +
|
|
||||||
"{.end}\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "Header=77\n" +
|
|
||||||
"Header=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{.section Data}{.end} {Header}\n",
|
|
||||||
|
|
||||||
out: " Header\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{.section Integer}{@}{.end}",
|
|
||||||
|
|
||||||
out: "77",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Repeated
|
|
||||||
{
|
|
||||||
in: "{.section Pdata }\n" +
|
|
||||||
"{.repeated section @ }\n" +
|
|
||||||
"{Item}={Value}\n" +
|
|
||||||
"{.end}\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "ItemNumber1=ValueNumber1\n" +
|
|
||||||
"ItemNumber2=ValueNumber2\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Vec }\n" +
|
|
||||||
"{@}\n" +
|
|
||||||
"{.end}\n",
|
|
||||||
|
|
||||||
out: "elt1\n" +
|
|
||||||
"elt2\n",
|
|
||||||
},
|
|
||||||
// Same but with a space before {.end}: was a bug.
|
|
||||||
{
|
|
||||||
in: "{.repeated section Vec }\n" +
|
|
||||||
"{@} {.end}\n",
|
|
||||||
|
|
||||||
out: "elt1 elt2 \n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Integer}{.end}",
|
|
||||||
|
|
||||||
err: "line 1: .repeated: cannot repeat Integer (type int)",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Nested names
|
|
||||||
{
|
|
||||||
in: "{.section @ }\n" +
|
|
||||||
"{InnerT.Item}={InnerT.Value}\n" +
|
|
||||||
"{.end}",
|
|
||||||
|
|
||||||
out: "ItemNumber1=ValueNumber1\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section @ }\n" +
|
|
||||||
"{InnerT.Item}={.section InnerT}{.section Value}{@}{.end}{.end}\n" +
|
|
||||||
"{.end}",
|
|
||||||
|
|
||||||
out: "ItemNumber1=ValueNumber1\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{.section Emptystring}emptystring{.end}\n" +
|
|
||||||
"{.section Header}header{.end}\n",
|
|
||||||
|
|
||||||
out: "\nheader\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{.section True}1{.or}2{.end}\n" +
|
|
||||||
"{.section False}3{.or}4{.end}\n",
|
|
||||||
|
|
||||||
out: "1\n4\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Maps
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{Mp.mapkey}\n",
|
|
||||||
|
|
||||||
out: "Ahoy!\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Innermap.Mp.innerkey}\n",
|
|
||||||
|
|
||||||
out: "55\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Innermap}{.section Mp}{innerkey}{.end}{.end}\n",
|
|
||||||
|
|
||||||
out: "55\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section JSON}{.repeated section maps}{a}{b}{.end}{.end}\n",
|
|
||||||
|
|
||||||
out: "1234\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Stringmap.stringkey1}\n",
|
|
||||||
|
|
||||||
out: "stringresult\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Stringmap}\n" +
|
|
||||||
"{@}\n" +
|
|
||||||
"{.end}",
|
|
||||||
|
|
||||||
out: "stringresult\n" +
|
|
||||||
"stringresult\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Stringmap}\n" +
|
|
||||||
"\t{@}\n" +
|
|
||||||
"{.end}",
|
|
||||||
|
|
||||||
out: "\tstringresult\n" +
|
|
||||||
"\tstringresult\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{*Ptrmap.stringkey1}\n",
|
|
||||||
|
|
||||||
out: "pointedToString\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Ptrmap}\n" +
|
|
||||||
"{*@}\n" +
|
|
||||||
"{.end}",
|
|
||||||
|
|
||||||
out: "pointedToString\n" +
|
|
||||||
"pointedToString\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Interface values
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{Iface}",
|
|
||||||
|
|
||||||
out: "[1 2 3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.repeated section Iface}{@}{.alternates with} {.end}",
|
|
||||||
|
|
||||||
out: "1 2 3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Iface}{@}{.end}",
|
|
||||||
|
|
||||||
out: "[1 2 3]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{.section Ifaceptr}{Item} {Value}{.end}",
|
|
||||||
|
|
||||||
out: "Item Value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAll(t *testing.T) {
|
|
||||||
// Parse
|
|
||||||
testAll(t, func(test *Test) (*Template, error) { return Parse(test.in, formatters) })
|
|
||||||
// ParseFile
|
|
||||||
f, err := ioutil.TempFile("", "template-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
name := f.Name()
|
|
||||||
f.Close()
|
|
||||||
os.Remove(name)
|
|
||||||
}()
|
|
||||||
testAll(t, func(test *Test) (*Template, error) {
|
|
||||||
err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected write error:", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ParseFile(f.Name(), formatters)
|
|
||||||
})
|
|
||||||
// tmpl.ParseFile
|
|
||||||
testAll(t, func(test *Test) (*Template, error) {
|
|
||||||
err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected write error:", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tmpl := New(formatters)
|
|
||||||
return tmpl, tmpl.ParseFile(f.Name())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAll(t *testing.T, parseFunc func(*Test) (*Template, error)) {
|
|
||||||
s := new(S)
|
|
||||||
// initialized by hand for clarity.
|
|
||||||
s.Header = "Header"
|
|
||||||
s.HeaderPtr = &s.Header
|
|
||||||
s.Integer = 77
|
|
||||||
s.IntegerPtr = &s.Integer
|
|
||||||
s.InnerT = t1
|
|
||||||
s.Data = []T{t1, t2}
|
|
||||||
s.Pdata = []*T{&t1, &t2}
|
|
||||||
s.Empty = []*T{}
|
|
||||||
s.Null = nil
|
|
||||||
s.Vec = []interface{}{"elt1", "elt2"}
|
|
||||||
s.True = true
|
|
||||||
s.False = false
|
|
||||||
s.Mp = make(map[string]string)
|
|
||||||
s.Mp["mapkey"] = "Ahoy!"
|
|
||||||
json.Unmarshal([]byte(`{"maps":[{"a":1,"b":2},{"a":3,"b":4}]}`), &s.JSON)
|
|
||||||
s.Innermap.Mp = make(map[string]int)
|
|
||||||
s.Innermap.Mp["innerkey"] = 55
|
|
||||||
s.Stringmap = make(map[string]string)
|
|
||||||
s.Stringmap["stringkey1"] = "stringresult" // the same value so repeated section is order-independent
|
|
||||||
s.Stringmap["stringkey2"] = "stringresult"
|
|
||||||
s.Ptrmap = make(map[string]*string)
|
|
||||||
x := "pointedToString"
|
|
||||||
s.Ptrmap["stringkey1"] = &x // the same value so repeated section is order-independent
|
|
||||||
s.Ptrmap["stringkey2"] = &x
|
|
||||||
s.Iface = []int{1, 2, 3}
|
|
||||||
s.Ifaceptr = &T{"Item", "Value"}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for _, test := range tests {
|
|
||||||
buf.Reset()
|
|
||||||
tmpl, err := parseFunc(test)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected parse error: ", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&buf, s)
|
|
||||||
if test.err == "" {
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected execute error:", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected execute error %q, got nil", test.err)
|
|
||||||
} else if err.Error() != test.err {
|
|
||||||
t.Errorf("expected execute error %q, got %q", test.err, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buf.String() != test.out {
|
|
||||||
t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapDriverType(t *testing.T) {
|
|
||||||
mp := map[string]string{"footer": "Ahoy!"}
|
|
||||||
tmpl, err := Parse("template: {footer}", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = tmpl.Execute(&b, mp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected execute error:", err)
|
|
||||||
}
|
|
||||||
s := b.String()
|
|
||||||
expect := "template: Ahoy!"
|
|
||||||
if s != expect {
|
|
||||||
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapNoEntry(t *testing.T) {
|
|
||||||
mp := make(map[string]int)
|
|
||||||
tmpl, err := Parse("template: {notthere}!", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = tmpl.Execute(&b, mp)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected execute error:", err)
|
|
||||||
}
|
|
||||||
s := b.String()
|
|
||||||
expect := "template: 0!"
|
|
||||||
if s != expect {
|
|
||||||
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(&b, "hello")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected execute error:", err)
|
|
||||||
}
|
|
||||||
s := b.String()
|
|
||||||
expect := "template: hello"
|
|
||||||
if s != expect {
|
|
||||||
t.Errorf("failed passing string as data: expected %q got %q", expect, 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(&b, "hello")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
s := b.String()
|
|
||||||
expect := "template: hello"
|
|
||||||
if s != expect {
|
|
||||||
t.Errorf("failed passing string as data: expected %q got %q", expect, s)
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&b, "hello")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
s = b.String()
|
|
||||||
expect += expect
|
|
||||||
if s != expect {
|
|
||||||
t.Errorf("failed passing string as data: expected %q got %q", expect, 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(&b, "hello")
|
|
||||||
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(&buf, s)
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTMLFormatterWithByte(t *testing.T) {
|
|
||||||
s := "Test string."
|
|
||||||
b := []byte(s)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
HTMLFormatter(&buf, "", b)
|
|
||||||
bs := buf.String()
|
|
||||||
if bs != s {
|
|
||||||
t.Errorf("munged []byte, expected: %s got: %s", s, bs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type UF struct {
|
|
||||||
I int
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReferenceToUnexported(t *testing.T) {
|
|
||||||
u := &UF{3, "hello"}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
input := "{.section @}{I}{s}{.end}"
|
|
||||||
tmpl, err := Parse(input, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(&buf, u)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected execute error, got none")
|
|
||||||
}
|
|
||||||
if strings.Index(err.Error(), "not exported") < 0 {
|
|
||||||
t.Fatal("expected unexported error; got", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var formatterTests = []Test{
|
|
||||||
{
|
|
||||||
in: "{Header|uppercase}={Integer|+1}\n" +
|
|
||||||
"{Header|html}={Integer|str}\n",
|
|
||||||
|
|
||||||
out: "HEADER=78\n" +
|
|
||||||
"Header=77\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
in: "{Header|uppercase}={Integer Header|multiword}\n" +
|
|
||||||
"{Header|html}={Header Integer|multiword}\n" +
|
|
||||||
"{Header|html}={Header Integer}\n",
|
|
||||||
|
|
||||||
out: "HEADER=<77><Header>\n" +
|
|
||||||
"Header=<Header><77>\n" +
|
|
||||||
"Header=Header77\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Raw}\n" +
|
|
||||||
"{Raw|html}\n",
|
|
||||||
|
|
||||||
out: "a <&> b\n" +
|
|
||||||
"a <&> b\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Bytes}",
|
|
||||||
out: "hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Raw|uppercase|html|html}",
|
|
||||||
out: "A &lt;&amp;&gt; B",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Header Integer|multiword|html}",
|
|
||||||
out: "<Header><77>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Integer|no_formatter|html}",
|
|
||||||
err: `unknown formatter: "no_formatter"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: "{Integer|||||}", // empty string is a valid formatter
|
|
||||||
out: "77",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: `{"%.02f 0x%02X" 1.1 10|printf}`,
|
|
||||||
out: "1.10 0x0A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: `{""|}{""||}{""|printf}`, // Issue #1896.
|
|
||||||
out: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatters(t *testing.T) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"Header": "Header",
|
|
||||||
"Integer": 77,
|
|
||||||
"Raw": "a <&> b",
|
|
||||||
"Bytes": []byte("hello"),
|
|
||||||
}
|
|
||||||
for _, c := range formatterTests {
|
|
||||||
tmpl, err := Parse(c.in, formatters)
|
|
||||||
if err != nil {
|
|
||||||
if c.err == "" {
|
|
||||||
t.Error("unexpected parse error:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Index(err.Error(), c.err) < 0 {
|
|
||||||
t.Errorf("unexpected error: expected %q, got %q", c.err, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if c.err != "" {
|
|
||||||
t.Errorf("For %q, expected error, got none.", c.in)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err = tmpl.Execute(&buf, data)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected Execute error: ", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
actual := buf.String()
|
|
||||||
if actual != c.out {
|
|
||||||
t.Errorf("for %q: expected %q but got %q.", c.in, c.out, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user