Add file completion flag
This commit is contained in:
parent
6311b602ab
commit
5e07cbd4c2
15
command.go
15
command.go
|
@ -11,7 +11,7 @@ type Command struct {
|
|||
|
||||
// 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 []string, only bool) {
|
||||
func (c *Command) options(args []string) (options []Option, only bool) {
|
||||
|
||||
// remove the first argument, which is the command name
|
||||
args = args[1:]
|
||||
|
@ -19,7 +19,7 @@ func (c *Command) options(args []string) (options []string, only bool) {
|
|||
// if prev has something that needs to follow it,
|
||||
// it is the most relevant completion
|
||||
if options, ok := c.Flags[last(args)]; ok && options.HasFollow {
|
||||
return options.FollowsOptions, true
|
||||
return options.follows(), true
|
||||
}
|
||||
|
||||
sub, options, only := c.searchSub(args)
|
||||
|
@ -35,13 +35,13 @@ func (c *Command) options(args []string) (options []string, only bool) {
|
|||
|
||||
// add global available complete options
|
||||
for flag := range c.Flags {
|
||||
options = append(options, flag)
|
||||
options = append(options, Arg(flag))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Command) searchSub(args []string) (sub string, all []string, only bool) {
|
||||
func (c *Command) searchSub(args []string) (sub string, all []Option, only bool) {
|
||||
for i, arg := range args {
|
||||
if cmd, ok := c.Sub[arg]; ok {
|
||||
sub = arg
|
||||
|
@ -52,11 +52,10 @@ func (c *Command) searchSub(args []string) (sub string, all []string, only bool)
|
|||
return "", nil, false
|
||||
}
|
||||
|
||||
func (c *Command) subCommands() []string {
|
||||
subs := make([]string, 0, len(c.Sub))
|
||||
func (c *Command) subCommands() []Option {
|
||||
subs := make([]Option, 0, len(c.Sub))
|
||||
for sub := range c.Sub {
|
||||
subs = append(subs, sub)
|
||||
subs = append(subs, Arg(sub))
|
||||
}
|
||||
return subs
|
||||
}
|
||||
|
||||
|
|
19
complete.go
19
complete.go
|
@ -13,23 +13,19 @@ const (
|
|||
|
||||
type Completer struct {
|
||||
Command
|
||||
log func(format string, args ...interface{})
|
||||
}
|
||||
|
||||
func New(c Command) *Completer {
|
||||
return &Completer{
|
||||
Command: c,
|
||||
log: logger(),
|
||||
}
|
||||
return &Completer{Command: c}
|
||||
}
|
||||
|
||||
func (c *Completer) Complete() {
|
||||
args := getLine()
|
||||
c.log("Completing args: %s", args)
|
||||
logger("Completing args: %s", args)
|
||||
|
||||
options := c.complete(args)
|
||||
|
||||
c.log("Completion: %s", options)
|
||||
logger("Completion: %s", options)
|
||||
output(options)
|
||||
}
|
||||
|
||||
|
@ -38,13 +34,10 @@ func (c *Completer) complete(args []string) []string {
|
|||
return c.chooseRelevant(last(args), all)
|
||||
}
|
||||
|
||||
func (c *Completer) chooseRelevant(last string, list []string) (opts []string) {
|
||||
if last == "" {
|
||||
return list
|
||||
}
|
||||
func (c *Completer) chooseRelevant(last string, list []Option) (options []string) {
|
||||
for _, sub := range list {
|
||||
if strings.HasPrefix(sub, last) {
|
||||
opts = append(opts, sub)
|
||||
if sub.Matches(last) {
|
||||
options = append(options, sub.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
|
@ -9,7 +9,9 @@ import (
|
|||
func TestCompleter_Complete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
os.Setenv(envDebug, "1")
|
||||
if testing.Verbose() {
|
||||
os.Setenv(envDebug, "1")
|
||||
}
|
||||
|
||||
c := Completer{
|
||||
Command: Command{
|
||||
|
@ -30,9 +32,9 @@ func TestCompleter_Complete(t *testing.T) {
|
|||
Flags: map[string]FlagOptions{
|
||||
"-h": FlagNoFollow,
|
||||
"-global1": FlagUnknownFollow,
|
||||
"-o": FlagFileFilter("./gocomplete/*.go"),
|
||||
},
|
||||
},
|
||||
log: t.Logf,
|
||||
}
|
||||
|
||||
allGlobals := []string{}
|
||||
|
@ -53,7 +55,7 @@ func TestCompleter_Complete(t *testing.T) {
|
|||
},
|
||||
{
|
||||
args: "-",
|
||||
want: []string{"-h", "-global1"},
|
||||
want: []string{"-h", "-global1", "-o"},
|
||||
},
|
||||
{
|
||||
args: "-h ",
|
||||
|
@ -77,11 +79,11 @@ func TestCompleter_Complete(t *testing.T) {
|
|||
},
|
||||
{
|
||||
args: "sub1 ",
|
||||
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
||||
want: []string{"-flag1", "-flag2", "-h", "-global1", "-o"},
|
||||
},
|
||||
{
|
||||
args: "sub2 ",
|
||||
want: []string{"-flag2", "-flag3", "-h", "-global1"},
|
||||
want: []string{"-flag2", "-flag3", "-h", "-global1", "-o"},
|
||||
},
|
||||
{
|
||||
args: "sub1 -fl",
|
||||
|
@ -97,7 +99,7 @@ func TestCompleter_Complete(t *testing.T) {
|
|||
},
|
||||
{
|
||||
args: "sub1 -flag2 ",
|
||||
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
||||
want: []string{"-flag1", "-flag2", "-h", "-global1", "-o"},
|
||||
},
|
||||
{
|
||||
args: "-no-such-flag",
|
||||
|
@ -115,6 +117,18 @@ func TestCompleter_Complete(t *testing.T) {
|
|||
args: "no-such-command ",
|
||||
want: allGlobals,
|
||||
},
|
||||
{
|
||||
args: "-o ",
|
||||
want: []string{"./gocomplete/complete.go"},
|
||||
},
|
||||
{
|
||||
args: "-o goco",
|
||||
want: []string{"./gocomplete/complete.go"},
|
||||
},
|
||||
{
|
||||
args: "-o ./goco",
|
||||
want: []string{"./gocomplete/complete.go"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
55
flag.go
55
flag.go
|
@ -1,8 +1,20 @@
|
|||
package complete
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FlagOptions struct {
|
||||
HasFollow bool
|
||||
FollowsOptions []string
|
||||
FollowsOptions func() []Option
|
||||
}
|
||||
|
||||
func (f *FlagOptions) follows() []Option {
|
||||
if f.FollowsOptions == nil {
|
||||
return nil
|
||||
}
|
||||
return f.FollowsOptions()
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -10,3 +22,44 @@ var (
|
|||
FlagUnknownFollow = FlagOptions{HasFollow: true}
|
||||
)
|
||||
|
||||
func FlagFileFilter(pattern string) FlagOptions {
|
||||
return FlagOptions{
|
||||
HasFollow: true,
|
||||
FollowsOptions: glob(pattern),
|
||||
}
|
||||
}
|
||||
|
||||
func glob(pattern string) func() []Option {
|
||||
return func() []Option {
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
logger("failed glob operation with pattern '%s': %s", pattern, err)
|
||||
}
|
||||
if !filepath.IsAbs(pattern) {
|
||||
filesToRel(files)
|
||||
}
|
||||
options := make([]Option, len(files))
|
||||
for i, f := range files {
|
||||
options[i] = ArgFileName(f)
|
||||
}
|
||||
return options
|
||||
}
|
||||
}
|
||||
func filesToRel(files []string) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := range files {
|
||||
abs, err := filepath.Abs(files[i])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rel, err := filepath.Rel(wd, abs)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
files[i] = "./" + rel
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
3
log.go
3
log.go
|
@ -7,8 +7,9 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
var logger = getLogger()
|
||||
|
||||
func logger() func(format string, args ...interface{}) {
|
||||
func getLogger() func(format string, args ...interface{}) {
|
||||
var logfile io.Writer = ioutil.Discard
|
||||
if os.Getenv(envDebug) != "" {
|
||||
logfile = os.Stderr
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package complete
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Option interface {
|
||||
String() string
|
||||
Matches(prefix string) bool
|
||||
}
|
||||
|
||||
type Arg string
|
||||
|
||||
func (a Arg) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (a Arg) Matches(prefix string) bool {
|
||||
return strings.HasPrefix(string(a), prefix)
|
||||
}
|
||||
|
||||
type ArgFileName string
|
||||
|
||||
func (a ArgFileName) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (a ArgFileName) Matches(prefix string) bool {
|
||||
full, err := filepath.Abs(string(a))
|
||||
if err != nil {
|
||||
logger("failed getting abs path of %s: %s", a, err)
|
||||
}
|
||||
prefixFull, err := filepath.Abs(prefix)
|
||||
if err != nil {
|
||||
logger("failed getting abs path of %s: %s", prefix, err)
|
||||
}
|
||||
return strings.HasPrefix(full, prefixFull)
|
||||
}
|
Loading…
Reference in New Issue