Merge pull request #12 from posener/improves
Enhance program structure and data structures
This commit is contained in:
commit
d3bbb859d5
|
@ -0,0 +1,39 @@
|
||||||
|
package complete
|
||||||
|
|
||||||
|
// Args describes command line arguments
|
||||||
|
type Args struct {
|
||||||
|
All []string
|
||||||
|
Completed []string
|
||||||
|
Last string
|
||||||
|
LastCompleted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArgs(line []string) Args {
|
||||||
|
completed := removeLast(line)
|
||||||
|
return Args{
|
||||||
|
All: line[1:],
|
||||||
|
Completed: completed,
|
||||||
|
Last: last(line),
|
||||||
|
LastCompleted: last(completed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Args) from(i int) Args {
|
||||||
|
a.All = a.All[i:]
|
||||||
|
a.Completed = a.Completed[i:]
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeLast(a []string) []string {
|
||||||
|
if len(a) > 0 {
|
||||||
|
return a[:len(a)-1]
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func last(args []string) (last string) {
|
||||||
|
if len(args) > 0 {
|
||||||
|
last = args[len(args)-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
77
command.go
77
command.go
|
@ -3,7 +3,7 @@ package complete
|
||||||
import "github.com/posener/complete/match"
|
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 a given typed 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.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
// Sub is map of sub commands of the current command
|
// Sub is map of sub commands of the current command
|
||||||
|
@ -12,88 +12,79 @@ type Command struct {
|
||||||
Sub Commands
|
Sub Commands
|
||||||
|
|
||||||
// Flags is a map of flags that the command accepts.
|
// Flags is a map of flags that the command accepts.
|
||||||
// The key is the flag name, and the value is it's prediction options.
|
// The key is the flag name, and the value is it's predictions.
|
||||||
Flags Flags
|
Flags Flags
|
||||||
|
|
||||||
// Args are extra arguments that the command accepts, those who are
|
// Args are extra arguments that the command accepts, those who are
|
||||||
// given without any flag before.
|
// given without any flag before.
|
||||||
Args Predicate
|
Args Predictor
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
type Commands map[string]Command
|
type Commands map[string]Command
|
||||||
|
|
||||||
// Flags is the type Flags of the Flags member, it maps a flag name to the flag
|
// Flags is the type Flags of the Flags member, it maps a flag name to the flag predictions.
|
||||||
// prediction options.
|
type Flags map[string]Predictor
|
||||||
type Flags map[string]Predicate
|
|
||||||
|
|
||||||
// options returns all available complete options for the given command
|
// Predict returns all possible predictions for args according to the command struct
|
||||||
// args are all except the last command line arguments relevant to the command
|
func (c *Command) Predict(a Args) (predictions []string) {
|
||||||
func (c *Command) options(args []string) (options []match.Matcher, only bool) {
|
predictions, _ = c.predict(a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) predict(a Args) (options []string, only bool) {
|
||||||
|
|
||||||
// remove the first argument, which is the command name
|
|
||||||
args = args[1:]
|
|
||||||
wordCurrent := last(args)
|
|
||||||
wordCompleted := last(removeLast(args))
|
|
||||||
// if wordCompleted has something that needs to follow it,
|
// if wordCompleted has something that needs to follow it,
|
||||||
// it is the most relevant completion
|
// it is the most relevant completion
|
||||||
if predicate, ok := c.Flags[wordCompleted]; ok && predicate != nil {
|
if predictor, ok := c.Flags[a.LastCompleted]; ok && predictor != nil {
|
||||||
Log("Predicting according to flag %s", wordCurrent)
|
Log("Predicting according to flag %s", a.Last)
|
||||||
return predicate.predict(wordCurrent), true
|
return predictor.Predict(a), true
|
||||||
}
|
}
|
||||||
|
|
||||||
sub, options, only := c.searchSub(args)
|
sub, options, only := c.searchSub(a)
|
||||||
if only {
|
if only {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no subcommand was entered in any of the args, add the
|
// if no sub command was found, return a list of the sub commands
|
||||||
// subcommands as complete options.
|
|
||||||
if sub == "" {
|
if sub == "" {
|
||||||
options = append(options, c.subCommands()...)
|
options = append(options, c.subCommands(a.Last)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add global available complete options
|
// add global available complete options
|
||||||
for flag := range c.Flags {
|
for flag := range c.Flags {
|
||||||
options = append(options, match.Prefix(flag))
|
if match.Prefix(flag, a.Last) {
|
||||||
|
options = append(options, flag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add additional expected argument of the command
|
// add additional expected argument of the command
|
||||||
options = append(options, c.Args.predict(wordCurrent)...)
|
if c.Args != nil {
|
||||||
|
options = append(options, c.Args.Predict(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchSub searches recursively within sub commands if the sub command appear
|
// searchSub searches recursively within sub commands if the sub command appear
|
||||||
// in the on of the arguments.
|
// in the on of the arguments.
|
||||||
func (c *Command) searchSub(args []string) (sub string, all []match.Matcher, only bool) {
|
func (c *Command) searchSub(a Args) (sub string, all []string, only bool) {
|
||||||
|
for i, arg := range a.Completed {
|
||||||
// search for sub command in all arguments except the last one
|
|
||||||
// because that one might not be completed yet
|
|
||||||
searchArgs := removeLast(args)
|
|
||||||
|
|
||||||
for i, arg := range searchArgs {
|
|
||||||
if cmd, ok := c.Sub[arg]; ok {
|
if cmd, ok := c.Sub[arg]; ok {
|
||||||
sub = arg
|
sub = arg
|
||||||
all, only = cmd.options(args[i:])
|
all, only = cmd.predict(a.from(i))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// suvCommands returns a list of matchers according to the sub command names
|
// subCommands returns a list of matching sub commands
|
||||||
func (c *Command) subCommands() []match.Matcher {
|
func (c *Command) subCommands(last string) (prediction []string) {
|
||||||
subs := make([]match.Matcher, 0, len(c.Sub))
|
|
||||||
for sub := range c.Sub {
|
for sub := range c.Sub {
|
||||||
subs = append(subs, match.Prefix(sub))
|
if match.Prefix(sub, last) {
|
||||||
|
prediction = append(prediction, sub)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return subs
|
return
|
||||||
}
|
|
||||||
|
|
||||||
func removeLast(a []string) []string {
|
|
||||||
if len(a) > 0 {
|
|
||||||
return a[:len(a)-1]
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
31
complete.go
31
complete.go
|
@ -41,37 +41,23 @@ func New(name string, command Command) *Complete {
|
||||||
// returns success if the completion ran or if the cli matched
|
// returns success if the completion ran or if the cli matched
|
||||||
// any of the given flags, false otherwise
|
// any of the given flags, false otherwise
|
||||||
func (c *Complete) Run() bool {
|
func (c *Complete) Run() bool {
|
||||||
args, ok := getLine()
|
line, ok := getLine()
|
||||||
if !ok {
|
if !ok {
|
||||||
// make sure flags parsed,
|
// make sure flags parsed,
|
||||||
// in case they were not added in the main program
|
// in case they were not added in the main program
|
||||||
return c.CLI.Run()
|
return c.CLI.Run()
|
||||||
}
|
}
|
||||||
Log("Completing args: %s", args)
|
Log("Completing line: %s", line)
|
||||||
|
|
||||||
options := complete(c.Command, args)
|
a := newArgs(line)
|
||||||
|
|
||||||
|
options := c.Command.Predict(a)
|
||||||
|
|
||||||
Log("Completion: %s", options)
|
Log("Completion: %s", options)
|
||||||
output(options)
|
output(options)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete get a command an command line arguments and returns
|
|
||||||
// matching completion options
|
|
||||||
func complete(c Command, args []string) (matching []string) {
|
|
||||||
options, _ := c.options(args)
|
|
||||||
|
|
||||||
// choose only matching options
|
|
||||||
l := last(args)
|
|
||||||
for _, option := range options {
|
|
||||||
Log("option %T, %s -> %t", option, option, option.Match(l))
|
|
||||||
if option.Match(l) {
|
|
||||||
matching = append(matching, option.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLine() ([]string, bool) {
|
func getLine() ([]string, bool) {
|
||||||
line := os.Getenv(envComplete)
|
line := os.Getenv(envComplete)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
|
@ -80,13 +66,6 @@ func getLine() ([]string, bool) {
|
||||||
return strings.Split(line, " "), true
|
return strings.Split(line, " "), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func last(args []string) (last string) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
last = args[len(args)-1]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func output(options []string) {
|
func output(options []string) {
|
||||||
Log("")
|
Log("")
|
||||||
// stdout of program defines the complete options
|
// stdout of program defines the complete options
|
||||||
|
|
|
@ -13,20 +13,20 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
c := Command{
|
c := Command{
|
||||||
Sub: map[string]Command{
|
Sub: map[string]Command{
|
||||||
"sub1": {
|
"sub1": {
|
||||||
Flags: map[string]Predicate{
|
Flags: map[string]Predictor{
|
||||||
"-flag1": PredictAnything,
|
"-flag1": PredictAnything,
|
||||||
"-flag2": PredictNothing,
|
"-flag2": PredictNothing,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sub2": {
|
"sub2": {
|
||||||
Flags: map[string]Predicate{
|
Flags: map[string]Predictor{
|
||||||
"-flag2": PredictNothing,
|
"-flag2": PredictNothing,
|
||||||
"-flag3": PredictSet("opt1", "opt2", "opt12"),
|
"-flag3": PredictSet("opt1", "opt2", "opt12"),
|
||||||
},
|
},
|
||||||
Args: Predicate(PredictDirs("*")).Or(PredictFiles("*.md")),
|
Args: PredictOr(PredictDirs("*"), PredictFiles("*.md")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Flags: map[string]Predicate{
|
Flags: map[string]Predictor{
|
||||||
"-h": PredictNothing,
|
"-h": PredictNothing,
|
||||||
"-global1": PredictAnything,
|
"-global1": PredictAnything,
|
||||||
"-o": PredictFiles("*.txt"),
|
"-o": PredictFiles("*.txt"),
|
||||||
|
@ -174,9 +174,9 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
|
|
||||||
tt.args = "cmd " + tt.args
|
tt.args = "cmd " + tt.args
|
||||||
os.Setenv(envComplete, tt.args)
|
os.Setenv(envComplete, tt.args)
|
||||||
args, _ := getLine()
|
line, _ := getLine()
|
||||||
|
|
||||||
got := complete(c, args)
|
got := c.Predict(newArgs(line))
|
||||||
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
|
|
@ -6,9 +6,11 @@ import "github.com/posener/complete"
|
||||||
var (
|
var (
|
||||||
predictEllipsis = complete.PredictSet("./...")
|
predictEllipsis = complete.PredictSet("./...")
|
||||||
|
|
||||||
goFilesOrPackages = complete.PredictFiles("*.go").
|
goFilesOrPackages = complete.PredictOr(
|
||||||
Or(complete.PredictDirs("*")).
|
complete.PredictFiles("*.go"),
|
||||||
Or(predictEllipsis)
|
complete.PredictDirs("*"),
|
||||||
|
predictEllipsis,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -17,15 +17,16 @@ import (
|
||||||
// and then all the relevant function names.
|
// and then all the relevant function names.
|
||||||
// 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 predictTest(funcPrefix ...string) complete.Predicate {
|
func predictTest(funcPrefix ...string) complete.Predictor {
|
||||||
return func(last string) []match.Matcher {
|
return complete.PredictFunc(func(a complete.Args) (prediction []string) {
|
||||||
tests := testNames(funcPrefix)
|
tests := testNames(funcPrefix)
|
||||||
options := make([]match.Matcher, len(tests))
|
for _, t := range tests {
|
||||||
for i := range tests {
|
if match.Prefix(t, a.Last) {
|
||||||
options[i] = match.Prefix(tests[i])
|
prediction = append(prediction, t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return options
|
return
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all test names in current directory
|
// get all test names in current directory
|
||||||
|
|
|
@ -1,26 +1,16 @@
|
||||||
package match
|
package match
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File is a file name Matcher, if the last word can prefix the
|
// File returns true if prefix can match the file
|
||||||
// File path, there is a possible match
|
func File(file, prefix string) bool {
|
||||||
type File string
|
|
||||||
|
|
||||||
func (a File) String() string {
|
|
||||||
return string(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match returns true if prefix's abs path prefixes a's abs path
|
|
||||||
func (a File) Match(prefix string) bool {
|
|
||||||
|
|
||||||
// special case for current directory completion
|
// special case for current directory completion
|
||||||
if a == "./" && (prefix == "." || prefix == "") {
|
if file == "./" && (prefix == "." || prefix == "") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
cmp := strings.TrimPrefix(string(a), "./")
|
file = strings.TrimPrefix(file, "./")
|
||||||
prefix = strings.TrimPrefix(prefix, "./")
|
prefix = strings.TrimPrefix(prefix, "./")
|
||||||
return strings.HasPrefix(cmp, prefix)
|
return strings.HasPrefix(file, prefix)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
package match
|
package match
|
||||||
|
|
||||||
import "fmt"
|
// Match matches two strings
|
||||||
|
// it is used for comparing a term to the last typed
|
||||||
// Matcher matches itself to a string
|
// word, the prefix, and see if it is a possible auto complete option.
|
||||||
// it is used for comparing a given argument to the last typed
|
type Match func(term, prefix string) bool
|
||||||
// word, and see if it is a possible auto complete option.
|
|
||||||
type Matcher interface {
|
|
||||||
fmt.Stringer
|
|
||||||
Match(prefix string) bool
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package match
|
package match
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -21,11 +22,13 @@ func TestMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
m Matcher
|
m Match
|
||||||
|
long string
|
||||||
tests []matcherTest
|
tests []matcherTest
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
m: Prefix("abcd"),
|
m: Prefix,
|
||||||
|
long: "abcd",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
{prefix: "ab", want: true},
|
{prefix: "ab", want: true},
|
||||||
|
@ -33,14 +36,16 @@ func TestMatch(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: Prefix(""),
|
m: Prefix,
|
||||||
|
long: "",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "ac", want: false},
|
{prefix: "ac", want: false},
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: File("file.txt"),
|
m: File,
|
||||||
|
long: "file.txt",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
{prefix: "f", want: true},
|
{prefix: "f", want: true},
|
||||||
|
@ -59,7 +64,8 @@ func TestMatch(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: File("./file.txt"),
|
m: File,
|
||||||
|
long: "./file.txt",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
{prefix: "f", want: true},
|
{prefix: "f", want: true},
|
||||||
|
@ -78,7 +84,8 @@ func TestMatch(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: File("/file.txt"),
|
m: File,
|
||||||
|
long: "/file.txt",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
{prefix: "f", want: false},
|
{prefix: "f", want: false},
|
||||||
|
@ -97,7 +104,8 @@ func TestMatch(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: File("./"),
|
m: File,
|
||||||
|
long: "./",
|
||||||
tests: []matcherTest{
|
tests: []matcherTest{
|
||||||
{prefix: "", want: true},
|
{prefix: "", want: true},
|
||||||
{prefix: ".", want: true},
|
{prefix: ".", want: true},
|
||||||
|
@ -109,9 +117,9 @@ func TestMatch(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
for _, ttt := range tt.tests {
|
for _, ttt := range tt.tests {
|
||||||
name := "matcher='" + tt.m.String() + "'&prefix='" + ttt.prefix + "'"
|
name := fmt.Sprintf("matcher=%T&long='%s'&prefix='%s'", tt.m, tt.long, ttt.prefix)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
got := tt.m.Match(ttt.prefix)
|
got := tt.m(tt.long, ttt.prefix)
|
||||||
if got != ttt.want {
|
if got != ttt.want {
|
||||||
t.Errorf("Failed %s: got = %t, want: %t", name, got, ttt.want)
|
t.Errorf("Failed %s: got = %t, want: %t", name, got, ttt.want)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,7 @@ package match
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// Prefix is a simple Matcher, if the word is it's prefix, there is a match
|
// Prefix is a simple Matcher, if the word is it's prefix, there is a match
|
||||||
type Prefix string
|
|
||||||
|
|
||||||
func (a Prefix) String() string {
|
|
||||||
return string(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match returns true if a has the prefix as prefix
|
// Match returns true if a has the prefix as prefix
|
||||||
func (a Prefix) Match(prefix string) bool {
|
func Prefix(long, prefix string) bool {
|
||||||
return strings.HasPrefix(string(a), prefix)
|
return strings.HasPrefix(long, prefix)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,52 +7,66 @@ import (
|
||||||
"github.com/posener/complete/match"
|
"github.com/posener/complete/match"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Predicate determines what terms can follow a command or a flag
|
// Predictor implements a predict method, in which given
|
||||||
// It is used for auto completion, given last - the last word in the already
|
// command line arguments returns a list of options it predicts.
|
||||||
// in the command line, what words can complete it.
|
type Predictor interface {
|
||||||
type Predicate func(last string) []match.Matcher
|
Predict(Args) []string
|
||||||
|
|
||||||
// Or unions two predicate functions, so that the result predicate
|
|
||||||
// returns the union of their predication
|
|
||||||
func (p Predicate) Or(other Predicate) Predicate {
|
|
||||||
if p == nil {
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
if other == nil {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return func(last string) []match.Matcher { return append(p.predict(last), other.predict(last)...) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Predicate) predict(last string) []match.Matcher {
|
// PredictOr unions two predicate functions, so that the result predicate
|
||||||
|
// returns the union of their predication
|
||||||
|
func PredictOr(predictors ...Predictor) Predictor {
|
||||||
|
return PredictFunc(func(a Args) (prediction []string) {
|
||||||
|
for _, p := range predictors {
|
||||||
|
if p == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prediction = append(prediction, p.Predict(a)...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PredictFunc determines what terms can follow a command or a flag
|
||||||
|
// It is used for auto completion, given last - the last word in the already
|
||||||
|
// in the command line, what words can complete it.
|
||||||
|
type PredictFunc func(Args) []string
|
||||||
|
|
||||||
|
// Predict invokes the predict function and implements the Predictor interface
|
||||||
|
func (p PredictFunc) Predict(a Args) []string {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p(last)
|
return p(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PredictNothing does not expect anything after.
|
// PredictNothing does not expect anything after.
|
||||||
var PredictNothing Predicate
|
var PredictNothing Predictor
|
||||||
|
|
||||||
// PredictAnything expects something, but nothing particular, such as a number
|
// PredictAnything expects something, but nothing particular, such as a number
|
||||||
// or arbitrary name.
|
// or arbitrary name.
|
||||||
func PredictAnything(last string) []match.Matcher { return nil }
|
var PredictAnything = PredictFunc(func(Args) []string { return nil })
|
||||||
|
|
||||||
// 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) Predicate {
|
func PredictSet(options ...string) Predictor {
|
||||||
return func(last string) []match.Matcher {
|
return predictSet(options)
|
||||||
ret := make([]match.Matcher, len(options))
|
}
|
||||||
for i := range options {
|
|
||||||
ret[i] = match.Prefix(options[i])
|
type predictSet []string
|
||||||
|
|
||||||
|
func (p predictSet) Predict(a Args) (prediction []string) {
|
||||||
|
for _, m := range p {
|
||||||
|
if match.Prefix(m, a.Last) {
|
||||||
|
prediction = append(prediction, m)
|
||||||
}
|
}
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PredictDirs will search for directories in the given started to be typed
|
// PredictDirs will search for directories in the given started to be typed
|
||||||
// path, if no path was started to be typed, it will complete to directories
|
// path, if no path was started to be typed, it will complete to directories
|
||||||
// in the current working directory.
|
// in the current working directory.
|
||||||
func PredictDirs(pattern string) Predicate {
|
func PredictDirs(pattern string) Predictor {
|
||||||
return files(pattern, true, false)
|
return files(pattern, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,19 +74,19 @@ func PredictDirs(pattern string) Predicate {
|
||||||
// be typed path, if no path was started to be typed, it will complete to files that
|
// be typed path, if no path was started to be typed, it will complete to files that
|
||||||
// match the pattern in the current working directory.
|
// match the pattern in the current working directory.
|
||||||
// To match any file, use "*" as pattern. To match go files use "*.go", and so on.
|
// To match any file, use "*" as pattern. To match go files use "*.go", and so on.
|
||||||
func PredictFiles(pattern string) Predicate {
|
func PredictFiles(pattern string) Predictor {
|
||||||
return files(pattern, false, true)
|
return files(pattern, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PredictFilesOrDirs predict any file or directory that matches the pattern
|
// PredictFilesOrDirs any file or directory that matches the pattern
|
||||||
func PredictFilesOrDirs(pattern string) Predicate {
|
func PredictFilesOrDirs(pattern string) Predictor {
|
||||||
return files(pattern, true, true)
|
return files(pattern, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func files(pattern string, allowDirs, allowFiles bool) Predicate {
|
func files(pattern string, allowDirs, allowFiles bool) PredictFunc {
|
||||||
return func(last string) []match.Matcher {
|
return func(a Args) (prediction []string) {
|
||||||
dir := dirFromLast(last)
|
dir := dirFromLast(a.Last)
|
||||||
Log("looking for files in %s (last=%s)", dir, last)
|
Log("looking for files in %s (last=%s)", dir, a.Last)
|
||||||
files, err := filepath.Glob(filepath.Join(dir, pattern))
|
files, err := filepath.Glob(filepath.Join(dir, pattern))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log("failed glob operation with pattern '%s': %s", pattern, err)
|
Log("failed glob operation with pattern '%s': %s", pattern, err)
|
||||||
|
@ -84,7 +98,13 @@ func files(pattern string, allowDirs, allowFiles bool) Predicate {
|
||||||
if !filepath.IsAbs(pattern) {
|
if !filepath.IsAbs(pattern) {
|
||||||
filesToRel(files)
|
filesToRel(files)
|
||||||
}
|
}
|
||||||
return filesToMatchers(files)
|
// add all matching files to prediction
|
||||||
|
for _, f := range files {
|
||||||
|
if match.File(f, a.Last) {
|
||||||
|
prediction = append(prediction, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,14 +150,6 @@ func filesToRel(files []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func filesToMatchers(files []string) []match.Matcher {
|
|
||||||
options := make([]match.Matcher, len(files))
|
|
||||||
for i, f := range files {
|
|
||||||
options[i] = match.File(f)
|
|
||||||
}
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirFromLast gives the directory of the current written
|
// dirFromLast gives the directory of the current written
|
||||||
// last argument if it represents a file name being written.
|
// last argument if it represents a file name being written.
|
||||||
// in case that it is not, we fall back to the current directory.
|
// in case that it is not, we fall back to the current directory.
|
|
@ -12,7 +12,7 @@ func TestPredicate(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
p Predicate
|
p Predictor
|
||||||
arg string
|
arg string
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
|
@ -37,29 +37,24 @@ func TestPredicate(t *testing.T) {
|
||||||
p: PredictAnything,
|
p: PredictAnything,
|
||||||
want: []string{},
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "nothing",
|
|
||||||
p: PredictNothing,
|
|
||||||
want: []string{},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "or: word with nil",
|
name: "or: word with nil",
|
||||||
p: PredictSet("a").Or(PredictNothing),
|
p: PredictOr(PredictSet("a"), nil),
|
||||||
want: []string{"a"},
|
want: []string{"a"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "or: nil with word",
|
name: "or: nil with word",
|
||||||
p: PredictNothing.Or(PredictSet("a")),
|
p: PredictOr(nil, PredictSet("a")),
|
||||||
want: []string{"a"},
|
want: []string{"a"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "or: nil with nil",
|
name: "or: nil with nil",
|
||||||
p: PredictNothing.Or(PredictNothing),
|
p: PredictOr(PredictNothing, PredictNothing),
|
||||||
want: []string{},
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "or: word with word with word",
|
name: "or: word with word with word",
|
||||||
p: PredictSet("a").Or(PredictSet("b")).Or(PredictSet("c")),
|
p: PredictOr(PredictSet("a"), PredictSet("b"), PredictSet("c")),
|
||||||
want: []string{"a", "b", "c"},
|
want: []string{"a", "b", "c"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -118,18 +113,12 @@ func TestPredicate(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name+"?arg='"+tt.arg+"'", func(t *testing.T) {
|
t.Run(tt.name+"?arg='"+tt.arg+"'", func(t *testing.T) {
|
||||||
|
|
||||||
matchers := tt.p.predict(tt.arg)
|
matches := tt.p.Predict(newArgs(strings.Split(tt.arg, " ")))
|
||||||
|
|
||||||
matchersString := []string{}
|
sort.Strings(matches)
|
||||||
for _, m := range matchers {
|
|
||||||
if m.Match(tt.arg) {
|
|
||||||
matchersString = append(matchersString, m.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(matchersString)
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
|
|
||||||
got := strings.Join(matchersString, ",")
|
got := strings.Join(matches, ",")
|
||||||
want := strings.Join(tt.want, ",")
|
want := strings.Join(tt.want, ",")
|
||||||
|
|
||||||
if got != want {
|
if got != want {
|
Loading…
Reference in New Issue