diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..404abb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +coverage/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4bab277 --- /dev/null +++ b/Makefile @@ -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 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b41b956 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f212493 --- /dev/null +++ b/go.sum @@ -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= diff --git a/templates.go b/templates.go index fe38919..f03e456 100644 --- a/templates.go +++ b/templates.go @@ -1,59 +1,31 @@ -package main +package tmpl import ( "html/template" - "io" "io/fs" "path/filepath" "strings" txttmpl "text/template" + "unicode/utf8" ) -// Options for getting a template tree -type Options struct { - Globs []string - Prefix string +// Templates for getting a template tree +type Templates struct { Suffix string Root string Funcs map[string]interface{} + fs.FS } -func (o Options) getRoot() string { - if o.Root == "" { - 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) { +// HTML returns templates based on a filesystem +func (t Templates) HTML(name string) (*template.Template, error) { var ret *template.Template parseTemplates := func(name string, body []byte) error { var tmpl *template.Template if ret == nil { tmpl = template.New(name) - if opts.Funcs != nil { - tmpl = tmpl.Funcs(template.FuncMap(opts.Funcs)) + if t.Funcs != nil { + tmpl = tmpl.Funcs(template.FuncMap(t.Funcs)) } } else { tmpl = ret.New(name) @@ -65,22 +37,24 @@ func Templates(files fs.FS, opts Options) (*template.Template, error) { ret = tmpl 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 } -// TextTemplates returns templates based on a filesystem -func TextTemplates(files fs.FS, opts Options) (*txttmpl.Template, error) { +// Text returns templates based on a filesystem +func (t Templates) Text(name string) (*txttmpl.Template, error) { var ret *txttmpl.Template parseTemplates := func(name string, body []byte) error { var tmpl *txttmpl.Template if ret == nil { tmpl = txttmpl.New(name) - if opts.Funcs != nil { - tmpl = tmpl.Funcs(txttmpl.FuncMap(opts.Funcs)) + if t.Funcs != nil { + tmpl = tmpl.Funcs(txttmpl.FuncMap(t.Funcs)) } } else { tmpl = ret.New(name) @@ -93,54 +67,60 @@ func TextTemplates(files fs.FS, opts Options) (*txttmpl.Template, error) { 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 ret, nil } -func walk(files fs.FS, opts Options, handle func(string, []byte) error) error { - walker := func(path string, d fs.DirEntry, er error) (err error) { +func (t Templates) walk(handle func(string, []byte) error) error { + return fs.WalkDir(t.FS, t.getRoot(), func(path string, d fs.DirEntry, er error) (err error) { if er != nil { return er } - matches, err := opts.matchesGlob(path) - if err != nil { - return err + if d.IsDir() { + return nil } - if !matches { + if !strings.HasSuffix(path, t.Suffix) { return nil } - body, err := readfile(files, path) + body, err := fs.ReadFile(t, path) if err != nil { return err } - if err := handle(opts.trim(path), body); err != nil { - return err + if !utf8.Valid(body) { + return nil } - return nil - } + return handle(t.trim(path), body) + }) +} - if err := fs.WalkDir(files, opts.getRoot(), walker); err != nil { - return err +func (t Templates) getRoot() string { + if t.Root == "" { + return "." } - return nil + return filepath.Clean(t.Root) } -func readfile(files fs.FS, path string) ([]byte, error) { - f, err := files.Open(path) - if err != nil { - return nil, err - } - body, err := io.ReadAll(f) - if err != nil { - if err := f.Close(); err != nil { - return nil, err - } - return nil, err - } - if err := f.Close(); err != nil { - return nil, err - } - return body, nil -} \ No newline at end of file +func (t Templates) trim(path string) string { + cleaned := strings.TrimPrefix(filepath.Clean(path), filepath.Clean(t.Root)) + cleaned = strings.TrimPrefix(cleaned, "/") + cleaned = strings.TrimSuffix(cleaned, t.Suffix) + return strings.TrimSuffix(cleaned, ".") +} diff --git a/tests/data/broken.invalid b/tests/data/broken.invalid new file mode 100644 index 0000000..96c1122 --- /dev/null +++ b/tests/data/broken.invalid @@ -0,0 +1 @@ +{{ invalid go template diff --git a/tests/data/broken.template b/tests/data/broken.template new file mode 100644 index 0000000..07df186 --- /dev/null +++ b/tests/data/broken.template @@ -0,0 +1 @@ +{{ return_error }} diff --git a/tests/data/first/1/bar.txt.tmpl b/tests/data/first/1/bar.txt.tmpl new file mode 100644 index 0000000..24e3df5 --- /dev/null +++ b/tests/data/first/1/bar.txt.tmpl @@ -0,0 +1 @@ +bar template: {{ . | upper -}} diff --git a/tests/data/first/1/foo.txt.tmpl b/tests/data/first/1/foo.txt.tmpl new file mode 100644 index 0000000..0dc7d54 --- /dev/null +++ b/tests/data/first/1/foo.txt.tmpl @@ -0,0 +1 @@ +foo template: {{ . -}} diff --git a/tests/data/first/2/item.txt.tmpl b/tests/data/first/2/item.txt.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/first/2/list.txt.tmpl b/tests/data/first/2/list.txt.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/first/index.txt.tmpl b/tests/data/first/index.txt.tmpl new file mode 100644 index 0000000..d11156e --- /dev/null +++ b/tests/data/first/index.txt.tmpl @@ -0,0 +1,3 @@ +## First +Foo: {{ template "first/1/foo.txt" . }} +Bar: {{ template "first/1/bar.txt" . -}} diff --git a/tests/data/first/static.txt b/tests/data/first/static.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/index.html.tmpl b/tests/data/index.html.tmpl new file mode 100644 index 0000000..7ea941a --- /dev/null +++ b/tests/data/index.html.tmpl @@ -0,0 +1,9 @@ + + +
+