Add shared report format flag
This commit is contained in:
50
builtins.go
50
builtins.go
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"text/template"
|
||||
|
||||
internalcobra "go.maximumdirect.net/commandkit/internal/cobra"
|
||||
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
|
||||
yaml "go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
const commandKitModulePath = "go.maximumdirect.net/commandkit"
|
||||
@@ -17,16 +17,17 @@ type helpInput struct {
|
||||
}
|
||||
|
||||
type versionInput struct {
|
||||
Scope string
|
||||
Format string
|
||||
Scope string
|
||||
}
|
||||
|
||||
type versionDocument struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Revision string `yaml:"revision"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Revision string `json:"revision"`
|
||||
}
|
||||
|
||||
var versionTextTemplate = template.Must(ParseTextTemplate("version", "{{.Name}} {{.Version}}{{if .Revision}} ({{.Revision}}){{end}}\n", nil))
|
||||
|
||||
func (a *App) builtinCommands() []Declaration {
|
||||
help := captureCommand(CommandSpec[helpInput]{
|
||||
Name: "help",
|
||||
@@ -39,14 +40,12 @@ func (a *App) builtinCommands() []Declaration {
|
||||
return current.renderHelpTargets(input.Targets.Value)
|
||||
})
|
||||
scopeDefault := "app"
|
||||
formatDefault := "text"
|
||||
version := NewCommand(CommandSpec[versionInput]{
|
||||
Name: "version",
|
||||
Summary: "Show application or CommandKit version.",
|
||||
Description: "Report identity and build metadata without loading application configuration.",
|
||||
Flags: []Flag[versionInput]{
|
||||
ValueFlag(FlagMeta{Name: "scope", Usage: "version subject", Completion: CompleteChoices(CompletionChoice{Value: "app"}, CompletionChoice{Value: "commandkit"})}, &scopeDefault, parseVersionScope, func(input *versionInput, value Supplied[string]) { input.Scope = value.Value }),
|
||||
ValueFlag(FlagMeta{Name: "format", Usage: "output format", Completion: CompleteChoices(CompletionChoice{Value: "text"}, CompletionChoice{Value: "yaml"})}, &formatDefault, parseVersionFormat, func(input *versionInput, value Supplied[string]) { input.Format = value.Value }),
|
||||
},
|
||||
Run: func(invocation Invocation, input versionInput) Outcome {
|
||||
identity := a.identity
|
||||
@@ -56,7 +55,7 @@ func (a *App) builtinCommands() []Declaration {
|
||||
if identity.Version == "" {
|
||||
identity.Version = "dev"
|
||||
}
|
||||
return renderVersionOutcome(identity, input.Format, invocation.Reporter())
|
||||
return renderVersionOutcome(identity, invocation.Reporter())
|
||||
},
|
||||
})
|
||||
return []Declaration{help, version}
|
||||
@@ -69,13 +68,6 @@ func parseVersionScope(value string) (string, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func parseVersionFormat(value string) (string, error) {
|
||||
if value != "text" && value != "yaml" {
|
||||
return "", fmt.Errorf("format must be text or yaml, got %q", value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (a *App) renderHelpViewOutcomeWithSession(view commandmeta.HelpView, reporter *Reporter, session *internalcobra.Session) Outcome {
|
||||
content, failure := session.RenderHelpView(view)
|
||||
if failure != nil {
|
||||
@@ -91,36 +83,14 @@ func (a *App) renderHelpViewOutcomeWithSession(view commandmeta.HelpView, report
|
||||
return Success()
|
||||
}
|
||||
|
||||
func renderVersionOutcome(identity Identity, format string, reporter *Reporter) Outcome {
|
||||
func renderVersionOutcome(identity Identity, reporter *Reporter) Outcome {
|
||||
if identity.Version == "" {
|
||||
identity.Version = "dev"
|
||||
}
|
||||
switch format {
|
||||
case "text":
|
||||
line := identity.Name + " " + identity.Version
|
||||
if identity.Revision != "" {
|
||||
line += " (" + identity.Revision + ")"
|
||||
}
|
||||
_ = reporter.RenderOutput(func(w io.Writer) error {
|
||||
_, err := io.WriteString(w, line+"\n")
|
||||
return err
|
||||
})
|
||||
case "yaml":
|
||||
_ = reporter.RenderOutput(func(w io.Writer) error {
|
||||
return encodeYAML(w, versionDocument{Name: identity.Name, Version: identity.Version, Revision: identity.Revision})
|
||||
})
|
||||
default:
|
||||
return Usage(fmt.Sprintf("unsupported version format %q", format))
|
||||
}
|
||||
_ = reporter.Report(Report{Value: versionDocument{Name: identity.Name, Version: identity.Version, Revision: identity.Revision}, TextTemplate: versionTextTemplate})
|
||||
return Success()
|
||||
}
|
||||
|
||||
func encodeYAML(writer io.Writer, document any) error {
|
||||
encoder := yaml.NewEncoder(writer)
|
||||
encoder.SetIndent(2)
|
||||
return encoder.Encode(document)
|
||||
}
|
||||
|
||||
func readBuildInfo() *debug.BuildInfo {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
|
||||
@@ -43,6 +43,7 @@ func TestCompletionQueryDispatch(t *testing.T) {
|
||||
{"alias value", []string{"__complete", "shortcut", "--colour=bl"}, "blue\tcool color\n", false},
|
||||
{"quiet is data", []string{"__complete", "run", "--quiet", "--color=bl"}, "blue\tcool color\n", false},
|
||||
{"debug is data", []string{"__complete", "run", "--debug", "--color=bl"}, "blue\tcool color\n", false},
|
||||
{"format value", []string{"__complete", "run", "--format", "j"}, "json\n", false},
|
||||
{"short attached choice", []string{"__complete", "run", "-cfoo=ba"}, "foo=bar\n", false},
|
||||
{"unknown route", []string{"__complete", "missing", ""}, emptyReply, true},
|
||||
} {
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestCompletionHintsFollowCapturedBindings(t *testing.T) {
|
||||
t.Fatalf("no completion: %#v", got)
|
||||
}
|
||||
for _, flag := range append(commandmeta.ReportingFlags(), commandmeta.HelpFlag()) {
|
||||
if !reflect.DeepEqual(flag.Completion.Choices, []commandmeta.CompletionChoice{{Value: "true"}, {Value: "false"}}) {
|
||||
if flag.Boolean && !reflect.DeepEqual(flag.Completion.Choices, []commandmeta.CompletionChoice{{Value: "true"}, {Value: "false"}}) {
|
||||
t.Fatalf("reporting flag choices: %#v", flag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@ func TestPublicInstallationAcceptsMixedDeclarations(t *testing.T) {
|
||||
Name: "show",
|
||||
Summary: "Show a value.",
|
||||
Flags: []commandkit.Flag[struct{}]{
|
||||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(*struct{}, commandkit.Supplied[string]) {}),
|
||||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "style"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(*struct{}, commandkit.Supplied[string]) {}),
|
||||
},
|
||||
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() },
|
||||
})
|
||||
declarations := []commandkit.Declaration{
|
||||
command,
|
||||
commandkit.Alias("inspect", "show"),
|
||||
commandkit.FlagAlias("--fmt", "--format"),
|
||||
commandkit.FlagAliasFor("show", "-f", "--format"),
|
||||
commandkit.FlagAlias("--fmt", "--style"),
|
||||
commandkit.FlagAliasFor("show", "-f", "--style"),
|
||||
}
|
||||
app, err := commandkit.New(commandkit.Identity{Name: "Example"}, declarations...)
|
||||
if err != nil {
|
||||
@@ -84,7 +84,7 @@ func TestPublicAliasesPreserveCommandsAcrossDeclarationOrders(t *testing.T) {
|
||||
func(value string) (string, error) { optionalParses++; return value, nil },
|
||||
func(target *showInput, value commandkit.Supplied[string]) { target.profile = value },
|
||||
)},
|
||||
Flags: []commandkit.Flag[showInput]{commandkit.ValueFlag(commandkit.FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []commandkit.Flag[showInput]{commandkit.ValueFlag(commandkit.FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *showInput, value commandkit.Supplied[string]) { target.format = value },
|
||||
)},
|
||||
@@ -99,7 +99,7 @@ func TestPublicAliasesPreserveCommandsAcrossDeclarationOrders(t *testing.T) {
|
||||
commandkit.Alias("r full", "inspect"),
|
||||
commandkit.Alias("inspect-shortcut", "inspect"),
|
||||
commandkit.Alias("i", "inspect-shortcut"),
|
||||
commandkit.FlagAlias("--fmt", "--format"),
|
||||
commandkit.FlagAlias("--fmt", "--style"),
|
||||
}
|
||||
declarations := append([]commandkit.Declaration(nil), commands...)
|
||||
if test.rulesFirst {
|
||||
@@ -154,7 +154,7 @@ func TestPublicRootGroupRegistrationAndFailedBatchRecovery(t *testing.T) {
|
||||
var formats []string
|
||||
show := commandkit.NewCommand(commandkit.CommandSpec[showInput]{
|
||||
Name: "show",
|
||||
Flags: []commandkit.Flag[showInput]{commandkit.ValueFlag(commandkit.FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []commandkit.Flag[showInput]{commandkit.ValueFlag(commandkit.FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { formatParses++; return value, nil },
|
||||
func(target *showInput, value commandkit.Supplied[string]) { target.format = value },
|
||||
)},
|
||||
@@ -181,7 +181,7 @@ func TestPublicRootGroupRegistrationAndFailedBatchRecovery(t *testing.T) {
|
||||
if err := app.Register(family,
|
||||
commandkit.Alias("cfg-show", "config show"),
|
||||
commandkit.Alias("cs", "cfg-show"),
|
||||
commandkit.FlagAlias("--fmt", "--format"),
|
||||
commandkit.FlagAlias("--fmt", "--style"),
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
18
docs/cli.md
18
docs/cli.md
@@ -46,7 +46,7 @@ The supported flags are:
|
||||
| Flag | Values | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `--scope` | `app`, `commandkit` | `app` | Select the caller's application identity or the embedded CommandKit module identity. |
|
||||
| `--format` | `text`, `yaml` | `text` | Select the output format. |
|
||||
| `--format` | `text`, `yaml`, `json` | automatic | Select the output format; automatic uses text for version. |
|
||||
|
||||
Text output is `NAME VERSION`, followed by ` (REVISION)` when the selected
|
||||
identity has a revision. An empty application version is displayed as `dev`;
|
||||
@@ -58,7 +58,7 @@ versionless replacement, an untagged module, or missing build information
|
||||
displays `dev`. The CommandKit identity name is `CommandKit`; dependency build
|
||||
metadata does not supply a revision.
|
||||
|
||||
YAML output uses these explicit fields, including an empty revision:
|
||||
YAML and JSON output use these fields, including an empty revision:
|
||||
|
||||
```yaml
|
||||
name: ExampleApp
|
||||
@@ -111,6 +111,14 @@ caller passes result and diagnostic writers to `App.Execute`; substantive
|
||||
results go to `Streams.Out`, and categorized messages, terminal explanations,
|
||||
and usage guidance go to `Streams.Err`.
|
||||
|
||||
The shared `--format` flag selects `text`, `yaml`, or `json` for reports
|
||||
submitted through the core reporter. When omitted, a report's text template
|
||||
selects text; otherwise scalar values and nonempty string lists use text, while
|
||||
structured values and empty collections use YAML. Explicit text without a
|
||||
template uses YAML for structured values. Invalid or empty format values are
|
||||
usage errors. Raw output, help, completion, and diagnostics are not encoded as
|
||||
reports.
|
||||
|
||||
### Modes and visibility
|
||||
|
||||
Each invocation starts in ordinary mode. `--quiet`, `--verbose`, and `--debug`
|
||||
@@ -154,9 +162,9 @@ example version --quiet
|
||||
Reporting flags do not go before or between command-path components. Tokens
|
||||
consumed as values and tokens after `--` retain their normal meaning.
|
||||
|
||||
The reserved long spellings are `--quiet`, `--verbose`, `--debug`, and
|
||||
`--progress`; application declarations cannot reuse them. There are no built-in
|
||||
short spellings. Applications may define aliases such as `-q → --quiet`,
|
||||
The reserved long spellings are `--quiet`, `--verbose`, `--debug`,
|
||||
`--progress`, and `--format`; application declarations cannot reuse them. 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
|
||||
the same ordering. Thus `example export --verbose -q` selects quiet mode.
|
||||
|
||||
|
||||
@@ -135,7 +135,8 @@ installation alongside aliases and integration commands.
|
||||
## Invocation reporting
|
||||
|
||||
`Invocation.Reporter()` supplies the reporter for the current handler. Use
|
||||
`Output()` or `RenderOutput()` for substantive results; use `Warn`, `Info`,
|
||||
`Report()` for formatted substantive values and optional text templates;
|
||||
use `Output()` or `RenderOutput()` for deliberate raw output. Use `Warn`, `Info`,
|
||||
`Progress`, and `Debug` for categorized messages. Queue a summary with
|
||||
`AtEnd().Info` when it belongs after the handler returns. The
|
||||
[CLI reference](../cli.md#reporting) owns flag grammar, visibility, prefixes,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Shared reporting and PromptKit v0.20.0 implementation plan
|
||||
|
||||
Status: implementation in progress; Stages 1–3 complete.
|
||||
Status: implementation in progress; Stages 1–4 complete.
|
||||
|
||||
## Scope, authority, and execution
|
||||
|
||||
@@ -160,6 +160,11 @@ boundary in GoDoc.
|
||||
|
||||
## Stage 4: Universal format input, core version, and a core consumer
|
||||
|
||||
**Complete.** Core format selection, version reporting, and a maintained core
|
||||
consumer use the shared report path. Focused parser, routing, completion, and
|
||||
reporting tests and the root workspace suite (`GOTOOLCHAIN=local go test ./...`
|
||||
with Go 1.27.0) passed. Integration migrations remain in later stages.
|
||||
|
||||
Extend neutral core flag metadata and parser selection in
|
||||
[metadata.go](../../internal/commandmeta/metadata.go),
|
||||
[parser.go](../../parser.go), and the internal Cobra adapter. Add core-owned
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
commandkit "go.maximumdirect.net/commandkit"
|
||||
)
|
||||
@@ -20,6 +21,15 @@ type reportInput struct {
|
||||
Scenario string
|
||||
}
|
||||
|
||||
type greetingReport struct {
|
||||
Count string `json:"count"`
|
||||
Tags string `json:"tags"`
|
||||
Entered string `json:"entered"`
|
||||
Canonical string `json:"canonical"`
|
||||
}
|
||||
|
||||
var greetingTemplate = template.Must(commandkit.ParseTextTemplate("greeting", "Hello; count={{.Count}}; tags={{.Tags}}; entered={{printf \"%q\" .Entered}}; canonical={{printf \"%q\" .Canonical}}\n", nil))
|
||||
|
||||
var (
|
||||
errRecoveredFixture = errors.New("a local read failed and was recovered")
|
||||
errWarningFixture = errors.New("a local warning has a cause")
|
||||
@@ -50,10 +60,7 @@ func newApp() (*commandkit.App, error) {
|
||||
if input.Tags.Set {
|
||||
tags = fmt.Sprintf("explicit %v", input.Tags.Value)
|
||||
}
|
||||
_ = reporter.RenderOutput(func(writer io.Writer) error {
|
||||
_, err := fmt.Fprintf(writer, "Hello; count=%s; tags=%s; entered=%q; canonical=%q\n", count, tags, invocation.EnteredPath(), invocation.CanonicalPath())
|
||||
return err
|
||||
})
|
||||
_ = reporter.Report(commandkit.Report{Value: greetingReport{Count: count, Tags: tags, Entered: invocation.EnteredPath(), Canonical: invocation.CanonicalPath()}, TextTemplate: greetingTemplate})
|
||||
return commandkit.Success()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -475,10 +475,15 @@ func TestVersionCommandFormatsApplicationAndCommandKitIdentity(t *testing.T) {
|
||||
}
|
||||
out.Reset()
|
||||
yamlOutcome := app.Execute(context.Background(), []string{"version", "--format=yaml"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if yamlOutcome.Kind != commandkit.OutcomeSuccess || out.String() != "name: ExampleApp\nversion: dev\nrevision: abc123\n" {
|
||||
if yamlOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "name: ExampleApp\n") || !strings.Contains(out.String(), "version: dev\n") || !strings.Contains(out.String(), "revision: abc123\n") {
|
||||
t.Fatalf("YAML version = %#v, output %q", yamlOutcome, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
jsonOutcome := app.Execute(context.Background(), []string{"version", "--format=json"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if jsonOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), `"revision": "abc123"`) {
|
||||
t.Fatalf("JSON version = %#v, output %q", jsonOutcome, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
component := app.Execute(context.Background(), []string{"version", "--scope=commandkit"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if component.Kind != commandkit.OutcomeSuccess || !strings.HasPrefix(out.String(), "CommandKit ") || !strings.HasSuffix(out.String(), "\n") {
|
||||
t.Fatalf("CommandKit version = %#v, output %q", component, out.String())
|
||||
|
||||
@@ -23,8 +23,8 @@ func stringFlag(name, short string) Flag[struct{}] {
|
||||
}
|
||||
|
||||
func TestFlagAliasChainsUseScopedOverridesAndCanonicalEndpoints(t *testing.T) {
|
||||
inspect := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("format", "f"))
|
||||
show := flagAliasCommand("show", stringFlag("color", "c"), stringFlag("format", "f"))
|
||||
inspect := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("style", "f"))
|
||||
show := flagAliasCommand("show", stringFlag("color", "c"), stringFlag("style", "f"))
|
||||
group := Group("Tools", "tools", show)
|
||||
app, err := New(Identity{Name: "Example"},
|
||||
inspect,
|
||||
@@ -35,8 +35,8 @@ func TestFlagAliasChainsUseScopedOverridesAndCanonicalEndpoints(t *testing.T) {
|
||||
FlagAlias("-x", "--color"),
|
||||
FlagAlias("--tint", "-c"),
|
||||
FlagAliasFor("Tools", "--group-help", "--help"),
|
||||
FlagAliasFor("inspect", "--hue", "--format"),
|
||||
FlagAliasFor("inspect", "--local", "--format"),
|
||||
FlagAliasFor("inspect", "--hue", "--style"),
|
||||
FlagAliasFor("inspect", "--local", "--style"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -44,11 +44,11 @@ func TestFlagAliasChainsUseScopedOverridesAndCanonicalEndpoints(t *testing.T) {
|
||||
|
||||
inspectAliases := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling
|
||||
for source, target := range map[string]string{
|
||||
"--colour": "--format",
|
||||
"--hue": "--format",
|
||||
"--colour": "--style",
|
||||
"--hue": "--style",
|
||||
"-x": "--color",
|
||||
"--tint": "-c",
|
||||
"--local": "--format",
|
||||
"--local": "--style",
|
||||
} {
|
||||
alias, ok := inspectAliases[flagSpellingKey(capturedSpelling(source))]
|
||||
if !ok || alias.target.token() != target {
|
||||
@@ -126,7 +126,7 @@ func TestFlagAliasDiscoveryContextsUseGlobalReportingAliases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReportingFlagsAreReservedAndAcceptApplicationShortAliases(t *testing.T) {
|
||||
for _, name := range []string{"quiet", "verbose", "debug", "progress"} {
|
||||
for _, name := range []string{"quiet", "verbose", "debug", "progress", "format"} {
|
||||
command := flagAliasCommand("reserved", stringFlag(name, ""))
|
||||
if len(command.record.issues) == 0 {
|
||||
t.Errorf("capturing --%s did not retain a declaration error", name)
|
||||
@@ -154,7 +154,7 @@ func TestReportingFlagsAreReservedAndAcceptApplicationShortAliases(t *testing.T)
|
||||
}
|
||||
|
||||
func TestReportingFlagSpellingsConflictWithAliasSources(t *testing.T) {
|
||||
for _, name := range []string{"quiet", "verbose", "debug", "progress"} {
|
||||
for _, name := range []string{"quiet", "verbose", "debug", "progress", "format"} {
|
||||
if _, err := New(Identity{Name: "Example"}, FlagAlias("--"+name, "--help")); err == nil || !strings.Contains(err.Error(), "conflicts with a canonical spelling") {
|
||||
t.Errorf("alias source --%s returned %v, want a canonical spelling collision", name, err)
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func TestReportingFlagSpellingsConflictWithAliasSources(t *testing.T) {
|
||||
|
||||
func TestRoutingSnapshotCarriesReportingMetadataOutsideConsumerBindings(t *testing.T) {
|
||||
app, err := New(Identity{Name: "Example"},
|
||||
flagAliasCommand("run", stringFlag("format", "f")),
|
||||
flagAliasCommand("run", stringFlag("style", "f")),
|
||||
Group("tools", "", flagAliasCommand("show")),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -174,7 +174,7 @@ func TestRoutingSnapshotCarriesReportingMetadataOutsideConsumerBindings(t *testi
|
||||
}
|
||||
var check func(*commandmeta.Command)
|
||||
check = func(node *commandmeta.Command) {
|
||||
if len(node.CoreFlags) != 4 {
|
||||
if len(node.CoreFlags) != 5 {
|
||||
t.Errorf("%s core reporting flags = %#v", node.Name, node.CoreFlags)
|
||||
}
|
||||
for _, flag := range node.Flags {
|
||||
@@ -190,15 +190,15 @@ func TestRoutingSnapshotCarriesReportingMetadataOutsideConsumerBindings(t *testi
|
||||
}
|
||||
|
||||
func TestFlagAliasDuplicatesAndScopesAreDeterministic(t *testing.T) {
|
||||
command := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("format", "f"))
|
||||
command := flagAliasCommand("inspect", stringFlag("color", "c"), stringFlag("style", "f"))
|
||||
duplicate := FlagAlias("--colour", "--color")
|
||||
app, err := New(Identity{Name: "Example"}, command, flagAliasCommand("plain", stringFlag("color", "c")), duplicate, FlagAlias("--colour", "--color"),
|
||||
FlagAliasFor("inspect", "--colour", "--format"))
|
||||
FlagAliasFor("inspect", "--colour", "--style"))
|
||||
if err != nil {
|
||||
t.Fatalf("identical duplicates and scoped override were rejected: %v", err)
|
||||
}
|
||||
if got := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--format" {
|
||||
t.Fatalf("scoped alias target = %s, want --format", got)
|
||||
if got := app.flagAliases[commandpath.Path{"inspect"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--style" {
|
||||
t.Fatalf("scoped alias target = %s, want --style", got)
|
||||
}
|
||||
if got := app.flagAliases[commandpath.Path{"plain"}.Key()].bySpelling[flagSpellingKey(capturedFlagSpelling{name: "colour"})].target.token(); got != "--color" {
|
||||
t.Fatalf("effective global alias target = %s, want --color", got)
|
||||
@@ -207,11 +207,11 @@ func TestFlagAliasDuplicatesAndScopesAreDeterministic(t *testing.T) {
|
||||
for name, rules := range map[string][]RoutingRule{
|
||||
"competing global targets": {
|
||||
FlagAlias("--colour", "--color"),
|
||||
FlagAlias("--colour", "--format"),
|
||||
FlagAlias("--colour", "--style"),
|
||||
},
|
||||
"competing scoped targets": {
|
||||
FlagAliasFor("inspect", "--colour", "--color"),
|
||||
FlagAliasFor("inspect", "--colour", "--format"),
|
||||
FlagAliasFor("inspect", "--colour", "--style"),
|
||||
},
|
||||
"unknown scope": {FlagAliasFor("missing", "--colour", "--color")},
|
||||
"missing scoped destination": {FlagAliasFor("inspect", "--colour", "--missing")},
|
||||
|
||||
82
format_flag_test.go
Normal file
82
format_flag_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
commandmeta "go.maximumdirect.net/commandkit/internal/commandmeta"
|
||||
)
|
||||
|
||||
func TestSharedFormatFlag(t *testing.T) {
|
||||
factories, runs := 0, 0
|
||||
command := NewCommand(CommandSpec[struct{}]{Name: "run", NewInput: func() struct{} { factories++; return struct{}{} }, Run: func(inv Invocation, _ struct{}) Outcome {
|
||||
runs++
|
||||
_ = inv.Reporter().Report(Report{Value: map[string]any{"answer": 42}})
|
||||
return Success()
|
||||
}})
|
||||
app, err := New(Identity{Name: "Example", Version: "1"}, command, FlagAlias("--fmt", "--format"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{[]string{"run", "--format", "text"}, "yaml"},
|
||||
{[]string{"run", "--format=yaml"}, "yaml"},
|
||||
{[]string{"run", "--fmt=json"}, "json"},
|
||||
{[]string{"run", "--format=yaml", "--fmt", "json"}, "json"},
|
||||
{[]string{"run"}, "yaml"},
|
||||
} {
|
||||
var out bytes.Buffer
|
||||
got := app.Execute(context.Background(), tc.args, Streams{Out: &out})
|
||||
if got.Status != 0 {
|
||||
t.Fatalf("%v: %#v", tc.args, got)
|
||||
}
|
||||
var value any
|
||||
if tc.expected == "json" {
|
||||
dec := json.NewDecoder(&out)
|
||||
dec.UseNumber()
|
||||
err = dec.Decode(&value)
|
||||
} else {
|
||||
value, err = DecodeYAMLReport(&out)
|
||||
}
|
||||
if err != nil || !reflect.DeepEqual(value, map[string]any{"answer": json.Number("42")}) {
|
||||
t.Fatalf("%v: %#v %v", tc.args, value, err)
|
||||
}
|
||||
}
|
||||
beforeFactories, beforeRuns := factories, runs
|
||||
for _, args := range [][]string{{"run", "--format="}, {"run", "--fmt=xml"}, {"run", "--format=bad", "--format=json"}, {"run", "--format", "--quiet"}} {
|
||||
var diagnostics bytes.Buffer
|
||||
got := app.Execute(context.Background(), args, Streams{Err: &diagnostics})
|
||||
if got.Kind != OutcomeUsage || !strings.Contains(diagnostics.String(), "format") {
|
||||
t.Fatalf("%v: %#v %q", args, got, &diagnostics)
|
||||
}
|
||||
}
|
||||
if factories != beforeFactories || runs != beforeRuns {
|
||||
t.Fatalf("invalid formats ran domain code: factories %d, runs %d", factories, runs)
|
||||
}
|
||||
var help bytes.Buffer
|
||||
got := app.Execute(context.Background(), []string{"run", "--help"}, Streams{Out: &help})
|
||||
if got.Status != 0 || !strings.Contains(help.String(), "--format") || factories != beforeFactories || runs != beforeRuns {
|
||||
t.Fatalf("help: %#v, %q", got, &help)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharedFormatParseOrderAndTerminator(t *testing.T) {
|
||||
app, err := New(Identity{Name: "Example"}, flagAliasCommand("run", stringFlag("message", "")), FlagAlias("--fmt", "--format"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := parseReportingArgs(app, []string{"run", "--verbose", "--fmt", "json", "--quiet", "--message=--format", "--", "--format=yaml"})
|
||||
if result.Failure != nil || result.Reporting.Format != "json" || result.Reporting.Mode != commandmeta.ReportingModeQuiet || !reflect.DeepEqual(result.Operands, []string{"--format=yaml"}) {
|
||||
t.Fatalf("%#v", result)
|
||||
}
|
||||
if result.FlagOccurrences[1].Spelling != "--fmt" || result.FlagOccurrences[1].RawValue != "json" {
|
||||
t.Fatalf("%#v", result.FlagOccurrences)
|
||||
}
|
||||
}
|
||||
@@ -80,10 +80,10 @@ func TestParseInterspersedOperandsBooleanFlagsAndTerminator(t *testing.T) {
|
||||
func TestReportingFlagsParseWithoutChangingConsumerFlagIndexes(t *testing.T) {
|
||||
tree := &Command{Name: "app", CoreFlags: commandmeta.ReportingFlags(), Children: []*Command{
|
||||
{Name: "config", CoreFlags: commandmeta.ReportingFlags(), Children: []*Command{
|
||||
{Name: "show", Runnable: true, CoreFlags: commandmeta.ReportingFlags(), Flags: []Flag{{Name: "format"}}},
|
||||
{Name: "show", Runnable: true, CoreFlags: commandmeta.ReportingFlags(), Flags: []Flag{{Name: "style"}}},
|
||||
}},
|
||||
}}
|
||||
result := Parse(tree, []string{"config", "show", "--quiet", "-v", "--format=yaml"}, ParseOptions{
|
||||
result := Parse(tree, []string{"config", "show", "--quiet", "-v", "--style=yaml"}, ParseOptions{
|
||||
FlagAliases: []commandmeta.FlagAlias{{Source: "-v", Target: "--verbose"}},
|
||||
})
|
||||
if result.Failure != nil {
|
||||
@@ -92,7 +92,7 @@ func TestReportingFlagsParseWithoutChangingConsumerFlagIndexes(t *testing.T) {
|
||||
if !reflect.DeepEqual(result.FlagValues, [][]string{{"yaml"}}) {
|
||||
t.Fatalf("consumer binding values = %#v", result.FlagValues)
|
||||
}
|
||||
if !reflect.DeepEqual(result.FlagSpellings, [][]string{{"--format"}}) {
|
||||
if !reflect.DeepEqual(result.FlagSpellings, [][]string{{"--style"}}) {
|
||||
t.Fatalf("consumer binding spellings = %#v", result.FlagSpellings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ var reportingFlags = [...]Flag{
|
||||
{Name: "verbose", Usage: "show warning and informational messages", Default: "false", Boolean: true},
|
||||
{Name: "debug", Usage: "show debugging messages", Default: "false", Boolean: true},
|
||||
{Name: "progress", Usage: "show progress messages", Default: "false", Boolean: true},
|
||||
{Name: "format", Usage: "report format: text, yaml, or json", Completion: CompletionHint{Kind: CompletionChoices, Choices: []CompletionChoice{{Value: "text"}, {Value: "yaml"}, {Value: "json"}}}},
|
||||
}
|
||||
|
||||
// BooleanCompletion returns the ordinary explicit boolean suggestions.
|
||||
@@ -95,7 +96,11 @@ func HelpFlag() Flag {
|
||||
func ReportingFlags() []Flag {
|
||||
flags := append([]Flag(nil), reportingFlags[:]...)
|
||||
for index := range flags {
|
||||
flags[index].Completion = BooleanCompletion()
|
||||
if flags[index].Boolean {
|
||||
flags[index].Completion = BooleanCompletion()
|
||||
} else {
|
||||
flags[index].Completion.Choices = append([]CompletionChoice(nil), flags[index].Completion.Choices...)
|
||||
}
|
||||
}
|
||||
return flags
|
||||
}
|
||||
@@ -160,6 +165,7 @@ type ReportingSelection struct {
|
||||
Mode ReportingMode
|
||||
Progress bool
|
||||
ProgressSet bool
|
||||
Format string
|
||||
}
|
||||
|
||||
// Result contains the selected command and raw input collected by the adapter.
|
||||
|
||||
@@ -5,7 +5,7 @@ import "testing"
|
||||
func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
||||
flags := ReportingFlags()
|
||||
want := []string{"quiet", "verbose", "debug", "progress"}
|
||||
if len(flags) != len(want) {
|
||||
if len(flags) != len(want)+1 {
|
||||
t.Fatalf("reporting flags = %#v", flags)
|
||||
}
|
||||
for index, name := range want {
|
||||
@@ -16,6 +16,13 @@ func TestReportingFlagsAreBooleanAndReturnedIndependently(t *testing.T) {
|
||||
t.Errorf("%q is not recognized as a reporting flag", name)
|
||||
}
|
||||
}
|
||||
if flags[4].Name != "format" || flags[4].Boolean || !IsReportingFlag("format") {
|
||||
t.Fatalf("format metadata = %#v", flags[4])
|
||||
}
|
||||
flags[4].Completion.Choices[0].Value = "changed"
|
||||
if ReportingFlags()[4].Completion.Choices[0].Value != "text" {
|
||||
t.Fatal("caller mutation changed format choices")
|
||||
}
|
||||
flags[0].Name = "changed"
|
||||
if ReportingFlags()[0].Name != "quiet" {
|
||||
t.Fatal("caller mutation changed shared reporting metadata")
|
||||
|
||||
12
parser.go
12
parser.go
@@ -24,6 +24,18 @@ func reduceReportingOccurrences(result commandmeta.Result) commandmeta.Result {
|
||||
if !commandmeta.IsReportingFlag(occurrence.Name) {
|
||||
continue
|
||||
}
|
||||
if occurrence.Name == "format" {
|
||||
switch Format(occurrence.RawValue) {
|
||||
case FormatText, FormatYAML, FormatJSON:
|
||||
selection.Format = occurrence.RawValue
|
||||
default:
|
||||
result.FlagOccurrences = result.FlagOccurrences[:index+1]
|
||||
result.Reporting = selection
|
||||
result.Failure = &commandmeta.Failure{Message: fmt.Sprintf("invalid format %q for %s (expected text, yaml, or json)", occurrence.RawValue, occurrence.Spelling), Usage: true}
|
||||
return result
|
||||
}
|
||||
continue
|
||||
}
|
||||
enabled, err := strconv.ParseBool(occurrence.RawValue)
|
||||
if err != nil {
|
||||
result.FlagOccurrences = result.FlagOccurrences[:index+1]
|
||||
|
||||
@@ -134,6 +134,7 @@ func (r *Reporter) applySelection(selection commandmeta.ReportingSelection) {
|
||||
}
|
||||
state.progress = selection.Progress
|
||||
state.progressSet = selection.ProgressSet
|
||||
state.format = Format(selection.Format)
|
||||
}
|
||||
|
||||
// Output returns a managed writer for substantive command results. Writes are
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestRunnableParentKeepsNonCommandTokensAsOperands(t *testing.T) {
|
||||
Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 0, -1,
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *input, value Supplied[[]string]) { target.words = value.Value })},
|
||||
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "format", Short: "f"}, (*string)(nil),
|
||||
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "style", Short: "f"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *input, value Supplied[string]) { target.format = value })},
|
||||
Run: func(inv Invocation, value input) Outcome {
|
||||
@@ -36,7 +36,7 @@ func TestRunnableParentKeepsNonCommandTokensAsOperands(t *testing.T) {
|
||||
return Success()
|
||||
},
|
||||
})
|
||||
app, err := New(Identity{Name: "Demo"}, parent, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--format"))
|
||||
app, err := New(Identity{Name: "Demo"}, parent, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--style"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func TestRunnableParentKeepsNonCommandTokensAsOperands(t *testing.T) {
|
||||
}
|
||||
outcome = app.Execute(context.Background(), append(args, "--help"), Streams{Out: &output, Err: &diagnostics})
|
||||
if outcome.Kind != OutcomeSuccess || built != beforeBuilt+1 || parentRuns != beforeRuns+1 ||
|
||||
!strings.Contains(output.String(), "Demo "+spelling+" [ARG...]") || !strings.Contains(output.String(), "--fmt → --format") {
|
||||
!strings.Contains(output.String(), "Demo "+spelling+" [ARG...]") || !strings.Contains(output.String(), "--fmt → --style") {
|
||||
t.Fatalf("parent help = %#v, output %q, diagnostics %q", outcome, output.String(), diagnostics.String())
|
||||
}
|
||||
outcome = app.Execute(context.Background(), []string{spelling, "--", operand, "child", "--fmt=yaml"}, Streams{Out: io.Discard, Err: &diagnostics})
|
||||
|
||||
@@ -67,14 +67,14 @@ func TestParserFailuresRetainAliasUsageAndFlagSpellings(t *testing.T) {
|
||||
command := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "inspect",
|
||||
NewInput: func() struct{} { built++; return struct{}{} },
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(*struct{}, Supplied[string]) {})},
|
||||
Run: func(Invocation, struct{}) Outcome { t.Fatal("invalid input executed"); return Success() },
|
||||
})
|
||||
app, err := New(Identity{Name: "Demo"}, command, Alias("i", "inspect"),
|
||||
FlagAliasFor("inspect", "--fmt", "--format"),
|
||||
FlagAliasFor("inspect", "-F", "--format"), FlagAlias("--hlep", "--help"))
|
||||
FlagAliasFor("inspect", "--fmt", "--style"),
|
||||
FlagAliasFor("inspect", "-F", "--style"), FlagAlias("--hlep", "--help"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
||||
usage bool
|
||||
}{
|
||||
{name: "interspersed alias", args: []string{"first", "--fmt=yaml", "second"}},
|
||||
{name: "interspersed canonical flag", args: []string{"first", "--format=yaml", "second"}},
|
||||
{name: "interspersed canonical flag", args: []string{"first", "--style=yaml", "second"}},
|
||||
{name: "partial help", args: []string{"first", "--help"}, help: true},
|
||||
{name: "partial help with flag alias", args: []string{"first", "--fmt=yaml", "--help"}, help: true},
|
||||
{name: "excess operands", args: []string{"first", "second", "third", "--fmt=yaml"}, usage: true},
|
||||
@@ -125,7 +125,7 @@ func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
||||
built, ran := 0, false
|
||||
command := NewCommand(CommandSpec[input]{
|
||||
Name: "run", NewInput: func() input { built++; return input{} },
|
||||
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *input, value Supplied[string]) { target.format = value })},
|
||||
Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 2, 2,
|
||||
@@ -142,7 +142,7 @@ func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
||||
return Success()
|
||||
},
|
||||
})
|
||||
app, err := New(Identity{Name: "Demo"}, command, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--format"))
|
||||
app, err := New(Identity{Name: "Demo"}, command, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--style"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
||||
} else if outcome.Kind != OutcomeSuccess {
|
||||
t.Fatalf("outcome = %#v, diagnostics %q", outcome, diagnostics.String())
|
||||
}
|
||||
if test.help && (!strings.Contains(output.String(), "Demo "+spelling+" ARG") || !strings.Contains(output.String(), "--fmt → --format")) {
|
||||
if test.help && (!strings.Contains(output.String(), "Demo "+spelling+" ARG") || !strings.Contains(output.String(), "--fmt → --style")) {
|
||||
t.Errorf("command help = %q", output.String())
|
||||
}
|
||||
if test.help || test.usage {
|
||||
@@ -229,13 +229,13 @@ func TestDiscoveryHelpComposesGroupShortcutsAndOverrides(t *testing.T) {
|
||||
initialized := false
|
||||
inspect := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "inspect", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})},
|
||||
Run: func(Invocation, struct{}) Outcome { return Success() },
|
||||
})
|
||||
declarations := []Declaration{Group("box", "Box"), inspect, simpleRunnable("alternate"),
|
||||
Alias("pk", "box"), Alias("box admin pick", "inspect"), Alias("box admin other", "inspect"),
|
||||
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--format")}
|
||||
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--style")}
|
||||
wantPick := "pick → inspect"
|
||||
if override {
|
||||
declarations = append(declarations, Alias("pk admin pick", "alternate"), Alias("pk admin nested choose", "inspect"))
|
||||
@@ -308,7 +308,7 @@ func TestHelpDiscoversRoutesThroughIntermediateAliases(t *testing.T) {
|
||||
makeCommand := func(name string) Command {
|
||||
return NewCommand(CommandSpec[struct{}]{
|
||||
Name: name, NewInput: func() struct{} { built++; return struct{}{} },
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})},
|
||||
Run: func(inv Invocation, _ struct{}) Outcome {
|
||||
runs++
|
||||
@@ -327,7 +327,7 @@ func TestHelpDiscoversRoutesThroughIntermediateAliases(t *testing.T) {
|
||||
}
|
||||
declarations := []Declaration{group, makeCommand("inspect"), makeCommand("alternate"),
|
||||
Alias("p", "q"), Alias("q", target), Alias(middle+" pick", "inspect"), Alias(middle+" other", "inspect"),
|
||||
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--format")}
|
||||
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--style")}
|
||||
wantTarget := "inspect"
|
||||
if test.override {
|
||||
declarations = append(declarations, Alias(prefix+" pick", "alternate"))
|
||||
@@ -352,7 +352,7 @@ func TestHelpDiscoversRoutesThroughIntermediateAliases(t *testing.T) {
|
||||
t.Errorf("help %q omits %q", output.String(), fragment)
|
||||
}
|
||||
}
|
||||
if strings.Contains(output.String(), "--fmt") || strings.Contains(output.String(), "--format") ||
|
||||
if strings.Contains(output.String(), "--fmt") || strings.Contains(output.String(), "--style") ||
|
||||
(test.override && strings.Contains(output.String(), "pick → inspect")) {
|
||||
t.Errorf("help exposes a descendant flag or overridden destination: %q", output.String())
|
||||
}
|
||||
@@ -920,7 +920,7 @@ func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
|
||||
inputBuilds++
|
||||
return struct{}{}
|
||||
},
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "style"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(*struct{}, Supplied[string]) {},
|
||||
)},
|
||||
@@ -939,7 +939,7 @@ func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
|
||||
Alias("pk", "promptkit"),
|
||||
Alias("pk list bundles", "promptkit show prompt"),
|
||||
FlagAlias("--hlep", "--help"),
|
||||
FlagAliasFor("promptkit show prompt", "--fmt", "--format"),
|
||||
FlagAliasFor("promptkit show prompt", "--fmt", "--style"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -985,13 +985,13 @@ func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
|
||||
{
|
||||
name: "scoped flag alias appears on canonical command help",
|
||||
args: []string{"promptkit", "show", "prompt", "--hlep"},
|
||||
want: []string{"--fmt → --format"},
|
||||
want: []string{"--fmt → --style"},
|
||||
},
|
||||
{
|
||||
name: "group excludes descendant scoped flag aliases",
|
||||
args: []string{"promptkit", "--help"},
|
||||
want: []string{"Command Aliases:"},
|
||||
omit: []string{"--fmt → --format"},
|
||||
omit: []string{"--fmt → --style"},
|
||||
},
|
||||
{
|
||||
name: "root lists explicit command aliases separately",
|
||||
|
||||
Reference in New Issue
Block a user