From a28594d28ea9a838f174641a411fd537c2c495b9 Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Thu, 11 May 2017 18:49:59 +0300 Subject: [PATCH] Add args struct --- args.go | 38 ++++++++++++++++++++++++++++++++++++ command.go | 51 ++++++++++++++++-------------------------------- complete.go | 25 +++++++++--------------- complete_test.go | 4 ++-- 4 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 args.go diff --git a/args.go b/args.go new file mode 100644 index 0000000..bb45d1c --- /dev/null +++ b/args.go @@ -0,0 +1,38 @@ +package complete + +type args struct { + all []string + completed []string + beingTyped string + lastCompleted string +} + +func newArgs(line []string) args { + completed := removeLast(line) + return args{ + all: line[1:], + completed: completed, + beingTyped: last(line), + lastCompleted: last(completed), + } +} + +func (a args) from(i int) args { + a.all = a.all[i:] + a.completed = a.completed[i:] + return a +} + +func removeLast(a []string) []string { + if len(a) > 0 { + return a[:len(a)-1] + } + return a +} + +func last(args []string) (last string) { + if len(args) > 0 { + last = args[len(args)-1] + } + return +} diff --git a/command.go b/command.go index f52d175..6e4a773 100644 --- a/command.go +++ b/command.go @@ -3,7 +3,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 +// It holds the data that enables auto completion of command line // Command can also be a sub command. type Command struct { // Sub is map of sub commands of the current command @@ -12,7 +12,7 @@ type Command struct { Sub Commands // Flags is a map of flags that the command accepts. - // The key is the flag name, and the value is it's prediction options. + // The key is the flag name, and the value is it's prediction predict. Flags Flags // Args are extra arguments that the command accepts, those who are @@ -24,62 +24,52 @@ type Command struct { type Commands map[string]Command // Flags is the type Flags of the Flags member, it maps a flag name to the flag -// prediction options. +// prediction predict. 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 []match.Matcher, only bool) { +// predict returns all available complete predict for the given command +// all are all except the last command line arguments relevant to the command +func (c *Command) predict(a args) (options []match.Matcher, only bool) { - // remove the first argument, which is the command name - args = args[1:] - wordCurrent := last(args) - wordCompleted := last(removeLast(args)) // if wordCompleted has something that needs to follow it, // it is the most relevant completion - if predicate, ok := c.Flags[wordCompleted]; ok && predicate != nil { - Log("Predicting according to flag %s", wordCurrent) - return predicate.predict(wordCurrent), true + if predicate, ok := c.Flags[a.lastCompleted]; ok && predicate != nil { + Log("Predicting according to flag %s", a.beingTyped) + return predicate.predict(a.beingTyped), true } - sub, options, only := c.searchSub(args) + sub, options, only := c.searchSub(a) if only { return } - // if no subcommand was entered in any of the args, add the - // subcommands as complete options. + // if no sub command was found, return a list of the sub commands if sub == "" { options = append(options, c.subCommands()...) } - // add global available complete options + // add global available complete predict for flag := range c.Flags { options = append(options, match.Prefix(flag)) } // add additional expected argument of the command - options = append(options, c.Args.predict(wordCurrent)...) + options = append(options, c.Args.predict(a.beingTyped)...) return } // 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 []match.Matcher, only bool) { - - // search for sub command in all arguments except the last one - // because that one might not be completed yet - searchArgs := removeLast(args) - - for i, arg := range searchArgs { +func (c *Command) searchSub(a args) (sub string, all []match.Matcher, only bool) { + for i, arg := range a.completed { if cmd, ok := c.Sub[arg]; ok { sub = arg - all, only = cmd.options(args[i:]) + all, only = cmd.predict(a.from(i)) return } } - return "", nil, false + return } // suvCommands returns a list of matchers according to the sub command names @@ -90,10 +80,3 @@ func (c *Command) subCommands() []match.Matcher { } return subs } - -func removeLast(a []string) []string { - if len(a) > 0 { - return a[:len(a)-1] - } - return a -} diff --git a/complete.go b/complete.go index c91bf5f..2780e62 100644 --- a/complete.go +++ b/complete.go @@ -41,15 +41,17 @@ func New(name string, command Command) *Complete { // returns success if the completion ran or if the cli matched // any of the given flags, false otherwise func (c *Complete) Run() bool { - args, ok := getLine() + line, ok := getLine() if !ok { // make sure flags parsed, // in case they were not added in the main program return c.CLI.Run() } - Log("Completing args: %s", args) + Log("Completing line: %s", line) - options := complete(c.Command, args) + a := newArgs(line) + + options := complete(c.Command, a) Log("Completion: %s", options) output(options) @@ -58,14 +60,12 @@ func (c *Complete) Run() bool { // complete get a command an command line arguments and returns // matching completion options -func complete(c Command, args []string) (matching []string) { - options, _ := c.options(args) +func complete(c Command, a args) (matching []string) { + options, _ := c.predict(a) - // choose only matching options - l := last(args) for _, option := range options { - Log("option %T, %s -> %t", option, option, option.Match(l)) - if option.Match(l) { + Log("option %T, %s -> %t", option, option, option.Match(a.beingTyped)) + if option.Match(a.beingTyped) { matching = append(matching, option.String()) } } @@ -80,13 +80,6 @@ func getLine() ([]string, bool) { return strings.Split(line, " "), true } -func last(args []string) (last string) { - if len(args) > 0 { - last = args[len(args)-1] - } - return -} - func output(options []string) { Log("") // stdout of program defines the complete options diff --git a/complete_test.go b/complete_test.go index 282a2f6..0079c30 100644 --- a/complete_test.go +++ b/complete_test.go @@ -174,9 +174,9 @@ func TestCompleter_Complete(t *testing.T) { tt.args = "cmd " + tt.args os.Setenv(envComplete, tt.args) - args, _ := getLine() + line, _ := getLine() - got := complete(c, args) + got := complete(c, newArgs(line)) sort.Strings(tt.want) sort.Strings(got)