2026-10-11 01:34:25 +00:00
package ckconfig
import (
"fmt"
2026-10-11 01:39:23 +00:00
"strings"
2026-10-11 01:34:25 +00:00
commandkit "gitea.maximumdirect.net/eric/CommandKit"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
type configCommandInput [ I , S any ] struct {
selection Selection
scope commandkit . Supplied [ string ]
format commandkit . Supplied [ string ]
path commandkit . Supplied [ string ]
values commandkit . FlagValues [ I ]
plan * configOperationPlan [ I , S ]
}
type selectedScopeInput [ I , S any ] struct {
scope * preparedScope [ I , S ]
input I
}
type configOperationPlan [ I , S any ] struct {
prepared * preparedSuite [ I , S ]
operation Operation
selection Selection
scopeName string
format string
path configkit . Path
scopes [ ] selectedScopeInput [ I , S ]
}
func newConfigOperationCommand [ I , S any ] ( prepared * preparedSuite [ I , S ] , operation Operation ) commandkit . Command {
defaultScope := "app"
defaultFormat := "text"
if operation == Show {
defaultFormat = "yaml"
}
flags := [ ] commandkit . Flag [ * configCommandInput [ I , S ] ] {
commandkit . ValueFlag ( commandkit . FlagMeta { Name : "scope" , Usage : "Configuration scope name or all where supported." } , & defaultScope , parseConfigString ,
func ( input * * configCommandInput [ I , S ] , value commandkit . Supplied [ string ] ) { ( * input ) . scope = value } ) ,
commandkit . ValueFlag ( commandkit . FlagMeta { Name : "format" , Usage : "Output format." } , & defaultFormat , parseConfigString ,
func ( input * * configCommandInput [ I , S ] , value commandkit . Supplied [ string ] ) { ( * input ) . format = value } ) ,
commandkit . ValueFlag ( commandkit . FlagMeta { Name : "config" , Usage : "Configuration source." } , ( * string ) ( nil ) , parseConfigString ,
func ( input * * configCommandInput [ I , S ] , value commandkit . Supplied [ string ] ) {
( * input ) . selection . Config = value
} ) ,
commandkit . ValueFlag ( commandkit . FlagMeta { Name : "overlay" , Usage : "Overlay source." } , ( * string ) ( nil ) , parseConfigString ,
func ( input * * configCommandInput [ I , S ] , value commandkit . Supplied [ string ] ) {
( * input ) . selection . Overlay = value
} ) ,
}
if operation == Explain {
flags = append ( flags , commandkit . ValueFlag ( commandkit . FlagMeta { Name : "path" , Usage : "JSON Pointer path to explain." , Required : true } , ( * string ) ( nil ) , parseConfigString ,
func ( input * * configCommandInput [ I , S ] , value commandkit . Supplied [ string ] ) { ( * input ) . path = value } ) )
}
2026-10-11 13:05:22 +00:00
flags = append ( flags , commandkit . CaptureFlags ( prepared . bindingsByOperation [ operation ] , func ( input * * configCommandInput [ I , S ] ) * commandkit . FlagValues [ I ] {
2026-10-11 01:34:25 +00:00
if input == nil || * input == nil {
return nil
}
return & ( * input ) . values
} ) ... )
name , summary := operationDeclaration ( operation )
return commandkit . NewCommand ( commandkit . CommandSpec [ * configCommandInput [ I , S ] ] {
Name : name , Summary : summary ,
NewInput : func ( ) * configCommandInput [ I , S ] { return & configCommandInput [ I , S ] { } } ,
Flags : flags ,
CheckInput : func ( input * configCommandInput [ I , S ] ) error {
if input == nil {
return fmt . Errorf ( "configuration input must not be nil" )
}
return prepareConfigOperation ( prepared , operation , input )
} ,
Run : func ( invocation commandkit . Invocation , input * configCommandInput [ I , S ] ) commandkit . Outcome {
return runConfigOperation ( invocation , operation , input )
} ,
} )
}
func operationDeclaration ( operation Operation ) ( string , string ) {
switch operation {
case Validate :
return "validate" , "Validate configuration."
case Show :
return "show" , "Show effective configuration."
case Sources :
return "sources" , "List configuration sources."
case Explain :
return "explain" , "Explain a configuration path."
default :
return string ( operation ) , "Inspect configuration."
}
}
func prepareConfigOperation [ I , S any ] ( prepared * preparedSuite [ I , S ] , operation Operation , input * configCommandInput [ I , S ] ) error {
format := input . format . Value
if err := checkConfigFormat ( format ) ; err != nil {
return err
}
if operation == Show && format != "yaml" {
return fmt . Errorf ( "unsupported configuration show format %q" , format )
}
scopeName := "app"
if input . scope . Set {
scopeName = input . scope . Value
}
if scopeName == "" {
return fmt . Errorf ( "configuration scope must not be empty" )
}
if scopeName == "all" && operation != Validate && operation != Show {
return fmt . Errorf ( "scope all is supported only for validate and show" )
}
2026-10-11 13:05:22 +00:00
var selected [ ] * preparedScope [ I , S ]
2026-10-11 01:34:25 +00:00
if scopeName == "all" {
2026-10-11 13:05:22 +00:00
selected = prepared . scopes
2026-10-11 01:34:25 +00:00
} else if scope , exists := prepared . byName [ scopeName ] ; exists {
2026-10-11 13:05:22 +00:00
selected = [ ] * preparedScope [ I , S ] { scope }
2026-10-11 01:34:25 +00:00
} else {
2026-10-11 13:05:22 +00:00
return fmt . Errorf ( "unknown configuration scope %q (available: %s)" , scopeName , prepared . scopeChoices )
2026-10-11 01:34:25 +00:00
}
for _ , scope := range selected {
if ! containsOperation ( scope . scope . Operations , operation ) {
return fmt . Errorf ( "configuration scope %q does not support %s (available: %s)" , scope . scope . Name , operation , joinOperations ( scope . scope . Operations ) )
}
}
var path configkit . Path
if operation == Explain {
if ! input . path . Set {
return fmt . Errorf ( "configuration explanation requires --path" )
}
parsed , err := configkit . ParsePath ( input . path . Value )
if err != nil {
return fmt . Errorf ( "invalid configuration path: %w" , err )
}
path = parsed
}
plan := & configOperationPlan [ I , S ] {
prepared : prepared ,
operation : operation ,
selection : input . selection ,
scopeName : scopeName ,
format : format ,
path : path ,
scopes : make ( [ ] selectedScopeInput [ I , S ] , 0 , len ( selected ) ) ,
}
for _ , scope := range selected {
var value I
if prepared . newInput != nil {
value = prepared . newInput ( )
}
2026-10-11 13:05:22 +00:00
// Availability was checked above, so every binding for this scope
// belongs to the operation's captured union.
if err := input . values . ApplyTo ( & value , scope . bindings ... ) ; err != nil {
2026-10-11 01:34:25 +00:00
return fmt . Errorf ( "configuration scope %q: %w" , scope . scope . Name , err )
}
if prepared . checkInput != nil {
if err := prepared . checkInput ( scope . scope . Name , input . selection , value ) ; err != nil {
return fmt . Errorf ( "configuration scope %q: %w" , scope . scope . Name , err )
}
}
plan . scopes = append ( plan . scopes , selectedScopeInput [ I , S ] { scope : scope , input : value } )
}
input . plan = plan
return nil
}
func joinOperations ( operations [ ] Operation ) string {
values := make ( [ ] string , len ( operations ) )
for index , operation := range operations {
values [ index ] = string ( operation )
}
if len ( values ) == 0 {
return "none"
}
return strings . Join ( values , ", " )
}