Convert POC package to tested version
Includes a Templates struct as a helper to generate templates using an io/fs filesystem interface. This adds options not available in the standard ParseFS provided by the Go standard library. • Only the base name is used for the name of templates. This means you can only look up one themplate by name from two different directories. For example, if directory "a" and directory "b" both have a template index.html, there is no way to specify using index.html for "a" or from "b" • Supports stripping a root directory and trimming a suffix to make importing templates easier Includes two helper functions • AsString takes a template with data and executes the template and returns a strin. • String is like AsString, but will ignore errors
This commit is contained in:
parent
98383235f9
commit
fa1dca3fd6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
coverage/
|
||||||
11
Makefile
Normal file
11
Makefile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
SHELL = bash
|
||||||
|
|
||||||
|
.PHONY: coverage
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
[[ -d coverage ]] || mkdir coverage
|
||||||
|
go test -coverpkg=./... -coverprofile=coverage/index.out ./...
|
||||||
|
go tool cover -html=coverage/index.out
|
||||||
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module git.buddy.wtf/lib/tmpl
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/go-cmp v0.5.6
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
)
|
||||||
15
go.sum
Normal file
15
go.sum
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
132
templates.go
132
templates.go
@ -1,59 +1,31 @@
|
|||||||
package main
|
package tmpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
txttmpl "text/template"
|
txttmpl "text/template"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options for getting a template tree
|
// Templates for getting a template tree
|
||||||
type Options struct {
|
type Templates struct {
|
||||||
Globs []string
|
|
||||||
Prefix string
|
|
||||||
Suffix string
|
Suffix string
|
||||||
Root string
|
Root string
|
||||||
Funcs map[string]interface{}
|
Funcs map[string]interface{}
|
||||||
|
fs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Options) getRoot() string {
|
// HTML returns templates based on a filesystem
|
||||||
if o.Root == "" {
|
func (t Templates) HTML(name string) (*template.Template, error) {
|
||||||
return "."
|
|
||||||
}
|
|
||||||
return o.Root
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Options) matchesGlob(path string) (bool, error) {
|
|
||||||
if len(o.Globs) == 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
for _, glob := range o.Globs {
|
|
||||||
ok, err := filepath.Match(glob, path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Options) trim(path string) string {
|
|
||||||
return strings.TrimSuffix(strings.TrimPrefix(path, o.Prefix), o.Suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates returns templates based on a filesystem
|
|
||||||
func Templates(files fs.FS, opts Options) (*template.Template, error) {
|
|
||||||
var ret *template.Template
|
var ret *template.Template
|
||||||
parseTemplates := func(name string, body []byte) error {
|
parseTemplates := func(name string, body []byte) error {
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
if ret == nil {
|
if ret == nil {
|
||||||
tmpl = template.New(name)
|
tmpl = template.New(name)
|
||||||
if opts.Funcs != nil {
|
if t.Funcs != nil {
|
||||||
tmpl = tmpl.Funcs(template.FuncMap(opts.Funcs))
|
tmpl = tmpl.Funcs(template.FuncMap(t.Funcs))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tmpl = ret.New(name)
|
tmpl = ret.New(name)
|
||||||
@ -65,22 +37,24 @@ func Templates(files fs.FS, opts Options) (*template.Template, error) {
|
|||||||
ret = tmpl
|
ret = tmpl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err := t.walk(parseTemplates); err != nil {
|
||||||
if err := walk(files, opts, parseTemplates); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if t := ret.Lookup(name); t != nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextTemplates returns templates based on a filesystem
|
// Text returns templates based on a filesystem
|
||||||
func TextTemplates(files fs.FS, opts Options) (*txttmpl.Template, error) {
|
func (t Templates) Text(name string) (*txttmpl.Template, error) {
|
||||||
var ret *txttmpl.Template
|
var ret *txttmpl.Template
|
||||||
parseTemplates := func(name string, body []byte) error {
|
parseTemplates := func(name string, body []byte) error {
|
||||||
var tmpl *txttmpl.Template
|
var tmpl *txttmpl.Template
|
||||||
if ret == nil {
|
if ret == nil {
|
||||||
tmpl = txttmpl.New(name)
|
tmpl = txttmpl.New(name)
|
||||||
if opts.Funcs != nil {
|
if t.Funcs != nil {
|
||||||
tmpl = tmpl.Funcs(txttmpl.FuncMap(opts.Funcs))
|
tmpl = tmpl.Funcs(txttmpl.FuncMap(t.Funcs))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tmpl = ret.New(name)
|
tmpl = ret.New(name)
|
||||||
@ -93,54 +67,60 @@ func TextTemplates(files fs.FS, opts Options) (*txttmpl.Template, error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := walk(files, opts, parseTemplates); err != nil {
|
if err := t.walk(parseTemplates); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t := ret.Lookup(name); t != nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of files
|
||||||
|
func (t Templates) List() ([]string, error) {
|
||||||
|
ret := []string{}
|
||||||
|
err := t.walk(func(name string, _ []byte) error {
|
||||||
|
ret = append(ret, name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func walk(files fs.FS, opts Options, handle func(string, []byte) error) error {
|
func (t Templates) walk(handle func(string, []byte) error) error {
|
||||||
walker := func(path string, d fs.DirEntry, er error) (err error) {
|
return fs.WalkDir(t.FS, t.getRoot(), func(path string, d fs.DirEntry, er error) (err error) {
|
||||||
if er != nil {
|
if er != nil {
|
||||||
return er
|
return er
|
||||||
}
|
}
|
||||||
matches, err := opts.matchesGlob(path)
|
if d.IsDir() {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !matches {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
body, err := readfile(files, path)
|
if !strings.HasSuffix(path, t.Suffix) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
body, err := fs.ReadFile(t, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handle(opts.trim(path), body); err != nil {
|
if !utf8.Valid(body) {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return handle(t.trim(path), body)
|
||||||
}
|
})
|
||||||
|
|
||||||
if err := fs.WalkDir(files, opts.getRoot(), walker); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readfile(files fs.FS, path string) ([]byte, error) {
|
func (t Templates) getRoot() string {
|
||||||
f, err := files.Open(path)
|
if t.Root == "" {
|
||||||
if err != nil {
|
return "."
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(f)
|
return filepath.Clean(t.Root)
|
||||||
if err != nil {
|
}
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
return nil, err
|
func (t Templates) trim(path string) string {
|
||||||
}
|
cleaned := strings.TrimPrefix(filepath.Clean(path), filepath.Clean(t.Root))
|
||||||
return nil, err
|
cleaned = strings.TrimPrefix(cleaned, "/")
|
||||||
}
|
cleaned = strings.TrimSuffix(cleaned, t.Suffix)
|
||||||
if err := f.Close(); err != nil {
|
return strings.TrimSuffix(cleaned, ".")
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return body, nil
|
|
||||||
}
|
}
|
||||||
1
tests/data/broken.invalid
Normal file
1
tests/data/broken.invalid
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ invalid go template
|
||||||
1
tests/data/broken.template
Normal file
1
tests/data/broken.template
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ return_error }}
|
||||||
1
tests/data/first/1/bar.txt.tmpl
Normal file
1
tests/data/first/1/bar.txt.tmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
bar template: {{ . | upper -}}
|
||||||
1
tests/data/first/1/foo.txt.tmpl
Normal file
1
tests/data/first/1/foo.txt.tmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo template: {{ . -}}
|
||||||
0
tests/data/first/2/item.txt.tmpl
Normal file
0
tests/data/first/2/item.txt.tmpl
Normal file
0
tests/data/first/2/list.txt.tmpl
Normal file
0
tests/data/first/2/list.txt.tmpl
Normal file
3
tests/data/first/index.txt.tmpl
Normal file
3
tests/data/first/index.txt.tmpl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## First
|
||||||
|
Foo: {{ template "first/1/foo.txt" . }}
|
||||||
|
Bar: {{ template "first/1/bar.txt" . -}}
|
||||||
0
tests/data/first/static.txt
Normal file
0
tests/data/first/static.txt
Normal file
9
tests/data/index.html.tmpl
Normal file
9
tests/data/index.html.tmpl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "second/index" . -}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2
tests/data/index.txt.tmpl
Normal file
2
tests/data/index.txt.tmpl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# root index
|
||||||
|
{{ template "first/index.txt" . }}
|
||||||
3
tests/data/second/1/list.html.tmpl
Normal file
3
tests/data/second/1/list.html.tmpl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<li>
|
||||||
|
<ul>{{- . | upper -}}</ul>
|
||||||
|
</li>
|
||||||
1
tests/data/second/1/single.html.tmpl
Normal file
1
tests/data/second/1/single.html.tmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<span>single: <a>{{ . }}</a></span>
|
||||||
9
tests/data/second/index.html.tmpl
Normal file
9
tests/data/second/index.html.tmpl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
{{ template "second/1/list" . }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ template "second/1/single" . }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
0
tests/data/second/static.txt
Normal file
0
tests/data/second/static.txt
Normal file
BIN
tests/data/tiny.png
Normal file
BIN
tests/data/tiny.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 B |
18
tests/expect/index-simple.html
Normal file
18
tests/expect/index-simple.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
<li>
|
||||||
|
<ul>TEST</ul>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>single: <a>test</a></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
tests/expect/root-text-template.txt
Normal file
4
tests/expect/root-text-template.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# root index
|
||||||
|
## First
|
||||||
|
Foo: foo template: test
|
||||||
|
Bar: bar template: TEST
|
||||||
4
tests/expect/rootTextTemplate.txt
Normal file
4
tests/expect/rootTextTemplate.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# root index
|
||||||
|
## First
|
||||||
|
Foo: foo template: test
|
||||||
|
Bar: bar template: TEST
|
||||||
88
tests/html_test.go
Normal file
88
tests/html_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package templtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.buddy.wtf/lib/tmpl"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed expect/index-simple.html
|
||||||
|
var expectdIndexHTML string
|
||||||
|
|
||||||
|
func TestHTML(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
Name string
|
||||||
|
tmpl.Templates
|
||||||
|
Expected string
|
||||||
|
Template string
|
||||||
|
Data interface{}
|
||||||
|
Err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tt := []testcase{
|
||||||
|
{
|
||||||
|
Name: "get template by name",
|
||||||
|
Expected: expectdIndexHTML,
|
||||||
|
Template: "index",
|
||||||
|
Data: "test",
|
||||||
|
Templates: tmpl.Templates{
|
||||||
|
FS: data,
|
||||||
|
Root: "data",
|
||||||
|
Suffix: "html.tmpl",
|
||||||
|
Funcs: map[string]interface{}{
|
||||||
|
"upper": strings.ToUpper,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "error on invalid template",
|
||||||
|
Err: true,
|
||||||
|
Template: "borked",
|
||||||
|
Templates: tmpl.Templates{FS: data, Suffix: "invalid"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "error calling invalid template",
|
||||||
|
Template: "borked",
|
||||||
|
Templates: tmpl.Templates{
|
||||||
|
FS: data,
|
||||||
|
Suffix: "template",
|
||||||
|
Funcs: map[string]interface{}{
|
||||||
|
"return_error": func() (string, error) {
|
||||||
|
return "invalid", errors.New("error running template")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
a := assert.New(t)
|
||||||
|
template, err := tc.Templates.HTML(tc.Template)
|
||||||
|
if tc.Err {
|
||||||
|
a.Error(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
a.NoError(err)
|
||||||
|
}
|
||||||
|
actual := tmpl.String(template, tc.Data)
|
||||||
|
a.Empty(cmp.Diff(singlespace(actual), singlespace(tc.Expected)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var spacesRegex = regexp.MustCompile(`\s+`)
|
||||||
|
|
||||||
|
func singlespace(in string) string {
|
||||||
|
return strings.Join(spacesRegex.Split(in, -1), " ")
|
||||||
|
}
|
||||||
73
tests/list_test.go
Normal file
73
tests/list_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package templtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.buddy.wtf/lib/tmpl"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
Name string
|
||||||
|
tmpl.Templates
|
||||||
|
Expected []string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
tt := []testcase{
|
||||||
|
{
|
||||||
|
Name: "list all files on empty config ",
|
||||||
|
Templates: tmpl.Templates{FS: data},
|
||||||
|
Expected: []string{
|
||||||
|
"data/broken.invalid",
|
||||||
|
"data/broken.template",
|
||||||
|
"data/first/1/bar.txt.tmpl",
|
||||||
|
"data/first/1/foo.txt.tmpl",
|
||||||
|
"data/first/2/item.txt.tmpl",
|
||||||
|
"data/first/2/list.txt.tmpl",
|
||||||
|
"data/first/index.txt.tmpl",
|
||||||
|
"data/first/static.txt",
|
||||||
|
"data/index.html.tmpl",
|
||||||
|
"data/index.txt.tmpl",
|
||||||
|
"data/second/1/list.html.tmpl",
|
||||||
|
"data/second/1/single.html.tmpl",
|
||||||
|
"data/second/index.html.tmpl",
|
||||||
|
"data/second/static.txt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "only list templates with matching suffix with suffix not in name",
|
||||||
|
Templates: tmpl.Templates{
|
||||||
|
FS: data,
|
||||||
|
Root: "data",
|
||||||
|
Suffix: "tmpl",
|
||||||
|
},
|
||||||
|
Expected: []string{
|
||||||
|
"first/1/bar.txt",
|
||||||
|
"first/1/foo.txt",
|
||||||
|
"first/2/item.txt",
|
||||||
|
"first/2/list.txt",
|
||||||
|
"first/index.txt",
|
||||||
|
"index.html",
|
||||||
|
"index.txt",
|
||||||
|
"second/1/list.html",
|
||||||
|
"second/1/single.html",
|
||||||
|
"second/index.html",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
a := assert.New(t)
|
||||||
|
actual, err := tc.Templates.List()
|
||||||
|
errorIs(a, err, tc.Err)
|
||||||
|
a.Empty(cmp.Diff(tc.Expected, actual))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
81
tests/text_test.go
Normal file
81
tests/text_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package templtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.buddy.wtf/lib/tmpl"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed expect/root-text-template.txt
|
||||||
|
var expectdRootText string
|
||||||
|
|
||||||
|
func TestText(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
Name string
|
||||||
|
tmpl.Templates
|
||||||
|
Expected string
|
||||||
|
Template string
|
||||||
|
Data interface{}
|
||||||
|
Err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tt := []testcase{
|
||||||
|
{
|
||||||
|
Name: "get template by name",
|
||||||
|
Expected: expectdRootText,
|
||||||
|
Template: "index.txt",
|
||||||
|
Data: "test",
|
||||||
|
Templates: tmpl.Templates{
|
||||||
|
FS: data,
|
||||||
|
Root: "data",
|
||||||
|
Suffix: "tmpl",
|
||||||
|
Funcs: map[string]interface{}{
|
||||||
|
"upper": strings.ToUpper,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "error on invalid template",
|
||||||
|
Err: true,
|
||||||
|
Template: "broken",
|
||||||
|
Templates: tmpl.Templates{FS: data, Suffix: "invalid"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "error calling invalid template",
|
||||||
|
Template: "borked",
|
||||||
|
Templates: tmpl.Templates{
|
||||||
|
FS: data,
|
||||||
|
Suffix: "template",
|
||||||
|
Funcs: map[string]interface{}{
|
||||||
|
"return_error": func() (string, error) {
|
||||||
|
return "invalid", errors.New("error running template")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
a := assert.New(t)
|
||||||
|
template, err := tc.Templates.Text(tc.Template)
|
||||||
|
if tc.Err {
|
||||||
|
a.Error(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
a.NoError(err)
|
||||||
|
}
|
||||||
|
actual := tmpl.String(template, tc.Data)
|
||||||
|
a.Empty(cmp.Diff(actual, tc.Expected))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tests/utils.go
Normal file
15
tests/utils.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package templtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed data
|
||||||
|
var data embed.FS
|
||||||
|
|
||||||
|
func errorIs(a interface {
|
||||||
|
Truef(value bool, msg string, args ...interface{}) bool
|
||||||
|
}, actual, expected error) {
|
||||||
|
a.Truef(errors.Is(actual, expected), "expected error %v to be %v", actual, expected)
|
||||||
|
}
|
||||||
28
utils.go
Normal file
28
utils.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package tmpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Executor interface {
|
||||||
|
Execute(wr io.Writer, data interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsString to convert template to string
|
||||||
|
func AsString(t Executor, data interface{}) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.Execute(&buf, data); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String to convert template to string and ignores errors
|
||||||
|
func String(t Executor, data interface{}) string {
|
||||||
|
if result, err := AsString(t, data); err == nil {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user