You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
2.8 KiB
Go
146 lines
2.8 KiB
Go
4 years ago
|
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
|
||
|
}
|