223 lines
4.8 KiB
Go
223 lines
4.8 KiB
Go
package template
|
|
|
|
import (
|
|
"bytes"
|
|
"embed"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/fs"
|
|
"path"
|
|
"strings"
|
|
|
|
"forge.cadoles.com/wpetit/formidable/internal/jsonpointer"
|
|
"github.com/Masterminds/sprig/v3"
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/pkg/errors"
|
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
|
)
|
|
|
|
var (
|
|
layouts map[string]*template.Template
|
|
blocks map[string]string
|
|
)
|
|
|
|
func Load(files embed.FS, baseDir string) error {
|
|
if blocks == nil {
|
|
blocks = make(map[string]string)
|
|
}
|
|
|
|
blockFiles, err := fs.ReadDir(files, path.Join(baseDir, "blocks"))
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
for _, f := range blockFiles {
|
|
templateData, err := fs.ReadFile(files, path.Join(baseDir, "blocks/"+f.Name()))
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
blocks[f.Name()] = string(templateData)
|
|
}
|
|
|
|
layoutFiles, err := fs.ReadDir(files, path.Join(baseDir, "layouts"))
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
for _, f := range layoutFiles {
|
|
templateData, err := fs.ReadFile(files, path.Join(baseDir, "layouts/"+f.Name()))
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
if err := loadLayout(f.Name(), string(templateData)); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func loadLayout(name string, rawTemplate string) error {
|
|
if layouts == nil {
|
|
layouts = make(map[string]*template.Template)
|
|
}
|
|
|
|
tmpl := template.New(name)
|
|
funcMap := mergeHelpers(
|
|
sprig.FuncMap(),
|
|
customHelpers(tmpl),
|
|
)
|
|
|
|
tmpl.Funcs(funcMap)
|
|
|
|
for blockName, b := range blocks {
|
|
if _, err := tmpl.Parse(b); err != nil {
|
|
return errors.Wrapf(err, "could not parse template block '%s'", blockName)
|
|
}
|
|
}
|
|
|
|
tmpl, err := tmpl.Parse(rawTemplate)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "could not parse template '%s'", name)
|
|
}
|
|
|
|
layouts[name] = tmpl
|
|
|
|
return nil
|
|
}
|
|
|
|
func Exec(name string, w io.Writer, data interface{}) error {
|
|
tmpl, exists := layouts[name]
|
|
if !exists {
|
|
return errors.Errorf("could not find template '%s'", name)
|
|
}
|
|
|
|
if err := tmpl.Execute(w, data); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func mergeHelpers(helpers ...template.FuncMap) template.FuncMap {
|
|
merged := template.FuncMap{}
|
|
|
|
for _, help := range helpers {
|
|
for name, fn := range help {
|
|
merged[name] = fn
|
|
}
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
type FormItemData struct {
|
|
Parent *FormItemData
|
|
Schema *jsonschema.Schema
|
|
Property string
|
|
Error *jsonschema.ValidationError
|
|
Values interface{}
|
|
Defaults interface{}
|
|
SuccessMessage string
|
|
}
|
|
|
|
func customHelpers(tpl *template.Template) template.FuncMap {
|
|
return template.FuncMap{
|
|
"formItemData": func(parent *FormItemData, property string, schema *jsonschema.Schema) *FormItemData {
|
|
return &FormItemData{
|
|
Parent: parent,
|
|
Property: property,
|
|
Schema: schema,
|
|
Defaults: parent.Defaults,
|
|
Values: parent.Values,
|
|
Error: parent.Error,
|
|
}
|
|
},
|
|
"dump": func(data interface{}) string {
|
|
spew.Dump(data)
|
|
|
|
return ""
|
|
},
|
|
"include": func(name string, data interface{}) (template.HTML, error) {
|
|
buf := bytes.NewBuffer([]byte{})
|
|
|
|
if err := tpl.ExecuteTemplate(buf, name, data); err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
|
|
return template.HTML(buf.String()), nil
|
|
},
|
|
"getFullProperty": func(parent *FormItemData, property string) string {
|
|
fullProperty := property
|
|
for {
|
|
fullProperty = fmt.Sprintf("%s/%s", parent.Property, strings.TrimPrefix(fullProperty, "/"))
|
|
parent = parent.Parent
|
|
if parent == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return fullProperty
|
|
},
|
|
"getValue": func(values interface{}, path string) (interface{}, error) {
|
|
if values == nil {
|
|
values = make(map[string]interface{})
|
|
}
|
|
|
|
pointer := jsonpointer.New(path)
|
|
|
|
val, err := pointer.Get(values)
|
|
if err != nil && !errors.Is(err, jsonpointer.ErrNotFound) {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return val, nil
|
|
},
|
|
"getItemSchema": func(arraySchema *jsonschema.Schema) (*jsonschema.Schema, error) {
|
|
itemSchema := arraySchema.Items
|
|
if itemSchema == nil {
|
|
itemSchema = arraySchema.Items2020
|
|
}
|
|
|
|
if itemSchema == nil {
|
|
return nil, errors.New("item schema not found")
|
|
}
|
|
|
|
switch schema := itemSchema.(type) {
|
|
case *jsonschema.Schema:
|
|
return schema, nil
|
|
case []*jsonschema.Schema:
|
|
if len(schema) > 0 {
|
|
return schema[0], nil
|
|
}
|
|
|
|
return nil, errors.New("no item schema found")
|
|
default:
|
|
return nil, errors.Errorf("unexpected schema type '%T'", schema)
|
|
}
|
|
},
|
|
"getPropertyError": findPropertyValidationError,
|
|
}
|
|
}
|
|
|
|
func findPropertyValidationError(err *jsonschema.ValidationError, property string) *jsonschema.ValidationError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if property == err.InstanceLocation {
|
|
return err
|
|
}
|
|
|
|
for _, cause := range err.Causes {
|
|
if err := findPropertyValidationError(cause, property); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|