Add configuration path discovery

This commit is contained in:
2026-11-07 04:44:01 +00:00
parent aeb70e5873
commit b38130548e
8 changed files with 307 additions and 20 deletions

View File

@@ -13,6 +13,10 @@ type configCommandInput[I, S any] struct {
against Selection
scope commandkit.Supplied[string]
path commandkit.Supplied[string]
list commandkit.Supplied[bool]
historical commandkit.Supplied[bool]
under commandkit.Supplied[string]
explicit commandkit.Supplied[bool]
values commandkit.FlagValues[I]
againstValues commandkit.FlagValues[I]
plan *configOperationPlan[I, S]
@@ -34,6 +38,11 @@ type configOperationPlan[I, S any] struct {
operation Operation
scopeName string
path configkit.Path
list bool
help bool
historical bool
under *configkit.Path
explicit commandkit.Supplied[bool]
scopes []selectedScopeInput[I, S]
comparison *preparedComparison[I, S]
}
@@ -89,8 +98,13 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
}
}
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 }))
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "path", Usage: "JSON Pointer path to explain."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).path = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "list", Usage: "List configuration paths."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).list = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "historical", Usage: "Include historical-only paths when listing."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).historical = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "under", Usage: "List paths under a JSON Pointer."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).under = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "explicit", Usage: "Filter listed current paths by explicitness."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).explicit = value }),
)
}
flags = append(flags, commandkit.CaptureFlags(prepared.bindingsByOperation[operation], func(input **configCommandInput[I, S]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
@@ -110,8 +124,9 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
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,
Description: operationDescription(operation),
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")
@@ -124,6 +139,13 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
})
}
func operationDescription(operation Operation) string {
if operation == Explain {
return "Use --list to discover paths or --path POINTER to explain one path."
}
return ""
}
func operationDeclaration(operation Operation) (string, string) {
switch operation {
case Validate:
@@ -142,6 +164,18 @@ func operationDeclaration(operation Operation) (string, string) {
}
func prepareConfigOperation[I, S any](prepared *preparedSuite[I, S], operation Operation, input *configCommandInput[I, S]) error {
if operation == Explain {
if input.list.Value && input.path.Set {
return fmt.Errorf("--list and --path cannot be combined")
}
if !input.list.Value && (input.historical.Set || input.under.Set || input.explicit.Set) {
return fmt.Errorf("--historical, --under, and --explicit require --list")
}
if !input.list.Value && !input.path.Set {
input.plan = &configOperationPlan[I, S]{prepared: prepared, operation: operation, help: true}
return nil
}
}
scopeName := "app"
if input.scope.Set {
@@ -170,26 +204,38 @@ func prepareConfigOperation[I, S any](prepared *preparedSuite[I, S], operation O
if (len(input.selection.Overrides) > 0 || len(input.against.Overrides) > 0) && !scope.scope.Features.Overrides {
return fmt.Errorf("configuration scope %q does not support overrides", scope.scope.Name)
}
if operation == Explain && input.list.Value && !scope.scope.Features.PathDiscovery {
return fmt.Errorf("configuration scope %q does not support path discovery", scope.scope.Name)
}
}
var path configkit.Path
if operation == Explain {
if !input.path.Set {
return fmt.Errorf("configuration explanation requires --path")
}
if operation == Explain && input.path.Set {
parsed, err := configkit.ParsePath(input.path.Value)
if err != nil {
return fmt.Errorf("invalid configuration path: %w", err)
}
path = parsed
}
var under *configkit.Path
if input.under.Set {
parsed, err := configkit.ParsePath(input.under.Value)
if err != nil {
return fmt.Errorf("invalid subtree path: %w", err)
}
under = &parsed
}
plan := &configOperationPlan[I, S]{
prepared: prepared,
operation: operation,
scopeName: scopeName,
path: path,
scopes: make([]selectedScopeInput[I, S], 0, len(selected)),
prepared: prepared,
operation: operation,
scopeName: scopeName,
path: path,
list: input.list.Value,
historical: input.historical.Value,
under: under,
explicit: input.explicit,
scopes: make([]selectedScopeInput[I, S], 0, len(selected)),
}
if operation == Diff {
scope := selected[0]