Initial commit

This commit is contained in:
2022-03-22 09:21:55 +01:00
commit ada7f18e36
49 changed files with 2635 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
{{define "form"}}
<form method="post">
<table width="100%">
<tbody>
<tr>
<td align="left" nowrap=""></td>
<td align="right" nowrap="">
<input type="submit" name="submit" value="Enregistrer" />
</td>
</tr>
</tbody>
</table>
<hr />
<strong>{{ .Schema.Title }}</strong>
<em>{{ .Schema.Description }}</em>
<hr />
{{template "form_item" .}}
</form>
{{end}}

View File

@@ -0,0 +1,10 @@
{{define "form_input"}}
{{ if .Schema.Types }}
{{ $root := . }}
{{range .Schema.Types}}
{{ $inputBlock := printf "%s_%s" "form_input" . }}
{{ include $inputBlock $root }}
{{end}}
{{ else }}
{{ end }}
{{end}}

View File

@@ -0,0 +1,31 @@
{{ define "form_input_array" }}
{{ $root := . }}
{{ $fullProperty := getFullProperty .Parent .Property }}
{{ $values := getValue .Defaults .Values $fullProperty }}
<table width="100%">
<tbody>
{{ range $index, $value := $values }}
{{ $itemFullProperty := printf "%s/%d" $fullProperty $index }}
{{ $itemProperty := printf "%d" $index }}
{{ $itemSchema := getItemSchema $root.Schema }}
{{ $formItemData := formItemData $root $itemProperty $itemSchema }}
<tr>
{{ template "form_row" $formItemData }}
</tr>
<tr>
<td colspan="3">
<input type="submit" name="del:{{ $fullProperty }}/{{$index}}" value="Supprimer" />
<hr />
</td>
</tr>
{{end}}
<tr>
<td colspan="2"></td>
<td align="right">
<input type="submit" name="add:{{ $fullProperty }}/-" value="Ajouter" />
</td>
</tr>
</tbody>
</table>
{{ end }}

View File

@@ -0,0 +1,12 @@
{{define "form_input_boolean"}}
{{ $fullProperty := getFullProperty .Parent .Property }}
{{ $checked := getValue .Defaults .Values $fullProperty }}
<label for="yes:{{ $fullProperty }}">
Yes
<input type="radio" id="yes:{{ $fullProperty }}" name="bool:{{ $fullProperty }}" value="yes" {{if $checked}}checked="yes"{{end}} />
</label>
<label for="no:{{ $fullProperty }}">
No
<input type="radio" id="no:{{ $fullProperty }}" name="bool:{{ $fullProperty }}" value="no" {{if not $checked}}checked{{end}} />
</label>
{{end}}

View File

@@ -0,0 +1,3 @@
{{define "form_input_integer"}}
{{template "form_input_number" .}}
{{end}}

View File

@@ -0,0 +1 @@
{{define "form_input_null"}}{{end}}

View File

@@ -0,0 +1,5 @@
{{define "form_input_number"}}
{{ $fullProperty := getFullProperty .Parent .Property }}
{{ $value := getValue .Defaults .Values $fullProperty }}
<input type="number" name="{{ $fullProperty }}" value="{{ $value }}" />
{{end}}

View File

@@ -0,0 +1,5 @@
{{define "form_input_object"}}
<br />
{{ $formItemData := formItemData . "" .Schema }}
{{template "form_item" $formItemData}}
{{end}}

View File

@@ -0,0 +1,5 @@
{{define "form_input_string"}}
{{ $fullProperty := getFullProperty .Parent .Property }}
{{ $value := getValue .Defaults .Values $fullProperty }}
<input type="text" name="{{ $fullProperty }}" id="{{ $fullProperty }}" value="{{ $value }}" />
{{end}}

View File

@@ -0,0 +1,11 @@
{{define "form_item"}}
<table width="100%">
<tbody>
{{ $root := .}}
{{ range $property, $schema := .Schema.Properties}}
{{ $formItemData := formItemData $root $property $schema }}
{{template "form_row" $formItemData}}
{{end}}
</tbody>
</table>
{{end}}

View File

@@ -0,0 +1,23 @@
{{define "form_row"}}
{{ $fullProperty := getFullProperty .Parent .Property }}
<tr>
<td align="left" nowrap="">
<label for="{{ $fullProperty }}">
<strong>
{{ if .Schema.Title }}{{ .Schema.Title }}{{ else }}{{ .Property }}{{ end }}
</strong>
<br />
<span>{{ .Schema.Description }}</span>
</label>
</td>
<td align="left" nowrap="">
{{template "form_input" .}}
</td>
<td>
{{ $err := getPropertyError .Error $fullProperty }}
{{if $err}}
<em>{{ $err.Message }}</em>
{{end}}
</td>
</tr>
{{end}}

View File

@@ -0,0 +1,10 @@
{{define "head"}}
<head>
<title>Formidable</title>
<style>
body {
padding: 10px;
}
</style>
</head>
{{end}}

View File

@@ -0,0 +1,6 @@
<html>
{{ template "head" . }}
<body>
{{ template "form" . }}
</body>
</html>

View File

@@ -0,0 +1,233 @@
package template
import (
"bytes"
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"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 (
//go:embed layouts/* blocks/*
files embed.FS
layouts map[string]*template.Template
blocks map[string]string
)
func Load() error {
if blocks == nil {
blocks = make(map[string]string)
}
blockFiles, err := fs.ReadDir(files, "blocks")
if err != nil {
return errors.WithStack(err)
}
for _, f := range blockFiles {
templateData, err := fs.ReadFile(files, "blocks/"+f.Name())
if err != nil {
return errors.WithStack(err)
}
blocks[f.Name()] = string(templateData)
}
layoutFiles, err := fs.ReadDir(files, "layouts")
if err != nil {
return errors.WithStack(err)
}
for _, f := range layoutFiles {
templateData, err := fs.ReadFile(files, "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{}
}
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(defaults, values interface{}, path string) (interface{}, error) {
if defaults == nil {
defaults = make(map[string]interface{})
}
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)
}
if errors.Is(err, jsonpointer.ErrNotFound) {
val, err = pointer.Get(defaults)
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
}