diff --git a/templates.go b/templates.go new file mode 100644 index 0000000..fe38919 --- /dev/null +++ b/templates.go @@ -0,0 +1,146 @@ +package main + +import ( + "html/template" + "io" + "io/fs" + "path/filepath" + "strings" + txttmpl "text/template" +) + +// Options for getting a template tree +type Options struct { + Globs []string + Prefix string + Suffix string + Root string + Funcs map[string]interface{} +} + +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) { + 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)) + } + } else { + tmpl = ret.New(name) + } + tmpl, err := tmpl.Parse(string(body)) + if err != nil { + return err + } + ret = tmpl + return nil + } + + if err := walk(files, opts, parseTemplates); err != nil { + return nil, err + } + return ret, nil +} + +// TextTemplates returns templates based on a filesystem +func TextTemplates(files fs.FS, opts Options) (*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)) + } + } else { + tmpl = ret.New(name) + } + tmpl, err := tmpl.Parse(string(body)) + if err != nil { + return err + } + ret = tmpl + return nil + } + + if err := walk(files, opts, parseTemplates); 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) { + if er != nil { + return er + } + matches, err := opts.matchesGlob(path) + if err != nil { + return err + } + if !matches { + return nil + } + body, err := readfile(files, path) + if err != nil { + return err + } + if err := handle(opts.trim(path), body); err != nil { + return err + } + return nil + } + + if err := fs.WalkDir(files, opts.getRoot(), walker); err != nil { + return err + } + return nil +} + +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