Merge pull request #78 from zenoss/feature/bash-completion

Bash completion
This commit is contained in:
Jeremy Saenz 2014-04-14 10:00:36 -07:00
commit 640826c88f
7 changed files with 170 additions and 10 deletions

View File

@ -191,5 +191,46 @@ app.Commands = []cli.Command{
... ...
``` ```
### Bash Completion
You can enable completion commands by setting the EnableBashCompletion
flag on the App object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
ShortName: "c",
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
println(t)
}
},
}
}
...
```
#### To Enable
Source the autocomplete/bash_autocomplete file in your .bashrc file while
setting the PROG variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
## About ## About
cli.go is written by none other than the [Code Gangsta](http://codegangsta.io) cli.go is written by none other than the [Code Gangsta](http://codegangsta.io)

26
app.go
View File

@ -20,6 +20,10 @@ type App struct {
Commands []Command Commands []Command
// List of flags to parse // List of flags to parse
Flags []Flag Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready // An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run // If a non-nil error is returned, no subcommands are run
Before func(context *Context) error Before func(context *Context) error
@ -46,13 +50,14 @@ func compileTime() time.Time {
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App { func NewApp() *App {
return &App{ return &App{
Name: os.Args[0], Name: os.Args[0],
Usage: "A new cli application", Usage: "A new cli application",
Version: "0.0.0", Version: "0.0.0",
Action: helpCommand.Action, BashComplete: DefaultAppComplete,
Compiled: compileTime(), Action: helpCommand.Action,
Author: "Author", Compiled: compileTime(),
Email: "unknown@email", Author: "Author",
Email: "unknown@email",
} }
} }
@ -64,6 +69,9 @@ func (a *App) Run(arguments []string) error {
} }
//append version/help flags //append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
a.appendFlag(BoolFlag{"version, v", "print the version"}) a.appendFlag(BoolFlag{"version, v", "print the version"})
a.appendFlag(BoolFlag{"help, h", "show help"}) a.appendFlag(BoolFlag{"help, h", "show help"})
@ -88,6 +96,10 @@ func (a *App) Run(arguments []string) error {
return err return err
} }
if checkCompletions(context) {
return nil
}
if checkHelp(context) { if checkHelp(context) {
return nil return nil
} }

View File

@ -35,9 +35,9 @@ func ExampleAppHelp() {
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "describeit", Name: "describeit",
ShortName: "d", ShortName: "d",
Usage: "use it to see a description", Usage: "use it to see a description",
Description: "This is how we describe describeit the function", Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
fmt.Printf("i like to describe things") fmt.Printf("i like to describe things")
@ -58,6 +58,41 @@ func ExampleAppHelp() {
// OPTIONS: // OPTIONS:
} }
func ExampleAppBashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
app := cli.NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "describeit",
ShortName: "d",
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) {
fmt.Printf("i like to describe things")
},
}, {
Name: "next",
Usage: "next example",
Description: "more stuff to see when generating bash completion",
Action: func(c *cli.Context) {
fmt.Printf("the next example")
},
},
}
app.Run(os.Args)
// Output:
// describeit
// d
// next
// help
// h
}
func TestApp_Run(t *testing.T) { func TestApp_Run(t *testing.T) {
s := "" s := ""

View File

@ -0,0 +1,13 @@
#! /bin/bash
_cli_bash_autocomplete() {
local cur prev opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _cli_bash_autocomplete $PROG

View File

@ -16,6 +16,8 @@ type Command struct {
Usage string Usage string
// A longer explaination of how the command works // A longer explaination of how the command works
Description string Description string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// The function to call when this command is invoked // The function to call when this command is invoked
Action func(context *Context) Action func(context *Context)
// List of flags to parse // List of flags to parse
@ -30,6 +32,10 @@ func (c Command) Run(ctx *Context) error {
BoolFlag{"help, h", "show help"}, BoolFlag{"help, h", "show help"},
) )
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags) set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
@ -67,6 +73,11 @@ func (c Command) Run(ctx *Context) error {
return nerr return nerr
} }
context := NewContext(ctx.App, set, ctx.globalSet) context := NewContext(ctx.App, set, ctx.globalSet)
if checkCommandCompletions(context, c.Name) {
return nil
}
if checkCommandHelp(context, c.Name) { if checkCommandHelp(context, c.Name) {
return nil return nil
} }

View File

@ -7,6 +7,9 @@ import (
"strings" "strings"
) )
// This flag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{"generate-bash-completion", ""}
// Flag is a common interface related to parsing flags in cli. // Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recomended that // For more advanced flag parsing techniques, it is recomended that
// this interface be implemented. // this interface be implemented.

45
help.go
View File

@ -60,10 +60,21 @@ var helpCommand = Command{
// Prints help for the App // Prints help for the App
var HelpPrinter = printHelp var HelpPrinter = printHelp
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) {
HelpPrinter(AppHelpTemplate, c.App) HelpPrinter(AppHelpTemplate, c.App)
} }
// Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
fmt.Println(command.Name)
if command.ShortName != "" {
fmt.Println(command.ShortName)
}
}
}
// Prints help for the given command // Prints help for the given command
func ShowCommandHelp(c *Context, command string) { func ShowCommandHelp(c *Context, command string) {
for _, c := range c.App.Commands { for _, c := range c.App.Commands {
@ -81,6 +92,22 @@ func ShowVersion(c *Context) {
fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) fmt.Printf("%v version %v\n", c.App.Name, c.App.Version)
} }
// Prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// Prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(templ string, data interface{}) { func printHelp(templ string, data interface{}) {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Parse(templ)) t := template.Must(template.New("help").Parse(templ))
@ -117,3 +144,21 @@ func checkCommandHelp(c *Context, name string) bool {
return false return false
} }
func checkCompletions(c *Context) bool {
if c.GlobalBool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
}
return false
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
}
return false
}