2026-10-11 01:06:21 +00:00
package ckconfig
2026-10-07 13:44:46 +00:00
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
} ) ,
}
2026-10-07 14:56:13 +00:00
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 } ,
) ... )
2026-10-07 13:44:46 +00:00
for _ , selector := range suite . Selectors {
flags = append ( flags , adaptSelector ( selector ) )
}
return flags
}
func adaptSelector [ C any ] ( selector Selector [ C ] ) Flag [ configInput [ C ] ] {
2026-10-07 16:44:10 +00:00
flag := adaptContextSelector ( selector ,
2026-10-07 14:56:13 +00:00
func ( input * configInput [ C ] ) * C { return & input . SelectorContext } ,
func ( input * configInput [ C ] , state selectorState [ C ] ) { input . selectors = append ( input . selectors , state ) } ,
)
2026-10-07 16:44:10 +00:00
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
2026-10-07 14:56:13 +00:00
}
func adaptContextSelector [ C any , I any ] ( selector Selector [ C ] , selectContext func ( * I ) * C , record func ( * I , selectorState [ C ] ) ) Flag [ I ] {
2026-10-07 13:44:46 +00:00
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 ) + ")"
}
2026-10-07 14:56:13 +00:00
return Flag [ I ] { binding : flagBinding [ I ] {
2026-10-07 13:44:46 +00:00
meta : meta ,
defaultText : binding . defaultText ,
repeated : binding . repeated ,
boolean : binding . boolean ,
valid : binding . valid ,
2026-10-07 14:56:13 +00:00
prepare : func ( values [ ] string ) ( func ( * I ) , error ) {
2026-10-07 13:44:46 +00:00
apply , err := binding . prepare ( values )
if err != nil {
return nil , err
}
state := selectorState [ C ] {
2026-10-07 16:44:10 +00:00
name : binding . meta . Name ,
required : binding . meta . Required ,
2026-10-07 13:44:46 +00:00
scopes : append ( [ ] string ( nil ) , selector . Scopes ... ) ,
supplied : len ( values ) > 0 ,
apply : apply ,
clear : binding . clear ,
}
2026-10-07 14:56:13 +00:00
return func ( input * I ) {
apply ( selectContext ( input ) )
record ( input , state )
2026-10-07 13:44:46 +00:00
} , nil
} ,
} }
}
2026-10-07 14:56:13 +00:00
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 ) ,
}
}
2026-10-07 13:44:46 +00:00
func parseString ( value string ) ( string , error ) { return value , nil }
2026-10-09 01:46:19 +00:00
func preflightConfig [ C any ] ( prepared * preparedConfigSuite [ C ] , input configInput [ C ] , operation ConfigOperation ) ( [ ] string , error ) {
scopes , err := selectConfigScopes ( prepared , input , operation )
2026-10-07 13:50:48 +00:00
if err != nil {
return nil , err
}
2026-10-09 01:46:19 +00:00
if prepared . suite . CheckInput != nil {
if err := prepared . suite . CheckInput ( input . Selection , input . SelectorContext ) ; err != nil {
2026-10-07 13:50:48 +00:00
return nil , err
}
}
return scopes , nil
}
2026-10-09 01:46:19 +00:00
func selectConfigScopes [ C any ] ( prepared * preparedConfigSuite [ C ] , input configInput [ C ] , operation ConfigOperation ) ( [ ] string , error ) {
2026-10-07 13:44:46 +00:00
if err := checkConfigFormat ( input . Format ) ; err != nil {
return nil , err
}
2026-10-07 13:55:43 +00:00
if operation == ConfigShow && input . Format != "yaml" {
return nil , fmt . Errorf ( "unsupported configuration show format %q" , input . Format )
}
2026-10-07 13:44:46 +00:00
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" )
}
2026-10-09 01:46:19 +00:00
scopes := make ( [ ] string , 0 , len ( prepared . ordered ) )
2026-10-07 13:44:46 +00:00
if input . Scope == "all" {
2026-10-09 01:46:19 +00:00
for _ , scope := range prepared . ordered {
2026-10-07 13:44:46 +00:00
scopes = append ( scopes , scope . Name )
}
} else {
2026-10-09 01:46:19 +00:00
scope , ok := prepared . byName [ input . Scope ]
2026-10-07 13:44:46 +00:00
if ! ok {
2026-10-09 01:46:19 +00:00
choices := make ( [ ] string , 0 , len ( prepared . ordered ) )
for _ , available := range prepared . ordered {
2026-10-07 16:50:23 +00:00
choices = append ( choices , available . Name )
}
return nil , fmt . Errorf ( "unknown configuration scope %q (available: %s)" , input . Scope , joinStrings ( choices ) )
2026-10-07 13:44:46 +00:00
}
if ! containsOperation ( scope . Operations , operation ) {
return nil , unsupportedConfigOperation ( scope , operation )
}
scopes = append ( scopes , scope . Name )
}
if input . Scope == "all" {
for _ , scopeName := range scopes {
2026-10-09 01:46:19 +00:00
scope := prepared . byName [ scopeName ]
2026-10-07 13:44:46 +00:00
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
}
}
2026-10-07 16:44:10 +00:00
if applies && selector . required && ! selector . supplied {
return nil , fmt . Errorf ( "required flag --%s was not supplied" , selector . name )
}
if selector . supplied && ! applies {
2026-10-07 13:44:46 +00:00
return nil , fmt . Errorf ( "selector is not applicable to configuration scope %q" , input . Scope )
}
}
return scopes , nil
}
2026-10-09 01:46:19 +00:00
func contextsForScopes [ C any ] ( prepared * preparedConfigSuite [ C ] , input configInput [ C ] , scopeNames [ ] string ) map [ string ] C {
2026-10-07 13:44:46 +00:00
base := * new ( C )
2026-10-09 01:46:19 +00:00
if prepared . suite . NewContext != nil {
base = prepared . suite . NewContext ( )
2026-10-07 13:44:46 +00:00
}
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
}
2026-10-09 01:46:19 +00:00
func findConfigScope [ C any ] ( prepared * preparedConfigSuite [ C ] , name string ) ( ConfigScope [ C ] , bool ) {
scope , ok := prepared . byName [ name ]
return scope , ok
2026-10-07 13:44:46 +00:00
}
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 , ", " )
}