Move integration code into optional modules
This commit is contained in:
228
integrations/ckconfig/selectors.go
Normal file
228
integrations/ckconfig/selectors.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func configInputFlags[C any](suite ConfigSuite[C], formatDefault string) []Flag[configInput[C]] {
|
||||
scopeDefault := "app"
|
||||
flags := []Flag[configInput[C]]{
|
||||
ValueFlag[configInput[C]](FlagMeta{Name: "scope", Usage: "Configuration scope name or all where supported."}, &scopeDefault, parseString, func(input *configInput[C], value Supplied[string]) {
|
||||
input.Scope = value.Value
|
||||
}),
|
||||
ValueFlag[configInput[C]](FlagMeta{Name: "format", Usage: "Output format."}, &formatDefault, parseString, func(input *configInput[C], value Supplied[string]) {
|
||||
input.Format = value.Value
|
||||
}),
|
||||
}
|
||||
flags = append(flags, configurationSelectionFlags(
|
||||
func(input *configInput[C], value Supplied[string]) { input.Selection.Config = value },
|
||||
func(input *configInput[C], value Supplied[string]) { input.Selection.Overlay = value },
|
||||
)...)
|
||||
for _, selector := range suite.Selectors {
|
||||
flags = append(flags, adaptSelector(selector))
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func adaptSelector[C any](selector Selector[C]) Flag[configInput[C]] {
|
||||
flag := adaptContextSelector(selector,
|
||||
func(input *configInput[C]) *C { return &input.SelectorContext },
|
||||
func(input *configInput[C], state selectorState[C]) { input.selectors = append(input.selectors, state) },
|
||||
)
|
||||
if flag.binding.meta.Required {
|
||||
flag.binding.meta.Required = false // Scope-aware preflight enforces this requirement.
|
||||
flag.binding.meta.Usage += " (required where applicable)"
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func adaptContextSelector[C any, I any](selector Selector[C], selectContext func(*I) *C, record func(*I, selectorState[C])) Flag[I] {
|
||||
binding := selector.Binding.binding
|
||||
meta := binding.meta
|
||||
if len(selector.Scopes) == 0 {
|
||||
meta.Usage += " (applies to all scopes)"
|
||||
} else {
|
||||
meta.Usage += " (applies to: " + joinNames(selector.Scopes) + ")"
|
||||
}
|
||||
return Flag[I]{binding: flagBinding[I]{
|
||||
meta: meta,
|
||||
defaultText: binding.defaultText,
|
||||
repeated: binding.repeated,
|
||||
boolean: binding.boolean,
|
||||
valid: binding.valid,
|
||||
prepare: func(values []string) (func(*I), error) {
|
||||
apply, err := binding.prepare(values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := selectorState[C]{
|
||||
name: binding.meta.Name,
|
||||
required: binding.meta.Required,
|
||||
scopes: append([]string(nil), selector.Scopes...),
|
||||
supplied: len(values) > 0,
|
||||
apply: apply,
|
||||
clear: binding.clear,
|
||||
}
|
||||
return func(input *I) {
|
||||
apply(selectContext(input))
|
||||
record(input, state)
|
||||
}, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func configurationSelectionFlags[I any](setConfig, setOverlay func(*I, Supplied[string])) []Flag[I] {
|
||||
return []Flag[I]{
|
||||
ValueFlag[I](FlagMeta{Name: "config", Usage: "Configuration source."}, nil, parseString, setConfig),
|
||||
ValueFlag[I](FlagMeta{Name: "overlay", Usage: "Overlay source."}, nil, parseString, setOverlay),
|
||||
}
|
||||
}
|
||||
|
||||
func parseString(value string) (string, error) { return value, nil }
|
||||
|
||||
func preflightConfig[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
||||
scopes, err := selectConfigScopes(prepared, input, operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prepared.suite.CheckInput != nil {
|
||||
if err := prepared.suite.CheckInput(input.Selection, input.SelectorContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func selectConfigScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
||||
if err := checkConfigFormat(input.Format); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if operation == ConfigShow && input.Format != "yaml" {
|
||||
return nil, fmt.Errorf("unsupported configuration show format %q", input.Format)
|
||||
}
|
||||
if input.Scope == "all" {
|
||||
if operation != ConfigValidate && operation != ConfigShow {
|
||||
return nil, fmt.Errorf("scope all is supported only for validate and show")
|
||||
}
|
||||
} else if input.Scope == "" {
|
||||
return nil, fmt.Errorf("configuration scope must not be empty")
|
||||
}
|
||||
|
||||
scopes := make([]string, 0, len(prepared.ordered))
|
||||
if input.Scope == "all" {
|
||||
for _, scope := range prepared.ordered {
|
||||
scopes = append(scopes, scope.Name)
|
||||
}
|
||||
} else {
|
||||
scope, ok := prepared.byName[input.Scope]
|
||||
if !ok {
|
||||
choices := make([]string, 0, len(prepared.ordered))
|
||||
for _, available := range prepared.ordered {
|
||||
choices = append(choices, available.Name)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown configuration scope %q (available: %s)", input.Scope, joinStrings(choices))
|
||||
}
|
||||
if !containsOperation(scope.Operations, operation) {
|
||||
return nil, unsupportedConfigOperation(scope, operation)
|
||||
}
|
||||
scopes = append(scopes, scope.Name)
|
||||
}
|
||||
|
||||
if input.Scope == "all" {
|
||||
for _, scopeName := range scopes {
|
||||
scope := prepared.byName[scopeName]
|
||||
if !containsOperation(scope.Operations, operation) {
|
||||
return nil, unsupportedConfigOperation(scope, operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, selector := range input.selectors {
|
||||
applies := false
|
||||
for _, scopeName := range scopes {
|
||||
if selectorApplies(selector.scopes, scopeName) {
|
||||
applies = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if applies && selector.required && !selector.supplied {
|
||||
return nil, fmt.Errorf("required flag --%s was not supplied", selector.name)
|
||||
}
|
||||
if selector.supplied && !applies {
|
||||
return nil, fmt.Errorf("selector is not applicable to configuration scope %q", input.Scope)
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func contextsForScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], scopeNames []string) map[string]C {
|
||||
base := *new(C)
|
||||
if prepared.suite.NewContext != nil {
|
||||
base = prepared.suite.NewContext()
|
||||
}
|
||||
contexts := make(map[string]C, len(scopeNames))
|
||||
for _, name := range scopeNames {
|
||||
context := base
|
||||
for _, selector := range input.selectors {
|
||||
if selectorApplies(selector.scopes, name) {
|
||||
selector.apply(&context)
|
||||
} else {
|
||||
selector.clear(&context)
|
||||
}
|
||||
}
|
||||
contexts[name] = context
|
||||
}
|
||||
return contexts
|
||||
}
|
||||
|
||||
func findConfigScope[C any](prepared *preparedConfigSuite[C], name string) (ConfigScope[C], bool) {
|
||||
scope, ok := prepared.byName[name]
|
||||
return scope, ok
|
||||
}
|
||||
|
||||
func containsOperation(operations []ConfigOperation, requested ConfigOperation) bool {
|
||||
for _, operation := range operations {
|
||||
if operation == requested {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unsupportedConfigOperation[C any](scope ConfigScope[C], operation ConfigOperation) error {
|
||||
return fmt.Errorf("configuration scope %q does not support %s (available: %s)", scope.Name, operation, joinOperations(scope.Operations))
|
||||
}
|
||||
|
||||
func selectorApplies(scopes []string, name string) bool {
|
||||
if len(scopes) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, scope := range scopes {
|
||||
if scope == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func joinNames(names []string) string {
|
||||
ordered := append([]string(nil), names...)
|
||||
sort.Strings(ordered)
|
||||
return joinStrings(ordered)
|
||||
}
|
||||
|
||||
func joinOperations(operations []ConfigOperation) string {
|
||||
values := make([]string, len(operations))
|
||||
for index, operation := range operations {
|
||||
values[index] = string(operation)
|
||||
}
|
||||
return joinStrings(values)
|
||||
}
|
||||
|
||||
func joinStrings(values []string) string {
|
||||
if len(values) == 0 {
|
||||
return "none"
|
||||
}
|
||||
return strings.Join(values, ", ")
|
||||
}
|
||||
Reference in New Issue
Block a user