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 errorsmaster
parent
98383235f9
commit
fa1dca3fd6
@ -0,0 +1 @@
|
|||||||
|
coverage/
|
@ -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
|
@ -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
|
||||||
|
)
|
@ -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=
|
@ -0,0 +1 @@
|
|||||||
|
{{ invalid go template
|
@ -0,0 +1 @@
|
|||||||
|
{{ return_error }}
|
@ -0,0 +1 @@
|
|||||||
|
bar template: {{ . | upper -}}
|
@ -0,0 +1 @@
|
|||||||
|
foo template: {{ . -}}
|
@ -0,0 +1,3 @@
|
|||||||
|
## First
|
||||||
|
Foo: {{ template "first/1/foo.txt" . }}
|
||||||
|
Bar: {{ template "first/1/bar.txt" . -}}
|
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "second/index" . -}}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,2 @@
|
|||||||
|
# root index
|
||||||
|
{{ template "first/index.txt" . }}
|
@ -0,0 +1,3 @@
|
|||||||
|
<li>
|
||||||
|
<ul>{{- . | upper -}}</ul>
|
||||||
|
</li>
|
@ -0,0 +1 @@
|
|||||||
|
<span>single: <a>{{ . }}</a></span>
|
@ -0,0 +1,9 @@
|
|||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
{{ template "second/1/list" . }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ template "second/1/single" . }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 103 B |
@ -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>
|
@ -0,0 +1,4 @@
|
|||||||
|
# root index
|
||||||
|
## First
|
||||||
|
Foo: foo template: test
|
||||||
|
Bar: bar template: TEST
|
@ -0,0 +1,4 @@
|
|||||||
|
# root index
|
||||||
|
## First
|
||||||
|
Foo: foo template: test
|
||||||
|
Bar: bar template: TEST
|
@ -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), " ")
|
||||||
|
}
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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