Align shared debug help and completion
This commit is contained in:
@@ -35,8 +35,7 @@ type completionFlagState struct {
|
||||
func completionFlags(context completionContext) []commandmeta.Flag {
|
||||
flags := make([]commandmeta.Flag, 0, len(context.command.Flags)+len(context.command.CoreFlags)+1)
|
||||
flags = append(flags, commandmeta.HelpFlag())
|
||||
flags = append(flags, context.command.CoreFlags...)
|
||||
flags = append(flags, context.command.Flags...)
|
||||
flags = append(flags, commandmeta.EffectiveFlags(context.command.Flags, context.command.CoreFlags)...)
|
||||
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) {
|
||||
arguments := []commandmeta.Argument{
|
||||
{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())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,11 @@ capture, selection, application and rename, with visibility still independent.
|
||||
|
||||
## Stage 5 — Present one debug option in help and completion
|
||||
|
||||
**Complete.** Completion uses the same effective declarations as parsing and
|
||||
help. Canonical and alias help and usage failures expose the combined option;
|
||||
focused tests cover local shorts, requiredness, application defaults, choices,
|
||||
file and directory hints, boolean validation, and idle application callbacks.
|
||||
|
||||
Primary files: [completion_flags.go](../../completion_flags.go),
|
||||
[internal/cobra/parse.go](../../internal/cobra/parse.go),
|
||||
[internal/commandmeta](../../internal/commandmeta/metadata.go).
|
||||
|
||||
@@ -252,7 +252,7 @@ func displayCommandPath(rootName string, path []string) string {
|
||||
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) {
|
||||
if session == nil || session.root == nil {
|
||||
return "", &commandmeta.Failure{Message: "command tree is nil"}
|
||||
@@ -267,6 +267,22 @@ func (session *Session) UsageLine(path []string) (string, *commandmeta.Failure)
|
||||
if selected.HasSubCommands() {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user