Prepare configuration scope metadata
This commit is contained in:
109
config.go
109
config.go
@@ -97,6 +97,14 @@ type scopesInventory struct {
|
|||||||
Scopes []configScopeInfo `yaml:"scopes"`
|
Scopes []configScopeInfo `yaml:"scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type preparedConfigSuite[C any] struct {
|
||||||
|
suite ConfigSuite[C]
|
||||||
|
ordered []ConfigScope[C]
|
||||||
|
byName map[string]ConfigScope[C]
|
||||||
|
installed []ConfigOperation
|
||||||
|
inventory scopesInventory
|
||||||
|
}
|
||||||
|
|
||||||
type configScopesInput struct {
|
type configScopesInput struct {
|
||||||
Format string
|
Format string
|
||||||
}
|
}
|
||||||
@@ -135,42 +143,27 @@ func RegisterConfig[C any](app *App, suite ConfigSuite[C]) error {
|
|||||||
if _, exists := app.commands["config"]; exists {
|
if _, exists := app.commands["config"]; exists {
|
||||||
return fmt.Errorf("command path %q is already registered", "config")
|
return fmt.Errorf("command path %q is already registered", "config")
|
||||||
}
|
}
|
||||||
|
prepared := prepareConfigSuite(normalized)
|
||||||
|
|
||||||
commands := make([]Command, 0, 5)
|
commands := make([]Command, 0, 5)
|
||||||
installed := make([]ConfigOperation, 0, 4)
|
for _, operation := range prepared.installed {
|
||||||
if configOperationDeclared(normalized.Scopes, ConfigValidate) {
|
var command Command
|
||||||
validate, err := newConfigValidateCommand(normalized)
|
switch operation {
|
||||||
|
case ConfigValidate:
|
||||||
|
command, err = newConfigValidateCommand(prepared)
|
||||||
|
case ConfigShow:
|
||||||
|
command, err = newConfigShowCommand(prepared)
|
||||||
|
case ConfigSources:
|
||||||
|
command, err = newConfigSourcesCommand(prepared)
|
||||||
|
case ConfigExplain:
|
||||||
|
command, err = newConfigExplainCommand(prepared)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
commands = append(commands, validate)
|
commands = append(commands, command)
|
||||||
installed = append(installed, ConfigValidate)
|
|
||||||
}
|
}
|
||||||
if configOperationDeclared(normalized.Scopes, ConfigShow) {
|
command, err := newConfigScopesCommandWithPrepared(prepared)
|
||||||
show, err := newConfigShowCommand(normalized)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commands = append(commands, show)
|
|
||||||
installed = append(installed, ConfigShow)
|
|
||||||
}
|
|
||||||
if configOperationDeclared(normalized.Scopes, ConfigSources) {
|
|
||||||
sources, err := newConfigSourcesCommand(normalized)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commands = append(commands, sources)
|
|
||||||
installed = append(installed, ConfigSources)
|
|
||||||
}
|
|
||||||
if configOperationDeclared(normalized.Scopes, ConfigExplain) {
|
|
||||||
explain, err := newConfigExplainCommand(normalized)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commands = append(commands, explain)
|
|
||||||
installed = append(installed, ConfigExplain)
|
|
||||||
}
|
|
||||||
command, err := newConfigScopesCommandWithScopes(normalized.Scopes, installed)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -290,6 +283,8 @@ func normalizeConfigSuite[C any](suite ConfigSuite[C]) (ConfigSuite[C], error) {
|
|||||||
scope.Operations = operations
|
scope.Operations = operations
|
||||||
normalized.Scopes = append(normalized.Scopes, scope)
|
normalized.Scopes = append(normalized.Scopes, scope)
|
||||||
}
|
}
|
||||||
|
seenNames := make(map[string]struct{}, len(normalized.Selectors))
|
||||||
|
seenShorts := make(map[string]struct{}, len(normalized.Selectors))
|
||||||
for index := range normalized.Selectors {
|
for index := range normalized.Selectors {
|
||||||
selector := &normalized.Selectors[index]
|
selector := &normalized.Selectors[index]
|
||||||
binding := selector.Binding.binding
|
binding := selector.Binding.binding
|
||||||
@@ -321,16 +316,48 @@ func normalizeConfigSuite[C any](suite ConfigSuite[C]) (ConfigSuite[C], error) {
|
|||||||
unique = append(unique, scopeName)
|
unique = append(unique, scopeName)
|
||||||
}
|
}
|
||||||
selector.Scopes = append([]string(nil), unique...)
|
selector.Scopes = append([]string(nil), unique...)
|
||||||
for previous := 0; previous < index; previous++ {
|
if _, exists := seenNames[binding.meta.Name]; exists {
|
||||||
other := normalized.Selectors[previous].Binding.binding.meta
|
return ConfigSuite[C]{}, fmt.Errorf("duplicate configuration selector flag --%s", binding.meta.Name)
|
||||||
if other.Name == binding.meta.Name || binding.meta.Short != "" && other.Short == binding.meta.Short {
|
}
|
||||||
|
if binding.meta.Short != "" {
|
||||||
|
if _, exists := seenShorts[binding.meta.Short]; exists {
|
||||||
return ConfigSuite[C]{}, fmt.Errorf("duplicate configuration selector flag --%s", binding.meta.Name)
|
return ConfigSuite[C]{}, fmt.Errorf("duplicate configuration selector flag --%s", binding.meta.Name)
|
||||||
}
|
}
|
||||||
|
seenShorts[binding.meta.Short] = struct{}{}
|
||||||
}
|
}
|
||||||
|
seenNames[binding.meta.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
return normalized, nil
|
return normalized, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareConfigSuite[C any](suite ConfigSuite[C]) *preparedConfigSuite[C] {
|
||||||
|
prepared := &preparedConfigSuite[C]{
|
||||||
|
suite: suite,
|
||||||
|
ordered: orderedConfigScopes(suite.Scopes),
|
||||||
|
byName: make(map[string]ConfigScope[C], len(suite.Scopes)),
|
||||||
|
installed: make([]ConfigOperation, 0, 4),
|
||||||
|
inventory: scopesInventory{Scopes: make([]configScopeInfo, 0, len(suite.Scopes))},
|
||||||
|
}
|
||||||
|
for _, scope := range suite.Scopes {
|
||||||
|
prepared.byName[scope.Name] = scope
|
||||||
|
}
|
||||||
|
for _, operation := range []ConfigOperation{ConfigValidate, ConfigShow, ConfigSources, ConfigExplain} {
|
||||||
|
if configOperationDeclared(suite.Scopes, operation) {
|
||||||
|
prepared.installed = append(prepared.installed, operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, scope := range prepared.ordered {
|
||||||
|
operations := make([]ConfigOperation, 0, len(scope.Operations))
|
||||||
|
for _, operation := range scope.Operations {
|
||||||
|
if containsOperation(prepared.installed, operation) {
|
||||||
|
operations = append(operations, operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prepared.inventory.Scopes = append(prepared.inventory.Scopes, configScopeInfo{Name: scope.Name, Operations: operations})
|
||||||
|
}
|
||||||
|
return prepared
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeConfigOperations(operations []ConfigOperation) ([]ConfigOperation, error) {
|
func normalizeConfigOperations(operations []ConfigOperation) ([]ConfigOperation, error) {
|
||||||
seen := make(map[ConfigOperation]struct{}, len(operations))
|
seen := make(map[ConfigOperation]struct{}, len(operations))
|
||||||
for _, operation := range operations {
|
for _, operation := range operations {
|
||||||
@@ -350,7 +377,7 @@ func normalizeConfigOperations(operations []ConfigOperation) ([]ConfigOperation,
|
|||||||
return ordered, nil
|
return ordered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigScopesCommandWithScopes[C any](scopes []ConfigScope[C], installed []ConfigOperation) (Command, error) {
|
func newConfigScopesCommandWithPrepared[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||||
formatDefault := "text"
|
formatDefault := "text"
|
||||||
spec := CommandSpec[configScopesInput]{
|
spec := CommandSpec[configScopesInput]{
|
||||||
Name: "scopes",
|
Name: "scopes",
|
||||||
@@ -362,22 +389,12 @@ func newConfigScopesCommandWithScopes[C any](scopes []ConfigScope[C], installed
|
|||||||
})},
|
})},
|
||||||
CheckInput: func(input configScopesInput) error { return checkConfigFormat(input.Format) },
|
CheckInput: func(input configScopesInput) error { return checkConfigFormat(input.Format) },
|
||||||
Run: func(invocation Invocation, input configScopesInput) Outcome {
|
Run: func(invocation Invocation, input configScopesInput) Outcome {
|
||||||
inventory := scopesInventory{Scopes: make([]configScopeInfo, 0, len(scopes))}
|
|
||||||
for _, scope := range orderedConfigScopes(scopes) {
|
|
||||||
operations := make([]ConfigOperation, 0, len(scope.Operations))
|
|
||||||
for _, operation := range scope.Operations {
|
|
||||||
if containsOperation(installed, operation) {
|
|
||||||
operations = append(operations, operation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inventory.Scopes = append(inventory.Scopes, configScopeInfo{Name: scope.Name, Operations: operations})
|
|
||||||
}
|
|
||||||
var output []byte
|
var output []byte
|
||||||
var err error
|
var err error
|
||||||
if input.Format == "yaml" {
|
if input.Format == "yaml" {
|
||||||
output, err = yaml.Marshal(inventory)
|
output, err = yaml.Marshal(prepared.inventory)
|
||||||
} else {
|
} else {
|
||||||
output = []byte(formatScopeInventory(inventory))
|
output = []byte(formatScopeInventory(prepared.inventory))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
|
|||||||
@@ -85,6 +85,77 @@ func TestConfigInputDefaultsToAppAndRetainsAbsentSelections(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterConfigOwnsPreparedScopeAndSelectorMetadata(t *testing.T) {
|
||||||
|
type selectorInput struct{ Region Supplied[string] }
|
||||||
|
var acquired selectorInput
|
||||||
|
suite := ConfigSuite[selectorInput]{
|
||||||
|
Selectors: []Selector[selectorInput]{{
|
||||||
|
Binding: ValueFlag[selectorInput](FlagMeta{Name: "region"}, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Region = value }),
|
||||||
|
Scopes: []string{"app"},
|
||||||
|
}},
|
||||||
|
Scopes: []ConfigScope[selectorInput]{{
|
||||||
|
Name: "app",
|
||||||
|
Operations: []ConfigOperation{ConfigValidate},
|
||||||
|
Acquire: func(_ context.Context, _ ConfigRequest, input selectorInput) (Acquired[ConfigView], error) {
|
||||||
|
acquired = input
|
||||||
|
return Acquired[ConfigView]{}, nil
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
app, err := New(Identity{Name: "example"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := RegisterConfig(app, suite); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.Scopes[0].Name = "renamed"
|
||||||
|
suite.Scopes[0].Operations[0] = ConfigShow
|
||||||
|
suite.Selectors[0].Scopes[0] = "renamed"
|
||||||
|
|
||||||
|
var inventory bytes.Buffer
|
||||||
|
if result := app.Execute(context.Background(), []string{"config", "scopes"}, Streams{Out: &inventory}); result.Kind != OutcomeSuccess {
|
||||||
|
t.Fatalf("scope inventory outcome = %#v", result)
|
||||||
|
}
|
||||||
|
if got := inventory.String(); got != "app: validate\n" {
|
||||||
|
t.Fatalf("scope inventory after caller mutation = %q", got)
|
||||||
|
}
|
||||||
|
if result := app.Execute(context.Background(), []string{"config", "validate", "--scope=app", "--region=west"}, Streams{}); result.Kind != OutcomeSuccess {
|
||||||
|
t.Fatalf("registered scope execution after caller mutation = %#v", result)
|
||||||
|
}
|
||||||
|
if acquired.Region != (Supplied[string]{Value: "west", Set: true}) {
|
||||||
|
t.Fatalf("acquired selector after caller mutation = %#v", acquired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterConfigRejectsDuplicateSelectorNamesAndShorts(t *testing.T) {
|
||||||
|
type selectorInput struct{ First, Second string }
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
firstMeta FlagMeta
|
||||||
|
secondMeta FlagMeta
|
||||||
|
}{
|
||||||
|
{name: "name", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "region", Short: "g"}},
|
||||||
|
{name: "short", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "group", Short: "r"}},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
app, err := New(Identity{Name: "example"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
suite := ConfigSuite[selectorInput]{Selectors: []Selector[selectorInput]{
|
||||||
|
{Binding: ValueFlag[selectorInput](test.firstMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.First = value.Value })},
|
||||||
|
{Binding: ValueFlag[selectorInput](test.secondMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Second = value.Value })},
|
||||||
|
}}
|
||||||
|
if err := RegisterConfig(app, suite); err == nil || !strings.Contains(err.Error(), "duplicate configuration selector flag") {
|
||||||
|
t.Fatalf("RegisterConfig error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRegisterConfigRejectsInvalidSuitesAndCollisions(t *testing.T) {
|
func TestRegisterConfigRejectsInvalidSuitesAndCollisions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -147,13 +218,14 @@ func TestConfigSelectorPreflightAndScopedContextProjection(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
preparedSuite := prepareConfigSuite(normalized)
|
||||||
spec := CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}
|
spec := CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}
|
||||||
prepared, err := prepareInput(spec, [][]string{{"all"}, {"text"}, {""}, nil, {"0"}, {"false"}, nil}, nil, true)
|
prepared, err := prepareInput(spec, [][]string{{"all"}, {"text"}, {""}, nil, {"0"}, {"false"}, nil}, nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
input := prepared.build()
|
input := prepared.build()
|
||||||
scopeNames, err := preflightConfig(normalized, input, ConfigValidate)
|
scopeNames, err := preflightConfig(preparedSuite, input, ConfigValidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -167,7 +239,7 @@ func TestConfigSelectorPreflightAndScopedContextProjection(t *testing.T) {
|
|||||||
t.Fatalf("NewContext called during preflight %d times", baseContextCalls)
|
t.Fatalf("NewContext called during preflight %d times", baseContextCalls)
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts := contextsForScopes(normalized, input, scopeNames)
|
contexts := contextsForScopes(preparedSuite, input, scopeNames)
|
||||||
if baseContextCalls != 1 {
|
if baseContextCalls != 1 {
|
||||||
t.Fatalf("NewContext calls = %d, want one", baseContextCalls)
|
t.Fatalf("NewContext calls = %d, want one", baseContextCalls)
|
||||||
}
|
}
|
||||||
@@ -194,11 +266,12 @@ func TestConfigPreflightRejectsUnsupportedAggregateAndInapplicableSelector(t *te
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
preparedSuite := prepareConfigSuite(normalized)
|
||||||
prepared, err := prepareInput(CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}, [][]string{{"all"}, {"text"}, nil, nil, nil}, nil, true)
|
prepared, err := prepareInput(CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}, [][]string{{"all"}, {"text"}, nil, nil, nil}, nil, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := preflightConfig(normalized, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "does not support validate") {
|
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "does not support validate") {
|
||||||
t.Fatalf("unsupported aggregate error = %v", err)
|
t.Fatalf("unsupported aggregate error = %v", err)
|
||||||
}
|
}
|
||||||
if newContextCalls != 0 {
|
if newContextCalls != 0 {
|
||||||
@@ -209,7 +282,7 @@ func TestConfigPreflightRejectsUnsupportedAggregateAndInapplicableSelector(t *te
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := preflightConfig(normalized, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "not applicable") {
|
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "not applicable") {
|
||||||
t.Fatalf("inapplicable selector error = %v", err)
|
t.Fatalf("inapplicable selector error = %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -779,7 +779,7 @@ allocation costs, with timing variation on repeated success and deep dispatch.
|
|||||||
|
|
||||||
## Stage 10: prepare configuration scope metadata once
|
## Stage 10: prepare configuration scope metadata once
|
||||||
|
|
||||||
**Status:** not started. **Prerequisite:** Stage 9.
|
**Status:** complete. **Prerequisite:** Stage 9.
|
||||||
|
|
||||||
**Files:** [config.go](../../config.go), [selectors.go](../../selectors.go),
|
**Files:** [config.go](../../config.go), [selectors.go](../../selectors.go),
|
||||||
configuration handler files, configuration/cancellation tests, scope benchmarks,
|
configuration handler files, configuration/cancellation tests, scope benchmarks,
|
||||||
@@ -821,7 +821,58 @@ Preserve native reporting and per-operation output timing.
|
|||||||
scope lookup is indexed, and duplicate selector checks are linear in selector
|
scope lookup is indexed, and duplicate selector checks are linear in selector
|
||||||
count plus declared scope references.
|
count plus declared scope references.
|
||||||
|
|
||||||
**Evidence:** pending.
|
**Evidence:** `GOWORK=off GOTOOLCHAIN=local GOPROXY=off GONOPROXY=none
|
||||||
|
GOSUMDB=off go test -mod=readonly -count=1 .` passed, including existing
|
||||||
|
configuration, selector, discovery, and cancellation behavior. Added coverage
|
||||||
|
that caller mutations to scope and selector slices after registration do not
|
||||||
|
change discovery, capability selection, or projected inputs, and that duplicate
|
||||||
|
selector names and short names retain their error category. Registration now
|
||||||
|
normalizes owned metadata, prepares the ordered scopes, name index, installed
|
||||||
|
operation list, and discovery DTO once, then passes that private value through
|
||||||
|
validate/show/sources/explain and their preflight/run paths. Aggregate selection
|
||||||
|
checks every capability using the prepared metadata before any acquisition.
|
||||||
|
`NewContext`, acquisition, checks, native writers, and encoding are not called
|
||||||
|
while preparing the suite. Discovery renders the retained DTO at invocation
|
||||||
|
time. Scope sorting occurs once, named lookup uses the map, and duplicate
|
||||||
|
selector validation uses seen-name/seen-short maps while preserving validation
|
||||||
|
order and deduplicating scope references. Public `ConfigSuite` and `ConfigScope`
|
||||||
|
declarations and signatures are unchanged.
|
||||||
|
|
||||||
|
Measurements used Go 1.26.8, linux/amd64, Linux 6.17.0-41-generic, Intel Core
|
||||||
|
i5-7260U CPU @ 2.20 GHz, based on source revision `a7960e5` with this stage's
|
||||||
|
changes in the working tree. The five-sample command passed:
|
||||||
|
`GOWORK=off GOTOOLCHAIN=local GOPROXY=off GONOPROXY=none GOSUMDB=off go test -mod=readonly -run '^$' -bench '^BenchmarkPerformance(ConfigScopes|ConfigRegistration)$' -benchmem -benchtime=200ms -count=5 .`.
|
||||||
|
Raw output is in `/tmp/commandkit-performance-config-after-stage10.txt`;
|
||||||
|
focused repeats for selector-heavy registration and the ordinary two-scope
|
||||||
|
aggregate are in `/tmp/commandkit-performance-config-registration-repeat.txt`
|
||||||
|
and `/tmp/commandkit-performance-config-two-scope-repeat.txt`.
|
||||||
|
|
||||||
|
Against Stage 2, two-scope discovery measured 16,788–21,062 ns/op, 18,557
|
||||||
|
B/op, and 195 allocations (baseline: 23,840–30,007 ns/op, 21,161–21,162
|
||||||
|
B/op, 223 allocations). Named selection measured 18,375–21,756 ns/op,
|
||||||
|
19,621 B/op, and 207 allocations (baseline: 24,841–26,340 ns/op, 21,817
|
||||||
|
B/op, 228 allocations). Two-scope aggregate validation measured 22,977–28,152
|
||||||
|
ns/op, 19,637–19,638 B/op, and 208 allocations in the focused repeat (baseline:
|
||||||
|
25,231–31,126 ns/op, 22,361–22,362 B/op, 237 allocations).
|
||||||
|
|
||||||
|
At 32 scopes, discovery measured 18,253–20,311 ns/op, 21,070 B/op, and 199
|
||||||
|
allocations (baseline: 31,439–37,167 ns/op, 28,141–28,142 B/op, 257
|
||||||
|
allocations). Named selection measured 18,679–20,529 ns/op, 20,582 B/op, and
|
||||||
|
207 allocations (baseline: 29,123–74,374 ns/op, 22,775–22,778 B/op, 228
|
||||||
|
allocations). Aggregate validation measured 25,036–33,981 ns/op, 22,702 B/op,
|
||||||
|
and 240 allocations (baseline: 45,548–63,471 ns/op, 29,781–29,782 B/op, 269
|
||||||
|
allocations).
|
||||||
|
|
||||||
|
Registration with 4 selectors measured 11,272–14,623 ns/op, 9,310 B/op, and
|
||||||
|
107 allocations (baseline: 12,573–14,789 ns/op, 8,911 B/op, 104 allocations).
|
||||||
|
With 64 selectors, the focused repeat measured 72,002–74,712 ns/op, 90,197–
|
||||||
|
90,200 B/op, and 494 allocations (baseline: 91,675–105,548 ns/op,
|
||||||
|
82,817–82,818 B/op, 485 allocations). The retained ordered slice, lookup map,
|
||||||
|
and discovery DTO add registration storage: about 399 bytes and 3 allocations
|
||||||
|
for 4 selectors, and 7.4 KiB and 9 allocations for 64 selectors. Selector-heavy
|
||||||
|
registration timing improved in the focused repeat; execution ranges improved
|
||||||
|
for discovery and named/aggregate selection. These benchmark timings vary and
|
||||||
|
are observations, not thresholds.
|
||||||
|
|
||||||
## Stage 11: audit the completed target and reconcile documentation
|
## Stage 11: audit the completed target and reconcile documentation
|
||||||
|
|
||||||
|
|||||||
16
explain.go
16
explain.go
@@ -9,8 +9,8 @@ import (
|
|||||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
func newConfigExplainCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||||
flags := configInputFlags(suite, "text")
|
flags := configInputFlags(prepared.suite, "text")
|
||||||
flags = append(flags, ValueFlag[configInput[C]](FlagMeta{
|
flags = append(flags, ValueFlag[configInput[C]](FlagMeta{
|
||||||
Name: "path",
|
Name: "path",
|
||||||
Usage: "Configuration path as a JSON Pointer; an empty value selects the root.",
|
Usage: "Configuration path as a JSON Pointer; an empty value selects the root.",
|
||||||
@@ -23,7 +23,7 @@ func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
|||||||
Summary: "Explain the effective and historical value at a configuration path.",
|
Summary: "Explain the effective and historical value at a configuration path.",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
CheckInput: func(input configInput[C]) error {
|
CheckInput: func(input configInput[C]) error {
|
||||||
if _, err := preflightConfig(suite, input, ConfigExplain); err != nil {
|
if _, err := preflightConfig(prepared, input, ConfigExplain); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !input.Path.Set {
|
if !input.Path.Set {
|
||||||
@@ -35,7 +35,7 @@ func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||||
return runConfigExplanation(invocation, suite, input)
|
return runConfigExplanation(invocation, prepared, input)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return NewCommand(spec)
|
return NewCommand(spec)
|
||||||
@@ -82,11 +82,11 @@ type secretReferenceDTO struct {
|
|||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfigExplanation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
func runConfigExplanation[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
scopeNames, err := selectConfigScopes(suite, input, ConfigExplain)
|
scopeNames, err := selectConfigScopes(prepared, input, ConfigExplain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
@@ -98,8 +98,8 @@ func runConfigExplanation[C any](invocation Invocation, suite ConfigSuite[C], in
|
|||||||
return Usage(fmt.Errorf("invalid configuration path %q: %w", input.Path.Value, err))
|
return Usage(fmt.Errorf("invalid configuration path %q: %w", input.Path.Value, err))
|
||||||
}
|
}
|
||||||
name := scopeNames[0]
|
name := scopeNames[0]
|
||||||
contexts := contextsForScopes(suite, input, scopeNames)
|
contexts := contextsForScopes(prepared, input, scopeNames)
|
||||||
scope, ok := findConfigScope(suite.Scopes, name)
|
scope, ok := findConfigScope(prepared, name)
|
||||||
if !ok || scope.Acquire == nil {
|
if !ok || scope.Acquire == nil {
|
||||||
return Failure(fmt.Errorf("configuration scope %q has no acquisition callback", name))
|
return Failure(fmt.Errorf("configuration scope %q has no acquisition callback", name))
|
||||||
}
|
}
|
||||||
|
|||||||
38
selectors.go
38
selectors.go
@@ -82,20 +82,20 @@ func configurationSelectionFlags[I any](setConfig, setOverlay func(*I, Supplied[
|
|||||||
|
|
||||||
func parseString(value string) (string, error) { return value, nil }
|
func parseString(value string) (string, error) { return value, nil }
|
||||||
|
|
||||||
func preflightConfig[C any](suite ConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
func preflightConfig[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
||||||
scopes, err := selectConfigScopes(suite, input, operation)
|
scopes, err := selectConfigScopes(prepared, input, operation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if suite.CheckInput != nil {
|
if prepared.suite.CheckInput != nil {
|
||||||
if err := suite.CheckInput(input.Selection, input.SelectorContext); err != nil {
|
if err := prepared.suite.CheckInput(input.Selection, input.SelectorContext); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scopes, nil
|
return scopes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
func selectConfigScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
|
||||||
if err := checkConfigFormat(input.Format); err != nil {
|
if err := checkConfigFormat(input.Format); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -110,16 +110,16 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
|
|||||||
return nil, fmt.Errorf("configuration scope must not be empty")
|
return nil, fmt.Errorf("configuration scope must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
scopes := make([]string, 0, len(suite.Scopes))
|
scopes := make([]string, 0, len(prepared.ordered))
|
||||||
if input.Scope == "all" {
|
if input.Scope == "all" {
|
||||||
for _, scope := range orderedConfigScopes(suite.Scopes) {
|
for _, scope := range prepared.ordered {
|
||||||
scopes = append(scopes, scope.Name)
|
scopes = append(scopes, scope.Name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope, ok := findConfigScope(suite.Scopes, input.Scope)
|
scope, ok := prepared.byName[input.Scope]
|
||||||
if !ok {
|
if !ok {
|
||||||
choices := make([]string, 0, len(suite.Scopes))
|
choices := make([]string, 0, len(prepared.ordered))
|
||||||
for _, available := range orderedConfigScopes(suite.Scopes) {
|
for _, available := range prepared.ordered {
|
||||||
choices = append(choices, available.Name)
|
choices = append(choices, available.Name)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown configuration scope %q (available: %s)", input.Scope, joinStrings(choices))
|
return nil, fmt.Errorf("unknown configuration scope %q (available: %s)", input.Scope, joinStrings(choices))
|
||||||
@@ -132,7 +132,7 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
|
|||||||
|
|
||||||
if input.Scope == "all" {
|
if input.Scope == "all" {
|
||||||
for _, scopeName := range scopes {
|
for _, scopeName := range scopes {
|
||||||
scope, _ := findConfigScope(suite.Scopes, scopeName)
|
scope := prepared.byName[scopeName]
|
||||||
if !containsOperation(scope.Operations, operation) {
|
if !containsOperation(scope.Operations, operation) {
|
||||||
return nil, unsupportedConfigOperation(scope, operation)
|
return nil, unsupportedConfigOperation(scope, operation)
|
||||||
}
|
}
|
||||||
@@ -156,10 +156,10 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
|
|||||||
return scopes, nil
|
return scopes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextsForScopes[C any](suite ConfigSuite[C], input configInput[C], scopeNames []string) map[string]C {
|
func contextsForScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], scopeNames []string) map[string]C {
|
||||||
base := *new(C)
|
base := *new(C)
|
||||||
if suite.NewContext != nil {
|
if prepared.suite.NewContext != nil {
|
||||||
base = suite.NewContext()
|
base = prepared.suite.NewContext()
|
||||||
}
|
}
|
||||||
contexts := make(map[string]C, len(scopeNames))
|
contexts := make(map[string]C, len(scopeNames))
|
||||||
for _, name := range scopeNames {
|
for _, name := range scopeNames {
|
||||||
@@ -176,13 +176,9 @@ func contextsForScopes[C any](suite ConfigSuite[C], input configInput[C], scopeN
|
|||||||
return contexts
|
return contexts
|
||||||
}
|
}
|
||||||
|
|
||||||
func findConfigScope[C any](scopes []ConfigScope[C], name string) (ConfigScope[C], bool) {
|
func findConfigScope[C any](prepared *preparedConfigSuite[C], name string) (ConfigScope[C], bool) {
|
||||||
for _, scope := range scopes {
|
scope, ok := prepared.byName[name]
|
||||||
if scope.Name == name {
|
return scope, ok
|
||||||
return scope, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ConfigScope[C]{}, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsOperation(operations []ConfigOperation, requested ConfigOperation) bool {
|
func containsOperation(operations []ConfigOperation, requested ConfigOperation) bool {
|
||||||
|
|||||||
16
show.go
16
show.go
@@ -8,17 +8,17 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newConfigShowCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
func newConfigShowCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||||
spec := CommandSpec[configInput[C]]{
|
spec := CommandSpec[configInput[C]]{
|
||||||
Name: string(ConfigShow),
|
Name: string(ConfigShow),
|
||||||
Summary: "Show effective configuration as YAML.",
|
Summary: "Show effective configuration as YAML.",
|
||||||
Flags: configInputFlags(suite, "yaml"),
|
Flags: configInputFlags(prepared.suite, "yaml"),
|
||||||
CheckInput: func(input configInput[C]) error {
|
CheckInput: func(input configInput[C]) error {
|
||||||
_, err := preflightConfig(suite, input, ConfigShow)
|
_, err := preflightConfig(prepared, input, ConfigShow)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||||
return runConfigShow(invocation, suite, input)
|
return runConfigShow(invocation, prepared, input)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return NewCommand(spec)
|
return NewCommand(spec)
|
||||||
@@ -29,19 +29,19 @@ type effectiveDocument struct {
|
|||||||
view ConfigView
|
view ConfigView
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfigShow[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
func runConfigShow[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
scopeNames, err := selectConfigScopes(suite, input, ConfigShow)
|
scopeNames, err := selectConfigScopes(prepared, input, ConfigShow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
contexts := contextsForScopes(suite, input, scopeNames)
|
contexts := contextsForScopes(prepared, input, scopeNames)
|
||||||
documents := make([]effectiveDocument, 0, len(scopeNames))
|
documents := make([]effectiveDocument, 0, len(scopeNames))
|
||||||
diagnostics := make([]Diagnostic, 0)
|
diagnostics := make([]Diagnostic, 0)
|
||||||
for _, name := range scopeNames {
|
for _, name := range scopeNames {
|
||||||
scope, ok := findConfigScope(suite.Scopes, name)
|
scope, ok := findConfigScope(prepared, name)
|
||||||
if !ok || scope.Acquire == nil {
|
if !ok || scope.Acquire == nil {
|
||||||
return showFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, false)
|
return showFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, false)
|
||||||
}
|
}
|
||||||
|
|||||||
16
sources.go
16
sources.go
@@ -6,17 +6,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newConfigSourcesCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
func newConfigSourcesCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||||
spec := CommandSpec[configInput[C]]{
|
spec := CommandSpec[configInput[C]]{
|
||||||
Name: string(ConfigSources),
|
Name: string(ConfigSources),
|
||||||
Summary: "List configuration sources used by a scope.",
|
Summary: "List configuration sources used by a scope.",
|
||||||
Flags: configInputFlags(suite, "text"),
|
Flags: configInputFlags(prepared.suite, "text"),
|
||||||
CheckInput: func(input configInput[C]) error {
|
CheckInput: func(input configInput[C]) error {
|
||||||
_, err := preflightConfig(suite, input, ConfigSources)
|
_, err := preflightConfig(prepared, input, ConfigSources)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||||
return runConfigSources(invocation, suite, input)
|
return runConfigSources(invocation, prepared, input)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return NewCommand(spec)
|
return NewCommand(spec)
|
||||||
@@ -43,17 +43,17 @@ type locationDTO struct {
|
|||||||
Column int `yaml:"column"`
|
Column int `yaml:"column"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfigSources[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
func runConfigSources[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
scopeNames, err := selectConfigScopes(suite, input, ConfigSources)
|
scopeNames, err := selectConfigScopes(prepared, input, ConfigSources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
contexts := contextsForScopes(suite, input, scopeNames)
|
contexts := contextsForScopes(prepared, input, scopeNames)
|
||||||
name := scopeNames[0]
|
name := scopeNames[0]
|
||||||
scope, ok := findConfigScope(suite.Scopes, name)
|
scope, ok := findConfigScope(prepared, name)
|
||||||
if !ok || scope.Acquire == nil {
|
if !ok || scope.Acquire == nil {
|
||||||
return sourceReportFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), nil, false)
|
return sourceReportFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), nil, false)
|
||||||
}
|
}
|
||||||
|
|||||||
16
validate.go
16
validate.go
@@ -17,35 +17,35 @@ func configOperationDeclared[C any](scopes []ConfigScope[C], operation ConfigOpe
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigValidateCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
func newConfigValidateCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||||
spec := CommandSpec[configInput[C]]{
|
spec := CommandSpec[configInput[C]]{
|
||||||
Name: string(ConfigValidate),
|
Name: string(ConfigValidate),
|
||||||
Summary: "Validate selected configuration scopes.",
|
Summary: "Validate selected configuration scopes.",
|
||||||
Flags: configInputFlags(suite, "text"),
|
Flags: configInputFlags(prepared.suite, "text"),
|
||||||
CheckInput: func(input configInput[C]) error {
|
CheckInput: func(input configInput[C]) error {
|
||||||
_, err := preflightConfig(suite, input, ConfigValidate)
|
_, err := preflightConfig(prepared, input, ConfigValidate)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||||
return runConfigValidation(invocation, suite, input)
|
return runConfigValidation(invocation, prepared, input)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return NewCommand(spec)
|
return NewCommand(spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfigValidation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
func runConfigValidation[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
scopeNames, err := selectConfigScopes(suite, input, ConfigValidate)
|
scopeNames, err := selectConfigScopes(prepared, input, ConfigValidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failure(err)
|
return Failure(err)
|
||||||
}
|
}
|
||||||
contexts := contextsForScopes(suite, input, scopeNames)
|
contexts := contextsForScopes(prepared, input, scopeNames)
|
||||||
diagnostics := make([]Diagnostic, 0)
|
diagnostics := make([]Diagnostic, 0)
|
||||||
writtenRecord := false
|
writtenRecord := false
|
||||||
for _, name := range scopeNames {
|
for _, name := range scopeNames {
|
||||||
scope, ok := findConfigScope(suite.Scopes, name)
|
scope, ok := findConfigScope(prepared, name)
|
||||||
if !ok || scope.Acquire == nil {
|
if !ok || scope.Acquire == nil {
|
||||||
return validationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, writtenRecord)
|
return validationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, writtenRecord)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user