Add args struct
This commit is contained in:
parent
dd2171d085
commit
a28594d28e
|
@ -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
|
||||||
|
}
|
51
command.go
51
command.go
|
@ -3,7 +3,7 @@ package complete
|
||||||
import "github.com/posener/complete/match"
|
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 command line
|
||||||
// Command can also be a sub command.
|
// Command can also be a sub command.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
// Sub is map of sub commands of the current command
|
// Sub is map of sub commands of the current command
|
||||||
|
@ -12,7 +12,7 @@ type Command struct {
|
||||||
Sub Commands
|
Sub Commands
|
||||||
|
|
||||||
// Flags is a map of flags that the command accepts.
|
// 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
|
Flags Flags
|
||||||
|
|
||||||
// Args are extra arguments that the command accepts, those who are
|
// Args are extra arguments that the command accepts, those who are
|
||||||
|
@ -24,62 +24,52 @@ type Command struct {
|
||||||
type Commands map[string]Command
|
type Commands map[string]Command
|
||||||
|
|
||||||
// Flags is the type Flags of the Flags member, it maps a flag name to the flag
|
// 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
|
type Flags map[string]Predicate
|
||||||
|
|
||||||
// options returns all available complete options for the given command
|
// predict returns all available complete predict for the given command
|
||||||
// args are all except the last command line arguments relevant to the command
|
// all are all except the last command line arguments relevant to the command
|
||||||
func (c *Command) options(args []string) (options []match.Matcher, only bool) {
|
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,
|
// if wordCompleted has something that needs to follow it,
|
||||||
// it is the most relevant completion
|
// it is the most relevant completion
|
||||||
if predicate, ok := c.Flags[wordCompleted]; ok && predicate != nil {
|
if predicate, ok := c.Flags[a.lastCompleted]; ok && predicate != nil {
|
||||||
Log("Predicting according to flag %s", wordCurrent)
|
Log("Predicting according to flag %s", a.beingTyped)
|
||||||
return predicate.predict(wordCurrent), true
|
return predicate.predict(a.beingTyped), true
|
||||||
}
|
}
|
||||||
|
|
||||||
sub, options, only := c.searchSub(args)
|
sub, options, only := c.searchSub(a)
|
||||||
if only {
|
if only {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no subcommand was entered in any of the args, add the
|
// if no sub command was found, return a list of the sub commands
|
||||||
// subcommands as complete options.
|
|
||||||
if sub == "" {
|
if sub == "" {
|
||||||
options = append(options, c.subCommands()...)
|
options = append(options, c.subCommands()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add global available complete options
|
// add global available complete predict
|
||||||
for flag := range c.Flags {
|
for flag := range c.Flags {
|
||||||
options = append(options, match.Prefix(flag))
|
options = append(options, match.Prefix(flag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// add additional expected argument of the command
|
// add additional expected argument of the command
|
||||||
options = append(options, c.Args.predict(wordCurrent)...)
|
options = append(options, c.Args.predict(a.beingTyped)...)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 []match.Matcher, only bool) {
|
func (c *Command) searchSub(a args) (sub string, all []match.Matcher, only bool) {
|
||||||
|
for i, arg := range a.completed {
|
||||||
// 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 {
|
|
||||||
if cmd, ok := c.Sub[arg]; ok {
|
if cmd, ok := c.Sub[arg]; ok {
|
||||||
sub = arg
|
sub = arg
|
||||||
all, only = cmd.options(args[i:])
|
all, only = cmd.predict(a.from(i))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// suvCommands returns a list of matchers according to the sub command names
|
// suvCommands returns a list of matchers according to the sub command names
|
||||||
|
@ -90,10 +80,3 @@ func (c *Command) subCommands() []match.Matcher {
|
||||||
}
|
}
|
||||||
return subs
|
return subs
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeLast(a []string) []string {
|
|
||||||
if len(a) > 0 {
|
|
||||||
return a[:len(a)-1]
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
25
complete.go
25
complete.go
|
@ -41,15 +41,17 @@ func New(name string, command Command) *Complete {
|
||||||
// returns success if the completion ran or if the cli matched
|
// returns success if the completion ran or if the cli matched
|
||||||
// any of the given flags, false otherwise
|
// any of the given flags, false otherwise
|
||||||
func (c *Complete) Run() bool {
|
func (c *Complete) Run() bool {
|
||||||
args, ok := getLine()
|
line, ok := getLine()
|
||||||
if !ok {
|
if !ok {
|
||||||
// make sure flags parsed,
|
// make sure flags parsed,
|
||||||
// in case they were not added in the main program
|
// in case they were not added in the main program
|
||||||
return c.CLI.Run()
|
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)
|
Log("Completion: %s", options)
|
||||||
output(options)
|
output(options)
|
||||||
|
@ -58,14 +60,12 @@ func (c *Complete) Run() bool {
|
||||||
|
|
||||||
// complete get a command an command line arguments and returns
|
// complete get a command an command line arguments and returns
|
||||||
// matching completion options
|
// matching completion options
|
||||||
func complete(c Command, args []string) (matching []string) {
|
func complete(c Command, a args) (matching []string) {
|
||||||
options, _ := c.options(args)
|
options, _ := c.predict(a)
|
||||||
|
|
||||||
// choose only matching options
|
|
||||||
l := last(args)
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
Log("option %T, %s -> %t", option, option, option.Match(l))
|
Log("option %T, %s -> %t", option, option, option.Match(a.beingTyped))
|
||||||
if option.Match(l) {
|
if option.Match(a.beingTyped) {
|
||||||
matching = append(matching, option.String())
|
matching = append(matching, option.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,13 +80,6 @@ func getLine() ([]string, bool) {
|
||||||
return strings.Split(line, " "), true
|
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) {
|
func output(options []string) {
|
||||||
Log("")
|
Log("")
|
||||||
// stdout of program defines the complete options
|
// stdout of program defines the complete options
|
||||||
|
|
|
@ -174,9 +174,9 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
|
|
||||||
tt.args = "cmd " + tt.args
|
tt.args = "cmd " + tt.args
|
||||||
os.Setenv(envComplete, 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(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
|
Loading…
Reference in New Issue