Capture immutable command declarations

This commit is contained in:
2026-10-11 01:15:13 +00:00
parent 476e98a463
commit de9bfdcc4c
16 changed files with 368 additions and 474 deletions

View File

@@ -10,7 +10,6 @@ import (
"testing"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
@@ -24,7 +23,7 @@ func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
firstContext := context.WithValue(context.Background(), struct{}{}, "first")
secondContext := context.WithValue(context.Background(), struct{}{}, "second")
var firstOut, secondOut, firstDiagnostic, secondDiagnostic bytes.Buffer
command, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "run",
Flags: []commandkit.Flag[input]{
commandkit.ValueFlag(commandkit.FlagMeta{Name: "name"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(target *input, value commandkit.Supplied[string]) { target.name = value }),
@@ -37,9 +36,6 @@ func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
return commandkit.Success()
},
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -70,7 +66,7 @@ func TestExecuteUsesFreshSessionForHelpAfterFlagBearingInvocation(t *testing.T)
enabled commandkit.Supplied[bool]
}
var received []input
command, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "run",
Flags: []commandkit.Flag[input]{
commandkit.ValueFlag(commandkit.FlagMeta{Name: "name"}, (*string)(nil), func(value string) (string, error) {
@@ -83,9 +79,6 @@ func TestExecuteUsesFreshSessionForHelpAfterFlagBearingInvocation(t *testing.T)
return commandkit.Success()
},
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -115,14 +108,11 @@ func TestExecutePresentsHandlerUsageAfterRegisteringCommand(t *testing.T) {
usageCause := errors.New("handler requests usage")
var app *commandkit.App
var calls int
late, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
late := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "late",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() },
})
if err != nil {
t.Fatal(err)
}
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
calls++
@@ -132,10 +122,7 @@ func TestExecutePresentsHandlerUsageAfterRegisteringCommand(t *testing.T) {
return commandkit.Usage(usageCause)
},
})
if err != nil {
t.Fatal(err)
}
app, err = commandkit.New(commandkit.Identity{Name: "example"}, command)
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
}
@@ -156,7 +143,7 @@ func TestExecuteReturnsUsageForParsingRequirementsAndInputChecks(t *testing.T) {
type input struct{ count commandkit.Supplied[int] }
parseFailure := errors.New("bad count")
checkFailure := errors.New("incompatible inputs")
command, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "run",
Flags: []commandkit.Flag[input]{commandkit.ValueFlag(commandkit.FlagMeta{Name: "count", Required: true}, nil, func(value string) (int, error) {
if value == "bad" {
@@ -168,9 +155,6 @@ func TestExecuteReturnsUsageForParsingRequirementsAndInputChecks(t *testing.T) {
CheckInput: func(input) error { return checkFailure },
Run: func(commandkit.Invocation, input) commandkit.Outcome { return commandkit.Success() },
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -192,8 +176,8 @@ func TestExecuteReturnsUsageForParsingRequirementsAndInputChecks(t *testing.T) {
func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
domainCause := errors.New("domain rejected the request")
warning := commandkit.MessageDiagnostic(configkit.SeverityWarning, "review needed")
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
warning := commandkit.MessageDiagnostic(commandkit.SeverityWarning, "review needed")
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Domain("sample", 2, domainCause)
@@ -201,9 +185,6 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
return outcome
},
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -216,7 +197,7 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
if diagnostic.String() != "review needed\ndomain rejected the request\n" || outcome.DiagnosticDelivery != commandkit.DeliveryComplete {
t.Fatalf("warning delivery = %q, %#v", diagnostic.String(), outcome)
}
warningOnly, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
warningOnly := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "warn",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
result := commandkit.Success()
@@ -224,9 +205,6 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
return result
},
})
if err != nil {
t.Fatal(err)
}
app, err = commandkit.New(commandkit.Identity{Name: "example"}, warningOnly)
if err != nil {
t.Fatal(err)
@@ -240,20 +218,17 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
func TestExecuteDoesNotRepeatCompletedDiagnosticsOrPresentedCauses(t *testing.T) {
cause := errors.New("already shown")
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Domain("sample", 2, cause)
outcome.Diagnostics = []commandkit.Diagnostic{{Severity: configkit.SeverityError, Write: func(writer io.Writer) error {
outcome.Diagnostics = []commandkit.Diagnostic{{Severity: commandkit.SeverityError, Write: func(writer io.Writer) error {
_, err := io.WriteString(writer, "presented\n")
return err
}}}
return outcome
},
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -267,10 +242,7 @@ func TestExecuteDoesNotRepeatCompletedDiagnosticsOrPresentedCauses(t *testing.T)
diagnostic.Reset()
completed := commandkit.Domain("sample", 2, cause)
completed.DiagnosticDelivery = commandkit.DeliveryComplete
command, err = commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "done", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return completed }})
if err != nil {
t.Fatal(err)
}
command = commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "done", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return completed }})
app, err = commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -284,7 +256,7 @@ func TestExecuteDoesNotRepeatCompletedDiagnosticsOrPresentedCauses(t *testing.T)
func TestExecuteRetainsOperationAndDiagnosticDeliveryFailures(t *testing.T) {
operationFailure := context.Canceled
writeFailure := errors.New("writer failed")
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Failure(operationFailure)
@@ -292,9 +264,6 @@ func TestExecuteRetainsOperationAndDiagnosticDeliveryFailures(t *testing.T) {
return outcome
},
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -313,7 +282,7 @@ func TestExecuteRetainsOperationAndDiagnosticDeliveryFailures(t *testing.T) {
func TestExecuteHelpSkipsRequiredInputsAndRejectsNilContext(t *testing.T) {
type input struct{ id commandkit.Supplied[string] }
factoryCalls, checkCalls, runCalls := 0, 0, 0
command, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "run",
NewInput: func() input {
factoryCalls++
@@ -323,9 +292,6 @@ func TestExecuteHelpSkipsRequiredInputsAndRejectsNilContext(t *testing.T) {
CheckInput: func(input) error { checkCalls++; return nil },
Run: func(commandkit.Invocation, input) commandkit.Outcome { runCalls++; return commandkit.Success() },
})
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -344,7 +310,7 @@ func TestExecuteRunsApplicationOwnedNestedHelp(t *testing.T) {
type input struct{ topic commandkit.Supplied[string] }
var received []input
domainCause := errors.New("application help result")
help, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
help := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "help",
Description: "Explain a session topic.",
Arguments: []commandkit.Argument[input]{commandkit.ValueArgument(
@@ -357,13 +323,7 @@ func TestExecuteRunsApplicationOwnedNestedHelp(t *testing.T) {
return commandkit.Domain("session-help", 7, domainCause)
},
})
if err != nil {
t.Fatal(err)
}
session, err := commandkit.Group("session", "Session commands.", help)
if err != nil {
t.Fatal(err)
}
session := commandkit.Group("session", "Session commands.", help)
app, err := commandkit.New(commandkit.Identity{Name: "example"}, session)
if err != nil {
t.Fatal(err)
@@ -401,7 +361,7 @@ func TestHelpRendersRegisteredMetadataAndResolvesExactPaths(t *testing.T) {
tags commandkit.Supplied[[]string]
}
factoryCalls, runCalls := 0, 0
show, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
show := commandkit.NewCommand(commandkit.CommandSpec[input]{
Name: "show",
Summary: "Show one item.",
Description: "Display the selected item in detail.",
@@ -418,25 +378,9 @@ func TestHelpRendersRegisteredMetadataAndResolvesExactPaths(t *testing.T) {
Arguments: []commandkit.Argument[input]{commandkit.RepeatArgument(commandkit.ArgumentMeta{Name: "ID", Usage: "item identifier"}, 1, 3, func(value string) (string, error) { return value, nil }, func(*input, commandkit.Supplied[[]string]) {})},
Run: func(commandkit.Invocation, input) commandkit.Outcome { runCalls++; return commandkit.Success() },
})
if err != nil {
t.Fatal(err)
}
config, err := commandkit.Group("config", "Inspect configuration.", show)
if err != nil {
t.Fatal(err)
}
now, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "now", Summary: "Deploy immediately.", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() }})
if err != nil {
t.Fatal(err)
}
deployParent, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deploy", Summary: "Deploy artifacts.", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { runCalls++; return commandkit.Success() }})
if err != nil {
t.Fatal(err)
}
deploy, err := commandkit.WithChildren(deployParent, now)
if err != nil {
t.Fatal(err)
}
config := commandkit.Group("config", "Inspect configuration.", show)
now := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "now", Summary: "Deploy immediately.", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() }})
deploy := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deploy", Summary: "Deploy artifacts.", Children: []commandkit.Command{now}, Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { runCalls++; return commandkit.Success() }})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, config, deploy)
if err != nil {
t.Fatal(err)
@@ -559,10 +503,7 @@ func (shortWriter) Write(data []byte) (int, error) {
}
func TestHelpEntryFormsPresentReportAndDiagnosticFailures(t *testing.T) {
group, err := commandkit.Group("notes", "Notes metadata.")
if err != nil {
t.Fatal(err)
}
group := commandkit.Group("notes", "Notes metadata.")
app, err := commandkit.New(commandkit.Identity{Name: "example"}, group)
if err != nil {
t.Fatal(err)
@@ -595,10 +536,7 @@ func TestExecuteNormalizesOutcomeDefaultsAndPreservesDomainValues(t *testing.T)
{commandkit.Outcome{Kind: commandkit.OutcomeDomain, Status: 2, Domain: "needs action", Cause: cause}, commandkit.OutcomeDomain, 2, "needs action", cause},
{commandkit.Outcome{Kind: commandkit.OutcomeDomain, Status: 0, Domain: "complete"}, commandkit.OutcomeDomain, 0, "complete", nil},
} {
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return tc.outcome }})
if err != nil {
t.Fatal(err)
}
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return tc.outcome }})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -617,11 +555,8 @@ func TestExecuteNormalizesOutcomeDefaultsAndPreservesDomainValues(t *testing.T)
func TestNilDiagnosticWritersHaveNoOutputAndDoNotHideCauses(t *testing.T) {
cause := errors.New("operation failed")
for _, base := range []commandkit.Outcome{commandkit.Success(), commandkit.Domain("needs action", 2, nil), commandkit.Failure(cause)} {
base.Diagnostics = []commandkit.Diagnostic{{}, {Severity: configkit.SeverityWarning}, {Severity: configkit.SeverityError}}
command, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return base }})
if err != nil {
t.Fatal(err)
}
base.Diagnostics = []commandkit.Diagnostic{{}, {Severity: commandkit.SeverityWarning}, {Severity: commandkit.SeverityError}}
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return base }})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
@@ -649,50 +584,3 @@ func TestNilDiagnosticWritersHaveNoOutputAndDoNotHideCauses(t *testing.T) {
}
}
}
func TestParentHelpDiscoversMetadataOnlyGroups(t *testing.T) {
leaf, err := commandkit.Group("notes", "Read notes metadata.")
if err != nil {
t.Fatal(err)
}
parent, err := commandkit.Group("topics", "Browse topic metadata.", leaf)
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, parent)
if err != nil {
t.Fatal(err)
}
loads := 0
if err := commandkit.RegisterConfig(app, commandkit.ConfigSuite[struct{}]{Scopes: []commandkit.ConfigScope[struct{}]{{Name: "app", Operations: []commandkit.ConfigOperation{commandkit.ConfigValidate}, Acquire: func(context.Context, commandkit.ConfigRequest, struct{}) (commandkit.Acquired[commandkit.ConfigView], error) {
loads++
return commandkit.Acquired[commandkit.ConfigView]{}, nil
}}}}); err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
args []string
content string
}{
{nil, "Browse topic metadata."},
{[]string{"topics"}, "Read notes metadata."},
{[]string{"topics", "--help"}, "Read notes metadata."},
{[]string{"help", "topics"}, "Read notes metadata."},
{[]string{"topics", "notes"}, "Read notes metadata."},
{[]string{"help", "topics", "notes"}, "Read notes metadata."},
} {
var out bytes.Buffer
result := app.Execute(context.Background(), tc.args, commandkit.Streams{Out: &out})
if result.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), tc.content) || loads != 0 {
t.Fatalf("%v: result=%#v output=%q loads=%d", tc.args, result, out.String(), loads)
}
if len(tc.args) == 1 && (!strings.Contains(out.String(), "Available Commands:") || !strings.Contains(out.String(), "[command]")) {
t.Fatalf("missing child inventory/usage: %q", out.String())
}
}
var diagnostic bytes.Buffer
result := app.Execute(context.Background(), []string{"topics", "unknown"}, commandkit.Streams{Err: &diagnostic})
if result.Kind != commandkit.OutcomeUsage || !strings.Contains(diagnostic.String(), "[command]") {
t.Fatalf("result=%#v diagnostic=%q", result, diagnostic.String())
}
}