commit
6dd6a04d24
|
@ -8,9 +8,7 @@ before_install:
|
|||
- go get -u -t ./...
|
||||
|
||||
script:
|
||||
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
|
||||
- ./test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
comment: off
|
||||
|
|
14
command.go
14
command.go
|
@ -1,5 +1,7 @@
|
|||
package complete
|
||||
|
||||
import "github.com/posener/complete/match"
|
||||
|
||||
// Command represents a command line
|
||||
// It holds the data that enables auto completion of a given typed command line
|
||||
// Command can also be a sub command.
|
||||
|
@ -27,7 +29,7 @@ type Flags map[string]Predicate
|
|||
|
||||
// options returns all available complete options for the given command
|
||||
// args are all except the last command line arguments relevant to the command
|
||||
func (c *Command) options(args []string) (options []Matcher, only bool) {
|
||||
func (c *Command) options(args []string) (options []match.Matcher, only bool) {
|
||||
|
||||
// remove the first argument, which is the command name
|
||||
args = args[1:]
|
||||
|
@ -51,7 +53,7 @@ func (c *Command) options(args []string) (options []Matcher, only bool) {
|
|||
|
||||
// add global available complete options
|
||||
for flag := range c.Flags {
|
||||
options = append(options, MatchPrefix(flag))
|
||||
options = append(options, match.Prefix(flag))
|
||||
}
|
||||
|
||||
// add additional expected argument of the command
|
||||
|
@ -62,7 +64,7 @@ func (c *Command) options(args []string) (options []Matcher, only bool) {
|
|||
|
||||
// searchSub searches recursively within sub commands if the sub command appear
|
||||
// in the on of the arguments.
|
||||
func (c *Command) searchSub(args []string) (sub string, all []Matcher, only bool) {
|
||||
func (c *Command) searchSub(args []string) (sub string, all []match.Matcher, only bool) {
|
||||
for i, arg := range args {
|
||||
if cmd, ok := c.Sub[arg]; ok {
|
||||
sub = arg
|
||||
|
@ -74,10 +76,10 @@ func (c *Command) searchSub(args []string) (sub string, all []Matcher, only bool
|
|||
}
|
||||
|
||||
// suvCommands returns a list of matchers according to the sub command names
|
||||
func (c *Command) subCommands() []Matcher {
|
||||
subs := make([]Matcher, 0, len(c.Sub))
|
||||
func (c *Command) subCommands() []match.Matcher {
|
||||
subs := make([]match.Matcher, 0, len(c.Sub))
|
||||
for sub := range c.Sub {
|
||||
subs = append(subs, MatchPrefix(sub))
|
||||
subs = append(subs, match.Prefix(sub))
|
||||
}
|
||||
return subs
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Package main is complete tool for the go command line
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
import "github.com/posener/complete"
|
||||
|
||||
var (
|
||||
predictEllipsis = complete.PredictSet("./...")
|
||||
|
|
|
@ -9,14 +9,15 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/posener/complete"
|
||||
"github.com/posener/complete/match"
|
||||
)
|
||||
|
||||
func predictTest(testType string) complete.Predicate {
|
||||
return func(last string) []complete.Matcher {
|
||||
return func(last string) []match.Matcher {
|
||||
tests := testNames(testType)
|
||||
options := make([]complete.Matcher, len(tests))
|
||||
options := make([]match.Matcher, len(tests))
|
||||
for i := range tests {
|
||||
options[i] = complete.MatchPrefix(tests[i])
|
||||
options[i] = match.Prefix(tests[i])
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
|
50
match.go
50
match.go
|
@ -1,50 +0,0 @@
|
|||
package complete
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Matcher matches itself to a string
|
||||
// it is used for comparing a given argument to the last typed
|
||||
// word, and see if it is a possible auto complete option.
|
||||
type Matcher interface {
|
||||
String() string
|
||||
Match(prefix string) bool
|
||||
}
|
||||
|
||||
// MatchPrefix is a simple Matcher, if the word is it's prefix, there is a match
|
||||
type MatchPrefix string
|
||||
|
||||
func (a MatchPrefix) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Match returns true if a has the prefix as prefix
|
||||
func (a MatchPrefix) Match(prefix string) bool {
|
||||
return strings.HasPrefix(string(a), prefix)
|
||||
}
|
||||
|
||||
// MatchFileName is a file name Matcher, if the last word can prefix the
|
||||
// MatchFileName path, there is a possible match
|
||||
type MatchFileName string
|
||||
|
||||
func (a MatchFileName) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Match returns true if prefix's abs path prefixes a's abs path
|
||||
func (a MatchFileName) Match(prefix string) bool {
|
||||
full, err := filepath.Abs(string(a))
|
||||
if err != nil {
|
||||
Log("failed getting abs path of %s: %s", a, err)
|
||||
}
|
||||
prefixFull, err := filepath.Abs(prefix)
|
||||
if err != nil {
|
||||
Log("failed getting abs path of %s: %s", prefix, err)
|
||||
}
|
||||
|
||||
// if the file has the prefix as prefix,
|
||||
// but we don't want to show too many files, so, if it is in a deeper directory - omit it.
|
||||
return strings.HasPrefix(full, prefixFull) && (full == prefixFull || !strings.Contains(full[len(prefixFull)+1:], "/"))
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package match
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// File is a file name Matcher, if the last word can prefix the
|
||||
// File path, there is a possible match
|
||||
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 {
|
||||
full, err := filepath.Abs(string(a))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
prefixFull, err := filepath.Abs(prefix)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the file has the prefix as prefix,
|
||||
// but we don't want to show too many files, so, if it is in a deeper directory - omit it.
|
||||
return strings.HasPrefix(full, prefixFull) && (full == prefixFull || !strings.Contains(full[len(prefixFull)+1:], "/"))
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package match
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Matcher matches itself to a string
|
||||
// it is used for comparing a given argument to the last typed
|
||||
// word, and see if it is a possible auto complete option.
|
||||
type Matcher interface {
|
||||
fmt.Stringer
|
||||
Match(prefix string) bool
|
||||
}
|
|
@ -1,10 +1,19 @@
|
|||
package complete
|
||||
package match
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
initTests()
|
||||
|
||||
// Change to tests directory for testing completion of
|
||||
// files and directories
|
||||
err := os.Chdir("../tests")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
m Matcher
|
||||
|
@ -12,82 +21,82 @@ func TestMatch(t *testing.T) {
|
|||
want bool
|
||||
}{
|
||||
{
|
||||
m: MatchPrefix("abcd"),
|
||||
m: Prefix("abcd"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchPrefix("abcd"),
|
||||
m: Prefix("abcd"),
|
||||
prefix: "ab",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchPrefix("abcd"),
|
||||
m: Prefix("abcd"),
|
||||
prefix: "ac",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchPrefix(""),
|
||||
m: Prefix(""),
|
||||
prefix: "ac",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchPrefix(""),
|
||||
m: Prefix(""),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("file.txt"),
|
||||
m: File("file.txt"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "f",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "file.",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "./f",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "other.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("./file.txt"),
|
||||
m: File("./file.txt"),
|
||||
prefix: "/file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("/file.txt"),
|
||||
m: File("/file.txt"),
|
||||
prefix: "file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("/file.txt"),
|
||||
m: File("/file.txt"),
|
||||
prefix: "./file.txt",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("/file.txt"),
|
||||
m: File("/file.txt"),
|
||||
prefix: "/file.txt",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
m: MatchFileName("/file.txt"),
|
||||
m: File("/file.txt"),
|
||||
prefix: "/fil",
|
||||
want: true,
|
||||
},
|
|
@ -0,0 +1,15 @@
|
|||
package match
|
||||
|
||||
import "strings"
|
||||
|
||||
// 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
|
||||
func (a Prefix) Match(prefix string) bool {
|
||||
return strings.HasPrefix(string(a), prefix)
|
||||
}
|
31
predicate.go
31
predicate.go
|
@ -3,12 +3,14 @@ package complete
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/posener/complete/match"
|
||||
)
|
||||
|
||||
// Predicate 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 Predicate func(last string) []Matcher
|
||||
type Predicate func(last string) []match.Matcher
|
||||
|
||||
// Or unions two predicate functions, so that the result predicate
|
||||
// returns the union of their predication
|
||||
|
@ -19,10 +21,10 @@ func (p Predicate) Or(other Predicate) Predicate {
|
|||
if other == nil {
|
||||
return p
|
||||
}
|
||||
return func(last string) []Matcher { return append(p.predict(last), other.predict(last)...) }
|
||||
return func(last string) []match.Matcher { return append(p.predict(last), other.predict(last)...) }
|
||||
}
|
||||
|
||||
func (p Predicate) predict(last string) []Matcher {
|
||||
func (p Predicate) predict(last string) []match.Matcher {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -34,14 +36,14 @@ var PredictNothing Predicate
|
|||
|
||||
// PredictAnything expects something, but nothing particular, such as a number
|
||||
// or arbitrary name.
|
||||
func PredictAnything(last string) []Matcher { return nil }
|
||||
func PredictAnything(last string) []match.Matcher { return nil }
|
||||
|
||||
// PredictSet expects specific set of terms, given in the options argument.
|
||||
func PredictSet(options ...string) Predicate {
|
||||
return func(last string) []Matcher {
|
||||
ret := make([]Matcher, len(options))
|
||||
return func(last string) []match.Matcher {
|
||||
ret := make([]match.Matcher, len(options))
|
||||
for i := range options {
|
||||
ret[i] = MatchPrefix(options[i])
|
||||
ret[i] = match.Prefix(options[i])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ func PredictSet(options ...string) Predicate {
|
|||
// 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
|
||||
// in the current working directory.
|
||||
func PredictDirs(last string) (options []Matcher) {
|
||||
func PredictDirs(last string) (options []match.Matcher) {
|
||||
dir := dirFromLast(last)
|
||||
return dirsAt(dir)
|
||||
}
|
||||
|
@ -60,7 +62,7 @@ func PredictDirs(last string) (options []Matcher) {
|
|||
// match the pattern in the current working directory.
|
||||
// To match any file, use "*" as pattern. To match go files use "*.go", and so on.
|
||||
func PredictFiles(pattern string) Predicate {
|
||||
return func(last string) []Matcher {
|
||||
return func(last string) []match.Matcher {
|
||||
dir := dirFromLast(last)
|
||||
files, err := filepath.Glob(filepath.Join(dir, pattern))
|
||||
if err != nil {
|
||||
|
@ -73,9 +75,12 @@ func PredictFiles(pattern string) Predicate {
|
|||
}
|
||||
}
|
||||
|
||||
func dirsAt(path string) []Matcher {
|
||||
func dirsAt(path string) []match.Matcher {
|
||||
dirs := []string{}
|
||||
filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
dirs = append(dirs, path)
|
||||
}
|
||||
|
@ -111,10 +116,10 @@ func filesToRel(files []string) {
|
|||
return
|
||||
}
|
||||
|
||||
func filesToMatchers(files []string) []Matcher {
|
||||
options := make([]Matcher, len(files))
|
||||
func filesToMatchers(files []string) []match.Matcher {
|
||||
options := make([]match.Matcher, len(files))
|
||||
for i, f := range files {
|
||||
options[i] = MatchFileName(f)
|
||||
options[i] = match.File(f)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue