147 lines
7.4 KiB
Go
147 lines
7.4 KiB
Go
|
|
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
|
||
|
|
}
|