Move match to a separate package
This commit is contained in:
parent
e8f6dfad58
commit
6fb4875efa
14
command.go
14
command.go
|
@ -1,5 +1,7 @@
|
||||||
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 a given typed command line
|
// It holds the data that enables auto completion of a given typed command line
|
||||||
// Command can also be a sub command.
|
// 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
|
// options returns all available complete options for the given command
|
||||||
// args are all except the last command line arguments relevant to the 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
|
// remove the first argument, which is the command name
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
|
@ -51,7 +53,7 @@ func (c *Command) options(args []string) (options []Matcher, only bool) {
|
||||||
|
|
||||||
// add global available complete options
|
// add global available complete options
|
||||||
for flag := range c.Flags {
|
for flag := range c.Flags {
|
||||||
options = append(options, MatchPrefix(flag))
|
options = append(options, match.Prefix(flag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// add additional expected argument of the command
|
// 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
|
// 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 []Matcher, only bool) {
|
func (c *Command) searchSub(args []string) (sub string, all []match.Matcher, only bool) {
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
if cmd, ok := c.Sub[arg]; ok {
|
if cmd, ok := c.Sub[arg]; ok {
|
||||||
sub = arg
|
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
|
// suvCommands returns a list of matchers according to the sub command names
|
||||||
func (c *Command) subCommands() []Matcher {
|
func (c *Command) subCommands() []match.Matcher {
|
||||||
subs := make([]Matcher, 0, len(c.Sub))
|
subs := make([]match.Matcher, 0, len(c.Sub))
|
||||||
for sub := range c.Sub {
|
for sub := range c.Sub {
|
||||||
subs = append(subs, MatchPrefix(sub))
|
subs = append(subs, match.Prefix(sub))
|
||||||
}
|
}
|
||||||
return subs
|
return subs
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Package main is complete tool for the go command line
|
// Package main is complete tool for the go command line
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/posener/complete"
|
||||||
"github.com/posener/complete"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
predictEllipsis = complete.PredictSet("./...")
|
predictEllipsis = complete.PredictSet("./...")
|
||||||
|
|
|
@ -9,14 +9,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
"github.com/posener/complete/match"
|
||||||
)
|
)
|
||||||
|
|
||||||
func predictTest(testType string) complete.Predicate {
|
func predictTest(testType string) complete.Predicate {
|
||||||
return func(last string) []complete.Matcher {
|
return func(last string) []match.Matcher {
|
||||||
tests := testNames(testType)
|
tests := testNames(testType)
|
||||||
options := make([]complete.Matcher, len(tests))
|
options := make([]match.Matcher, len(tests))
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
options[i] = complete.MatchPrefix(tests[i])
|
options[i] = match.Prefix(tests[i])
|
||||||
}
|
}
|
||||||
return options
|
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,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
|
||||||
|
}
|
|
@ -1,10 +1,19 @@
|
||||||
package complete
|
package match
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestMatch(t *testing.T) {
|
func TestMatch(t *testing.T) {
|
||||||
t.Parallel()
|
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 {
|
tests := []struct {
|
||||||
m Matcher
|
m Matcher
|
||||||
|
@ -12,82 +21,82 @@ func TestMatch(t *testing.T) {
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
m: MatchPrefix("abcd"),
|
m: Prefix("abcd"),
|
||||||
prefix: "",
|
prefix: "",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchPrefix("abcd"),
|
m: Prefix("abcd"),
|
||||||
prefix: "ab",
|
prefix: "ab",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchPrefix("abcd"),
|
m: Prefix("abcd"),
|
||||||
prefix: "ac",
|
prefix: "ac",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchPrefix(""),
|
m: Prefix(""),
|
||||||
prefix: "ac",
|
prefix: "ac",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchPrefix(""),
|
m: Prefix(""),
|
||||||
prefix: "",
|
prefix: "",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("file.txt"),
|
m: File("file.txt"),
|
||||||
prefix: "",
|
prefix: "",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "",
|
prefix: "",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "f",
|
prefix: "f",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "file.",
|
prefix: "file.",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "./f",
|
prefix: "./f",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "other.txt",
|
prefix: "other.txt",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("./file.txt"),
|
m: File("./file.txt"),
|
||||||
prefix: "/file.txt",
|
prefix: "/file.txt",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("/file.txt"),
|
m: File("/file.txt"),
|
||||||
prefix: "file.txt",
|
prefix: "file.txt",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("/file.txt"),
|
m: File("/file.txt"),
|
||||||
prefix: "./file.txt",
|
prefix: "./file.txt",
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("/file.txt"),
|
m: File("/file.txt"),
|
||||||
prefix: "/file.txt",
|
prefix: "/file.txt",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
m: MatchFileName("/file.txt"),
|
m: File("/file.txt"),
|
||||||
prefix: "/fil",
|
prefix: "/fil",
|
||||||
want: true,
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/posener/complete/match"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Predicate determines what terms can follow a command or a flag
|
// 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
|
// It is used for auto completion, given last - the last word in the already
|
||||||
// in the command line, what words can complete it.
|
// 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
|
// Or unions two predicate functions, so that the result predicate
|
||||||
// returns the union of their predication
|
// returns the union of their predication
|
||||||
|
@ -19,10 +21,10 @@ func (p Predicate) Or(other Predicate) Predicate {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return p
|
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 {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -34,14 +36,14 @@ var PredictNothing Predicate
|
||||||
|
|
||||||
// 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) []Matcher { return nil }
|
func PredictAnything(last string) []match.Matcher { 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) Predicate {
|
||||||
return func(last string) []Matcher {
|
return func(last string) []match.Matcher {
|
||||||
ret := make([]Matcher, len(options))
|
ret := make([]match.Matcher, len(options))
|
||||||
for i := range options {
|
for i := range options {
|
||||||
ret[i] = MatchPrefix(options[i])
|
ret[i] = match.Prefix(options[i])
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
@ -50,7 +52,7 @@ func PredictSet(options ...string) Predicate {
|
||||||
// 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(last string) (options []Matcher) {
|
func PredictDirs(last string) (options []match.Matcher) {
|
||||||
dir := dirFromLast(last)
|
dir := dirFromLast(last)
|
||||||
return dirsAt(dir)
|
return dirsAt(dir)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +62,7 @@ func PredictDirs(last string) (options []Matcher) {
|
||||||
// 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) Predicate {
|
||||||
return func(last string) []Matcher {
|
return func(last string) []match.Matcher {
|
||||||
dir := dirFromLast(last)
|
dir := dirFromLast(last)
|
||||||
files, err := filepath.Glob(filepath.Join(dir, pattern))
|
files, err := filepath.Glob(filepath.Join(dir, pattern))
|
||||||
if err != nil {
|
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{}
|
dirs := []string{}
|
||||||
filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
dirs = append(dirs, path)
|
dirs = append(dirs, path)
|
||||||
}
|
}
|
||||||
|
@ -111,10 +116,10 @@ func filesToRel(files []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func filesToMatchers(files []string) []Matcher {
|
func filesToMatchers(files []string) []match.Matcher {
|
||||||
options := make([]Matcher, len(files))
|
options := make([]match.Matcher, len(files))
|
||||||
for i, f := range files {
|
for i, f := range files {
|
||||||
options[i] = MatchFileName(f)
|
options[i] = match.File(f)
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue