template/interpolate: basic + some funcs
This commit is contained in:
parent
c8b3dfff5f
commit
ff6573ce10
|
@ -0,0 +1,46 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Funcs are the interpolation funcs that are available within interpolations.
|
||||
var FuncGens = map[string]FuncGenerator{
|
||||
"env": funcGenEnv,
|
||||
"user": funcGenUser,
|
||||
}
|
||||
|
||||
// FuncGenerator is a function that given a context generates a template
|
||||
// function for the template.
|
||||
type FuncGenerator func(*Context) interface{}
|
||||
|
||||
// Funcs returns the functions that can be used for interpolation given
|
||||
// a context.
|
||||
func Funcs(ctx *Context) template.FuncMap {
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range FuncGens {
|
||||
result[k] = v(ctx)
|
||||
}
|
||||
|
||||
return template.FuncMap(result)
|
||||
}
|
||||
|
||||
func funcGenEnv(ctx *Context) interface{} {
|
||||
return func(k string) (string, error) {
|
||||
if ctx.DisableEnv {
|
||||
// The error message doesn't have to be that detailed since
|
||||
// semantic checks should catch this.
|
||||
return "", errors.New("env vars are not allowed here")
|
||||
}
|
||||
|
||||
return os.Getenv(k), nil
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenUser(ctx *Context) interface{} {
|
||||
return func() string {
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncEnv(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV"}}`,
|
||||
`foo`,
|
||||
},
|
||||
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV_NOPE"}}`,
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
os.Setenv("PACKER_TEST_ENV", "foo")
|
||||
defer os.Setenv("PACKER_TEST_ENV", "")
|
||||
|
||||
ctx := &Context{}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncEnv_disable(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV"}}`,
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{DisableEnv: true}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Context is the context that an interpolation is done in. This defines
|
||||
// things such as available variables.
|
||||
type Context struct {
|
||||
DisableEnv bool
|
||||
}
|
||||
|
||||
// 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(ctx *Context) (string, error) {
|
||||
tpl, err := i.template(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
data := map[string]interface{}{}
|
||||
if err := tpl.Execute(&result, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func (i *I) template(ctx *Context) (*template.Template, error) {
|
||||
return template.New("root").Funcs(Funcs(ctx)).Parse(i.Value)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIRender(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Ctx *Context
|
||||
Value string
|
||||
Result string
|
||||
}{
|
||||
"basic": {
|
||||
nil,
|
||||
"foo",
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
i := &I{Value: tc.Value}
|
||||
result, err := i.Render(tc.Ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("%s\n\ninput: %s\n\nerr: %s", k, tc.Value, err)
|
||||
}
|
||||
if result != tc.Result {
|
||||
t.Fatalf(
|
||||
"%s\n\ninput: %s\n\nexpected: %s\n\ngot: %s",
|
||||
k, tc.Value, tc.Result, result)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
"text/template/parse"
|
||||
)
|
||||
|
||||
// functionsCalled returns a map (to be used as a set) of the functions
|
||||
// that are called from the given text template.
|
||||
func functionsCalled(t *template.Template) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
functionsCalledWalk(t.Tree.Root, result)
|
||||
return result
|
||||
}
|
||||
|
||||
func functionsCalledWalk(raw parse.Node, r map[string]struct{}) {
|
||||
switch node := raw.(type) {
|
||||
case *parse.ActionNode:
|
||||
functionsCalledWalk(node.Pipe, r)
|
||||
case *parse.CommandNode:
|
||||
if in, ok := node.Args[0].(*parse.IdentifierNode); ok {
|
||||
r[in.Ident] = struct{}{}
|
||||
}
|
||||
|
||||
for _, n := range node.Args[1:] {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.ListNode:
|
||||
for _, n := range node.Nodes {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.PipeNode:
|
||||
for _, n := range node.Cmds {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.StringNode, *parse.TextNode:
|
||||
// Ignore
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type: %T", node))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func TestFunctionsCalled(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Result map[string]struct{}
|
||||
}{
|
||||
{
|
||||
"foo",
|
||||
map[string]struct{}{},
|
||||
},
|
||||
|
||||
{
|
||||
"foo {{user `bar`}}",
|
||||
map[string]struct{}{
|
||||
"user": struct{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
funcs := Funcs(&Context{})
|
||||
for _, tc := range cases {
|
||||
tpl, err := template.New("root").Funcs(funcs).Parse(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("err parsing: %v\n\n%s", tc.Input, err)
|
||||
}
|
||||
|
||||
actual := functionsCalled(tpl)
|
||||
if !reflect.DeepEqual(actual, tc.Result) {
|
||||
t.Fatalf("bad: %v\n\ngot: %#v", tc.Input, actual)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue