Filter matches as a final stage
This simplifies the prediction logic writing, the predictor doesn't need to filter our according to line matching, instead it returns everything and the filtering is done at the end. This does not break current behavior.
This commit is contained in:
parent
88e59760ad
commit
7ee9623f2b
13
command.go
13
command.go
|
@ -1,7 +1,5 @@
|
||||||
package complete
|
package complete
|
||||||
|
|
||||||
import "github.com/posener/complete/match"
|
|
||||||
|
|
||||||
// Command represents a command line
|
// Command represents a command line
|
||||||
// It holds the data that enables auto completion of command line
|
// It holds the data that enables auto completion of command line
|
||||||
// Command can also be a sub command.
|
// Command can also be a sub command.
|
||||||
|
@ -25,9 +23,9 @@ type Command struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Predict returns all possible predictions for args according to the command struct
|
// Predict returns all possible predictions for args according to the command struct
|
||||||
func (c *Command) Predict(a Args) (predictions []string) {
|
func (c *Command) Predict(a Args) []string {
|
||||||
predictions, _ = c.predict(a)
|
options, _ := c.predict(a)
|
||||||
return
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands is the type of Sub member, it maps a command name to a command struct
|
// Commands is the type of Sub member, it maps a command name to a command struct
|
||||||
|
@ -36,10 +34,8 @@ type Commands map[string]Command
|
||||||
// Predict completion of sub command names names according to command line arguments
|
// Predict completion of sub command names names according to command line arguments
|
||||||
func (c Commands) Predict(a Args) (prediction []string) {
|
func (c Commands) Predict(a Args) (prediction []string) {
|
||||||
for sub := range c {
|
for sub := range c {
|
||||||
if match.Prefix(sub, a.Last) {
|
|
||||||
prediction = append(prediction, sub)
|
prediction = append(prediction, sub)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +52,8 @@ func (f Flags) Predict(a Args) (prediction []string) {
|
||||||
if flagHyphenStart && !lastHyphenStart {
|
if flagHyphenStart && !lastHyphenStart {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if match.Prefix(flag, a.Last) {
|
|
||||||
prediction = append(prediction, flag)
|
prediction = append(prediction, flag)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
complete.go
23
complete.go
|
@ -8,10 +8,12 @@ package complete
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/posener/complete/cmd"
|
"github.com/posener/complete/cmd"
|
||||||
|
"github.com/posener/complete/match"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -23,6 +25,7 @@ const (
|
||||||
type Complete struct {
|
type Complete struct {
|
||||||
Command Command
|
Command Command
|
||||||
cmd.CLI
|
cmd.CLI
|
||||||
|
Out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new complete command.
|
// New creates a new complete command.
|
||||||
|
@ -34,6 +37,7 @@ func New(name string, command Command) *Complete {
|
||||||
return &Complete{
|
return &Complete{
|
||||||
Command: command,
|
Command: command,
|
||||||
CLI: cmd.CLI{Name: name},
|
CLI: cmd.CLI{Name: name},
|
||||||
|
Out: os.Stdout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,13 +63,19 @@ func (c *Complete) Complete() bool {
|
||||||
return c.CLI.Run()
|
return c.CLI.Run()
|
||||||
}
|
}
|
||||||
Log("Completing line: %s", line)
|
Log("Completing line: %s", line)
|
||||||
|
|
||||||
a := newArgs(line)
|
a := newArgs(line)
|
||||||
|
|
||||||
options := c.Command.Predict(a)
|
options := c.Command.Predict(a)
|
||||||
|
Log("Options: %s", options)
|
||||||
|
|
||||||
Log("Completion: %s", options)
|
// filter only options that match the last argument
|
||||||
output(options)
|
matches := []string{}
|
||||||
|
for _, option := range options {
|
||||||
|
if match.Prefix(option, a.Last) {
|
||||||
|
matches = append(matches, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log("Matches: %s", matches)
|
||||||
|
c.output(matches)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +87,9 @@ func getLine() ([]string, bool) {
|
||||||
return strings.Split(line, " "), true
|
return strings.Split(line, " "), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func output(options []string) {
|
func (c *Complete) output(options []string) {
|
||||||
Log("")
|
|
||||||
// stdout of program defines the complete options
|
// stdout of program defines the complete options
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
fmt.Println(option)
|
fmt.Fprintln(c.Out, option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package complete
|
package complete
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,6 +36,7 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
"-global1": PredictAnything,
|
"-global1": PredictAnything,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmp := New("cmd", c)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
args string
|
args string
|
||||||
|
@ -175,18 +178,13 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.args, func(t *testing.T) {
|
t.Run(tt.args, func(t *testing.T) {
|
||||||
|
got := runComplete(cmp, tt.args)
|
||||||
tt.args = "cmd " + tt.args
|
|
||||||
os.Setenv(envComplete, tt.args)
|
|
||||||
line, _ := getLine()
|
|
||||||
|
|
||||||
got := c.Predict(newArgs(line))
|
|
||||||
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
|
||||||
if !equalSlices(got, tt.want) {
|
if !equalSlices(got, tt.want) {
|
||||||
t.Errorf("failed '%s'\ngot = %s\nwant: %s", t.Name(), got, tt.want)
|
t.Errorf("failed '%s'\ngot: %s\nwant: %s", t.Name(), got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -222,6 +220,8 @@ func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmp := New("cmd", c)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
args string
|
args string
|
||||||
want []string
|
want []string
|
||||||
|
@ -258,12 +258,7 @@ func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.args, func(t *testing.T) {
|
t.Run(tt.args, func(t *testing.T) {
|
||||||
|
got := runComplete(cmp, tt.args)
|
||||||
tt.args = "cmd " + tt.args
|
|
||||||
os.Setenv(envComplete, tt.args)
|
|
||||||
line, _ := getLine()
|
|
||||||
|
|
||||||
got := c.Predict(newArgs(line))
|
|
||||||
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
@ -275,6 +270,29 @@ func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runComplete runs the complete login for test purposes
|
||||||
|
// it gets the complete struct and command line arguments and returns
|
||||||
|
// the complete options
|
||||||
|
func runComplete(c *Complete, args string) (completions []string) {
|
||||||
|
os.Setenv(envComplete, "cmd "+args)
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
c.Out = b
|
||||||
|
c.Complete()
|
||||||
|
completions = parseOutput(b.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOutput(output string) []string {
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
options := []string{}
|
||||||
|
for _, l := range lines {
|
||||||
|
if l != "" {
|
||||||
|
options = append(options, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
func equalSlices(a, b []string) bool {
|
func equalSlices(a, b []string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/posener/complete/match"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -21,14 +20,8 @@ var (
|
||||||
// for test names use prefix of 'Test' or 'Example', and for benchmark
|
// for test names use prefix of 'Test' or 'Example', and for benchmark
|
||||||
// test names use 'Benchmark'
|
// test names use 'Benchmark'
|
||||||
func funcPredict(funcRegexp *regexp.Regexp) complete.Predictor {
|
func funcPredict(funcRegexp *regexp.Regexp) complete.Predictor {
|
||||||
return complete.PredictFunc(func(a complete.Args) (prediction []string) {
|
return complete.PredictFunc(func(a complete.Args) []string {
|
||||||
tests := funcNames(funcRegexp)
|
return funcNames(funcRegexp)
|
||||||
for _, t := range tests {
|
|
||||||
if match.Prefix(t, a.Last) {
|
|
||||||
prediction = append(prediction, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,38 +22,11 @@ func TestPredictions(t *testing.T) {
|
||||||
predictor: predictTest,
|
predictor: predictTest,
|
||||||
want: []string{"TestPredictions", "Example"},
|
want: []string{"TestPredictions", "Example"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "predict tests not found",
|
|
||||||
predictor: predictTest,
|
|
||||||
last: "X",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "predict benchmark ok",
|
name: "predict benchmark ok",
|
||||||
predictor: predictBenchmark,
|
predictor: predictBenchmark,
|
||||||
want: []string{"BenchmarkFake"},
|
want: []string{"BenchmarkFake"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "predict benchmarks not found",
|
|
||||||
predictor: predictBenchmark,
|
|
||||||
last: "X",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "predict local ok",
|
|
||||||
predictor: complete.PredictFunc(predictPackages),
|
|
||||||
last: ".",
|
|
||||||
want: []string{"./"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "predict system ok",
|
|
||||||
predictor: complete.PredictFunc(predictPackages),
|
|
||||||
last: "github.com/posener/complete/goc",
|
|
||||||
want: []string{"github.com/posener/complete/gocomplete/"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "predict packages not found",
|
|
||||||
predictor: complete.PredictFunc(predictPackages),
|
|
||||||
last: "X",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package complete
|
package complete
|
||||||
|
|
||||||
import "github.com/posener/complete/match"
|
|
||||||
|
|
||||||
// PredictSet expects specific set of terms, given in the options argument.
|
// PredictSet expects specific set of terms, given in the options argument.
|
||||||
func PredictSet(options ...string) Predictor {
|
func PredictSet(options ...string) Predictor {
|
||||||
return predictSet(options)
|
return predictSet(options)
|
||||||
|
@ -9,11 +7,6 @@ func PredictSet(options ...string) Predictor {
|
||||||
|
|
||||||
type predictSet []string
|
type predictSet []string
|
||||||
|
|
||||||
func (p predictSet) Predict(a Args) (prediction []string) {
|
func (p predictSet) Predict(a Args) []string {
|
||||||
for _, m := range p {
|
return p
|
||||||
if match.Prefix(m, a.Last) {
|
|
||||||
prediction = append(prediction, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,6 @@ func TestPredicate(t *testing.T) {
|
||||||
p: PredictSet("a", "b", "c"),
|
p: PredictSet("a", "b", "c"),
|
||||||
want: []string{"a", "b", "c"},
|
want: []string{"a", "b", "c"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "set with does",
|
|
||||||
p: PredictSet("./..", "./x"),
|
|
||||||
argList: []string{"./.", "./.."},
|
|
||||||
want: []string{"./.."},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "set/empty",
|
name: "set/empty",
|
||||||
p: PredictSet(),
|
p: PredictSet(),
|
||||||
|
|
Loading…
Reference in New Issue