Interpret flags and operands for completion
This commit is contained in:
146
completion_flags_test.go
Normal file
146
completion_flags_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
|
||||
)
|
||||
|
||||
func TestCompletionFlagAndValuePositions(t *testing.T) {
|
||||
parse := func(value string) (string, error) { return value, nil }
|
||||
choice := CompleteChoices(CompletionChoice{Value: "blue"}, CompletionChoice{Value: "green"})
|
||||
command := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "run",
|
||||
Flags: []Flag[struct{}]{
|
||||
ValueFlag(FlagMeta{Name: "color", Short: "c", Completion: choice}, (*string)(nil), parse, func(*struct{}, Supplied[string]) {}),
|
||||
RepeatFlag(FlagMeta{Name: "tag", Short: "t", Completion: choice}, nil, parse, func(*struct{}, Supplied[[]string]) {}),
|
||||
BoolFlag(FlagMeta{Name: "switch", Short: "s"}, false, func(*struct{}, Supplied[bool]) {}),
|
||||
},
|
||||
Run: func(Invocation, struct{}) Outcome { return Success() },
|
||||
})
|
||||
app, err := New(Identity{Name: "example"}, command,
|
||||
FlagAliasFor("run", "--colour", "--color"), FlagAliasFor("run", "-C", "--color"), FlagAlias("-v", "--verbose"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshot := app.routeSnapshot()
|
||||
for _, tc := range []struct {
|
||||
name, current, prefix, value string
|
||||
completed []string
|
||||
kind completionTargetKind
|
||||
hint commandmeta.CompletionKind
|
||||
}{
|
||||
{"pending takes option", "--quiet", "", "--quiet", []string{"run", "--color"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"terminator consumed as value", "-x", "", "-x", []string{"run", "--color", "--", "--tag"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"repeated singleton value", "--color=gr", "--color=", "gr", []string{"run", "--color=blue"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"long alias value", "--colour=bl", "--colour=", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"long empty value", "--color=", "--color=", "", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"short alias value", "-sCbl", "-sC", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"short equals", "-sC=bl", "-sC=", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"reporting boolean", "--verbose=f", "--verbose=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"short boolean alias", "-v=f", "-v=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
|
||||
{"application boolean", "--switch=f", "--switch=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionNone},
|
||||
{"scoped alias unavailable at root", "--colour=bl", "", "", nil, completionTargetNone, commandmeta.CompletionNone},
|
||||
{"unknown completed", "--c", "", "", []string{"run", "--unknown"}, completionTargetNone, commandmeta.CompletionNone},
|
||||
{"unknown attached", "--unknown=x", "", "", []string{"run"}, completionTargetNone, commandmeta.CompletionNone},
|
||||
{"terminator", "--color", "", "", []string{"run", "--"}, completionTargetNone, commandmeta.CompletionNone},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
target := completionTargetFor(snapshot, resolveCompletionContext(snapshot, tc.completed, tc.current))
|
||||
if target.kind != tc.kind || target.hint.Kind != tc.hint || target.prefix != tc.prefix || target.valuePrefix != tc.value {
|
||||
t.Fatalf("target=%#v", target)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
context := resolveCompletionContext(snapshot, []string{"run", "--colour=blue", "--tag=one", "--verbose"}, "--")
|
||||
target := completionTargetFor(snapshot, context)
|
||||
names := completionChoiceNames(target.choices)
|
||||
if containsCompletionName(names, "--color") || !containsCompletionName(names, "--tag") || !containsCompletionName(names, "--verbose") {
|
||||
t.Fatalf("flag suggestions=%q", names)
|
||||
}
|
||||
cluster := completionTargetFor(snapshot, resolveCompletionContext(snapshot, []string{"run"}, "-sv"))
|
||||
if cluster.kind != completionTargetFlags || !containsCompletionName(completionChoiceNames(cluster.choices), "-svc") || containsCompletionName(completionChoiceNames(cluster.choices), "-svs") {
|
||||
t.Fatalf("short cluster=%#v", cluster)
|
||||
}
|
||||
dash := completionTargetFor(snapshot, resolveCompletionContext(snapshot, []string{"run"}, "-"))
|
||||
if !containsCompletionName(completionChoiceNames(dash.choices), "--color") || !containsCompletionName(completionChoiceNames(dash.choices), "-c") {
|
||||
t.Fatalf("dash suggestions=%#v", dash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletionOperandAllocation(t *testing.T) {
|
||||
arguments := []commandmeta.Argument{
|
||||
{Name: "optional", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}},
|
||||
{Name: "repeated", Min: 0, Max: -1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionDirectories}},
|
||||
{Name: "required", Min: 1, Max: 1, Completion: commandmeta.BooleanCompletion()},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
completed int
|
||||
want commandmeta.CompletionKind
|
||||
}{
|
||||
{0, commandmeta.CompletionChoices},
|
||||
{1, commandmeta.CompletionChoices},
|
||||
{2, commandmeta.CompletionChoices},
|
||||
} {
|
||||
hint, ok := completionOperandHint(arguments, tc.completed)
|
||||
if !ok || hint.Kind != tc.want {
|
||||
t.Fatalf("%d operands: %#v, %v", tc.completed, hint, ok)
|
||||
}
|
||||
}
|
||||
bounded := []commandmeta.Argument{{Name: "only", Max: 1, Completion: commandmeta.BooleanCompletion()}}
|
||||
if _, ok := completionOperandHint(bounded, 1); ok {
|
||||
t.Fatal("excess operand received a hint")
|
||||
}
|
||||
trailing := []commandmeta.Argument{{Name: "first", Min: 1, Max: 1, Completion: commandmeta.BooleanCompletion()}, {Name: "rest", Min: 0, Max: -1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionDirectories}}}
|
||||
if hint, ok := completionOperandHint(trailing, 1); !ok || hint.Kind != commandmeta.CompletionDirectories {
|
||||
t.Fatalf("repeated trailing operand: %#v, %v", hint, ok)
|
||||
}
|
||||
if got, want := completionChoiceNames(commandmeta.BooleanCompletion().Choices), []string{"true", "false"}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("boolean choices=%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletionOperandAndFlagContexts(t *testing.T) {
|
||||
command := &commandmeta.Command{
|
||||
Arguments: []commandmeta.Argument{{Name: "item", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}}},
|
||||
Flags: []commandmeta.Flag{{Name: "name", Completion: commandmeta.BooleanCompletion()}},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name, current string
|
||||
tail []string
|
||||
kind completionTargetKind
|
||||
hint commandmeta.CompletionKind
|
||||
}{
|
||||
{"empty chooses operand", "", nil, completionTargetOperand, commandmeta.CompletionFiles},
|
||||
{"dash chooses flag", "--n", nil, completionTargetFlags, commandmeta.CompletionNone},
|
||||
{"after terminator", "--n", []string{"--"}, completionTargetOperand, commandmeta.CompletionFiles},
|
||||
{"interspersed flag value", "value", []string{"--name", "--quiet"}, completionTargetOperand, commandmeta.CompletionFiles},
|
||||
{"excess operand", "value", []string{"already"}, completionTargetNone, commandmeta.CompletionNone},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
target := completionTargetFor(nil, completionContext{command: command, tail: tc.tail, current: tc.current})
|
||||
if target.kind != tc.kind || target.hint.Kind != tc.hint {
|
||||
t.Fatalf("target=%#v", target)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func completionChoiceNames(choices []commandmeta.CompletionChoice) []string {
|
||||
var names []string
|
||||
for _, choice := range choices {
|
||||
names = append(names, choice.Value)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func containsCompletionName(names []string, want string) bool {
|
||||
for _, name := range names {
|
||||
if name == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user