Add args struct

This commit is contained in:
Eyal Posener 2017-05-11 18:49:59 +03:00
parent dd2171d085
commit a28594d28e
4 changed files with 66 additions and 52 deletions

38
args.go Normal file
View File

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

View File

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

View File

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

View File

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