mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
lang: Factor out the base function table
Whichever function were responsible for instantiating this map would inevitably end up scaling in length based on the number of funtions and thus violate the function length linter. As a compromise, we'll factor out the function table into its own function that is exempt from that linter but focused only on constructing the base function table, and then Scope.Functions can be shorter by dealing only with the scope-specific customizations of the returned table. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
parent
69bf43dd56
commit
972324e5ea
@ -33,6 +33,83 @@ const CoreNamespace = addrs.FunctionNamespaceCore + "::"
|
|||||||
func (s *Scope) Functions() map[string]function.Function {
|
func (s *Scope) Functions() map[string]function.Function {
|
||||||
s.funcsLock.Lock()
|
s.funcsLock.Lock()
|
||||||
if s.funcs == nil {
|
if s.funcs == nil {
|
||||||
|
s.funcs = makeBaseFunctionTable(s.BaseDir)
|
||||||
|
if s.ConsoleMode {
|
||||||
|
// The type function is only available in OpenTofu console.
|
||||||
|
s.funcs["type"] = funcs.TypeFunc
|
||||||
|
} else {
|
||||||
|
// The plantimestamp function doesn't make sense in the OpenTofu
|
||||||
|
// console.
|
||||||
|
s.funcs["plantimestamp"] = funcs.MakeStaticTimestampFunc(s.PlanTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.PureOnly {
|
||||||
|
// Force our few impure functions to return unknown so that we
|
||||||
|
// can defer evaluating them until a later pass.
|
||||||
|
for _, name := range impureFunctions {
|
||||||
|
s.funcs[name] = function.Unpredictable(s.funcs[name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coreNames := make([]string, 0)
|
||||||
|
// Add a description to each function and parameter based on the
|
||||||
|
// contents of descriptionList.
|
||||||
|
// One must create a matching description entry whenever a new
|
||||||
|
// function is introduced.
|
||||||
|
for name, f := range s.funcs {
|
||||||
|
s.funcs[name] = funcs.WithDescription(name, f)
|
||||||
|
coreNames = append(coreNames, name)
|
||||||
|
}
|
||||||
|
// Copy all stdlib funcs into core:: namespace
|
||||||
|
for _, name := range coreNames {
|
||||||
|
s.funcs[CoreNamespace+name] = s.funcs[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.funcsLock.Unlock()
|
||||||
|
|
||||||
|
return s.funcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// experimentalFunction checks whether the given experiment is enabled for
|
||||||
|
// the receiving scope. If so, it will return the given function verbatim.
|
||||||
|
// If not, it will return a placeholder function that just returns an
|
||||||
|
// error explaining that the function requires the experiment to be enabled.
|
||||||
|
//
|
||||||
|
//lint:ignore U1000 Ignore unused function error for now
|
||||||
|
func (s *Scope) experimentalFunction(experiment experiments.Experiment, fn function.Function) function.Function {
|
||||||
|
if s.activeExperiments.Has(experiment) {
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"this function is experimental and available only when the experiment keyword %s is enabled for the current module",
|
||||||
|
experiment.Keyword(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return function.New(&function.Spec{
|
||||||
|
Params: fn.Params(),
|
||||||
|
VarParam: fn.VarParam(),
|
||||||
|
Type: func(args []cty.Value) (cty.Type, error) {
|
||||||
|
return cty.DynamicPseudoType, err
|
||||||
|
},
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
// It would be weird to get here because the Type function always
|
||||||
|
// fails, but we'll return an error here too anyway just to be
|
||||||
|
// robust.
|
||||||
|
return cty.DynamicVal, err
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeBaseFunctionTable constructs the initial table of functions that we uses as
|
||||||
|
// the basis for the function table in each scope.
|
||||||
|
//
|
||||||
|
// This function intentionally always returns a fresh map on each call because the
|
||||||
|
// caller is expected to modify it further before storing it as part of a
|
||||||
|
// particular [Scope], based on the unique settings of that scope.
|
||||||
|
//
|
||||||
|
//nolint:funlen // The length of this function naturally scales with the number of functions in the OpenTofu language.
|
||||||
|
func makeBaseFunctionTable(baseDir string) map[string]function.Function {
|
||||||
// Some of our functions are just directly the cty stdlib functions.
|
// Some of our functions are just directly the cty stdlib functions.
|
||||||
// Others are implemented in the subdirectory "funcs" here in this
|
// Others are implemented in the subdirectory "funcs" here in this
|
||||||
// repository. New functions should generally start out their lives
|
// repository. New functions should generally start out their lives
|
||||||
@ -40,7 +117,7 @@ func (s *Scope) Functions() map[string]function.Function {
|
|||||||
// later if the functionality seems to be something domain-agnostic
|
// later if the functionality seems to be something domain-agnostic
|
||||||
// that would be useful to all applications using cty functions.
|
// that would be useful to all applications using cty functions.
|
||||||
|
|
||||||
s.funcs = map[string]function.Function{
|
ret := map[string]function.Function{
|
||||||
"abs": stdlib.AbsoluteFunc,
|
"abs": stdlib.AbsoluteFunc,
|
||||||
"abspath": funcs.AbsPathFunc,
|
"abspath": funcs.AbsPathFunc,
|
||||||
"alltrue": funcs.AllTrueFunc,
|
"alltrue": funcs.AllTrueFunc,
|
||||||
@ -72,16 +149,16 @@ func (s *Scope) Functions() map[string]function.Function {
|
|||||||
"element": stdlib.ElementFunc,
|
"element": stdlib.ElementFunc,
|
||||||
"endswith": funcs.EndsWithFunc,
|
"endswith": funcs.EndsWithFunc,
|
||||||
"chunklist": stdlib.ChunklistFunc,
|
"chunklist": stdlib.ChunklistFunc,
|
||||||
"file": funcs.MakeFileFunc(s.BaseDir, false),
|
"file": funcs.MakeFileFunc(baseDir, false),
|
||||||
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
|
"fileexists": funcs.MakeFileExistsFunc(baseDir),
|
||||||
"fileset": funcs.MakeFileSetFunc(s.BaseDir),
|
"fileset": funcs.MakeFileSetFunc(baseDir),
|
||||||
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
|
"filebase64": funcs.MakeFileFunc(baseDir, true),
|
||||||
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir),
|
"filebase64sha256": funcs.MakeFileBase64Sha256Func(baseDir),
|
||||||
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir),
|
"filebase64sha512": funcs.MakeFileBase64Sha512Func(baseDir),
|
||||||
"filemd5": funcs.MakeFileMd5Func(s.BaseDir),
|
"filemd5": funcs.MakeFileMd5Func(baseDir),
|
||||||
"filesha1": funcs.MakeFileSha1Func(s.BaseDir),
|
"filesha1": funcs.MakeFileSha1Func(baseDir),
|
||||||
"filesha256": funcs.MakeFileSha256Func(s.BaseDir),
|
"filesha256": funcs.MakeFileSha256Func(baseDir),
|
||||||
"filesha512": funcs.MakeFileSha512Func(s.BaseDir),
|
"filesha512": funcs.MakeFileSha512Func(baseDir),
|
||||||
"flatten": stdlib.FlattenFunc,
|
"flatten": stdlib.FlattenFunc,
|
||||||
"floor": stdlib.FloorFunc,
|
"floor": stdlib.FloorFunc,
|
||||||
"format": stdlib.FormatFunc,
|
"format": stdlib.FormatFunc,
|
||||||
@ -162,83 +239,17 @@ func (s *Scope) Functions() map[string]function.Function {
|
|||||||
"zipmap": stdlib.ZipmapFunc,
|
"zipmap": stdlib.ZipmapFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.funcs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, func() map[string]function.Function {
|
ret["templatefile"] = funcs.MakeTemplateFileFunc(baseDir, func() map[string]function.Function {
|
||||||
// The templatefile function prevents recursive calls to itself
|
// The templatefile function prevents recursive calls to itself
|
||||||
// by copying this map and overwriting the "templatefile" entry.
|
// by copying this map and overwriting the "templatefile" entry.
|
||||||
return s.funcs
|
return ret
|
||||||
})
|
})
|
||||||
|
|
||||||
// Registers "templatestring" function in function map.
|
// Registers "templatestring" function in function map.
|
||||||
s.funcs["templatestring"] = funcs.MakeTemplateStringFunc(s.BaseDir, func() map[string]function.Function {
|
ret["templatestring"] = funcs.MakeTemplateStringFunc(baseDir, func() map[string]function.Function {
|
||||||
// This anonymous function returns the existing map of functions for initialization.
|
// This anonymous function returns the existing map of functions for initialization.
|
||||||
return s.funcs
|
return ret
|
||||||
})
|
})
|
||||||
|
|
||||||
if s.ConsoleMode {
|
return ret
|
||||||
// The type function is only available in OpenTofu console.
|
|
||||||
s.funcs["type"] = funcs.TypeFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.ConsoleMode {
|
|
||||||
// The plantimestamp function doesn't make sense in the OpenTofu
|
|
||||||
// console.
|
|
||||||
s.funcs["plantimestamp"] = funcs.MakeStaticTimestampFunc(s.PlanTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.PureOnly {
|
|
||||||
// Force our few impure functions to return unknown so that we
|
|
||||||
// can defer evaluating them until a later pass.
|
|
||||||
for _, name := range impureFunctions {
|
|
||||||
s.funcs[name] = function.Unpredictable(s.funcs[name])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coreNames := make([]string, 0)
|
|
||||||
// Add a description to each function and parameter based on the
|
|
||||||
// contents of descriptionList.
|
|
||||||
// One must create a matching description entry whenever a new
|
|
||||||
// function is introduced.
|
|
||||||
for name, f := range s.funcs {
|
|
||||||
s.funcs[name] = funcs.WithDescription(name, f)
|
|
||||||
coreNames = append(coreNames, name)
|
|
||||||
}
|
|
||||||
// Copy all stdlib funcs into core:: namespace
|
|
||||||
for _, name := range coreNames {
|
|
||||||
s.funcs[CoreNamespace+name] = s.funcs[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.funcsLock.Unlock()
|
|
||||||
|
|
||||||
return s.funcs
|
|
||||||
}
|
|
||||||
|
|
||||||
// experimentalFunction checks whether the given experiment is enabled for
|
|
||||||
// the receiving scope. If so, it will return the given function verbatim.
|
|
||||||
// If not, it will return a placeholder function that just returns an
|
|
||||||
// error explaining that the function requires the experiment to be enabled.
|
|
||||||
//
|
|
||||||
//lint:ignore U1000 Ignore unused function error for now
|
|
||||||
func (s *Scope) experimentalFunction(experiment experiments.Experiment, fn function.Function) function.Function {
|
|
||||||
if s.activeExperiments.Has(experiment) {
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"this function is experimental and available only when the experiment keyword %s is enabled for the current module",
|
|
||||||
experiment.Keyword(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return function.New(&function.Spec{
|
|
||||||
Params: fn.Params(),
|
|
||||||
VarParam: fn.VarParam(),
|
|
||||||
Type: func(args []cty.Value) (cty.Type, error) {
|
|
||||||
return cty.DynamicPseudoType, err
|
|
||||||
},
|
|
||||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
|
||||||
// It would be weird to get here because the Type function always
|
|
||||||
// fails, but we'll return an error here too anyway just to be
|
|
||||||
// robust.
|
|
||||||
return cty.DynamicVal, err
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,8 @@ import (
|
|||||||
// it really is registered correctly) and possibly a small set of additional
|
// it really is registered correctly) and possibly a small set of additional
|
||||||
// functions showing real-world use-cases that rely on type conversion
|
// functions showing real-world use-cases that rely on type conversion
|
||||||
// behaviors.
|
// behaviors.
|
||||||
|
//
|
||||||
|
//nolint:gocognit // This test intentionally embraces mainloop complexity so that maintenence can primarily focus on the declarative test table rather than the actual test logic.
|
||||||
func TestFunctions(t *testing.T) {
|
func TestFunctions(t *testing.T) {
|
||||||
// used in `pathexpand()` test
|
// used in `pathexpand()` test
|
||||||
homePath, err := homedir.Dir()
|
homePath, err := homedir.Dir()
|
||||||
|
Loading…
Reference in New Issue
Block a user