Merge pull request #53 from posener/finally-filter-matches

Filter matches as a final stage
This commit is contained in:
Eyal Posener 2017-11-04 11:52:09 +02:00 committed by GitHub
commit 00c86494ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 57 additions and 84 deletions

View File

@ -1,8 +1,8 @@
language: go language: go
sudo: false sudo: false
go: go:
- 1.9
- 1.8 - 1.8
- tip
before_install: before_install:
- go get -u -t ./... - go get -u -t ./...

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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
}) })
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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(),