133 lines
3.3 KiB
Go
133 lines
3.3 KiB
Go
package interpolate
|
|
|
|
import (
|
|
"bytes"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Context is the context that an interpolation is done in. This defines
|
|
// things such as available variables.
|
|
type Context struct {
|
|
// Data is the data for the template that is available
|
|
Data interface{}
|
|
|
|
// Funcs are extra functions available in the template
|
|
Funcs map[string]interface{}
|
|
|
|
// UserVariables is the mapping of user variables that the
|
|
// "user" function reads from.
|
|
UserVariables map[string]string
|
|
|
|
// SensitiveVariables is a list of variables to sanitize.
|
|
SensitiveVariables []string
|
|
|
|
// EnableEnv enables the env function
|
|
EnableEnv bool
|
|
|
|
// All the fields below are used for built-in functions.
|
|
//
|
|
// BuildName and BuildType are the name and type, respectively,
|
|
// of the builder being used.
|
|
//
|
|
// TemplatePath is the path to the template that this is being
|
|
// rendered within.
|
|
BuildName string
|
|
BuildType string
|
|
TemplatePath string
|
|
}
|
|
|
|
// NewContext returns an initialized empty context.
|
|
func NewContext() *Context {
|
|
return &Context{}
|
|
}
|
|
|
|
// RenderOnce is shorthand for constructing an I and calling Render one time.
|
|
func RenderOnce(v string, ctx *Context) (string, error) {
|
|
return (&I{Value: v}).Render(ctx)
|
|
}
|
|
|
|
// Render is shorthand for constructing an I and calling Render until all variables are rendered.
|
|
func Render(v string, ctx *Context) (rendered string, err error) {
|
|
// Keep interpolating until all variables are done
|
|
// Sometimes a variable can been inside another one
|
|
for {
|
|
rendered, err = (&I{Value: v}).Render(ctx)
|
|
if err != nil || rendered == v {
|
|
break
|
|
}
|
|
v = rendered
|
|
}
|
|
return
|
|
}
|
|
|
|
// Render is shorthand for constructing an I and calling Render.
|
|
// Use regex to filter variables that are not supposed to be interpolated now
|
|
func RenderRegex(v string, ctx *Context, regex string) (string, error) {
|
|
re := regexp.MustCompile(regex)
|
|
matches := re.FindAllStringSubmatch(v, -1)
|
|
|
|
// Replace variables to be excluded with a unique UUID
|
|
excluded := make(map[string]string)
|
|
for _, value := range matches {
|
|
id := uuid.New().String()
|
|
excluded[id] = value[0]
|
|
v = strings.ReplaceAll(v, value[0], id)
|
|
}
|
|
|
|
rendered, err := (&I{Value: v}).Render(ctx)
|
|
if err != nil {
|
|
return rendered, err
|
|
}
|
|
|
|
// Replace back by the UUID the previously excluded values
|
|
for id, value := range excluded {
|
|
rendered = strings.ReplaceAll(rendered, id, value)
|
|
}
|
|
|
|
return rendered, nil
|
|
}
|
|
|
|
// Validate is shorthand for constructing an I and calling Validate.
|
|
func Validate(v string, ctx *Context) error {
|
|
return (&I{Value: v}).Validate(ctx)
|
|
}
|
|
|
|
// I stands for "interpolation" and is the main interpolation struct
|
|
// in order to render values.
|
|
type I struct {
|
|
Value string
|
|
}
|
|
|
|
// Render renders the interpolation with the given context.
|
|
func (i *I) Render(ictx *Context) (string, error) {
|
|
tpl, err := i.template(ictx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var result bytes.Buffer
|
|
var data interface{}
|
|
if ictx != nil {
|
|
data = ictx.Data
|
|
}
|
|
if err := tpl.Execute(&result, data); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return result.String(), nil
|
|
}
|
|
|
|
// Validate validates that the template is syntactically valid.
|
|
func (i *I) Validate(ctx *Context) error {
|
|
_, err := i.template(ctx)
|
|
return err
|
|
}
|
|
|
|
func (i *I) template(ctx *Context) (*template.Template, error) {
|
|
return template.New("root").Funcs(Funcs(ctx)).Parse(i.Value)
|
|
}
|