github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/userfunc/public.go (about) 1 package userfunc 2 3 import ( 4 "github.com/hashicorp/hcl/v2" 5 "github.com/zclconf/go-cty/cty" 6 "github.com/zclconf/go-cty/cty/function" 7 ) 8 9 // A ContextFunc is a callback used to produce the base EvalContext for 10 // running a particular set of functions. 11 // 12 // This is a function rather than an EvalContext directly to allow functions 13 // to be decoded before their context is complete. This will be true, for 14 // example, for applications that wish to allow functions to refer to themselves. 15 // 16 // The simplest use of a ContextFunc is to give user functions access to the 17 // same global variables and functions available elsewhere in an application's 18 // configuration language, but more complex applications may use different 19 // contexts to support lexical scoping depending on where in a configuration 20 // structure a function declaration is found, etc. 21 type ContextFunc func() *hcl.EvalContext 22 23 // DecodeUserFunctions looks for blocks of the given type in the given body 24 // and, for each one found, interprets it as a custom function definition. 25 // 26 // On success, the result is a mapping of function names to implementations, 27 // along with a new body that represents the remaining content of the given 28 // body which can be used for further processing. 29 // 30 // The result expression of each function is parsed during decoding but not 31 // evaluated until the function is called. 32 // 33 // If the given ContextFunc is non-nil, it will be called to obtain the 34 // context in which the function result expressions will be evaluated. If nil, 35 // or if it returns nil, the result expression will have access only to 36 // variables named after the declared parameters. A non-nil context turns 37 // the returned functions into closures, bound to the given context. 38 // 39 // If the returned diagnostics set has errors then the function map and 40 // remain body may be nil or incomplete. 41 func DecodeUserFunctions(body hcl.Body, blockType string, context ContextFunc) (funcs map[string]function.Function, remain hcl.Body, diags hcl.Diagnostics) { 42 return decodeUserFunctions(body, blockType, context) 43 } 44 45 // NewFunction creates a new function instance from preparsed HCL expressions. 46 func NewFunction(paramsExpr, varParamExpr, resultExpr hcl.Expression, getBaseCtx func() *hcl.EvalContext) (function.Function, hcl.Diagnostics) { 47 var params []string 48 var varParam string 49 50 paramExprs, paramsDiags := hcl.ExprList(paramsExpr) 51 if paramsDiags.HasErrors() { 52 return function.Function{}, paramsDiags 53 } 54 for _, paramExpr := range paramExprs { 55 param := hcl.ExprAsKeyword(paramExpr) 56 if param == "" { 57 return function.Function{}, hcl.Diagnostics{{ 58 Severity: hcl.DiagError, 59 Summary: "Invalid param element", 60 Detail: "Each parameter name must be an identifier.", 61 Subject: paramExpr.Range().Ptr(), 62 }} 63 } 64 params = append(params, param) 65 } 66 67 if varParamExpr != nil { 68 varParam = hcl.ExprAsKeyword(varParamExpr) 69 if varParam == "" { 70 return function.Function{}, hcl.Diagnostics{{ 71 Severity: hcl.DiagError, 72 Summary: "Invalid variadic_param", 73 Detail: "The variadic parameter name must be an identifier.", 74 Subject: varParamExpr.Range().Ptr(), 75 }} 76 } 77 } 78 79 spec := &function.Spec{} 80 for _, paramName := range params { 81 spec.Params = append(spec.Params, function.Parameter{ 82 Name: paramName, 83 Type: cty.DynamicPseudoType, 84 }) 85 } 86 if varParamExpr != nil { 87 spec.VarParam = &function.Parameter{ 88 Name: varParam, 89 Type: cty.DynamicPseudoType, 90 } 91 } 92 impl := func(args []cty.Value) (cty.Value, error) { 93 ctx := getBaseCtx() 94 ctx = ctx.NewChild() 95 ctx.Variables = make(map[string]cty.Value) 96 97 // The cty function machinery guarantees that we have at least 98 // enough args to fill all of our params. 99 for i, paramName := range params { 100 ctx.Variables[paramName] = args[i] 101 } 102 if spec.VarParam != nil { 103 varArgs := args[len(params):] 104 ctx.Variables[varParam] = cty.TupleVal(varArgs) 105 } 106 107 result, diags := resultExpr.Value(ctx) 108 if diags.HasErrors() { 109 // Smuggle the diagnostics out via the error channel, since 110 // a diagnostics sequence implements error. Caller can 111 // type-assert this to recover the individual diagnostics 112 // if desired. 113 return cty.DynamicVal, diags 114 } 115 return result, nil 116 } 117 spec.Type = func(args []cty.Value) (cty.Type, error) { 118 val, err := impl(args) 119 return val.Type(), err 120 } 121 spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) { 122 return impl(args) 123 } 124 return function.New(spec), nil 125 }