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  }