summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEyal Posener <[email protected]>2017-05-05 21:57:21 +0300
committerEyal Posener <[email protected]>2017-05-05 21:57:21 +0300
commit5e07cbd4c20a5a3bb5bc84148dc4d4ebffa3d033 (patch)
tree14562796afbc9b220836384339ae80403aa03076
parent6311b602abc0f3c0a854c244fca147101b623eba (diff)
Add file completion flag
-rw-r--r--command.go15
-rw-r--r--complete.go19
-rw-r--r--complete_test.go26
-rw-r--r--flag.go55
-rw-r--r--log.go3
-rw-r--r--option.go39
6 files changed, 128 insertions, 29 deletions
diff --git a/command.go b/command.go
index db60c3b..f3321fb 100644
--- a/command.go
+++ b/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
}
-
diff --git a/complete.go b/complete.go
index eedaa81..6345e63 100644
--- a/complete.go
+++ b/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
diff --git a/complete_test.go b/complete_test.go
index 55934bd..3485a99 100644
--- a/complete_test.go
+++ b/complete_test.go
@@ -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 {
diff --git a/flag.go b/flag.go
index 2673c7a..645cb83 100644
--- a/flag.go
+++ b/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
+}
diff --git a/log.go b/log.go
index b3fdb8e..0b0a54a 100644
--- a/log.go
+++ b/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
diff --git a/option.go b/option.go
new file mode 100644
index 0000000..9333e84
--- /dev/null
+++ b/option.go
@@ -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)
+}