Compare commits
15 Commits
integratio
...
v0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1613ff95ca | |||
| 68288ed684 | |||
| 56e3b013f9 | |||
| 96c96bb793 | |||
| d914295ced | |||
| a54ff03aef | |||
| 7aff296c86 | |||
| 3271c18b77 | |||
| 7dd30f7df3 | |||
| 5e5f61a306 | |||
| 6727d22ce6 | |||
| f23af30234 | |||
| ec0e58efff | |||
| 69257066e2 | |||
| 88ac6e76e4 |
@@ -84,6 +84,12 @@ func RenameFlag[I any](flag Flag[I], name string) Flag[I] {
|
|||||||
// BoolFlag declares a boolean flag. A bare flag is supplied by the parser as
|
// BoolFlag declares a boolean flag. A bare flag is supplied by the parser as
|
||||||
// "true"; explicit values are converted with strconv.ParseBool. Set distinguishes
|
// "true"; explicit values are converted with strconv.ParseBool. Set distinguishes
|
||||||
// an omitted flag from an explicit false value.
|
// an omitted flag from an explicit false value.
|
||||||
|
//
|
||||||
|
// A BoolFlag named debug receives the shared reporting flag's input. Its
|
||||||
|
// default and requiredness apply to application input, not reporting mode;
|
||||||
|
// explicit false satisfies requiredness. Its optional short spelling and
|
||||||
|
// completion metadata follow ordinary flag rules. The final input value can
|
||||||
|
// differ from [Invocation.ReportingMode] when other mode flags are supplied.
|
||||||
func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] {
|
func BoolFlag[I any](meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool])) Flag[I] {
|
||||||
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true}
|
binding := flagBinding[I]{identity: newBindingIdentity(), meta: meta, defaultText: fmt.Sprint(defaultValue), boolean: true}
|
||||||
if set == nil {
|
if set == nil {
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ type FlagMeta struct {
|
|||||||
// Name is a nonempty long name without leading dashes. It consists of
|
// Name is a nonempty long name without leading dashes. It consists of
|
||||||
// Unicode lowercase letters or digits in segments separated by single
|
// Unicode lowercase letters or digits in segments separated by single
|
||||||
// hyphens; digit-leading and numeric-only names are valid. The names help,
|
// hyphens; digit-leading and numeric-only names are valid. The names help,
|
||||||
// quiet, verbose, debug, progress and format are reserved.
|
// quiet, verbose, debug, progress and format are reserved, except that a
|
||||||
|
// BoolFlag named debug attaches to the shared reporting flag.
|
||||||
Name string
|
Name string
|
||||||
// Short is an optional one-byte spelling other than '-' or the reserved 'h'.
|
// Short is an optional one-byte spelling other than '-' or the reserved 'h'.
|
||||||
Short string
|
Short string
|
||||||
@@ -498,7 +499,7 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
|
|||||||
if binding.meta.Name == "help" {
|
if binding.meta.Name == "help" {
|
||||||
return fmt.Errorf("flag name %q is reserved for help", binding.meta.Name)
|
return fmt.Errorf("flag name %q is reserved for help", binding.meta.Name)
|
||||||
}
|
}
|
||||||
if commandmeta.IsReportingFlag(binding.meta.Name) {
|
if commandmeta.IsReportingFlag(binding.meta.Name) && !(binding.meta.Name == "debug" && binding.boolean) {
|
||||||
return fmt.Errorf("flag name %q is reserved for reporting", binding.meta.Name)
|
return fmt.Errorf("flag name %q is reserved for reporting", binding.meta.Name)
|
||||||
}
|
}
|
||||||
if _, exists := flagNames[binding.meta.Name]; exists {
|
if _, exists := flagNames[binding.meta.Name]; exists {
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ type completionFlagState struct {
|
|||||||
func completionFlags(context completionContext) []commandmeta.Flag {
|
func completionFlags(context completionContext) []commandmeta.Flag {
|
||||||
flags := make([]commandmeta.Flag, 0, len(context.command.Flags)+len(context.command.CoreFlags)+1)
|
flags := make([]commandmeta.Flag, 0, len(context.command.Flags)+len(context.command.CoreFlags)+1)
|
||||||
flags = append(flags, commandmeta.HelpFlag())
|
flags = append(flags, commandmeta.HelpFlag())
|
||||||
flags = append(flags, context.command.CoreFlags...)
|
flags = append(flags, commandmeta.EffectiveFlags(context.command.Flags, context.command.CoreFlags)...)
|
||||||
flags = append(flags, context.command.Flags...)
|
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,34 @@ func TestCompletionFlagAndValuePositions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugCompletionHints(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
hint CompletionHint
|
||||||
|
want commandmeta.CompletionKind
|
||||||
|
}{
|
||||||
|
{"none", NoCompletion(), commandmeta.CompletionNone},
|
||||||
|
{"choices", CompleteChoices(CompletionChoice{Value: "true"}), commandmeta.CompletionChoices},
|
||||||
|
{"files", CompleteFiles(), commandmeta.CompletionFiles},
|
||||||
|
{"directories", CompleteDirectories(), commandmeta.CompletionDirectories},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
command := NewCommand(CommandSpec[struct{}]{Name: "run", Flags: []Flag[struct{}]{BoolFlag(FlagMeta{Name: "debug", Short: "d", Completion: tc.hint}, false, func(*struct{}, Supplied[bool]) {})}, Run: func(Invocation, struct{}) Outcome { return Success() }})
|
||||||
|
app, err := New(Identity{Name: "example"}, command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
snapshot := app.routeSnapshot()
|
||||||
|
for _, current := range []string{"--debug=", "-d="} {
|
||||||
|
target := completionTargetFor(snapshot, resolveCompletionContext(snapshot, []string{"run"}, current))
|
||||||
|
if target.kind != completionTargetValue || target.hint.Kind != tc.want {
|
||||||
|
t.Fatalf("%s: %#v", current, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompletionOperandAllocation(t *testing.T) {
|
func TestCompletionOperandAllocation(t *testing.T) {
|
||||||
arguments := []commandmeta.Argument{
|
arguments := []commandmeta.Argument{
|
||||||
{Name: "optional", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}},
|
{Name: "optional", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}},
|
||||||
|
|||||||
@@ -120,3 +120,51 @@ func TestCompletionQueryDiscovery(t *testing.T) {
|
|||||||
t.Fatalf("discovery outcome=%#v reply=%q", outcome, output.String())
|
t.Fatalf("discovery outcome=%#v reply=%q", outcome, output.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugHelpAndCompletion(t *testing.T) {
|
||||||
|
var factories, setters, checks, handlers int
|
||||||
|
command := NewCommand(CommandSpec[struct{}]{
|
||||||
|
Name: "run",
|
||||||
|
NewInput: func() struct{} { factories++; return struct{}{} },
|
||||||
|
Flags: []Flag[struct{}]{BoolFlag(FlagMeta{Name: "debug", Short: "d", Usage: "application tracing", Required: true, Completion: CompleteChoices(CompletionChoice{Value: "true"}, CompletionChoice{Value: "trace"})}, true, func(*struct{}, Supplied[bool]) { setters++ })},
|
||||||
|
CheckInput: func(struct{}) error { checks++; return nil },
|
||||||
|
Run: func(Invocation, struct{}) Outcome { handlers++; return Success() },
|
||||||
|
})
|
||||||
|
app, err := New(Identity{Name: "example"}, command, Completion("example"), Alias("shortcut", "run"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, args := range [][]string{{"run", "--help"}, {"help", "run"}, {"shortcut", "--help"}, {"help", "shortcut"}, {"run", "--unknown"}, {"shortcut", "--unknown"}} {
|
||||||
|
var output, diagnostic bytes.Buffer
|
||||||
|
result := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostic})
|
||||||
|
text := output.String() + diagnostic.String()
|
||||||
|
if strings.Count(text, "--debug") != 1 || !strings.Contains(text, "-d") || !strings.Contains(text, "application tracing") || !strings.Contains(text, "application input defaults to true") || !strings.Contains(text, "required") || !strings.Contains(text, "default: false") {
|
||||||
|
t.Fatalf("%v: outcome=%#v text=%q", args, result, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
count int
|
||||||
|
}{
|
||||||
|
{[]string{"__complete", "run", "--deb"}, "--debug", 1},
|
||||||
|
{[]string{"__complete", "run", "-"}, "\n-d\t", 1},
|
||||||
|
{[]string{"__complete", "run", "--debug=tr"}, "trace", 1},
|
||||||
|
{[]string{"__complete", "run", "-d=tr"}, "trace", 1},
|
||||||
|
} {
|
||||||
|
var output bytes.Buffer
|
||||||
|
result := app.Execute(context.Background(), tc.args, Streams{Out: &output, Err: io.Discard})
|
||||||
|
if result.Kind != OutcomeSuccess || strings.Count(output.String(), tc.want) != tc.count {
|
||||||
|
t.Fatalf("%v: %#v %q", tc.args, result, output.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if factories != 0 || setters != 0 || checks != 0 || handlers != 0 {
|
||||||
|
t.Fatalf("callbacks: %d %d %d %d", factories, setters, checks, handlers)
|
||||||
|
}
|
||||||
|
if result := app.Execute(context.Background(), []string{"run", "--debug=invalid", "--help"}, Streams{Out: io.Discard, Err: io.Discard}); result.Kind != OutcomeUsage {
|
||||||
|
t.Fatalf("help skipped boolean validation: %#v", result)
|
||||||
|
}
|
||||||
|
if result := app.Execute(context.Background(), []string{"run", "--debug=trace"}, Streams{Out: io.Discard, Err: io.Discard}); result.Kind != OutcomeUsage {
|
||||||
|
t.Fatalf("suggestion bypassed boolean parsing: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -178,8 +178,11 @@ Reporting flags do not go before or between command-path components. Tokens
|
|||||||
consumed as values and tokens after `--` retain their normal meaning.
|
consumed as values and tokens after `--` retain their normal meaning.
|
||||||
|
|
||||||
The reserved long spellings are `--quiet`, `--verbose`, `--debug`,
|
The reserved long spellings are `--quiet`, `--verbose`, `--debug`,
|
||||||
`--progress`, and `--format`; application declarations cannot reuse them. There
|
`--progress`, and `--format`. An application may attach its own boolean input
|
||||||
are no built-in short spellings. Applications may define aliases such as `-q → --quiet`,
|
to the existing `--debug` option. Command help shows both purposes in one row,
|
||||||
|
including an application short spelling, requiredness, and any different
|
||||||
|
application default. Application input defaults do not select reporting mode.
|
||||||
|
There are no built-in short spellings. Applications may define aliases such as `-q → --quiet`,
|
||||||
`-v → --verbose`, and `-d → --debug`; canonical and aliased occurrences share
|
`-v → --verbose`, and `-d → --debug`; canonical and aliased occurrences share
|
||||||
the same ordering. Thus `example export --verbose -q` selects quiet mode.
|
the same ordering. Thus `example export --verbose -q` selects quiet mode.
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ applications.
|
|||||||
| `go.maximumdirect.net/ckconfig` | You want operators to view, validate, explain, and compare ConfigKit configuration. |
|
| `go.maximumdirect.net/ckconfig` | You want operators to view, validate, explain, and compare ConfigKit configuration. |
|
||||||
| `go.maximumdirect.net/ckprompt` | You want operators to inspect PromptKit resources, check and preview requests, or run smoke tests. |
|
| `go.maximumdirect.net/ckprompt` | You want operators to inspect PromptKit resources, check and preview requests, or run smoke tests. |
|
||||||
|
|
||||||
The following distinguishes published combinations from the current checkout:
|
The following lists the current release set and earlier module combinations:
|
||||||
|
|
||||||
| Selection | Core | ckconfig | ckprompt | Upstream libraries and status |
|
| Selection | Core | ckconfig | ckprompt | Upstream libraries and status |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Published core | v0.5.0 | — | — | Usable independently. |
|
| Published core | v0.5.0 | — | — | Usable independently. |
|
||||||
| Published ConfigKit integration | v0.4.0 | v0.4.0 | — | ConfigKit v1.1.0. |
|
| ConfigKit integration | v0.5.0 | v0.5.0 | — | ConfigKit v1.1.0. |
|
||||||
|
| PromptKit integration | v0.5.0 | v0.5.0 | v0.5.0 | ConfigKit v1.1.0 and PromptKit v0.20.0. |
|
||||||
| Earlier matched integration set | v0.3.0 | v0.2.0 | v0.2.0 | ConfigKit v0.8.0 and PromptKit v0.16.0, using their earlier import paths. |
|
| Earlier matched integration set | v0.3.0 | v0.2.0 | v0.2.0 | ConfigKit v0.8.0 and PromptKit v0.16.0, using their earlier import paths. |
|
||||||
| Current source | Local | Local | Local | ConfigKit v1.1.0 and PromptKit v0.20.0; use an aligned workspace until release dependencies are updated. |
|
|
||||||
|
|
||||||
Install the published core with:
|
Install the published core with:
|
||||||
|
|
||||||
@@ -29,13 +29,12 @@ Install the published core with:
|
|||||||
go get go.maximumdirect.net/commandkit@v0.5.0
|
go get go.maximumdirect.net/commandkit@v0.5.0
|
||||||
```
|
```
|
||||||
|
|
||||||
The integration releases being prepared are ckconfig v0.5.0 and ckprompt v0.5.0. Current
|
Select either integration with `go get go.maximumdirect.net/ckconfig@v0.5.0`
|
||||||
ckprompt source calls `ckconfig.AdaptDiagnostics`, which is absent from its
|
or `go get go.maximumdirect.net/ckprompt@v0.5.0`. The integrations select core
|
||||||
selected released ckconfig v0.4.0 dependency. It therefore needs local
|
v0.5.0, and ckprompt selects ckconfig v0.5.0, which includes `AdaptDiagnostics`.
|
||||||
ckconfig source in the workspace until [release
|
They build independently against these released dependencies; a local workspace
|
||||||
alignment](../roadmap/future.md#pending-release-and-integration-work) is
|
is optional for development. Published ckconfig v0.3.0 also uses ConfigKit v1.1.0
|
||||||
complete. Published ckconfig v0.3.0 also uses ConfigKit v1.1.0 and does not
|
and does not compose with ckprompt v0.2.0's earlier ConfigKit dependency.
|
||||||
compose with ckprompt v0.2.0's earlier ConfigKit dependency.
|
|
||||||
|
|
||||||
Importing a module does not install its commands. Pass commands to
|
Importing a module does not install its commands. Pass commands to
|
||||||
`commandkit.New` or `App.Register` explicitly. ckprompt depends on ckconfig's
|
`commandkit.New` or `App.Register` explicitly. ckprompt depends on ckconfig's
|
||||||
@@ -155,6 +154,35 @@ Deferred submissions are accepted until finalization begins; later submissions
|
|||||||
return `ErrReporterClosed`. The [CLI reference](../cli.md#reporting) owns flag
|
return `ErrReporterClosed`. The [CLI reference](../cli.md#reporting) owns flag
|
||||||
grammar, visibility, prefixes, and status behavior.
|
grammar, visibility, prefixes, and status behavior.
|
||||||
|
|
||||||
|
An application can attach a boolean input to the shared `--debug` option and
|
||||||
|
inspect the reporting selection separately. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type input struct { debug commandkit.Supplied[bool] }
|
||||||
|
|
||||||
|
command := commandkit.NewCommand(commandkit.CommandSpec[input]{
|
||||||
|
Name: "run",
|
||||||
|
Flags: []commandkit.Flag[input]{
|
||||||
|
commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d"}, false,
|
||||||
|
func(in *input, value commandkit.Supplied[bool]) { in.debug = value }),
|
||||||
|
},
|
||||||
|
Run: func(inv commandkit.Invocation, in input) commandkit.Outcome {
|
||||||
|
captureArtifacts := in.debug.Value
|
||||||
|
quiet := inv.ReportingMode() == commandkit.ReportingModeQuiet
|
||||||
|
_, _ = captureArtifacts, quiet // Apply application policy here.
|
||||||
|
return commandkit.Success()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_ = command
|
||||||
|
```
|
||||||
|
|
||||||
|
With `run --debug --quiet`, the input is true and explicitly supplied, while
|
||||||
|
the selected reporting mode is quiet. Application defaults and requiredness
|
||||||
|
affect input, not mode selection; explicit false counts as supplied. Captured
|
||||||
|
bindings retain the usual selection and application behavior. Applications own
|
||||||
|
any artifact creation and other effects. The mode reports selection rather than
|
||||||
|
successful delivery; see [Invocation.ReportingMode](../../identity.go).
|
||||||
|
|
||||||
Reports use JSON serialization to establish the common value representation
|
Reports use JSON serialization to establish the common value representation
|
||||||
for YAML and JSON. A text template receives the original value; without one,
|
for YAML and JSON. A text template receives the original value; without one,
|
||||||
text uses readable scalar or string-list output and YAML for structured values.
|
text uses readable scalar or string-list output and YAML for structured values.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ capability proposals in [future work](roadmap/future.md).
|
|||||||
| Public facade or adapter boundaries | [Internal overview](internal/overview.md), [ADR 0002](adr/0002-public-facade-and-cli-backend.md), [ADR 0004](adr/0004-optional-integration-modules.md), and the architecture policy |
|
| Public facade or adapter boundaries | [Internal overview](internal/overview.md), [ADR 0002](adr/0002-public-facade-and-cli-backend.md), [ADR 0004](adr/0004-optional-integration-modules.md), and the architecture policy |
|
||||||
| Module ownership and public contracts | [Consumer guide](consumers/guide.md), [internal overview](internal/overview.md), [core GoDoc](../doc.go), [ckconfig GoDoc](../integrations/ckconfig/doc.go), [ckprompt GoDoc](../integrations/ckprompt/doc.go), and applicable ADRs |
|
| Module ownership and public contracts | [Consumer guide](consumers/guide.md), [internal overview](internal/overview.md), [core GoDoc](../doc.go), [ckconfig GoDoc](../integrations/ckconfig/doc.go), [ckprompt GoDoc](../integrations/ckprompt/doc.go), and applicable ADRs |
|
||||||
| Unfinished work and capability proposals | [Future work](roadmap/future.md) |
|
| Unfinished work and capability proposals | [Future work](roadmap/future.md) |
|
||||||
| Documentation refresh record | [Documentation roadmap](roadmap/documentation-refresh.md) |
|
| Completed documentation refresh | [Retired refresh record](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/5c960532552037d38119941389e558f78bf73aad/docs/roadmap/documentation-refresh.md) |
|
||||||
| Completed planning and implementation history | [Completed dependency upgrades, PromptKit validation, discovery, smoke testing, and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/a6278a6d9f940948ec7745955d745523aaff0734/docs/roadmap), [earlier ConfigKit and PromptKit adoption and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/8a82620dfcd34c0ae56bbecb0d014eb49786bc92/docs/roadmap), [earlier retired records](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/6bc37e7ba64ca0920b4d0cc77039a18d4006b24e/docs/roadmap), and [completed module implementation and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/bb2528532b78d30e52f5082e491a5e1fd9b0fb99/docs/roadmap) in Git history |
|
| Completed planning and implementation history | [Completed dependency upgrades, PromptKit validation, discovery, smoke testing, and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/a6278a6d9f940948ec7745955d745523aaff0734/docs/roadmap), [earlier ConfigKit and PromptKit adoption and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/8a82620dfcd34c0ae56bbecb0d014eb49786bc92/docs/roadmap), [earlier retired records](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/6bc37e7ba64ca0920b4d0cc77039a18d4006b24e/docs/roadmap), and [completed module implementation and remediation](https://gitea.maximumdirect.net/eric/CommandKit/src/commit/bb2528532b78d30e52f5082e491a5e1fd9b0fb99/docs/roadmap) in Git history |
|
||||||
| Release tooling | [Maintainer release procedure](release.md) |
|
| Release tooling | [Maintainer release procedure](release.md) |
|
||||||
| Performance maintenance | [Internal overview](internal/overview.md), existing benchmarks, and [possible further efficiency work](roadmap/future.md#possible-further-efficiency-work) |
|
| Performance maintenance | [Internal overview](internal/overview.md), existing benchmarks, and [possible further efficiency work](roadmap/future.md#possible-further-efficiency-work) |
|
||||||
@@ -73,7 +73,8 @@ Read all applicable rows for cross-cutting work. Go declarations and GoDoc own
|
|||||||
current exported contracts; the CLI reference owns operator behavior. The
|
current exported contracts; the CLI reference owns operator behavior. The
|
||||||
[maintainer release procedure](release.md) owns current release operations.
|
[maintainer release procedure](release.md) owns current release operations.
|
||||||
Completed roadmaps and implementation evidence remain available in Git history,
|
Completed roadmaps and implementation evidence remain available in Git history,
|
||||||
including [audit remediation and its declaration-path follow-up](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/aac0e11ae67c78a35745ed9a25688aa51c15e793/docs/roadmap),
|
including [shared debug binding and reporting-mode implementation](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/56e3b013f9f83c9c9494f11602e802ce5ed8814e/docs/roadmap),
|
||||||
|
[audit remediation and its declaration-path follow-up](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/aac0e11ae67c78a35745ed9a25688aa51c15e793/docs/roadmap),
|
||||||
[shared reporting and PromptKit v0.20.0 implementation](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/2f43f78f76439f5d11ef313cd379bec036543504/docs/roadmap),
|
[shared reporting and PromptKit v0.20.0 implementation](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/2f43f78f76439f5d11ef313cd379bec036543504/docs/roadmap),
|
||||||
[PromptKit v0.18.0 adoption and implementation](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/540e2359d66d604a563df0be1c9fc961588f5b9c/docs/roadmap),
|
[PromptKit v0.18.0 adoption and implementation](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/540e2359d66d604a563df0be1c9fc961588f5b9c/docs/roadmap),
|
||||||
[ConfigKit adoption, reporting, release-tooling, and remediation records](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/260256d722f52bc947103d078cb67a129f4cc9f1/docs/roadmap),
|
[ConfigKit adoption, reporting, release-tooling, and remediation records](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/260256d722f52bc947103d078cb67a129f4cc9f1/docs/roadmap),
|
||||||
@@ -117,11 +118,10 @@ go work init . ./integrations/ckconfig ./integrations/ckprompt
|
|||||||
If a workspace already exists, ensure it includes these three modules. Preserve
|
If a workspace already exists, ensure it includes these three modules. Preserve
|
||||||
the ignored `go.work` and `go.work.sum`; do not commit them. Use
|
the ignored `go.work` and `go.work.sum`; do not commit them. Use
|
||||||
`GOWORK=off GOTOOLCHAIN=local` for dependency preparation
|
`GOWORK=off GOTOOLCHAIN=local` for dependency preparation
|
||||||
and independent module suites. The integrations select released core v0.4.0;
|
and independent module suites. Both integrations select released core v0.5.0;
|
||||||
ckprompt also selects released ckconfig v0.4.0.
|
ckprompt also selects released ckconfig v0.5.0. Their declared dependencies
|
||||||
Current ckprompt source requires a ckconfig helper absent from that release, so
|
support independent builds. The aligned workspace exercises the local sources
|
||||||
its independent suite remains pending release and dependency alignment; the
|
together.
|
||||||
aligned workspace exercises the current source together.
|
|
||||||
Run `GOWORK=off GOTOOLCHAIN=local go test ./...` separately from each module
|
Run `GOWORK=off GOTOOLCHAIN=local go test ./...` separately from each module
|
||||||
directory. From the repository root, a workspace containing all three modules can
|
directory. From the repository root, a workspace containing all three modules can
|
||||||
also run:
|
also run:
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ collected parser events through that selection. The selected mode therefore
|
|||||||
applies to command execution, help, and version; routing and parsing failures
|
applies to command execution, help, and version; routing and parsing failures
|
||||||
remain visible in every mode.
|
remain visible in every mode.
|
||||||
|
|
||||||
|
An effective flag view combines a shared application debug binding with the
|
||||||
|
core declaration for parsing, help, and completion, while preserving the
|
||||||
|
application binding's position. The same occurrence stream selects reporting
|
||||||
|
mode independently of the prepared application value. The invocation's mode
|
||||||
|
accessor reads the reporter's selection under its existing state mutex.
|
||||||
|
|
||||||
The reporter owns invocation-scoped visibility, message formatting, managed
|
The reporter owns invocation-scoped visibility, message formatting, managed
|
||||||
report rendering, delivery observations, and finalization. It presents queued
|
report rendering, delivery observations, and finalization. It presents queued
|
||||||
messages after the handler returns, then a failed outcome's explanation, and
|
messages after the handler returns, then a failed outcome's explanation, and
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Base Policy Version:** 1.6
|
**Base Policy Version:** 1.6
|
||||||
|
|
||||||
**Last Updated**: 2026-10-11
|
**Last Updated**: 2026-11-08
|
||||||
|
|
||||||
**Upstream Source:** [release.md](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/docs/policy/release.md)
|
**Upstream Source:** [release.md](https://gitea.maximumdirect.net/eric/policy/src/commit/e833faddf120c789a9b45348edaf75720de16348/docs/policy/release.md)
|
||||||
|
|
||||||
@@ -96,6 +96,22 @@ Additional compatibility promises and deliberate exceptions belong in the
|
|||||||
adopted policy or canonical contract documentation. Release notes explain
|
adopted policy or canonical contract documentation. Release notes explain
|
||||||
changes; they do not redefine current compatibility contracts.
|
changes; they do not redefine current compatibility contracts.
|
||||||
|
|
||||||
|
### CommandKit downstream dependency alignment
|
||||||
|
|
||||||
|
Each new core or integration release requires dependency updates and new releases
|
||||||
|
of all downstream CommandKit-owned integration modules, following direct and
|
||||||
|
transitive dependencies. Each affected integration advances at least its minor
|
||||||
|
version to incorporate the dependency updates, even when the initiating release
|
||||||
|
is a patch. Use a major release where the compatibility rules above require it.
|
||||||
|
Versions remain independent and need not match across modules.
|
||||||
|
|
||||||
|
A module with no downstream CommandKit integrations may release independently;
|
||||||
|
its release does not require new versions of its dependencies. This policy does
|
||||||
|
not require releases of external consuming applications. The
|
||||||
|
[runbook](../release.md#scope-and-release-order) owns dependency-order sequencing.
|
||||||
|
Downstream releases are required follow-up work, not additional completion gates
|
||||||
|
for an already published source release.
|
||||||
|
|
||||||
## Consumer Minimum And Release Toolchain
|
## Consumer Minimum And Release Toolchain
|
||||||
|
|
||||||
Distinguish the minimum toolchain supported for consumers from the toolchain
|
Distinguish the minimum toolchain supported for consumers from the toolchain
|
||||||
|
|||||||
@@ -9,11 +9,28 @@ Gitea release page; there are no binaries,
|
|||||||
checksums, or other delivery assets. CI, module proxy availability, and consumer
|
checksums, or other delivery assets. CI, module proxy availability, and consumer
|
||||||
installation are not completion gates.
|
installation are not completion gates.
|
||||||
|
|
||||||
Publish in dependency order: core, then `ckconfig`, then `ckprompt`. The
|
Plan the initiating release and all downstream integration releases required by
|
||||||
integration modules require the preceding CommandKit releases, so prepare and
|
the [dependency-alignment policy](policy/release.md#commandkit-downstream-dependency-alignment).
|
||||||
publish each module only after its CommandKit dependencies are available. A root
|
That policy sets the minimum version bump for affected integrations; select
|
||||||
release runs only the root module's default suite; it need not run suites for
|
each module's version independently according to its compatibility impact.
|
||||||
independently released integrations.
|
|
||||||
|
| Initiating release | Required downstream releases, in order |
|
||||||
|
| --- | --- |
|
||||||
|
| Core | `ckconfig`, then `ckprompt` |
|
||||||
|
| `ckconfig` | `ckprompt` |
|
||||||
|
| `ckprompt` | None with the current dependency graph |
|
||||||
|
|
||||||
|
As integrations are added, follow their actual dependencies to include every
|
||||||
|
affected downstream integration. Publish dependencies before their consumers.
|
||||||
|
Prepare each integration after its updated CommandKit dependencies are published,
|
||||||
|
incorporating all of those versions in one candidate. For a core update, release
|
||||||
|
`ckprompt` once after both core and `ckconfig`; it does not need separate releases
|
||||||
|
for each dependency bump. Unaffected dependencies do not need new releases.
|
||||||
|
|
||||||
|
Track any remaining downstream releases as pending coordination work. Each
|
||||||
|
successful source-tag push still completes that module's release; a downstream
|
||||||
|
failure does not undo it or authorize replacing its tag. A root release runs
|
||||||
|
only the root module's default suite; each downstream release runs its own suite.
|
||||||
|
|
||||||
Use branch `main`, remote `origin`, and expected effective push endpoint
|
Use branch `main`, remote `origin`, and expected effective push endpoint
|
||||||
`ssh://git@forge.maximumdirect.net:22222/base-layer/commandkit.git`. The selected
|
`ssh://git@forge.maximumdirect.net:22222/base-layer/commandkit.git`. The selected
|
||||||
@@ -98,14 +115,17 @@ standard offline suite and ReleaseKit’s candidate/note and publication guards.
|
|||||||
## Prepare and publish one candidate
|
## Prepare and publish one candidate
|
||||||
|
|
||||||
Prepare dependencies in the selected module before the candidate is finalized.
|
Prepare dependencies in the selected module before the candidate is finalized.
|
||||||
Do this after the preceding CommandKit module has been published and before the
|
Do this after any CommandKit dependency releases required by the coordinated
|
||||||
selected candidate suite is run. For `core`, run the command at the repository
|
update have been published and before the selected candidate suite is run.
|
||||||
root; for integrations, run it from that module directory.
|
For `core`, run the command at the repository root; for integrations, run it
|
||||||
|
from that module directory.
|
||||||
|
|
||||||
Before downloading dependencies, update each integration to the newly published
|
Before downloading dependencies, update each integration to the newly published
|
||||||
CommandKit versions. `go mod download` downloads the versions already selected;
|
CommandKit versions. `go mod download` downloads the versions already selected;
|
||||||
it does not upgrade requirements. Replace the version placeholders below with
|
it does not upgrade requirements. Replace the version placeholders below with
|
||||||
the actual published versions, which may differ between modules:
|
the actual published versions, which may differ between modules. When a
|
||||||
|
dependency is not being released in this update, use its current selected
|
||||||
|
release; a `ckconfig`-initiated update does not require a new core release:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# After publishing core, prepare ckconfig from its module directory.
|
# After publishing core, prepare ckconfig from its module directory.
|
||||||
@@ -117,11 +137,11 @@ GOWORK=off GOTOOLCHAIN=local go get \
|
|||||||
go.maximumdirect.net/ckconfig@vCKCONFIG_VERSION
|
go.maximumdirect.net/ckconfig@vCKCONFIG_VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
Run only the command for the integration being prepared. In the current release
|
Run only the command for the integration being prepared. ckprompt needs a
|
||||||
sequence, ckprompt needs a ckconfig release containing `AdaptDiagnostics`;
|
ckconfig release containing `AdaptDiagnostics`; v0.5.0 contains it, while v0.4.0
|
||||||
ckconfig v0.4.0 does not contain it. Validate the integration with the workspace
|
does not. Validate the integration with the workspace disabled against its
|
||||||
disabled against its updated requirements, using the selected module's release
|
updated requirements, using the selected module's release validation below.
|
||||||
validation below. An aligned workspace can hide an outdated dependency.
|
An aligned workspace can hide an outdated dependency.
|
||||||
|
|
||||||
Review and commit the resulting `go.mod` and `go.sum` changes with the release
|
Review and commit the resulting `go.mod` and `go.sum` changes with the release
|
||||||
candidate, and make its release note agree with the selected versions. The sum
|
candidate, and make its release note agree with the selected versions. The sum
|
||||||
|
|||||||
25
docs/releases/v0.6.0.md
Normal file
25
docs/releases/v0.6.0.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# CommandKit v0.6.0
|
||||||
|
|
||||||
|
CommandKit v0.6.0 lets applications bind the shared debug option to typed
|
||||||
|
input and inspect the selected reporting mode during and after an invocation.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- A `BoolFlag` named `debug` attaches to the existing reporting option. Its
|
||||||
|
application default, requiredness, short spelling, and completion hints are
|
||||||
|
retained; help describes the combined option.
|
||||||
|
- `Invocation.ReportingMode` returns the resolved visibility selection, which
|
||||||
|
can differ from the bound debug value when other mode flags are supplied.
|
||||||
|
|
||||||
|
## Compatibility and upgrade
|
||||||
|
|
||||||
|
Other reporting flag names remain reserved. Applications choosing a required
|
||||||
|
debug binding require an explicit value; an explicit false satisfies it. A
|
||||||
|
true application default does not enable debug reporting. See the
|
||||||
|
[consumer guide](../consumers/guide.md#invocation-reporting) and
|
||||||
|
[CLI reference](../cli.md#reporting). The core consumer minimum remains Go
|
||||||
|
1.26.0 and the maintainer release toolchain remains Go 1.27.0.
|
||||||
|
|
||||||
|
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/v0.6.0/COPYRIGHT) |
|
||||||
|
[LICENSE](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/v0.6.0/LICENSE) |
|
||||||
|
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/v0.6.0/docs/oss/attribution.md)
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
# Documentation refresh
|
|
||||||
|
|
||||||
Status: implemented and verified. Release/dependency alignment remains separate
|
|
||||||
work in [future.md](future.md#pending-release-and-integration-work).
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Refresh CommandKit's documentation for the next minor release. Operators should
|
|
||||||
be able to find a command, understand what it does, and interpret its results
|
|
||||||
without learning the implementation. Application developers should have a short
|
|
||||||
path from choosing a module to using its public API. Maintainer documentation
|
|
||||||
and exact API contracts can retain technical detail where it is useful.
|
|
||||||
|
|
||||||
This roadmap captures the accepted documentation review recommendations. The
|
|
||||||
[documentation policy](../policy/documentation.md) remains authoritative for
|
|
||||||
audience, canonical ownership, examples, and document lifecycle. No new policy,
|
|
||||||
documentation framework, or ADR is needed.
|
|
||||||
|
|
||||||
## Scope and boundaries
|
|
||||||
|
|
||||||
The sprint covers current READMEs, the CLI reference, consumer guidance,
|
|
||||||
maintained example instructions, generated help descriptions, package
|
|
||||||
introductions, and release notes being prepared for publication.
|
|
||||||
|
|
||||||
Preserve API and CLI behavior: command and flag names, defaults, format
|
|
||||||
selection, report fields, status semantics, availability, and ownership rules.
|
|
||||||
Source edits for help text and GoDoc are in scope; runtime refactoring and new
|
|
||||||
features are not. If review exposes a behavioral defect or an unresolved
|
|
||||||
contract, record it separately rather than silently changing behavior through
|
|
||||||
an editorial revision.
|
|
||||||
|
|
||||||
Use existing documentation owners. Move useful detail to its appropriate owner
|
|
||||||
or link to the existing explanation; do not duplicate contracts. Keep exact
|
|
||||||
ownership, callback lifetime, error identity, and concurrency rules in GoDoc.
|
|
||||||
Internal documentation and maintainer procedures need only changes justified by
|
|
||||||
clarity or affected navigation, not a wholesale simplification.
|
|
||||||
|
|
||||||
Preserve accepted ADR decisions, historical published release records, license
|
|
||||||
texts, and attribution. Apply release-note editing to unpublished candidates
|
|
||||||
after checking their publication status under the
|
|
||||||
[release policy](../policy/release.md). Dependency alignment and publication
|
|
||||||
remain separate work under the [release procedure](../release.md).
|
|
||||||
|
|
||||||
## Audience and writing approach
|
|
||||||
|
|
||||||
| Audience | Starting points | Approach |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Prospective users | Root and integration READMEs | Explain purpose, useful capabilities, and where to start. |
|
|
||||||
| CLI operators | CLI reference and generated help | Lead with tasks, commands, results, and relevant limitations. |
|
|
||||||
| Go application developers | Consumer guide, examples, package introductions | Show a small useful integration first; explain choices and responsibilities next. |
|
|
||||||
| API users and maintainers | Symbol GoDoc, internal docs, development and release guides | Retain precise technical detail needed for correct use and maintenance. |
|
|
||||||
|
|
||||||
Use short, direct sentences and familiar terms. Define necessary domain terms
|
|
||||||
where first used; avoid a separate glossary unless one proves useful. For
|
|
||||||
example, explain a configuration scope as a named configuration such as `app`
|
|
||||||
or `promptkit`.
|
|
||||||
|
|
||||||
Where meaning permits, prefer “load configuration” to “acquire configuration,”
|
|
||||||
“command output” to “substantive results,” and “files and other sources used to
|
|
||||||
load configuration” to “source inventory captured during acquisition.” These
|
|
||||||
are editorial examples, not a mechanical replacement list. Keep technical names
|
|
||||||
when referring to an API symbol or a distinction that matters to the reader.
|
|
||||||
|
|
||||||
## Work areas
|
|
||||||
|
|
||||||
### 1. Product and module introductions
|
|
||||||
|
|
||||||
Update the [root README](../../README.md), [ckconfig README](../../integrations/ckconfig/README.md),
|
|
||||||
and [ckprompt README](../../integrations/ckprompt/README.md).
|
|
||||||
|
|
||||||
- Explain what the library helps applications and operators do before describing
|
|
||||||
declarations, adapters, or reporting internals.
|
|
||||||
- Keep the module overview, explicit installation distinction, and navigation.
|
|
||||||
Link to the consumer guide for compatibility and integration details.
|
|
||||||
- Provide the shortest useful starting example or a direct link to it, rather
|
|
||||||
than a catalog of callback counts or implementation responsibilities.
|
|
||||||
|
|
||||||
An illustrative root opening is: “CommandKit helps Go applications build
|
|
||||||
consistent command-line interfaces. It provides typed flags and arguments,
|
|
||||||
help, output formatting, and error reporting. Optional integrations add commands
|
|
||||||
for inspecting configuration and working with PromptKit.” Final wording can
|
|
||||||
evolve during implementation.
|
|
||||||
|
|
||||||
### 2. CLI tasks and reference detail
|
|
||||||
|
|
||||||
Refresh the [CLI reference](../cli.md), particularly reporting, configuration,
|
|
||||||
prepared-request preview, requirements, and smoke testing.
|
|
||||||
|
|
||||||
- Begin each operation with its purpose and a small command example. Explain
|
|
||||||
results and important limitations before detailed report fields.
|
|
||||||
- Replace installation and callback introductions with an operator explanation
|
|
||||||
of command availability; link to the consumer guide for application wiring.
|
|
||||||
- Move API-only material such as callback construction, writer ownership, and
|
|
||||||
`Outcome.Presentation` access to its existing developer-facing owner where
|
|
||||||
appropriate. Retain observable output, failure, and status behavior here.
|
|
||||||
- Organize detailed report shapes and edge cases into reference subsections so
|
|
||||||
they remain available without interrupting the ordinary task.
|
|
||||||
- Make application-defined flags and optional convenience aliases identifiable;
|
|
||||||
examples must not imply every application installs them.
|
|
||||||
|
|
||||||
Retain distinctions operators need: omission versus an explicit empty value,
|
|
||||||
concise versus detailed reports, independent comparison sides, redaction,
|
|
||||||
partial evidence on failure, and which operations can contact providers.
|
|
||||||
Explain that requirements discovery, request validation, prepared preview, and
|
|
||||||
smoke testing answer different questions. Successful preparation does not
|
|
||||||
guarantee later execution, and a smoke command's successful status does not
|
|
||||||
mean every reported observation is positive.
|
|
||||||
|
|
||||||
### 3. Guided examples
|
|
||||||
|
|
||||||
Refresh the [core example](../../examples/core/README.md),
|
|
||||||
[ConfigKit example](../../integrations/ckconfig/examples/simple/README.md), and
|
|
||||||
[composed example](../../integrations/ckprompt/examples/composed/README.md).
|
|
||||||
|
|
||||||
- State the working directory, required Go version, and applicable workspace or
|
|
||||||
independent-module setup before commands.
|
|
||||||
- Start with a few useful commands and explain expected results beside them.
|
|
||||||
Group additional examples by task rather than presenting one long command block.
|
|
||||||
- Label intentional failures beside the relevant command, including how to
|
|
||||||
interpret the result. Distinguish application status from `go run`'s own exit
|
|
||||||
behavior when explaining shell results.
|
|
||||||
- Explain early that the composed example uses local fixtures and requires no
|
|
||||||
model service or credentials; do not imply this about every consuming app.
|
|
||||||
- Put alias wiring, shared state, and cache explanations after the walkthrough,
|
|
||||||
linking to API contracts rather than repeating them.
|
|
||||||
|
|
||||||
### 4. Consumer guide and public API orientation
|
|
||||||
|
|
||||||
Update the [consumer guide](../consumers/guide.md) and the package introductions
|
|
||||||
in [core](../../doc.go), [ckconfig](../../integrations/ckconfig/doc.go), and
|
|
||||||
[ckprompt](../../integrations/ckprompt/doc.go).
|
|
||||||
|
|
||||||
- Demonstrate `Reporter.Report` with a small value so the recommended path to
|
|
||||||
shared `--format` support is visible. Explain raw output as a separate choice.
|
|
||||||
- Make the initial greeting example's name, description, and output agree; it
|
|
||||||
currently prints the count rather than a greeting.
|
|
||||||
- Present module compatibility in a compact table that distinguishes published
|
|
||||||
releases from current source requirements. Keep exact version guidance in
|
|
||||||
this owner and link from module introductions.
|
|
||||||
- Lead package introductions with purpose and primary entry points. Keep exact
|
|
||||||
symbol contracts in GoDoc, including factory timing, supplied values, error
|
|
||||||
handling, and object ownership.
|
|
||||||
|
|
||||||
Recheck compatibility statements when implementing the refresh. Current source
|
|
||||||
and released dependencies can differ; the
|
|
||||||
[pending alignment work](future.md#pending-release-and-integration-work) owns
|
|
||||||
the outstanding release work. Do not present workspace success as proof that
|
|
||||||
the selected published dependencies support an independent build.
|
|
||||||
|
|
||||||
### 5. Generated help
|
|
||||||
|
|
||||||
Review command summaries, descriptions, flag usage, and argument usage across
|
|
||||||
core and both integrations. These strings are operator documentation even when
|
|
||||||
stored in Go source.
|
|
||||||
|
|
||||||
Use action-oriented descriptions and explain choices where useful. In
|
|
||||||
particular, the [smoke command](../../integrations/ckprompt/smoke_command.go)
|
|
||||||
should explain that it can run one test or all tests and identify accepted kinds;
|
|
||||||
“Run one PromptKit smoke test” and “Native smoke-test kind” are insufficient.
|
|
||||||
The [request flag descriptions](../../integrations/ckprompt/request_flags.go)
|
|
||||||
can use direct wording such as “Use this model.” Preserve flag names, grammar,
|
|
||||||
defaults, and supported operations.
|
|
||||||
|
|
||||||
### 6. Release notes
|
|
||||||
|
|
||||||
Review the [core v0.5.0 note](../releases/v0.5.0.md) and applicable unpublished
|
|
||||||
integration candidates for the upcoming releases. Explain user benefits before
|
|
||||||
implementation mechanisms: accepted deferred messages are no longer lost,
|
|
||||||
YAML values retain their intended types, and installation and help avoid repeated
|
|
||||||
work. Remove unnecessary discussion of scratch paths, chain suffixes, and batch
|
|
||||||
detachment from the release summary.
|
|
||||||
|
|
||||||
Retain compatibility guidance, required consumer actions, meaningful API names,
|
|
||||||
and license/attribution links. Verify candidate dependencies against the versions
|
|
||||||
actually selected for publication. Do not invent release dates or performance
|
|
||||||
guarantees, or revise published historical notes as part of this refresh.
|
|
||||||
|
|
||||||
## Suggested sprint sequence
|
|
||||||
|
|
||||||
1. Refresh README introductions and the CLI reference together to establish
|
|
||||||
vocabulary and the operator/developer split.
|
|
||||||
2. Update example walkthroughs and generated help to match that language.
|
|
||||||
3. Polish consumer guidance, package introductions, and candidate release notes.
|
|
||||||
4. Review the changed documentation together for consistency and validate the
|
|
||||||
affected examples, help, and navigation.
|
|
||||||
|
|
||||||
No unresolved product decision blocks implementation planning. Exact wording,
|
|
||||||
section placement, and illustrative examples are ordinary editorial choices.
|
|
||||||
|
|
||||||
## Validation and completion checklist
|
|
||||||
|
|
||||||
Use the existing [documentation checks](../development.md#documentation-validation)
|
|
||||||
and [testing policy](../policy/testing.md). Review prose and local links; run
|
|
||||||
affected maintained examples and inspect representative help when those change.
|
|
||||||
For Go example or help-source edits, run the relevant existing package tests.
|
|
||||||
Do not add wording snapshots, readability gates, or a new checker framework.
|
|
||||||
Record workspace and independent-module results separately, including any
|
|
||||||
dependency limitation. This work adds no release gates.
|
|
||||||
|
|
||||||
- [x] README openings explain purpose and give a clear starting path.
|
|
||||||
- [x] CLI sections explain operator tasks before reference detail; necessary
|
|
||||||
behavior, report fields, status rules, and limitations remain discoverable.
|
|
||||||
- [x] Example setup is explicit, commands have nearby explanations, and intended
|
|
||||||
failures are labeled.
|
|
||||||
- [x] Consumer guidance demonstrates formatted reporting, has a coherent initial
|
|
||||||
example, and distinguishes published compatibility from current source needs.
|
|
||||||
- [x] Package introductions and generated help use clear, accurate language;
|
|
||||||
precise API contracts remain intact.
|
|
||||||
- [x] Unpublished release notes explain benefits and consumer impact while
|
|
||||||
historical records and required notices remain preserved.
|
|
||||||
- [x] Changed commands, snippets, and help have appropriate verification; local
|
|
||||||
links and Markdown whitespace checks pass.
|
|
||||||
- [x] Final review confirms consistent terminology, canonical ownership, and no
|
|
||||||
unintended behavior or contract changes.
|
|
||||||
|
|
||||||
## Implementation and verification
|
|
||||||
|
|
||||||
Completed the six work areas above. READMEs and package introductions now lead
|
|
||||||
with purpose and starting points; the CLI reference separates operator tasks
|
|
||||||
from report detail and developer wiring. Examples are grouped by task with
|
|
||||||
setup, expected results, and intentional failures explained nearby. The consumer
|
|
||||||
guide demonstrates shared report formatting and distinguishes published modules
|
|
||||||
from current source. Help strings and unpublished candidate notes use the same
|
|
||||||
plain language. Exact symbol contracts and runtime behavior were preserved.
|
|
||||||
|
|
||||||
Verification used Go 1.27.0 and existing cached dependencies:
|
|
||||||
|
|
||||||
- The aligned workspace suites passed for core, ckconfig, and ckprompt, including
|
|
||||||
their maintained examples.
|
|
||||||
- Independent core and ckconfig suites passed with `GOWORK=off`.
|
|
||||||
- Independent ckprompt compilation confirmed the existing dependency limitation:
|
|
||||||
released ckconfig v0.4.0 lacks `AdaptDiagnostics`. No module requirements,
|
|
||||||
checksums, or workspace files were changed to hide it. Its README, example,
|
|
||||||
consumer guidance, and candidate note identify the pending alignment.
|
|
||||||
- All 48 `go run` invocations in the three example READMEs were exercised with
|
|
||||||
binaries built from the aligned workspace, from the documented directories.
|
|
||||||
Results matched the walkthroughs, including two failed-load examples, missing
|
|
||||||
request input, and comparison status 3. Shared help and completion behavior
|
|
||||||
also passed the existing suites; representative rendered help was inspected.
|
|
||||||
- Both new consumer-guide report snippets compiled in a temporary consumer using
|
|
||||||
local core. Six checks confirmed greeting counts, empty-list output, and YAML
|
|
||||||
and JSON formatting.
|
|
||||||
- Local Markdown links, including incoming links to renamed sections, and
|
|
||||||
whitespace were checked. Go source changes are limited to help strings,
|
|
||||||
package comments, and formatting; no new tests or validation framework were
|
|
||||||
added.
|
|
||||||
- The authoritative remote had neither the core v0.5.0 nor ckprompt v0.3.0 tag
|
|
||||||
when their candidate notes were revised. Published notes, accepted ADRs,
|
|
||||||
policies, and license/attribution records were left intact.
|
|
||||||
|
|
||||||
The updated ckprompt candidate still requires dependency guidance to be finalized
|
|
||||||
when release alignment occurs. This is release preparation, not unfinished
|
|
||||||
editorial work or evidence of an independently buildable candidate.
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
# Future work
|
# Future work
|
||||||
|
|
||||||
Status: pending release/dependency alignment, optional shell validation, and
|
Status: optional shell validation and future capability proposals.
|
||||||
future capability proposals.
|
|
||||||
No delivery dates are selected here.
|
No delivery dates are selected here.
|
||||||
|
|
||||||
## Purpose and authority
|
## Purpose and authority
|
||||||
@@ -9,6 +8,9 @@ No delivery dates are selected here.
|
|||||||
Earlier completed roadmaps, implementation plans, and review findings have been retired.
|
Earlier completed roadmaps, implementation plans, and review findings have been retired.
|
||||||
This document preserves unfinished work and unselected ideas under the
|
This document preserves unfinished work and unselected ideas under the
|
||||||
[release policy](../policy/release.md#release-documentation-and-preparation).
|
[release policy](../policy/release.md#release-documentation-and-preparation).
|
||||||
|
The completed shared debug binding and reporting-mode implementation is recorded
|
||||||
|
in its [retirement snapshot](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/56e3b013f9f83c9c9494f11602e802ce5ed8814e/docs/roadmap).
|
||||||
|
The completed [documentation refresh](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/5c960532552037d38119941389e558f78bf73aad/docs/roadmap/documentation-refresh.md) remains available in Git history.
|
||||||
The completed audits, consolidated remediation, implementation plan, and
|
The completed audits, consolidated remediation, implementation plan, and
|
||||||
declaration-path follow-up remain available in the
|
declaration-path follow-up remain available in the
|
||||||
[audit remediation retirement snapshot](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/aac0e11ae67c78a35745ed9a25688aa51c15e793/docs/roadmap).
|
[audit remediation retirement snapshot](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/aac0e11ae67c78a35745ed9a25688aa51c15e793/docs/roadmap).
|
||||||
@@ -28,25 +30,7 @@ govern any future implementation. The [CLI reference](../cli.md), public GoDoc,
|
|||||||
and [consumer guide](../consumers/guide.md) own implemented behavior. Retaining
|
and [consumer guide](../consumers/guide.md) own implemented behavior. Retaining
|
||||||
an idea here does not select its syntax, API, priority, or delivery date.
|
an idea here does not select its syntax, API, priority, or delivery date.
|
||||||
|
|
||||||
## Pending release and integration work
|
## Optional verification
|
||||||
|
|
||||||
### Audit remediation release alignment
|
|
||||||
|
|
||||||
The audit remediation is complete in source. The released ckconfig v0.4.0 does
|
|
||||||
not include `AdaptDiagnostics`, which current ckprompt source uses. Select and
|
|
||||||
publish appropriate releases in dependency order, align module requirements and
|
|
||||||
checksums, and rerun independent module suites under the
|
|
||||||
[release procedure](../release.md). No publication is implied by source completion.
|
|
||||||
|
|
||||||
### ckprompt release
|
|
||||||
|
|
||||||
The PromptKit v0.20.0 adoption is complete. The
|
|
||||||
[ckprompt v0.3.0 candidate](../../integrations/ckprompt/docs/releases/v0.3.0.md)
|
|
||||||
remains unpublished and needs revalidation against the current source and aligned
|
|
||||||
dependencies. Publication and hosted delivery remain separate operations
|
|
||||||
under the [release procedure](../release.md).
|
|
||||||
The published ckprompt v0.2.0 retains its earlier dependencies; see the
|
|
||||||
[consumer guide](../consumers/guide.md#module-selection) for module selection.
|
|
||||||
|
|
||||||
### Optional shell completion validation
|
### Optional shell completion validation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package commandkit
|
package commandkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -313,3 +314,27 @@ func TestFlagAliasSharedSuffixAndUnavailableDestination(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugDeclarationValidation(t *testing.T) {
|
||||||
|
valid := BoolFlag(FlagMeta{Name: "debug", Short: "d"}, true, func(*struct{}, Supplied[bool]) {})
|
||||||
|
if _, err := New(Identity{Name: "Example"}, flagAliasCommand("run", valid)); err != nil {
|
||||||
|
t.Fatalf("valid debug: %v", err)
|
||||||
|
}
|
||||||
|
for _, flags := range [][]Flag[struct{}]{
|
||||||
|
{valid, valid},
|
||||||
|
{BoolFlag[struct{}](FlagMeta{Name: "debug"}, false, nil)},
|
||||||
|
{BoolFlag(FlagMeta{Name: "debug", Short: "h"}, false, func(*struct{}, Supplied[bool]) {})},
|
||||||
|
{ValueFlag(FlagMeta{Name: "debug"}, (*bool)(nil), strconv.ParseBool, func(*struct{}, Supplied[bool]) {})},
|
||||||
|
{OccurrenceFlag(FlagMeta{Name: "debug"}, strconv.ParseBool, func(*struct{}, Supplied[bool]) {})},
|
||||||
|
} {
|
||||||
|
if _, err := New(Identity{Name: "Example"}, flagAliasCommand("bad", flags...)); err == nil {
|
||||||
|
t.Fatalf("invalid binding accepted: %#v", flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, name := range []string{"quiet", "verbose", "progress", "format"} {
|
||||||
|
flag := BoolFlag(FlagMeta{Name: name}, false, func(*struct{}, Supplied[bool]) {})
|
||||||
|
if _, err := New(Identity{Name: "Example"}, flagAliasCommand("bad", flag)); err == nil {
|
||||||
|
t.Errorf("reserved boolean --%s was accepted", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type FlagValues[I any] struct {
|
|||||||
// each adapted flag stores its prepared value in target, associated with the
|
// each adapted flag stores its prepared value in target, associated with the
|
||||||
// original inner binding identity. Copies of an original handle remain
|
// original inner binding identity. Copies of an original handle remain
|
||||||
// selectable; an independently constructed lookalike does not.
|
// selectable; an independently constructed lookalike does not.
|
||||||
|
// A captured debug BoolFlag shares reporting input as described by [BoolFlag];
|
||||||
|
// its requiredness is checked when selected for application.
|
||||||
func CaptureFlags[Outer, I any](flags []Flag[I], target func(*Outer) *FlagValues[I]) []Flag[Outer] {
|
func CaptureFlags[Outer, I any](flags []Flag[I], target func(*Outer) *FlagValues[I]) []Flag[Outer] {
|
||||||
captured := make([]Flag[Outer], len(flags))
|
captured := make([]Flag[Outer], len(flags))
|
||||||
for index, original := range flags {
|
for index, original := range flags {
|
||||||
|
|||||||
15
identity.go
15
identity.go
@@ -59,6 +59,21 @@ type Invocation struct {
|
|||||||
// manually constructed Invocation has no reporter.
|
// manually constructed Invocation has no reporter.
|
||||||
func (inv Invocation) Reporter() *Reporter { return inv.reporter }
|
func (inv Invocation) Reporter() *Reporter { return inv.reporter }
|
||||||
|
|
||||||
|
// ReportingMode returns the selected reporting mode, independently of delivery
|
||||||
|
// success. The selection is stable during the handler and remains available
|
||||||
|
// after finalization. A zero Invocation returns ReportingModeOrdinary. Reads
|
||||||
|
// are safe alongside concurrent reporting; reporting callbacks must not reenter
|
||||||
|
// the same reporter.
|
||||||
|
func (inv Invocation) ReportingMode() ReportingMode {
|
||||||
|
if inv.reporter == nil || inv.reporter.state == nil {
|
||||||
|
return ReportingModeOrdinary
|
||||||
|
}
|
||||||
|
state := inv.reporter.state
|
||||||
|
state.mu.Lock()
|
||||||
|
defer state.mu.Unlock()
|
||||||
|
return state.mode
|
||||||
|
}
|
||||||
|
|
||||||
// Help renders the selected command's help through this invocation's output.
|
// Help renders the selected command's help through this invocation's output.
|
||||||
// It returns an operational failure when the invocation has no help renderer.
|
// It returns an operational failure when the invocation has no help renderer.
|
||||||
func (inv Invocation) Help() Outcome {
|
func (inv Invocation) Help() Outcome {
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ does not install commands.
|
|||||||
|
|
||||||
The module path is `go.maximumdirect.net/ckprompt`. See the
|
The module path is `go.maximumdirect.net/ckprompt`. See the
|
||||||
[consumer guide](https://forge.maximumdirect.net/base-layer/commandkit/src/branch/main/docs/consumers/guide.md#module-selection)
|
[consumer guide](https://forge.maximumdirect.net/base-layer/commandkit/src/branch/main/docs/consumers/guide.md#module-selection)
|
||||||
for compatible releases, Go requirements, and the current source's dependency
|
for compatible releases and Go requirements.
|
||||||
alignment requirement.
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# CommandKit ckprompt v0.3.0
|
# CommandKit ckprompt v0.5.0
|
||||||
|
|
||||||
This unpublished candidate adds PromptKit v0.20.0 support, including request
|
CommandKit ckprompt v0.5.0 adds PromptKit v0.20.0 support, including request
|
||||||
preview, namespace selection, and shared text, YAML, and JSON output. It uses
|
preview, namespace selection, and shared text, YAML, and JSON output. It requires
|
||||||
ConfigKit v1.1.0 and requires Go 1.27.0.
|
CommandKit core v0.5.0, ckconfig v0.5.0, ConfigKit v1.1.0, and Go 1.27.0.
|
||||||
|
|
||||||
## Operator features
|
## Operator features
|
||||||
|
|
||||||
@@ -20,22 +20,33 @@ let application commands offer the same request flags. `ckprompt.Declarations`
|
|||||||
adds the standard commands with their shorter flag aliases. Literal and file
|
adds the standard commands with their shorter flag aliases. Literal and file
|
||||||
inputs keep their command-line order.
|
inputs keep their command-line order.
|
||||||
|
|
||||||
## Compatibility and preparation
|
## Correctness improvements
|
||||||
|
|
||||||
This candidate changes resource report shapes, input flags, formatting, and
|
Aggregate smoke testing retains cancellation when it stops remaining tests,
|
||||||
configuration comparison behavior. Review the current
|
including previously returned errors and reports. A prepared-request preview
|
||||||
|
fails if the provider returns no prepared request without an error. Parsed
|
||||||
|
provider-parameter objects are copied between independent request destinations,
|
||||||
|
so mutations in one callback do not change later requests. Configuration
|
||||||
|
inspection shares ckconfig's native diagnostic adapter.
|
||||||
|
|
||||||
|
## Compatibility and upgrade
|
||||||
|
|
||||||
|
This pre-1.0 minor release changes resource report shapes, input flags,
|
||||||
|
formatting, and configuration comparison behavior. Review the current
|
||||||
[CLI reference](../../../../docs/cli.md#promptkit-definition-inspection) and
|
[CLI reference](../../../../docs/cli.md#promptkit-definition-inspection) and
|
||||||
[consumer guide](../../../../docs/consumers/guide.md#promptkit-configuration-definitions-and-requirements)
|
[consumer guide](../../../../docs/consumers/guide.md#promptkit-configuration-definitions-and-requirements)
|
||||||
when updating declarations or code that reads reports. Applications continue to
|
when updating declarations or code that reads reports. Applications continue to
|
||||||
own engine construction, credentials, and execution.
|
own engine construction, credentials, and execution.
|
||||||
|
|
||||||
The current source requires `ckconfig.AdaptDiagnostics`, which is absent from
|
Upgrade all three CommandKit modules to v0.5.0 together. This release follows
|
||||||
its selected ckconfig v0.4.0 dependency. Before publication, complete
|
published ckprompt v0.2.0 and adopts the vanity imports
|
||||||
[dependency alignment](../../../../docs/roadmap/future.md#pending-release-and-integration-work)
|
`go.maximumdirect.net/configkit` and `go.maximumdirect.net/promptkit`.
|
||||||
and update this candidate's dependency guidance to the selected releases.
|
Replace earlier upstream import paths consistently; types from different
|
||||||
|
module identities cannot be mixed. Review callers of the former prompt
|
||||||
|
version-report API and use native prompt, profile, and backend inspections.
|
||||||
The [module selection table](../../../../docs/consumers/guide.md#module-selection)
|
The [module selection table](../../../../docs/consumers/guide.md#module-selection)
|
||||||
distinguishes published combinations from the current workspace.
|
records the release dependencies.
|
||||||
|
|
||||||
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.3.0/integrations/ckprompt/COPYRIGHT) |
|
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.5.0/integrations/ckprompt/COPYRIGHT) |
|
||||||
[LICENSE](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.3.0/integrations/ckprompt/LICENSE) |
|
[LICENSE](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.5.0/integrations/ckprompt/LICENSE) |
|
||||||
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.3.0/integrations/ckprompt/docs/oss/attribution.md)
|
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/commandkit/src/tag/integrations/ckprompt/v0.5.0/integrations/ckprompt/docs/oss/attribution.md)
|
||||||
@@ -4,11 +4,10 @@ This example combines application and PromptKit configuration, resource
|
|||||||
inspection, request checks, and smoke tests. It uses local fixtures and an
|
inspection, request checks, and smoke tests. It uses local fixtures and an
|
||||||
in-memory model client. **No model service or credentials are required.**
|
in-memory model client. **No model service or credentials are required.**
|
||||||
|
|
||||||
Use Go 1.27.0 on `PATH` and an aligned workspace containing all three local
|
Use Go 1.27.0 on `PATH`. An aligned workspace containing all three local modules
|
||||||
modules. Follow the [workspace setup](../../../../docs/development.md#documentation-validation)
|
exercises their current sources; follow the [workspace setup](../../../../docs/development.md#documentation-validation)
|
||||||
from the repository root first. Current ckprompt source needs a helper in local
|
from the repository root. The example also builds independently with
|
||||||
ckconfig source that its released dependency does not yet contain; independent
|
`GOWORK=off` against its module's released dependencies.
|
||||||
builds remain pending [release alignment](../../../../docs/roadmap/future.md#pending-release-and-integration-work).
|
|
||||||
The [consumer guide](../../../../docs/consumers/guide.md#module-selection) owns
|
The [consumer guide](../../../../docs/consumers/guide.md#module-selection) owns
|
||||||
version and compatibility details.
|
version and compatibility details.
|
||||||
|
|
||||||
@@ -168,5 +167,5 @@ for suite composition, builders, and ownership, and the [source](main.go) for
|
|||||||
the complete application.
|
the complete application.
|
||||||
|
|
||||||
Run `GOWORK=auto GOTOOLCHAIN=local go test ./...` from this example directory
|
Run `GOWORK=auto GOTOOLCHAIN=local go test ./...` from this example directory
|
||||||
for an aligned workspace check. Once dependency alignment is complete, also use
|
for an aligned workspace check. Use
|
||||||
`GOWORK=off GOTOOLCHAIN=local go test ./...` to check the declared releases.
|
`GOWORK=off GOTOOLCHAIN=local go test ./...` to check the declared releases.
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module go.maximumdirect.net/ckprompt
|
|||||||
go 1.27.0
|
go 1.27.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
go.maximumdirect.net/ckconfig v0.4.0
|
go.maximumdirect.net/ckconfig v0.5.0
|
||||||
go.maximumdirect.net/commandkit v0.4.0
|
go.maximumdirect.net/commandkit v0.5.0
|
||||||
go.maximumdirect.net/configkit v1.1.0
|
go.maximumdirect.net/configkit v1.1.0
|
||||||
go.maximumdirect.net/promptkit v0.20.0
|
go.maximumdirect.net/promptkit v0.20.0
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
go.yaml.in/yaml/v4 v4.0.0-rc.6
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
go.maximumdirect.net/ckconfig v0.4.0 h1:7MpI/jIy+3BZrVME5rhKQibh5U9IA3s97BUJ2EOG41s=
|
go.maximumdirect.net/ckconfig v0.4.0 h1:7MpI/jIy+3BZrVME5rhKQibh5U9IA3s97BUJ2EOG41s=
|
||||||
go.maximumdirect.net/ckconfig v0.4.0/go.mod h1:Mk6QOfHrFy00uE0/gw9h6/Y9LcdTShVEzM/oI/HGDns=
|
go.maximumdirect.net/ckconfig v0.4.0/go.mod h1:Mk6QOfHrFy00uE0/gw9h6/Y9LcdTShVEzM/oI/HGDns=
|
||||||
|
go.maximumdirect.net/ckconfig v0.5.0 h1:5YCPFySTFIFR+gzd0FjqkTPEVB7zkGa8kOEYh4aBvWc=
|
||||||
|
go.maximumdirect.net/ckconfig v0.5.0/go.mod h1:BONXRSQlHdJvYVw9mN6lMV4G4ldlV/5fRO9EEApA99s=
|
||||||
go.maximumdirect.net/commandkit v0.4.0 h1:bWqflDWDn98FUCgh+0e+w6LGmvWIkaeJK519H7PjD2Y=
|
go.maximumdirect.net/commandkit v0.4.0 h1:bWqflDWDn98FUCgh+0e+w6LGmvWIkaeJK519H7PjD2Y=
|
||||||
go.maximumdirect.net/commandkit v0.4.0/go.mod h1:ZS98DewKNVBygKVDoh5MFD09CnPlP37dS9pVsID5Eoc=
|
go.maximumdirect.net/commandkit v0.4.0/go.mod h1:ZS98DewKNVBygKVDoh5MFD09CnPlP37dS9pVsID5Eoc=
|
||||||
|
go.maximumdirect.net/commandkit v0.5.0 h1:Ln+bCp+FwCg4tZD3NaMKQXUDB3mPusgwsnRZmb0+3cM=
|
||||||
|
go.maximumdirect.net/commandkit v0.5.0/go.mod h1:ZS98DewKNVBygKVDoh5MFD09CnPlP37dS9pVsID5Eoc=
|
||||||
go.maximumdirect.net/configkit v1.1.0 h1:UzXN2ZMnEkcL0O4YSLqD9qd9M1gb/NWCHQR/H8AaQzA=
|
go.maximumdirect.net/configkit v1.1.0 h1:UzXN2ZMnEkcL0O4YSLqD9qd9M1gb/NWCHQR/H8AaQzA=
|
||||||
go.maximumdirect.net/configkit v1.1.0/go.mod h1:s1zLJ9GGB/1Q4a+aOHMLISonJxWFgNWpA6SpPlOafeA=
|
go.maximumdirect.net/configkit v1.1.0/go.mod h1:s1zLJ9GGB/1Q4a+aOHMLISonJxWFgNWpA6SpPlOafeA=
|
||||||
go.maximumdirect.net/promptkit v0.20.0 h1:ZpwbjiCZWXdnnZ26RuWKoOco0jFnaAX8m2tQAX7ZlnM=
|
go.maximumdirect.net/promptkit v0.20.0 h1:ZpwbjiCZWXdnnZ26RuWKoOco0jFnaAX8m2tQAX7ZlnM=
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ func (session *Session) Parse(argv []string) commandmeta.Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remaining := append(commandpath.CloneTokens(unconsumed), tail...)
|
remaining := append(commandpath.CloneTokens(unconsumed), tail...)
|
||||||
localFlags := make([]commandmeta.Flag, 0, len(selectedNode.Flags)+len(selectedNode.CoreFlags))
|
localFlags := commandmeta.EffectiveFlags(selectedNode.Flags, selectedNode.CoreFlags)
|
||||||
localFlags = append(localFlags, selectedNode.Flags...)
|
|
||||||
localFlags = append(localFlags, selectedNode.CoreFlags...)
|
|
||||||
aliases := session.flagAliases
|
aliases := session.flagAliases
|
||||||
if session.flagAliasesForPath != nil {
|
if session.flagAliasesForPath != nil {
|
||||||
aliases = session.flagAliasesForPath(result.Path)
|
aliases = session.flagAliasesForPath(result.Path)
|
||||||
@@ -254,7 +252,7 @@ func displayCommandPath(rootName string, path []string) string {
|
|||||||
return strings.Join(parts, " ")
|
return strings.Join(parts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsageLine returns Cobra's usage line for an exact path on this session.
|
// UsageLine returns usage for an exact path on this session.
|
||||||
func (session *Session) UsageLine(path []string) (string, *commandmeta.Failure) {
|
func (session *Session) UsageLine(path []string) (string, *commandmeta.Failure) {
|
||||||
if session == nil || session.root == nil {
|
if session == nil || session.root == nil {
|
||||||
return "", &commandmeta.Failure{Message: "command tree is nil"}
|
return "", &commandmeta.Failure{Message: "command tree is nil"}
|
||||||
@@ -269,6 +267,22 @@ func (session *Session) UsageLine(path []string) (string, *commandmeta.Failure)
|
|||||||
if selected.HasSubCommands() {
|
if selected.HasSubCommands() {
|
||||||
line += " [command]"
|
line += " [command]"
|
||||||
}
|
}
|
||||||
|
for _, flag := range declaration.Flags {
|
||||||
|
if flag.Name != "debug" || !flag.Boolean {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, effective := range commandmeta.EffectiveFlags(declaration.Flags, declaration.CoreFlags) {
|
||||||
|
if effective.Name == "debug" {
|
||||||
|
spelling := "--debug"
|
||||||
|
if effective.Short != "" {
|
||||||
|
spelling = "-" + effective.Short + ", " + spelling
|
||||||
|
}
|
||||||
|
line += "\n " + spelling + " " + flagUsage(effective)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
return line, nil
|
return line, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +330,7 @@ func buildTree(root *commandmeta.Command) (*spfcobra.Command, map[*spfcobra.Comm
|
|||||||
helpFlag := backend.Flags().Lookup("help")
|
helpFlag := backend.Flags().Lookup("help")
|
||||||
helpFlag.NoOptDefVal = "true"
|
helpFlag.NoOptDefVal = "true"
|
||||||
helpFlag.Usage = "help for this command (default: false)"
|
helpFlag.Usage = "help for this command (default: false)"
|
||||||
for _, declarationFlag := range append(append([]commandmeta.Flag(nil), declaration.Flags...), declaration.CoreFlags...) {
|
for _, declarationFlag := range commandmeta.EffectiveFlags(declaration.Flags, declaration.CoreFlags) {
|
||||||
value := &rawValue{boolean: declarationFlag.Boolean}
|
value := &rawValue{boolean: declarationFlag.Boolean}
|
||||||
backend.Flags().VarP(value, declarationFlag.Name, declarationFlag.Short, flagUsage(declarationFlag))
|
backend.Flags().VarP(value, declarationFlag.Name, declarationFlag.Short, flagUsage(declarationFlag))
|
||||||
backendFlag := backend.Flags().Lookup(declarationFlag.Name)
|
backendFlag := backend.Flags().Lookup(declarationFlag.Name)
|
||||||
|
|||||||
@@ -281,3 +281,14 @@ func TestMissingSessionTreeReturnsNeutralFailures(t *testing.T) {
|
|||||||
t.Fatalf("missing-tree usage failure = %#v", failure)
|
t.Fatalf("missing-tree usage failure = %#v", failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugExtractionPreservesConsumerIndexes(t *testing.T) {
|
||||||
|
tree := &Command{Name: "app", CoreFlags: commandmeta.ReportingFlags(), Children: []*Command{{Name: "run", Runnable: true, CoreFlags: commandmeta.ReportingFlags(), Flags: []Flag{{Name: "before"}, {Name: "debug", Short: "d", Boolean: true}, {Name: "after"}}}}}
|
||||||
|
result := Parse(tree, []string{"run", "--before=one", "-d", "--after=two", "-x=false"}, ParseOptions{FlagAliases: []commandmeta.FlagAlias{{Source: "-x", Target: "--debug"}}})
|
||||||
|
if result.Failure != nil {
|
||||||
|
t.Fatal(result.Failure)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result.FlagValues, [][]string{{"one"}, {"true", "false"}, {"two"}}) || !reflect.DeepEqual(result.FlagSpellings[1], []string{"-d", "-x"}) || len(result.FlagOccurrences) != 4 {
|
||||||
|
t.Fatalf("parse = %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
// its command-line backend.
|
// its command-line backend.
|
||||||
package commandmeta
|
package commandmeta
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// Key identifies one command inside one routing snapshot.
|
// Key identifies one command inside one routing snapshot.
|
||||||
type Key uint64
|
type Key uint64
|
||||||
|
|
||||||
@@ -100,6 +102,35 @@ func ReportingFlags() []Flag {
|
|||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EffectiveFlags combines the shared debug option while preserving consumer order.
|
||||||
|
func EffectiveFlags(flags, core []Flag) []Flag {
|
||||||
|
result := append([]Flag(nil), flags...)
|
||||||
|
shared := -1
|
||||||
|
for i, flag := range result {
|
||||||
|
if flag.Name == "debug" && flag.Boolean {
|
||||||
|
shared = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, flag := range core {
|
||||||
|
if flag.Name == "debug" && shared >= 0 {
|
||||||
|
app := result[shared]
|
||||||
|
usage := flag.Usage
|
||||||
|
if app.Usage != "" {
|
||||||
|
usage += "; " + app.Usage
|
||||||
|
}
|
||||||
|
if strings.EqualFold(app.Default, "true") {
|
||||||
|
usage += "; application input defaults to true (reporting defaults to ordinary)"
|
||||||
|
}
|
||||||
|
flag.Usage, flag.Short, flag.Required, flag.Completion = usage, app.Short, app.Required, app.Completion
|
||||||
|
result[shared] = flag
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, flag)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// IsReportingFlag reports whether name is reserved for a shared reporting flag.
|
// IsReportingFlag reports whether name is reserved for a shared reporting flag.
|
||||||
func IsReportingFlag(name string) bool {
|
func IsReportingFlag(name string) bool {
|
||||||
for _, flag := range reportingFlags {
|
for _, flag := range reportingFlags {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package commandmeta
|
package commandmeta
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
||||||
flags := ReportingFlags()
|
flags := ReportingFlags()
|
||||||
@@ -33,3 +36,15 @@ func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEffectiveFlagsCombinesDebugMetadata(t *testing.T) {
|
||||||
|
flags := []Flag{{Name: "before"}, {Name: "debug", Short: "d", Boolean: true, Default: "true", Required: true, Usage: "application diagnostics", Completion: CompletionHint{Kind: CompletionFiles}}, {Name: "after"}}
|
||||||
|
core := ReportingFlags()
|
||||||
|
got := EffectiveFlags(flags, core)
|
||||||
|
if len(got) != len(flags)+len(core)-1 || got[1].Name != "debug" || got[1].Default != "false" || got[1].Short != "d" || !got[1].Required || got[1].Completion.Kind != CompletionFiles || !strings.Contains(got[1].Usage, "application diagnostics") || !strings.Contains(got[1].Usage, "application input defaults to true") {
|
||||||
|
t.Fatalf("effective debug = %#v", got)
|
||||||
|
}
|
||||||
|
if flags[1].Default != "true" || core[2].Short != "" {
|
||||||
|
t.Fatal("input metadata changed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
40
reporter.go
40
reporter.go
@@ -10,13 +10,19 @@ import (
|
|||||||
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
|
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
|
||||||
)
|
)
|
||||||
|
|
||||||
type reportingMode uint8
|
// ReportingMode identifies the invocation's selected reporting visibility.
|
||||||
|
// Its zero value is ReportingModeOrdinary.
|
||||||
|
type ReportingMode uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
modeOrdinary reportingMode = iota
|
// ReportingModeOrdinary uses ordinary reporting visibility.
|
||||||
modeQuiet
|
ReportingModeOrdinary ReportingMode = iota
|
||||||
modeVerbose
|
// ReportingModeQuiet suppresses ordinary output and messages.
|
||||||
modeDebug
|
ReportingModeQuiet
|
||||||
|
// ReportingModeVerbose enables verbose reporting.
|
||||||
|
ReportingModeVerbose
|
||||||
|
// ReportingModeDebug enables debug reporting.
|
||||||
|
ReportingModeDebug
|
||||||
)
|
)
|
||||||
|
|
||||||
type reportingDestination uint8
|
type reportingDestination uint8
|
||||||
@@ -41,7 +47,7 @@ type reportingState struct {
|
|||||||
diagnostics destinationState
|
diagnostics destinationState
|
||||||
closed bool
|
closed bool
|
||||||
deferredClosed bool
|
deferredClosed bool
|
||||||
mode reportingMode
|
mode ReportingMode
|
||||||
format Format
|
format Format
|
||||||
progress bool
|
progress bool
|
||||||
progressSet bool
|
progressSet bool
|
||||||
@@ -109,7 +115,7 @@ func newReporter(streams Streams) *Reporter {
|
|||||||
return &Reporter{state: &reportingState{
|
return &Reporter{state: &reportingState{
|
||||||
output: destinationState{writer: streams.Out},
|
output: destinationState{writer: streams.Out},
|
||||||
diagnostics: destinationState{writer: streams.Err},
|
diagnostics: destinationState{writer: streams.Err},
|
||||||
mode: modeOrdinary,
|
mode: ReportingModeOrdinary,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +131,13 @@ func (r *Reporter) applySelection(selection commandmeta.ReportingSelection) {
|
|||||||
}
|
}
|
||||||
switch selection.Mode {
|
switch selection.Mode {
|
||||||
case commandmeta.ReportingModeQuiet:
|
case commandmeta.ReportingModeQuiet:
|
||||||
state.mode = modeQuiet
|
state.mode = ReportingModeQuiet
|
||||||
case commandmeta.ReportingModeVerbose:
|
case commandmeta.ReportingModeVerbose:
|
||||||
state.mode = modeVerbose
|
state.mode = ReportingModeVerbose
|
||||||
case commandmeta.ReportingModeDebug:
|
case commandmeta.ReportingModeDebug:
|
||||||
state.mode = modeDebug
|
state.mode = ReportingModeDebug
|
||||||
default:
|
default:
|
||||||
state.mode = modeOrdinary
|
state.mode = ReportingModeOrdinary
|
||||||
}
|
}
|
||||||
state.progress = selection.Progress
|
state.progress = selection.Progress
|
||||||
state.progressSet = selection.ProgressSet
|
state.progressSet = selection.ProgressSet
|
||||||
@@ -189,7 +195,7 @@ func (r *Reporter) RenderOutput(write func(io.Writer) error) error {
|
|||||||
destination.submitted = true
|
destination.submitted = true
|
||||||
writer := &coordinatedWriter{state: state, destination: destinationOutput}
|
writer := &coordinatedWriter{state: state, destination: destinationOutput}
|
||||||
renderErr := write(writer)
|
renderErr := write(writer)
|
||||||
if state.mode == modeQuiet {
|
if state.mode == ReportingModeQuiet {
|
||||||
destination.suppressed = true
|
destination.suppressed = true
|
||||||
} else if renderErr == nil && writer.writeError == nil {
|
} else if renderErr == nil && writer.writeError == nil {
|
||||||
destination.delivered = true
|
destination.delivered = true
|
||||||
@@ -489,17 +495,17 @@ func renderDetailsLocked(state *reportingState, details []Detail, visible bool)
|
|||||||
func (state *reportingState) visible(category messageCategory) bool {
|
func (state *reportingState) visible(category messageCategory) bool {
|
||||||
switch category {
|
switch category {
|
||||||
case categoryInfo, categoryWarning:
|
case categoryInfo, categoryWarning:
|
||||||
return state.mode == modeVerbose || state.mode == modeDebug
|
return state.mode == ReportingModeVerbose || state.mode == ReportingModeDebug
|
||||||
case categoryDebug:
|
case categoryDebug:
|
||||||
return state.mode == modeDebug
|
return state.mode == ReportingModeDebug
|
||||||
case categoryProgress:
|
case categoryProgress:
|
||||||
if state.mode == modeQuiet {
|
if state.mode == ReportingModeQuiet {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if state.progressSet {
|
if state.progressSet {
|
||||||
return state.progress
|
return state.progress
|
||||||
}
|
}
|
||||||
return state.mode == modeDebug
|
return state.mode == ReportingModeDebug
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -617,7 +623,7 @@ func (state *reportingState) destination(destination reportingDestination) *dest
|
|||||||
|
|
||||||
func (state *reportingState) suppressed(destination reportingDestination) bool {
|
func (state *reportingState) suppressed(destination reportingDestination) bool {
|
||||||
if destination == destinationOutput {
|
if destination == destinationOutput {
|
||||||
return state.mode == modeQuiet
|
return state.mode == ReportingModeQuiet
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestHiddenSimpleDeferredMessagesDoNotAccumulate(t *testing.T) {
|
func TestHiddenSimpleDeferredMessagesDoNotAccumulate(t *testing.T) {
|
||||||
for _, mode := range []reportingMode{modeOrdinary, modeQuiet} {
|
for _, mode := range []ReportingMode{ReportingModeOrdinary, ReportingModeQuiet} {
|
||||||
t.Run(fmt.Sprint(mode), func(t *testing.T) {
|
t.Run(fmt.Sprint(mode), func(t *testing.T) {
|
||||||
reporter := newReporter(Streams{})
|
reporter := newReporter(Streams{})
|
||||||
setReporterVisibility(reporter, mode, false, false)
|
setReporterVisibility(reporter, mode, false, false)
|
||||||
@@ -45,7 +45,7 @@ func TestHiddenSimpleDeferredMessagesDoNotAccumulate(t *testing.T) {
|
|||||||
func TestHiddenDeferredDetailsRemainDeferredAndCaptured(t *testing.T) {
|
func TestHiddenDeferredDetailsRemainDeferredAndCaptured(t *testing.T) {
|
||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
failure := errors.New("hidden rendering failed")
|
failure := errors.New("hidden rendering failed")
|
||||||
var order []string
|
var order []string
|
||||||
details := []Detail{func(w io.Writer) error {
|
details := []Detail{func(w io.Writer) error {
|
||||||
@@ -87,7 +87,7 @@ func TestReporterDeferredAcceptanceEndsBeforeDelivery(t *testing.T) {
|
|||||||
writer := &gatedDiagnosticWriter{entered: make(chan struct{}), release: make(chan struct{})}
|
writer := &gatedDiagnosticWriter{entered: make(chan struct{}), release: make(chan struct{})}
|
||||||
entered := writer.entered
|
entered := writer.entered
|
||||||
reporter := newReporter(Streams{Err: writer})
|
reporter := newReporter(Streams{Err: writer})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
deferred := reporter.AtEnd()
|
deferred := reporter.AtEnd()
|
||||||
if err := deferred.Warn("accepted"); err != nil {
|
if err := deferred.Warn("accepted"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestReporterExplainsItsOwnFailuresWithoutExposingCauses(t *testing.T) {
|
|||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
if quiet {
|
if quiet {
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
}
|
}
|
||||||
failure := errors.New("private rendering detail")
|
failure := errors.New("private rendering detail")
|
||||||
render := func(io.Writer) error { return failure }
|
render := func(io.Writer) error { return failure }
|
||||||
@@ -84,7 +84,7 @@ func TestReporterPreservesJoinedRendererEvidence(t *testing.T) {
|
|||||||
writeErr := errors.Join(errors.New("sink failure"), errors.New("sink detail"))
|
writeErr := errors.Join(errors.New("sink failure"), errors.New("sink detail"))
|
||||||
destination := &reporterTestWriter{err: writeErr}
|
destination := &reporterTestWriter{err: writeErr}
|
||||||
reporter := newReporter(Streams{Out: destination, Err: destination})
|
reporter := newReporter(Streams{Out: destination, Err: destination})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
render := func(w io.Writer) error {
|
render := func(w io.Writer) error {
|
||||||
_, err := io.WriteString(w, "report")
|
_, err := io.WriteString(w, "report")
|
||||||
return test.combine(renderErr, err)
|
return test.combine(renderErr, err)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func TestReporterReportQuietAndClosed(t *testing.T) {
|
func TestReporterReportQuietAndClosed(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &out})
|
reporter := newReporter(Streams{Out: &out})
|
||||||
reporter.state.mode = modeQuiet
|
reporter.state.mode = ReportingModeQuiet
|
||||||
err := reporter.Report(Report{Value: make(chan int)})
|
err := reporter.Report(Report{Value: make(chan int)})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected serialization error")
|
t.Fatal("expected serialization error")
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func TestReporterLeavesCallerWriterOpen(t *testing.T) {
|
|||||||
func TestReporterQuietOutputDiscardsSuccessfully(t *testing.T) {
|
func TestReporterQuietOutputDiscardsSuccessfully(t *testing.T) {
|
||||||
var destination bytes.Buffer
|
var destination bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &destination})
|
reporter := newReporter(Streams{Out: &destination})
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
writer := reporter.Output()
|
writer := reporter.Output()
|
||||||
|
|
||||||
if n, err := writer.Write([]byte("hidden")); err != nil || n != len("hidden") {
|
if n, err := writer.Write([]byte("hidden")); err != nil || n != len("hidden") {
|
||||||
@@ -177,7 +177,7 @@ func TestReporterClosedHandlesReturnClosedErrorIncludingQuiet(t *testing.T) {
|
|||||||
var destination bytes.Buffer
|
var destination bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &destination})
|
reporter := newReporter(Streams{Out: &destination})
|
||||||
writer := reporter.Output()
|
writer := reporter.Output()
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
_ = reporter.finalize(Success(), nil)
|
_ = reporter.finalize(Success(), nil)
|
||||||
|
|
||||||
if n, err := writer.Write([]byte("late")); n != 0 || !errors.Is(err, ErrReporterClosed) {
|
if n, err := writer.Write([]byte("late")); n != 0 || !errors.Is(err, ErrReporterClosed) {
|
||||||
@@ -193,11 +193,11 @@ func TestNewReporterNormalizesNilDestinationsAndStartsOrdinary(t *testing.T) {
|
|||||||
if reporter.state.output.writer != io.Discard || reporter.state.diagnostics.writer != io.Discard {
|
if reporter.state.output.writer != io.Discard || reporter.state.diagnostics.writer != io.Discard {
|
||||||
t.Fatalf("nil destinations were not normalized: %#v", reporter.state)
|
t.Fatalf("nil destinations were not normalized: %#v", reporter.state)
|
||||||
}
|
}
|
||||||
if reporter.state.mode != modeOrdinary || reporter.state.progress || reporter.state.progressSet {
|
if reporter.state.mode != ReportingModeOrdinary || reporter.state.progress || reporter.state.progressSet {
|
||||||
t.Fatalf("initial visibility = mode %v, progress %v (set %v)", reporter.state.mode, reporter.state.progress, reporter.state.progressSet)
|
t.Fatalf("initial visibility = mode %v, progress %v (set %v)", reporter.state.mode, reporter.state.progress, reporter.state.progressSet)
|
||||||
}
|
}
|
||||||
setReporterVisibility(reporter, modeDebug, true, true)
|
setReporterVisibility(reporter, ReportingModeDebug, true, true)
|
||||||
if reporter.state.mode != modeDebug || !reporter.state.progress || !reporter.state.progressSet {
|
if reporter.state.mode != ReportingModeDebug || !reporter.state.progress || !reporter.state.progressSet {
|
||||||
t.Fatalf("visibility was not retained: mode %v, progress %v (set %v)", reporter.state.mode, reporter.state.progress, reporter.state.progressSet)
|
t.Fatalf("visibility was not retained: mode %v, progress %v (set %v)", reporter.state.mode, reporter.state.progress, reporter.state.progressSet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,17 +205,17 @@ func TestNewReporterNormalizesNilDestinationsAndStartsOrdinary(t *testing.T) {
|
|||||||
func TestReporterMessageVisibilityAndProgressPrecedence(t *testing.T) {
|
func TestReporterMessageVisibilityAndProgressPrecedence(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
mode reportingMode
|
mode ReportingMode
|
||||||
progress bool
|
progress bool
|
||||||
progressSet bool
|
progressSet bool
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{name: "ordinary", mode: modeOrdinary},
|
{name: "ordinary", mode: ReportingModeOrdinary},
|
||||||
{name: "verbose", mode: modeVerbose, want: "not used\nWARN: not used\n"},
|
{name: "verbose", mode: ReportingModeVerbose, want: "not used\nWARN: not used\n"},
|
||||||
{name: "debug", mode: modeDebug, want: "not used\nWARN: not used\nDEBUG: DEBUG\nPROGRESS\n"},
|
{name: "debug", mode: ReportingModeDebug, want: "not used\nWARN: not used\nDEBUG: DEBUG\nPROGRESS\n"},
|
||||||
{name: "quiet overrides progress", mode: modeQuiet, progress: true, progressSet: true},
|
{name: "quiet overrides progress", mode: ReportingModeQuiet, progress: true, progressSet: true},
|
||||||
{name: "explicit ordinary progress", mode: modeOrdinary, progress: true, progressSet: true, want: "PROGRESS\n"},
|
{name: "explicit ordinary progress", mode: ReportingModeOrdinary, progress: true, progressSet: true, want: "PROGRESS\n"},
|
||||||
{name: "explicit debug disable", mode: modeDebug, progressSet: true, want: "not used\nWARN: not used\nDEBUG: DEBUG\n"},
|
{name: "explicit debug disable", mode: ReportingModeDebug, progressSet: true, want: "not used\nWARN: not used\nDEBUG: DEBUG\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -237,7 +237,7 @@ func TestReporterMessageVisibilityAndProgressPrecedence(t *testing.T) {
|
|||||||
func TestReporterFormatsMessagesAndPreservesDetailOrder(t *testing.T) {
|
func TestReporterFormatsMessagesAndPreservesDetailOrder(t *testing.T) {
|
||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
if err := reporter.Warn("Native warning\nwith second line\n", nil,
|
if err := reporter.Warn("Native warning\nwith second line\n", nil,
|
||||||
func(w io.Writer) error { _, err := io.WriteString(w, "detail one\n"); return err },
|
func(w io.Writer) error { _, err := io.WriteString(w, "detail one\n"); return err },
|
||||||
func(w io.Writer) error { _, err := io.WriteString(w, "detail two\n"); return err }); err != nil {
|
func(w io.Writer) error { _, err := io.WriteString(w, "detail two\n"); return err }); err != nil {
|
||||||
@@ -258,7 +258,7 @@ func TestReporterFormatsMessagesAndPreservesDetailOrder(t *testing.T) {
|
|||||||
func TestReporterHiddenRenderersRunAgainstDiscardAndRetainErrors(t *testing.T) {
|
func TestReporterHiddenRenderersRunAgainstDiscardAndRetainErrors(t *testing.T) {
|
||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
renderErr := errors.New("native renderer failed")
|
renderErr := errors.New("native renderer failed")
|
||||||
calls := 0
|
calls := 0
|
||||||
err := reporter.Warn("hidden warning", func(w io.Writer) error {
|
err := reporter.Warn("hidden warning", func(w io.Writer) error {
|
||||||
@@ -284,7 +284,7 @@ func TestReporterRecordsIgnoredRenderErrors(t *testing.T) {
|
|||||||
renderErr := errors.New("output renderer failed")
|
renderErr := errors.New("output renderer failed")
|
||||||
diagnosticErr := errors.New("diagnostic renderer failed")
|
diagnosticErr := errors.New("diagnostic renderer failed")
|
||||||
reporter := newReporter(Streams{Out: io.Discard, Err: io.Discard})
|
reporter := newReporter(Streams{Out: io.Discard, Err: io.Discard})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
_ = reporter.RenderOutput(func(io.Writer) error { return renderErr })
|
_ = reporter.RenderOutput(func(io.Writer) error { return renderErr })
|
||||||
_ = reporter.Warn("warning", func(io.Writer) error { return diagnosticErr })
|
_ = reporter.Warn("warning", func(io.Writer) error { return diagnosticErr })
|
||||||
|
|
||||||
@@ -297,7 +297,7 @@ func TestReporterRecordsIgnoredRenderErrors(t *testing.T) {
|
|||||||
func TestReporterRenderOutputRunsInQuietAndRetainsFailure(t *testing.T) {
|
func TestReporterRenderOutputRunsInQuietAndRetainsFailure(t *testing.T) {
|
||||||
destination := &reporterTestWriter{}
|
destination := &reporterTestWriter{}
|
||||||
reporter := newReporter(Streams{Out: destination})
|
reporter := newReporter(Streams{Out: destination})
|
||||||
setReporterVisibility(reporter, modeQuiet, false, false)
|
setReporterVisibility(reporter, ReportingModeQuiet, false, false)
|
||||||
renderErr := errors.New("quiet encoder failed")
|
renderErr := errors.New("quiet encoder failed")
|
||||||
calls := 0
|
calls := 0
|
||||||
_ = reporter.RenderOutput(func(w io.Writer) error {
|
_ = reporter.RenderOutput(func(w io.Writer) error {
|
||||||
@@ -321,7 +321,7 @@ func TestReporterCountsSuccessfulZeroByteRenderAsSubmitted(t *testing.T) {
|
|||||||
if err := reporter.RenderOutput(func(io.Writer) error { return nil }); err != nil {
|
if err := reporter.RenderOutput(func(io.Writer) error { return nil }); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
if err := reporter.Warn("", func(io.Writer) error { return nil }); err != nil {
|
if err := reporter.Warn("", func(io.Writer) error { return nil }); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -409,7 +409,7 @@ func TestReporterConcurrentOperationsRemainWhole(t *testing.T) {
|
|||||||
t.Run("render callback and message", func(t *testing.T) {
|
t.Run("render callback and message", func(t *testing.T) {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &output, Err: &output})
|
reporter := newReporter(Streams{Out: &output, Err: &output})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
start := make(chan struct{})
|
start := make(chan struct{})
|
||||||
ready := make(chan struct{}, 2)
|
ready := make(chan struct{}, 2)
|
||||||
done := make(chan struct{}, 2)
|
done := make(chan struct{}, 2)
|
||||||
@@ -443,7 +443,7 @@ func TestReporterConcurrentOperationsRemainWhole(t *testing.T) {
|
|||||||
t.Run("message details and render callback", func(t *testing.T) {
|
t.Run("message details and render callback", func(t *testing.T) {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &output, Err: &output})
|
reporter := newReporter(Streams{Out: &output, Err: &output})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
start := make(chan struct{})
|
start := make(chan struct{})
|
||||||
ready := make(chan struct{}, 2)
|
ready := make(chan struct{}, 2)
|
||||||
done := make(chan struct{}, 2)
|
done := make(chan struct{}, 2)
|
||||||
@@ -481,7 +481,7 @@ func TestReporterConcurrentOperationsRemainWhole(t *testing.T) {
|
|||||||
func TestReporterEmptyHeadingDoesNotAddPrefixOrBlankLine(t *testing.T) {
|
func TestReporterEmptyHeadingDoesNotAddPrefixOrBlankLine(t *testing.T) {
|
||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
if err := reporter.Warn("", func(w io.Writer) error {
|
if err := reporter.Warn("", func(w io.Writer) error {
|
||||||
_, err := io.WriteString(w, "native multiline\nsecond line\n")
|
_, err := io.WriteString(w, "native multiline\nsecond line\n")
|
||||||
return err
|
return err
|
||||||
@@ -499,7 +499,7 @@ func TestReporterEmptyHeadingDoesNotAddPrefixOrBlankLine(t *testing.T) {
|
|||||||
func TestReporterFinalizesDeferredMessagesBeforeCompletionAndExplanation(t *testing.T) {
|
func TestReporterFinalizesDeferredMessagesBeforeCompletionAndExplanation(t *testing.T) {
|
||||||
var output, diagnostics bytes.Buffer
|
var output, diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Out: &output, Err: &diagnostics})
|
reporter := newReporter(Streams{Out: &output, Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeDebug, false, false)
|
setReporterVisibility(reporter, ReportingModeDebug, false, false)
|
||||||
if err := reporter.Info("started"); err != nil {
|
if err := reporter.Info("started"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -525,7 +525,7 @@ func TestReporterFinalizesDeferredMessagesBeforeCompletionAndExplanation(t *test
|
|||||||
func TestReporterDeferredSubmissionCopiesDetailsAndRetainsDeferredFailures(t *testing.T) {
|
func TestReporterDeferredSubmissionCopiesDetailsAndRetainsDeferredFailures(t *testing.T) {
|
||||||
var diagnostics bytes.Buffer
|
var diagnostics bytes.Buffer
|
||||||
reporter := newReporter(Streams{Err: &diagnostics})
|
reporter := newReporter(Streams{Err: &diagnostics})
|
||||||
setReporterVisibility(reporter, modeVerbose, false, false)
|
setReporterVisibility(reporter, ReportingModeVerbose, false, false)
|
||||||
writeFirst := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "first detail\n"); return err })
|
writeFirst := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "first detail\n"); return err })
|
||||||
writeSecond := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "second detail\n"); return err })
|
writeSecond := Detail(func(w io.Writer) error { _, err := io.WriteString(w, "second detail\n"); return err })
|
||||||
details := []Detail{writeFirst}
|
details := []Detail{writeFirst}
|
||||||
@@ -545,7 +545,7 @@ func TestReporterDeferredSubmissionCopiesDetailsAndRetainsDeferredFailures(t *te
|
|||||||
|
|
||||||
renderErr := errors.New("deferred renderer failed")
|
renderErr := errors.New("deferred renderer failed")
|
||||||
failedReporter := newReporter(Streams{Err: io.Discard})
|
failedReporter := newReporter(Streams{Err: io.Discard})
|
||||||
setReporterVisibility(failedReporter, modeQuiet, false, false)
|
setReporterVisibility(failedReporter, ReportingModeQuiet, false, false)
|
||||||
if err := failedReporter.AtEnd().Warn("hidden", func(io.Writer) error { return renderErr }); err != nil {
|
if err := failedReporter.AtEnd().Warn("hidden", func(io.Writer) error { return renderErr }); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -699,14 +699,14 @@ func TestReporterFinalizationClosesOutputAndDeferredHandles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setReporterVisibility(reporter *Reporter, mode reportingMode, progress, progressSet bool) {
|
func setReporterVisibility(reporter *Reporter, mode ReportingMode, progress, progressSet bool) {
|
||||||
selection := commandmeta.ReportingSelection{Progress: progress, ProgressSet: progressSet}
|
selection := commandmeta.ReportingSelection{Progress: progress, ProgressSet: progressSet}
|
||||||
switch mode {
|
switch mode {
|
||||||
case modeQuiet:
|
case ReportingModeQuiet:
|
||||||
selection.Mode = commandmeta.ReportingModeQuiet
|
selection.Mode = commandmeta.ReportingModeQuiet
|
||||||
case modeVerbose:
|
case ReportingModeVerbose:
|
||||||
selection.Mode = commandmeta.ReportingModeVerbose
|
selection.Mode = commandmeta.ReportingModeVerbose
|
||||||
case modeDebug:
|
case ReportingModeDebug:
|
||||||
selection.Mode = commandmeta.ReportingModeDebug
|
selection.Mode = commandmeta.ReportingModeDebug
|
||||||
}
|
}
|
||||||
reporter.applySelection(selection)
|
reporter.applySelection(selection)
|
||||||
|
|||||||
205
shared_debug_external_test.go
Normal file
205
shared_debug_external_test.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package commandkit_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
commandkit "go.maximumdirect.net/commandkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvocationReportingMode(t *testing.T) {
|
||||||
|
if got := (commandkit.Invocation{}).ReportingMode(); got != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("zero invocation mode = %v", got)
|
||||||
|
}
|
||||||
|
var retained commandkit.Invocation
|
||||||
|
var observed commandkit.ReportingMode
|
||||||
|
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
||||||
|
retained = inv
|
||||||
|
observed = inv.ReportingMode()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
flag string
|
||||||
|
want commandkit.ReportingMode
|
||||||
|
}{{"", commandkit.ReportingModeOrdinary}, {"--quiet", commandkit.ReportingModeQuiet}, {"--verbose", commandkit.ReportingModeVerbose}, {"--debug", commandkit.ReportingModeDebug}, {"", commandkit.ReportingModeOrdinary}} {
|
||||||
|
args := []string{"run"}
|
||||||
|
if tc.flag != "" {
|
||||||
|
args = append(args, tc.flag)
|
||||||
|
}
|
||||||
|
result := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || observed != tc.want || retained.ReportingMode() != tc.want {
|
||||||
|
t.Fatalf("%q: outcome=%#v mode=%v", tc.flag, result, retained.ReportingMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvocationReportingModeConcurrentReads(t *testing.T) {
|
||||||
|
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome {
|
||||||
|
start := make(chan struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
<-start
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
if inv.ReportingMode() != commandkit.ReportingModeDebug {
|
||||||
|
t.Error("mode changed")
|
||||||
|
}
|
||||||
|
_ = inv.Reporter().Debug("diagnostic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
close(start)
|
||||||
|
wg.Wait()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if outcome := app.Execute(context.Background(), []string{"run", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != commandkit.OutcomeSuccess {
|
||||||
|
t.Fatal(outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugBindingAndReportingMode(t *testing.T) {
|
||||||
|
type input struct {
|
||||||
|
debug commandkit.Supplied[bool]
|
||||||
|
before, after commandkit.Supplied[string]
|
||||||
|
operand commandkit.Supplied[string]
|
||||||
|
}
|
||||||
|
var received input
|
||||||
|
var mode commandkit.ReportingMode
|
||||||
|
factories, calls := 0, 0
|
||||||
|
parse := func(s string) (string, error) { return s, nil }
|
||||||
|
cmd := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "run", NewInput: func() input { factories++; return input{} }, Flags: []commandkit.Flag[input]{
|
||||||
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "before"}, (*string)(nil), parse, func(i *input, v commandkit.Supplied[string]) { i.before = v }),
|
||||||
|
commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d"}, false, func(i *input, v commandkit.Supplied[bool]) { i.debug = v }),
|
||||||
|
commandkit.ValueFlag(commandkit.FlagMeta{Name: "after"}, (*string)(nil), parse, func(i *input, v commandkit.Supplied[string]) { i.after = v }),
|
||||||
|
}, Arguments: []commandkit.Argument[input]{commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "operand"}, false, parse, func(i *input, v commandkit.Supplied[string]) { i.operand = v })}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
|
||||||
|
calls++
|
||||||
|
received = i
|
||||||
|
mode = inv.ReportingMode()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, cmd, commandkit.FlagAlias("-x", "--debug"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
args []string
|
||||||
|
value, set bool
|
||||||
|
mode commandkit.ReportingMode
|
||||||
|
}{
|
||||||
|
{nil, false, false, commandkit.ReportingModeOrdinary}, {[]string{"--debug"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--quiet"}, true, true, commandkit.ReportingModeQuiet}, {[]string{"--quiet", "--debug"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--verbose"}, true, true, commandkit.ReportingModeVerbose}, {[]string{"--debug", "--debug=false"}, false, true, commandkit.ReportingModeOrdinary}, {[]string{"--verbose", "--debug", "--debug=false"}, false, true, commandkit.ReportingModeOrdinary}, {[]string{"--debug", "--quiet=false"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--progress=false"}, true, true, commandkit.ReportingModeDebug}, {[]string{"-x"}, true, true, commandkit.ReportingModeDebug}, {[]string{"-d=false"}, false, true, commandkit.ReportingModeOrdinary},
|
||||||
|
} {
|
||||||
|
args := append([]string{"run", "--before=one", "--after=two"}, tc.args...)
|
||||||
|
got := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if got.Kind != commandkit.OutcomeSuccess || received.debug.Value != tc.value || received.debug.Set != tc.set || mode != tc.mode || received.before.Value != "one" || received.after.Value != "two" {
|
||||||
|
t.Fatalf("%v: result=%#v input=%#v mode=%v", tc.args, got, received, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range []string{"--debug=", "--debug=invalid"} {
|
||||||
|
beforeFactories, beforeCalls := factories, calls
|
||||||
|
got := app.Execute(context.Background(), []string{"run", value}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if got.Kind != commandkit.OutcomeUsage || factories != beforeFactories || calls != beforeCalls {
|
||||||
|
t.Fatalf("%s result=%#v factories=%d calls=%d", value, got, factories, calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got := app.Execute(context.Background(), []string{"run", "--before", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if got.Kind != commandkit.OutcomeSuccess || received.before.Value != "--debug" || received.debug.Set || mode != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("option-looking value: %#v %#v mode=%v", got, received, mode)
|
||||||
|
}
|
||||||
|
got = app.Execute(context.Background(), []string{"run", "--", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if got.Kind != commandkit.OutcomeSuccess || received.operand.Value != "--debug" || received.debug.Set || mode != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("operand after terminator: %#v %#v mode=%v", got, received, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugCapturedDefaultsAndRequiredness(t *testing.T) {
|
||||||
|
type inner struct{ debug commandkit.Supplied[bool] }
|
||||||
|
type outer struct{ flags commandkit.FlagValues[inner] }
|
||||||
|
for _, required := range []bool{false, true} {
|
||||||
|
flag := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d", Required: required}, true, func(i *inner, v commandkit.Supplied[bool]) { i.debug = v })
|
||||||
|
captured := commandkit.CaptureFlags([]commandkit.Flag[inner]{flag}, func(i *outer) *commandkit.FlagValues[inner] { return &i.flags })
|
||||||
|
var applied, supplied inner
|
||||||
|
var applyErr error
|
||||||
|
var mode commandkit.ReportingMode
|
||||||
|
cmd := commandkit.NewCommand(commandkit.CommandSpec[outer]{Name: "run", Flags: captured, Run: func(inv commandkit.Invocation, i outer) commandkit.Outcome {
|
||||||
|
mode = inv.ReportingMode()
|
||||||
|
applied = inner{}
|
||||||
|
supplied = inner{debug: commandkit.Supplied[bool]{Value: true, Set: true}}
|
||||||
|
applyErr = i.flags.ApplyTo(&applied, flag)
|
||||||
|
if err := i.flags.ApplySuppliedTo(&supplied, flag); err != nil && applyErr == nil {
|
||||||
|
applyErr = err
|
||||||
|
}
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, cmd, commandkit.FlagAliasFor("run", "-x", "-d"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{})
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || mode != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("omission=%#v mode=%v", result, mode)
|
||||||
|
}
|
||||||
|
if required {
|
||||||
|
if !errors.Is(applyErr, commandkit.ErrFlagInput) {
|
||||||
|
t.Fatalf("missing required: %v", applyErr)
|
||||||
|
}
|
||||||
|
} else if applyErr != nil || !applied.debug.Value || applied.debug.Set || !supplied.debug.Value || !supplied.debug.Set {
|
||||||
|
t.Fatalf("defaults=%#v %#v %v", applied, supplied, applyErr)
|
||||||
|
}
|
||||||
|
result = app.Execute(context.Background(), []string{"run", "-x=false"}, commandkit.Streams{})
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || applyErr != nil || applied.debug.Value || !applied.debug.Set || supplied.debug.Value || !supplied.debug.Set || mode != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("false=%#v %#v %#v mode=%v err=%v", result, applied, supplied, mode, applyErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedDebugDirectRequiredAndRenamedBinding(t *testing.T) {
|
||||||
|
type input struct{ debug commandkit.Supplied[bool] }
|
||||||
|
var got input
|
||||||
|
var mode commandkit.ReportingMode
|
||||||
|
original := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Required: true}, true, func(i *input, v commandkit.Supplied[bool]) { i.debug = v })
|
||||||
|
required := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "required", Flags: []commandkit.Flag[input]{original}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
|
||||||
|
got = i
|
||||||
|
mode = inv.ReportingMode()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
renamed := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "renamed", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(original, "trace")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
|
||||||
|
got = i
|
||||||
|
mode = inv.ReportingMode()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
ordinary := commandkit.BoolFlag(commandkit.FlagMeta{Name: "local"}, false, func(i *input, v commandkit.Supplied[bool]) { i.debug = v })
|
||||||
|
attached := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "attached", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(ordinary, "debug")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome {
|
||||||
|
got = i
|
||||||
|
mode = inv.ReportingMode()
|
||||||
|
return commandkit.Success()
|
||||||
|
}})
|
||||||
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, required, renamed, attached)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if r := app.Execute(context.Background(), []string{"required"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeUsage {
|
||||||
|
t.Fatalf("required omission=%#v", r)
|
||||||
|
}
|
||||||
|
if r := app.Execute(context.Background(), []string{"required", "--debug=false"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeOrdinary {
|
||||||
|
t.Fatalf("explicit false=%#v %#v", r, got)
|
||||||
|
}
|
||||||
|
if r := app.Execute(context.Background(), []string{"renamed", "--trace=false", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug {
|
||||||
|
t.Fatalf("renamed=%#v %#v mode=%v", r, got, mode)
|
||||||
|
}
|
||||||
|
if r := app.Execute(context.Background(), []string{"attached", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || !got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug {
|
||||||
|
t.Fatalf("renamed to debug=%#v %#v mode=%v", r, got, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user