Move match to a separate package

This commit is contained in:
Eyal Posener 2017-05-07 19:53:55 +03:00
parent e8f6dfad58
commit 6fb4875efa
9 changed files with 113 additions and 94 deletions

View File

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

View File

@ -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("./...")

View File

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

View File

@ -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:], "/"))
}

30
match/file.go Normal file
View File

@ -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:], "/"))
}

9
match/match.go Normal file
View File

@ -0,0 +1,9 @@
package match
// 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
}

View File

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

15
match/prefix.go Normal file
View File

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

View File

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