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 }