feat(templates): generalised templating from file contents to any gonf value

This commit is contained in:
Julien Dessaux 2024-02-10 09:34:02 +01:00
parent 325f6e2130
commit c1d2b8912d
Signed by: adyxax
GPG key ID: F92E51B86E07177E
4 changed files with 99 additions and 69 deletions

View file

@ -5,9 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"io" "io"
"log/slog" "log/slog"
"net/url"
"os" "os"
"text/template"
) )
// ----- Globals --------------------------------------------------------------- // ----- Globals ---------------------------------------------------------------
@ -19,28 +17,24 @@ func init() {
} }
// ----- Public ---------------------------------------------------------------- // ----- Public ----------------------------------------------------------------
func File(filename string, contents []byte) *FilePromise { type FilePromise struct {
chain []Promise
contents Value
err error
filename Value
status Status
}
func File(filename Value, contents Value) *FilePromise {
return &FilePromise{ return &FilePromise{
chain: nil, chain: nil,
contents: contents, contents: contents,
err: nil, err: nil,
filename: filename, filename: filename,
status: PROMISED, status: PROMISED,
templateFunctions: nil,
useTemplate: false,
} }
} }
type FilePromise struct {
chain []Promise
contents []byte
err error
filename string
status Status
templateFunctions map[string]any
useTemplate bool
}
func (f *FilePromise) IfRepaired(p ...Promise) Promise { func (f *FilePromise) IfRepaired(p ...Promise) Promise {
f.chain = p f.chain = p
return f return f
@ -52,34 +46,17 @@ func (f *FilePromise) Promise() Promise {
} }
func (f *FilePromise) Resolve() { func (f *FilePromise) Resolve() {
if f.useTemplate { filename := f.filename.String()
tpl := template.New(f.filename)
tpl.Option("missingkey=error")
tpl.Funcs(builtinTemplateFunctions)
tpl.Funcs(f.templateFunctions)
if ttpl, err := tpl.Parse(string(f.contents)); err != nil {
f.status = BROKEN
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
} else {
var buff bytes.Buffer
if err := ttpl.Execute(&buff, 0 /* TODO */); err != nil {
f.status = BROKEN
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
}
f.contents = buff.Bytes()
}
}
var sumFile []byte var sumFile []byte
sumFile, f.err = sha256sumOfFile(f.filename) sumFile, f.err = sha256sumOfFile(filename)
if f.err != nil { if f.err != nil {
f.status = BROKEN f.status = BROKEN
return return
} }
sumContents := sha256sum(f.contents) contents := f.contents.Bytes()
sumContents := sha256sum(contents)
if !bytes.Equal(sumFile, sumContents) { if !bytes.Equal(sumFile, sumContents) {
if f.err = writeFile(f.filename, f.contents); f.err != nil { if f.err = writeFile(filename, contents); f.err != nil {
f.status = BROKEN f.status = BROKEN
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err) slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return return
@ -95,24 +72,7 @@ func (f *FilePromise) Resolve() {
slog.Debug("file", "filename", f.filename, "status", f.status) slog.Debug("file", "filename", f.filename, "status", f.status)
} }
func Template(filename string, contents []byte) *FilePromise {
f := File(filename, contents)
f.useTemplate = true
return f
}
func TemplateWith(filename string, contents []byte, templateFunctions map[string]any) *FilePromise {
f := Template(filename, contents)
f.templateFunctions = templateFunctions
return f
}
// ----- Internal -------------------------------------------------------------- // ----- Internal --------------------------------------------------------------
var builtinTemplateFunctions = map[string]any{
"encodeURIQueryParameter": url.QueryEscape,
"var": getVariable,
}
func resolveFiles() (status Status) { func resolveFiles() (status Status) {
status = KEPT status = KEPT
for _, f := range files { for _, f := range files {

45
gonf/templates.go Normal file
View file

@ -0,0 +1,45 @@
package gonf
import (
"bytes"
"log/slog"
"text/template"
)
// ----- Globals ---------------------------------------------------------------
var templates *template.Template
// ----- Init ------------------------------------------------------------------
func init() {
templates = template.New("")
templates.Option("missingkey=error")
templates.Funcs(builtinTemplateFunctions)
}
// ----- Public ----------------------------------------------------------------
type TemplateValue struct {
contents []byte
name string
}
func (t *TemplateValue) Bytes() []byte {
var buff bytes.Buffer
if err := templates.ExecuteTemplate(&buff, t.name, nil /* no data needed */); err != nil {
slog.Error("template", "step", "ExecuteTemplate", "name", t.name, "error", err)
return nil
}
return buff.Bytes()
}
func (t TemplateValue) String() string {
return string(t.Bytes()[:])
}
func Template(name string, contents []byte) *TemplateValue {
tpl := templates.New(name)
if _, err := tpl.Parse(string(contents)); err != nil {
slog.Error("template", "step", "Parse", "name", name, "error", err)
return nil
}
return &TemplateValue{contents, name}
}

View file

@ -1,6 +1,13 @@
package gonf package gonf
import "crypto/sha256" import (
"crypto/sha256"
)
var builtinTemplateFunctions = map[string]any{
//"encodeURIQueryParameter": url.QueryEscape,
"var": getVariable,
}
func sha256sum(contents []byte) []byte { func sha256sum(contents []byte) []byte {
h := sha256.New() h := sha256.New()

View file

@ -1,23 +1,41 @@
package gonf package gonf
type Value interface { type Value interface {
Equals(Value) bool Bytes() []byte
String() string String() string
} }
// ----- String variables ------------------------------------------------------ // ----- BytesValue -----------------------------------------------------------------
type BytesValue struct {
value []byte
}
func (b BytesValue) Bytes() []byte {
return b.value
}
func (b BytesValue) String() string {
return string(b.value[:])
}
func Bytes(value []byte) *BytesValue {
return &BytesValue{value}
}
// ----- StringValue ----------------------------------------------------------------
type StringValue struct { type StringValue struct {
Value string value string
} }
func (s StringValue) Equals(v Value) bool { func (s StringValue) Bytes() []byte {
sv, ok := v.(StringValue) return []byte(s.value)
return ok && s.Value == sv.Value
} }
func (s StringValue) String() string { func (s StringValue) String() string {
// TODO handle interpolation return s.value
return s.Value }
func String(value string) *StringValue {
return &StringValue{value}
} }
// TODO lists // TODO lists