Render help through command aliases
This commit is contained in:
1
app.go
1
app.go
@@ -24,6 +24,7 @@ type routingSnapshot struct {
|
||||
runners map[commandmeta.Key]commandPayload
|
||||
aliases *alias.Resolver
|
||||
canonical map[string]*canonicalNode
|
||||
commandAliases []commandmeta.CommandAlias
|
||||
flagAliases map[string]resolvedFlagAliases
|
||||
discoveryFlags map[string]resolvedFlagAliases
|
||||
}
|
||||
|
||||
21
builtins.go
21
builtins.go
@@ -89,11 +89,28 @@ func (a *App) renderHelpTargets(targets []string, streams Streams) Outcome {
|
||||
}
|
||||
|
||||
func (a *App) renderHelpTargetsWithSession(targets []string, streams Streams, session *internalcobra.Session) Outcome {
|
||||
return a.renderHelpOutcomeWithSession(targets, streams, session)
|
||||
snapshot := a.routeSnapshot()
|
||||
route := resolveEnteredRoute(targets, snapshot)
|
||||
if route.kind == routeUnknown {
|
||||
return a.renderHelpOutcomeWithSession(targets, streams, session)
|
||||
}
|
||||
options := commandmeta.ParseOptions{}
|
||||
switch route.kind {
|
||||
case routeCanonical:
|
||||
options.FlagAliases = snapshot.flagAliases[route.canonicalPath.Key()].neutralRecords()
|
||||
case routeDiscovery:
|
||||
options.FlagAliases = snapshot.discoveryFlags[route.discoveryPath.Key()].neutralRecords()
|
||||
}
|
||||
session = internalcobra.Prepare(snapshot.metadata, options)
|
||||
return a.renderHelpViewOutcomeWithSession(a.helpView(route, snapshot), streams, session)
|
||||
}
|
||||
|
||||
func (a *App) renderHelpOutcomeWithSession(path []string, streams Streams, session *internalcobra.Session) Outcome {
|
||||
content, failure := session.RenderHelp(path)
|
||||
return a.renderHelpViewOutcomeWithSession(commandmeta.HelpView{EnteredPath: path, CanonicalPath: path}, streams, session)
|
||||
}
|
||||
|
||||
func (a *App) renderHelpViewOutcomeWithSession(view commandmeta.HelpView, streams Streams, session *internalcobra.Session) Outcome {
|
||||
content, failure := session.RenderHelpView(view)
|
||||
if failure != nil {
|
||||
if failure.Usage {
|
||||
return Usage(errors.New(failure.Message))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Application-owned aliases implementation plan
|
||||
|
||||
Status: in progress; Stages 1–9 completed.
|
||||
Status: in progress; Stages 1–10 completed.
|
||||
|
||||
## Authority, baseline, and scope
|
||||
|
||||
@@ -531,6 +531,15 @@ shortcut help with a more-specific destination override; inactive/scoped flag
|
||||
visibility; no input/state factories or providers called. Preserve existing
|
||||
help output-delivery handling.
|
||||
|
||||
**Completed:** Help targets and `--help` routes now use shared command-alias
|
||||
resolution. Canonical help views render entered usage paths with canonical
|
||||
target annotations; root and group views list applicable declared aliases and
|
||||
effective flag aliases. Alias-only prefixes render discovery help through
|
||||
`help PREFIX` or global help spellings, while bare incomplete prefixes return a
|
||||
usage failure pointing to help. Verified help selection, scoped/global
|
||||
visibility, more-specific group overrides, and input/run callback lifetimes
|
||||
with root and both integration module suites under the local workspace.
|
||||
|
||||
## Stage 11: Preserve entered spellings in errors and delivery outcomes
|
||||
|
||||
**Work**
|
||||
|
||||
31
execute.go
31
execute.go
@@ -28,6 +28,13 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
|
||||
if parsed.Failure != nil {
|
||||
return current.present(Usage(errors.New(parsed.Failure.Message)), parsed.Path)
|
||||
}
|
||||
if current.route.kind == routeDiscovery {
|
||||
if parsed.HelpRequested {
|
||||
return current.present(current.renderHelp(parsed.Path), clonePath(current.route.enteredPath))
|
||||
}
|
||||
entered := current.route.enteredPath.String()
|
||||
return current.present(Usage(fmt.Errorf("incomplete command path %q; use %q --help", entered, entered)), clonePath(current.route.enteredPath))
|
||||
}
|
||||
payload, ok := routedSnapshot.runners[parsed.Key]
|
||||
if !ok || payload == nil {
|
||||
return current.present(current.renderHelp(parsed.Path), parsed.Path)
|
||||
@@ -139,14 +146,34 @@ func (current *execution) renderHelp(path []string) Outcome {
|
||||
if current.app == nil {
|
||||
return Failure(errors.New("cannot render help on a nil app"))
|
||||
}
|
||||
return current.app.renderHelpOutcomeWithSession(path, current.streams, current.presentationSession())
|
||||
session := current.presentationSession()
|
||||
view := current.app.helpView(current.route, current.snapshot)
|
||||
return current.app.renderHelpViewOutcomeWithSession(view, current.streams, session)
|
||||
}
|
||||
|
||||
func (current *execution) renderHelpTargets(targets []string) Outcome {
|
||||
if current.app == nil {
|
||||
return Failure(errors.New("cannot render help on a nil app"))
|
||||
}
|
||||
return current.app.renderHelpTargetsWithSession(targets, current.streams, current.presentationSession())
|
||||
snapshot := current.app.routeSnapshot()
|
||||
if snapshot == nil {
|
||||
return Failure(errors.New("cannot render help without command metadata"))
|
||||
}
|
||||
route := resolveEnteredRoute(targets, snapshot)
|
||||
if route.kind == routeUnknown {
|
||||
session := internalcobra.Prepare(snapshot.metadata, current.options)
|
||||
return current.app.renderHelpOutcomeWithSession(targets, current.streams, session)
|
||||
}
|
||||
options := current.options
|
||||
switch route.kind {
|
||||
case routeCanonical:
|
||||
options.FlagAliases = snapshot.flagAliases[route.canonicalPath.Key()].neutralRecords()
|
||||
case routeDiscovery:
|
||||
options.FlagAliases = snapshot.discoveryFlags[route.discoveryPath.Key()].neutralRecords()
|
||||
}
|
||||
session := internalcobra.Prepare(snapshot.metadata, options)
|
||||
view := current.app.helpView(route, snapshot)
|
||||
return current.app.renderHelpViewOutcomeWithSession(view, current.streams, session)
|
||||
}
|
||||
|
||||
func (current *execution) usageLine(path []string) string {
|
||||
|
||||
98
help_view.go
Normal file
98
help_view.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
|
||||
"gitea.maximumdirect.net/eric/CommandKit/internal/commandpath"
|
||||
)
|
||||
|
||||
func (a *App) helpView(route routeResult, snapshot *routingSnapshot) commandmeta.HelpView {
|
||||
view := commandmeta.HelpView{
|
||||
EnteredPath: clonePath(route.enteredPath),
|
||||
CanonicalPath: clonePath(route.canonicalPath),
|
||||
Discovery: route.kind == routeDiscovery,
|
||||
}
|
||||
if snapshot == nil {
|
||||
return view
|
||||
}
|
||||
if view.Discovery {
|
||||
view.CommandAliases = discoveryHelpAliases(snapshot.commandAliases, route.discoveryPath)
|
||||
view.FlagAliases = snapshot.discoveryFlags[route.discoveryPath.Key()].neutralRecords()
|
||||
return view
|
||||
}
|
||||
view.FlagAliases = snapshot.flagAliases[route.canonicalPath.Key()].neutralRecords()
|
||||
view.CommandAliases = routeHelpAliases(snapshot, route)
|
||||
return view
|
||||
}
|
||||
|
||||
func clonePath(path commandpath.Path) []string {
|
||||
return append([]string(nil), path...)
|
||||
}
|
||||
|
||||
func discoveryHelpAliases(aliases []commandmeta.CommandAlias, prefix commandpath.Path) []commandmeta.CommandAlias {
|
||||
var result []commandmeta.CommandAlias
|
||||
for _, alias := range aliases {
|
||||
source := commandpath.FromTokens(alias.Source)
|
||||
if len(source) <= len(prefix) || !source.HasPrefix(prefix) {
|
||||
continue
|
||||
}
|
||||
result = append(result, commandmeta.CommandAlias{
|
||||
Source: clonePath(source[len(prefix):]),
|
||||
Target: append([]string(nil), alias.Target...),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func routeHelpAliases(snapshot *routingSnapshot, route routeResult) []commandmeta.CommandAlias {
|
||||
if len(route.canonicalPath) == 0 {
|
||||
return append([]commandmeta.CommandAlias(nil), snapshot.commandAliases...)
|
||||
}
|
||||
node := traceCanonicalPath(snapshot.canonical, route.canonicalPath).exact
|
||||
if node == nil || len(node.children) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
entered := route.enteredPath
|
||||
canonical := route.canonicalPath
|
||||
bySource := make(map[string]commandmeta.CommandAlias)
|
||||
add := func(base commandpath.Path, includeOnlyWhenReached bool) {
|
||||
for _, alias := range snapshot.commandAliases {
|
||||
source := commandpath.FromTokens(alias.Source)
|
||||
if len(source) <= len(base) || !source.HasPrefix(base) {
|
||||
continue
|
||||
}
|
||||
suffix := source[len(base):]
|
||||
if includeOnlyWhenReached {
|
||||
enteredSource := append(entered.Clone(), suffix...)
|
||||
if !snapshot.aliases.Resolve(enteredSource).Equal(commandpath.FromTokens(alias.Target)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
key := commandpath.FromTokens(suffix).Key()
|
||||
if _, exists := bySource[key]; exists {
|
||||
continue
|
||||
}
|
||||
bySource[key] = commandmeta.CommandAlias{
|
||||
Source: clonePath(suffix),
|
||||
Target: append([]string(nil), alias.Target...),
|
||||
}
|
||||
}
|
||||
}
|
||||
add(entered, false)
|
||||
if !entered.Equal(canonical) {
|
||||
add(canonical, true)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(bySource))
|
||||
for key := range bySource {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
result := make([]commandmeta.CommandAlias, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
result = append(result, bySource[key])
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -142,6 +142,87 @@ func (session *Session) RenderHelp(path []string) (string, *commandmeta.Failure)
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// RenderHelpView renders canonical command metadata with the selected entered
|
||||
// path and its application-owned aliases.
|
||||
func (session *Session) RenderHelpView(view commandmeta.HelpView) (string, *commandmeta.Failure) {
|
||||
if session == nil || session.root == nil || session.metadata == nil {
|
||||
return "", &commandmeta.Failure{Message: "command tree is nil"}
|
||||
}
|
||||
if view.Discovery {
|
||||
return renderDiscoveryHelp(session.metadata.Name, view), nil
|
||||
}
|
||||
content, failure := session.RenderHelp(view.CanonicalPath)
|
||||
if failure != nil {
|
||||
return "", failure
|
||||
}
|
||||
if !commandpath.FromTokens(view.EnteredPath).Equal(commandpath.FromTokens(view.CanonicalPath)) {
|
||||
if len(view.CanonicalPath) > 0 {
|
||||
entered := displayCommandPath(session.metadata.Name, view.EnteredPath)
|
||||
canonical := displayCommandPath(session.metadata.Name, view.CanonicalPath)
|
||||
lines := strings.Split(content, "\n")
|
||||
adjusted := make([]string, 0, len(lines)+1)
|
||||
annotated := false
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, " "+canonical) {
|
||||
line = strings.Replace(line, canonical, entered, 1)
|
||||
if !annotated {
|
||||
adjusted = append(adjusted, line, "Alias for: "+displayCommandPath(session.metadata.Name, view.CanonicalPath))
|
||||
annotated = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
adjusted = append(adjusted, line)
|
||||
}
|
||||
content = strings.Join(adjusted, "\n")
|
||||
}
|
||||
}
|
||||
return appendHelpAliases(content, view.CommandAliases, view.FlagAliases), nil
|
||||
}
|
||||
|
||||
func appendHelpAliases(content string, commandAliases []commandmeta.CommandAlias, flagAliases []commandmeta.FlagAlias) string {
|
||||
var sections []string
|
||||
if len(commandAliases) > 0 {
|
||||
lines := []string{"Command Aliases:"}
|
||||
for _, alias := range commandAliases {
|
||||
lines = append(lines, " "+strings.Join(alias.Source, " ")+" → "+strings.Join(alias.Target, " "))
|
||||
}
|
||||
sections = append(sections, strings.Join(lines, "\n"))
|
||||
}
|
||||
if len(flagAliases) > 0 {
|
||||
lines := []string{"Flag Aliases:"}
|
||||
for _, alias := range flagAliases {
|
||||
lines = append(lines, " "+alias.Source+" → "+alias.Target)
|
||||
}
|
||||
sections = append(sections, strings.Join(lines, "\n"))
|
||||
}
|
||||
if len(sections) == 0 {
|
||||
return content
|
||||
}
|
||||
return strings.TrimRight(content, "\n") + "\n\n" + strings.Join(sections, "\n\n") + "\n"
|
||||
}
|
||||
|
||||
func renderDiscoveryHelp(rootName string, view commandmeta.HelpView) string {
|
||||
lines := []string{"Usage:", " " + displayCommandPath(rootName, view.EnteredPath) + " [command]"}
|
||||
if len(view.CommandAliases) > 0 {
|
||||
lines = append(lines, "", "Available Aliases:")
|
||||
for _, alias := range view.CommandAliases {
|
||||
lines = append(lines, " "+strings.Join(alias.Source, " ")+" → "+strings.Join(alias.Target, " "))
|
||||
}
|
||||
}
|
||||
if len(view.FlagAliases) > 0 {
|
||||
lines = append(lines, "", "Flag Aliases:")
|
||||
for _, alias := range view.FlagAliases {
|
||||
lines = append(lines, " "+alias.Source+" → "+alias.Target)
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
|
||||
func displayCommandPath(rootName string, path []string) string {
|
||||
parts := append([]string{rootName}, path...)
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// UsageLine returns Cobra's usage line for an exact path on this session.
|
||||
func (session *Session) UsageLine(path []string) (string, *commandmeta.Failure) {
|
||||
if session == nil || session.root == nil {
|
||||
|
||||
@@ -47,6 +47,21 @@ type FlagAlias struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
// CommandAlias describes one explicitly declared command route for help.
|
||||
type CommandAlias struct {
|
||||
Source []string
|
||||
Target []string
|
||||
}
|
||||
|
||||
// HelpView selects a canonical or alias-only command help presentation.
|
||||
type HelpView struct {
|
||||
EnteredPath []string
|
||||
CanonicalPath []string
|
||||
Discovery bool
|
||||
CommandAliases []CommandAlias
|
||||
FlagAliases []FlagAlias
|
||||
}
|
||||
|
||||
// Failure is a neutral parsing or presentation failure.
|
||||
type Failure struct {
|
||||
Message string
|
||||
|
||||
13
parser.go
13
parser.go
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra"
|
||||
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
|
||||
"gitea.maximumdirect.net/eric/CommandKit/internal/commandpath"
|
||||
)
|
||||
|
||||
func (a *App) parse(args []string, options commandmeta.ParseOptions) commandmeta.Result {
|
||||
@@ -34,6 +35,18 @@ func (a *App) buildRoutingSnapshot() *routingSnapshot {
|
||||
flagAliases: a.flagAliases,
|
||||
discoveryFlags: a.discoveryFlags,
|
||||
}
|
||||
for _, rule := range a.routingRules {
|
||||
if rule.record.kind != commandAliasRule {
|
||||
continue
|
||||
}
|
||||
snapshot.commandAliases = append(snapshot.commandAliases, commandmeta.CommandAlias{
|
||||
Source: []string(rule.record.sourcePath.Clone()),
|
||||
Target: []string(snapshot.aliases.Resolve(rule.record.targetPath)),
|
||||
})
|
||||
}
|
||||
sort.Slice(snapshot.commandAliases, func(i, j int) bool {
|
||||
return commandpath.FromTokens(snapshot.commandAliases[i].Source).Key() < commandpath.FromTokens(snapshot.commandAliases[j].Source).Key()
|
||||
})
|
||||
names := make([]string, 0, len(a.commands))
|
||||
for name := range a.commands {
|
||||
names = append(names, name)
|
||||
|
||||
11
route.go
11
route.go
@@ -46,17 +46,20 @@ func resolveEnteredRoute(args []string, snapshot *routingSnapshot) routeResult {
|
||||
result.enteredPath = enteredEndpoint(path, origins, len(result.canonicalPath))
|
||||
} else if len(path) == 0 {
|
||||
result.kind = routeCanonical
|
||||
} else if discovery, ok := findDiscoveryPath(snapshot.discoveryFlags, path); ok {
|
||||
} else if discovery, ok := findDiscoveryPath(snapshot.discoveryFlags, path); ok && len(discovery) == len(path) {
|
||||
result.kind = routeDiscovery
|
||||
result.discoveryPath = discovery
|
||||
result.enteredPath = discovery.Clone()
|
||||
result.args = commandpath.CloneTokens(trailing)
|
||||
} else {
|
||||
result.enteredPath = path.Clone()
|
||||
}
|
||||
|
||||
result.args = make([]string, 0, len(resolved)+len(trailing))
|
||||
result.args = append(result.args, resolved...)
|
||||
result.args = append(result.args, trailing...)
|
||||
if result.kind != routeDiscovery {
|
||||
result.args = make([]string, 0, len(resolved)+len(trailing))
|
||||
result.args = append(result.args, resolved...)
|
||||
result.args = append(result.args, trailing...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
120
route_test.go
120
route_test.go
@@ -347,7 +347,123 @@ func TestAliasOnlyPrefixProducesDiscoveryRoute(t *testing.T) {
|
||||
if got, want := result.enteredPath.String(), "list"; got != want {
|
||||
t.Fatalf("entered path = %q, want %q", got, want)
|
||||
}
|
||||
if !reflect.DeepEqual(result.args, []string{"list", "--help"}) {
|
||||
t.Fatalf("discovery args = %q, want [list --help]", result.args)
|
||||
if !reflect.DeepEqual(result.args, []string{"--help"}) {
|
||||
t.Fatalf("discovery args = %q, want [--help]", result.args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
|
||||
var inputBuilds, promptRuns int
|
||||
showPrompt := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "prompt",
|
||||
NewInput: func() struct{} {
|
||||
inputBuilds++
|
||||
return struct{}{}
|
||||
},
|
||||
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(*struct{}, Supplied[string]) {},
|
||||
)},
|
||||
Run: func(Invocation, struct{}) Outcome {
|
||||
promptRuns++
|
||||
return Success()
|
||||
},
|
||||
})
|
||||
commands := Group("promptkit", "PromptKit",
|
||||
Group("list", "List", simpleRunnable("collections")),
|
||||
Group("show", "Show", showPrompt),
|
||||
)
|
||||
app, err := New(Identity{Name: "example"}, commands,
|
||||
Alias("list collections", "promptkit list collections"),
|
||||
Alias("promptkit list bundles", "promptkit list collections"),
|
||||
Alias("pk", "promptkit"),
|
||||
Alias("pk list bundles", "promptkit show prompt"),
|
||||
FlagAlias("--hlep", "--help"),
|
||||
FlagAliasFor("promptkit show prompt", "--fmt", "--format"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
omit []string
|
||||
}{
|
||||
{
|
||||
name: "explicit command help resolves entered target",
|
||||
args: []string{"help", "list", "collections"},
|
||||
want: []string{"Usage:\n example list collections", "Alias for: example promptkit list collections"},
|
||||
},
|
||||
{
|
||||
name: "aliased help flag uses entered command path",
|
||||
args: []string{"list", "collections", "--hlep"},
|
||||
want: []string{"Usage:\n example list collections", "Alias for: example promptkit list collections", "--hlep → --help"},
|
||||
},
|
||||
{
|
||||
name: "canonical group shows direct aliases",
|
||||
args: []string{"help", "promptkit", "list"},
|
||||
want: []string{"Command Aliases:", "bundles → promptkit list collections"},
|
||||
},
|
||||
{
|
||||
name: "group shortcut shows more-specific override",
|
||||
args: []string{"pk", "--help"},
|
||||
want: []string{"Usage:\n example pk [flags]", "Alias for: example promptkit", "list bundles → promptkit show prompt"},
|
||||
omit: []string{"list bundles → promptkit list collections"},
|
||||
},
|
||||
{
|
||||
name: "explicit discovery target",
|
||||
args: []string{"help", "list"},
|
||||
want: []string{"Usage:\n example list [command]", "Available Aliases:", "collections → promptkit list collections", "--hlep → --help"},
|
||||
omit: []string{"--fmt"},
|
||||
},
|
||||
{
|
||||
name: "discovery help flag",
|
||||
args: []string{"list", "--hlep"},
|
||||
want: []string{"Usage:\n example list [command]", "collections → promptkit list collections"},
|
||||
},
|
||||
{
|
||||
name: "scoped flag alias appears on canonical command help",
|
||||
args: []string{"promptkit", "show", "prompt", "--hlep"},
|
||||
want: []string{"--fmt → --format"},
|
||||
},
|
||||
{
|
||||
name: "group excludes descendant scoped flag aliases",
|
||||
args: []string{"promptkit", "--help"},
|
||||
want: []string{"Command Aliases:"},
|
||||
omit: []string{"--fmt → --format"},
|
||||
},
|
||||
{
|
||||
name: "root lists explicit command aliases separately",
|
||||
args: []string{"--help"},
|
||||
want: []string{"Available Commands:", "Command Aliases:", "list collections → promptkit list collections", "pk → promptkit"},
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), test.args, Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != OutcomeSuccess {
|
||||
t.Fatalf("Execute(%q) = %#v; output %q", test.args, outcome, output.String())
|
||||
}
|
||||
for _, fragment := range test.want {
|
||||
if !strings.Contains(output.String(), fragment) {
|
||||
t.Errorf("help output %q does not contain %q", output.String(), fragment)
|
||||
}
|
||||
}
|
||||
for _, fragment := range test.omit {
|
||||
if strings.Contains(output.String(), fragment) {
|
||||
t.Errorf("help output %q unexpectedly contains %q", output.String(), fragment)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var diagnostics bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"list"}, Streams{Out: io.Discard, Err: &diagnostics})
|
||||
if outcome.Kind != OutcomeUsage || !strings.Contains(outcome.Cause.Error(), "--help") {
|
||||
t.Fatalf("bare discovery prefix = %#v, diagnostics %q; want usage pointing to list --help", outcome, diagnostics.String())
|
||||
}
|
||||
if inputBuilds != 0 || promptRuns != 0 {
|
||||
t.Fatalf("help built %d inputs and ran %d commands", inputBuilds, promptRuns)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user