Merge pull request #490 from nlewo/v2-completion

Generate completion code
This commit is contained in:
Jesse Szwedko 2016-10-25 21:55:42 -07:00 committed by GitHub
commit 76d8eaf9fe
8 changed files with 133 additions and 55 deletions

View File

@ -36,7 +36,7 @@ applications in an expressive way.
* [Subcommands](#subcommands) * [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories) * [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code) * [Exit code](#exit-code)
* [Bash Completion](#bash-completion) * [Shell Completion](#shell-completion)
+ [Enabling](#enabling) + [Enabling](#enabling)
+ [Distribution](#distribution) + [Distribution](#distribution)
+ [Customization](#customization) + [Customization](#customization)
@ -787,15 +787,15 @@ func main() {
} }
``` ```
### Bash Completion ### Shell Completion
You can enable completion commands by setting the `EnableBashCompletion` You can enable completion commands by setting the `EnableShellCompletion`
flag on the `App` object. By default, this setting will only auto-complete to 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 show an app's subcommands, but you can write your own completion methods for
the App or its subcommands. the App or its subcommands.
<!-- { <!-- {
"args": ["complete", "&#45;&#45;generate&#45;bash&#45;completion"], "args": ["complete", "&#45;&#45;generate&#45;completion"],
"output": "laundry" "output": "laundry"
} --> } -->
``` go ``` go
@ -812,7 +812,7 @@ func main() {
tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := &cli.App{ app := &cli.App{
EnableBashCompletion: true, EnableShellCompletion: true,
Commands: []*cli.Command{ Commands: []*cli.Command{
{ {
Name: "complete", Name: "complete",
@ -822,7 +822,7 @@ func main() {
fmt.Println("completed task: ", c.Args().First()) fmt.Println("completed task: ", c.Args().First())
return nil return nil
}, },
BashComplete: func(c *cli.Context) { ShellComplete: func(c *cli.Context) {
// This will complete if no args are passed // This will complete if no args are passed
if c.NArg() > 0 { if c.NArg() > 0 {
return return
@ -841,10 +841,18 @@ func main() {
#### Enabling #### Enabling
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion zsh`.
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` To setup for bash:
```
eval "`myprogram --init-completion bash`"
```
Alternatively, you can put the completion code in your `.bashrc` file:
```
myprogram --init-completion bash >> ~/.bashrc
```
#### Distribution #### Distribution
@ -864,8 +872,8 @@ to the name of their program (as above).
#### Customization #### Customization
The default bash completion flag (`--generate-bash-completion`) is defined as The default shell completion flag (`--generate-completion`) is defined as
`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: `cli.GenerateCompletionFlag`, and may be redefined if desired, e.g.:
<!-- { <!-- {
"args": ["&#45;&#45;compgen"], "args": ["&#45;&#45;compgen"],
@ -881,13 +889,13 @@ import (
) )
func main() { func main() {
cli.BashCompletionFlag = &cli.BoolFlag{ cli.GenerateCompletionFlag = &cli.BoolFlag{
Name: "compgen", Name: "compgen",
Hidden: true, Hidden: true,
} }
app := &cli.App{ app := &cli.App{
EnableBashCompletion: true, EnableShellCompletion: true,
Commands: []*cli.Command{ Commands: []*cli.Command{
{ {
Name: "wat", Name: "wat",
@ -1097,7 +1105,7 @@ func init() {
cli.SubcommandHelpTemplate += "\nor something\n" cli.SubcommandHelpTemplate += "\nor something\n"
cli.HelpFlag = &cli.BoolFlag{Name: "halp"} cli.HelpFlag = &cli.BoolFlag{Name: "halp"}
cli.BashCompletionFlag = &cli.BoolFlag{Name: "compgen", Hidden: true} cli.GenerateCompletionFlag = &cli.BoolFlag{Name: "compgen", Hidden: true}
cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}}
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
@ -1177,7 +1185,7 @@ func main() {
HideHelp: false, HideHelp: false,
Hidden: false, Hidden: false,
HelpName: "doo!", HelpName: "doo!",
BashComplete: func(c *cli.Context) { ShellComplete: func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "--better\n") fmt.Fprintf(c.App.Writer, "--better\n")
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
@ -1220,10 +1228,10 @@ func main() {
&cli.UintFlag{Name: "age"}, &cli.UintFlag{Name: "age"},
&cli.Uint64Flag{Name: "bigage"}, &cli.Uint64Flag{Name: "bigage"},
}, },
EnableBashCompletion: true, EnableShellCompletion: true,
HideHelp: false, HideHelp: false,
HideVersion: false, HideVersion: false,
BashComplete: func(c *cli.Context) { ShellComplete: func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n")
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {

29
app.go
View File

@ -29,16 +29,16 @@ 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 // Boolean to enable shell completion commands
EnableBashCompletion bool EnableShellCompletion bool
// Boolean to hide built-in help command // Boolean to hide built-in help command
HideHelp bool HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help // Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool HideVersion bool
// Categories contains the categorized commands and is populated on app startup // Categories contains the categorized commands and is populated on app startup
Categories CommandCategories Categories CommandCategories
// An action to execute when the bash-completion flag is set // An action to execute when the shell completion flag is set
BashComplete BashCompleteFunc ShellComplete ShellCompleteFunc
// 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 BeforeFunc Before BeforeFunc
@ -103,8 +103,8 @@ func (a *App) Setup() {
a.Version = "0.0.0" a.Version = "0.0.0"
} }
if a.BashComplete == nil { if a.ShellComplete == nil {
a.BashComplete = DefaultAppComplete a.ShellComplete = DefaultAppComplete
} }
if a.Action == nil { if a.Action == nil {
@ -136,8 +136,9 @@ func (a *App) Setup() {
} }
} }
if a.EnableBashCompletion { if a.EnableShellCompletion {
a.appendFlag(BashCompletionFlag) a.appendFlag(GenerateCompletionFlag)
a.appendFlag(InitCompletionFlag)
} }
if !a.HideVersion { if !a.HideVersion {
@ -176,6 +177,14 @@ func (a *App) Run(arguments []string) (err error) {
return nil return nil
} }
if done, cerr := checkInitCompletion(context); done {
if cerr != nil {
err = cerr
} else {
return nil
}
}
if err != nil { if err != nil {
if a.OnUsageError != nil { if a.OnUsageError != nil {
err := a.OnUsageError(context, err, false) err := a.OnUsageError(context, err, false)
@ -260,8 +269,8 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
a.Commands = newCmds a.Commands = newCmds
// append flags // append flags
if a.EnableBashCompletion { if a.EnableShellCompletion {
a.appendFlag(BashCompletionFlag) a.appendFlag(GenerateCompletionFlag)
} }
// parse flags // parse flags

View File

@ -27,7 +27,7 @@ func init() {
} }
type opCounts struct { type opCounts struct {
Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int Total, ShellComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int
} }
func ExampleApp_Run() { func ExampleApp_Run() {
@ -125,13 +125,13 @@ func ExampleApp_Run_help() {
// This is how we describe describeit the function // This is how we describe describeit the function
} }
func ExampleApp_Run_bashComplete() { func ExampleApp_Run_shellComplete() {
// set args for examples sake // set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"} os.Args = []string{"greet", "--generate-completion"}
app := &App{ app := &App{
Name: "greet", Name: "greet",
EnableBashCompletion: true, EnableShellCompletion: true,
Commands: []*Command{ Commands: []*Command{
{ {
Name: "describeit", Name: "describeit",
@ -145,7 +145,7 @@ func ExampleApp_Run_bashComplete() {
}, { }, {
Name: "next", Name: "next",
Usage: "next example", Usage: "next example",
Description: "more stuff to see when generating bash completion", Description: "more stuff to see when generating shell completion",
Action: func(c *Context) error { Action: func(c *Context) error {
fmt.Printf("the next example") fmt.Printf("the next example")
return nil return nil
@ -748,10 +748,10 @@ func TestApp_OrderOfOperations(t *testing.T) {
resetCounts := func() { counts = &opCounts{} } resetCounts := func() { counts = &opCounts{} }
app := &App{ app := &App{
EnableBashCompletion: true, EnableShellCompletion: true,
BashComplete: func(c *Context) { ShellComplete: func(c *Context) {
counts.Total++ counts.Total++
counts.BashComplete = counts.Total counts.ShellComplete = counts.Total
}, },
OnUsageError: func(c *Context, err error, isSubcommand bool) error { OnUsageError: func(c *Context, err error, isSubcommand bool) error {
counts.Total++ counts.Total++
@ -814,8 +814,8 @@ func TestApp_OrderOfOperations(t *testing.T) {
resetCounts() resetCounts()
_ = app.Run([]string{"command", "--generate-bash-completion"}) _ = app.Run([]string{"command", "--generate-completion"})
expect(t, counts.BashComplete, 1) expect(t, counts.ShellComplete, 1)
expect(t, counts.Total, 1) expect(t, counts.Total, 1)
resetCounts() resetCounts()

View File

@ -298,6 +298,23 @@ def _app_init(source):
'cli\\.NewApp\\(\\)', '(&cli.App{})', source, flags=re.UNICODE 'cli\\.NewApp\\(\\)', '(&cli.App{})', source, flags=re.UNICODE
) )
@_migrator
def _bash_complete(source):
return re.sub(
'BashComplete:', 'ShellComplete:',
re.sub('\\.BashComplete', '.ShellComplete', source, flags=re.UNICODE))
@_migrator
def _enable_bash_completion(source):
return re.sub(
'\\.EnableBashCompletion', '.EnableShellCompletion', source, flags=re.UNICODE
)
@_migrator
def _bash_completion_flag(source):
return re.sub(
'cli\\.BashCompletionFlag', 'cli.GenerateCompletionFlag', source, flags=re.UNICODE
)
def test_migrators(): def test_migrators():
import difflib import difflib

View File

@ -23,8 +23,8 @@ type Command struct {
ArgsUsage string ArgsUsage string
// The category the command is part of // The category the command is part of
Category string Category string
// The function to call when checking for bash command completions // The function to call when checking for shell command completions
BashComplete BashCompleteFunc ShellComplete ShellCompleteFunc
// An action to execute before any sub-subcommands are run, but after the context is ready // An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run // If a non-nil error is returned, no sub-subcommands are run
Before BeforeFunc Before BeforeFunc
@ -71,8 +71,8 @@ func (c *Command) Run(ctx *Context) (err error) {
c.appendFlag(HelpFlag) c.appendFlag(HelpFlag)
} }
if ctx.App.EnableBashCompletion { if ctx.App.EnableShellCompletion {
c.appendFlag(BashCompletionFlag) c.appendFlag(GenerateCompletionFlag)
} }
set := flagSet(c.Name, c.Flags) set := flagSet(c.Name, c.Flags)
@ -202,9 +202,9 @@ func (c *Command) startApp(ctx *Context) error {
sort.Sort(app.Categories.(*commandCategories)) sort.Sort(app.Categories.(*commandCategories))
// bash completion // bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion app.EnableShellCompletion = ctx.App.EnableShellCompletion
if c.BashComplete != nil { if c.ShellComplete != nil {
app.BashComplete = c.BashComplete app.ShellComplete = c.ShellComplete
} }
// set the actions // set the actions

12
flag.go
View File

@ -21,12 +21,18 @@ var (
commaWhitespace = regexp.MustCompile("[, ]+.*") commaWhitespace = regexp.MustCompile("[, ]+.*")
) )
// BashCompletionFlag enables bash-completion for all commands and subcommands // GenerateCompletionFlag enables completion for all commands and subcommands
var BashCompletionFlag = &BoolFlag{ var GenerateCompletionFlag = &BoolFlag{
Name: "generate-bash-completion", Name: "generate-completion",
Hidden: true, Hidden: true,
} }
// InitCompletionFlag generates completion code
var InitCompletionFlag = &StringFlag{
Name: "init-completion",
Usage: "generate completion code. Value must be 'bash' or 'zsh'",
}
// VersionFlag prints the version for the application // VersionFlag prints the version for the application
var VersionFlag = &BoolFlag{ var VersionFlag = &BoolFlag{
Name: "version", Name: "version",

View File

@ -1,7 +1,7 @@
package cli package cli
// BashCompleteFunc is an action to execute when the bash-completion flag is set // ShellCompleteFunc is an action to execute when the shell completion flag is set
type BashCompleteFunc func(*Context) type ShellCompleteFunc func(*Context)
// BeforeFunc is an action to execute before any subcommands are run, but after // BeforeFunc is 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 // the context is ready if a non-nil error is returned, no subcommands are run

50
help.go
View File

@ -182,16 +182,16 @@ func printVersion(c *Context) {
// ShowCompletions prints the lists of commands within a given context // ShowCompletions prints the lists of commands within a given context
func ShowCompletions(c *Context) { func ShowCompletions(c *Context) {
a := c.App a := c.App
if a != nil && a.BashComplete != nil { if a != nil && a.ShellComplete != nil {
a.BashComplete(c) a.ShellComplete(c)
} }
} }
// ShowCommandCompletions prints the custom completions for a given command // ShowCommandCompletions prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) { func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command) c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil { if c != nil && c.ShellComplete != nil {
c.BashComplete(ctx) c.ShellComplete(ctx)
} }
} }
@ -270,7 +270,7 @@ func checkSubcommandHelp(c *Context) bool {
} }
func checkCompletions(c *Context) bool { func checkCompletions(c *Context) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { if c.Bool(GenerateCompletionFlag.Name) && c.App.EnableShellCompletion {
ShowCompletions(c) ShowCompletions(c)
return true return true
} }
@ -279,10 +279,48 @@ func checkCompletions(c *Context) bool {
} }
func checkCommandCompletions(c *Context, name string) bool { func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { if c.Bool(GenerateCompletionFlag.Name) && c.App.EnableShellCompletion {
ShowCommandCompletions(c, name) ShowCommandCompletions(c, name)
return true return true
} }
return false return false
} }
func checkInitCompletion(c *Context) (bool, error) {
if c.IsSet(InitCompletionFlag.Name) {
shell := c.String(InitCompletionFlag.Name)
progName := os.Args[0]
switch shell {
case "bash":
fmt.Print(bashCompletionCode(progName))
return true, nil
case "zsh":
fmt.Print(zshCompletionCode(progName))
return true, nil
default:
return false, fmt.Errorf("--init-completion value cannot be '%s'", shell)
}
}
return false, nil
}
func bashCompletionCode(progName string) string {
var template = `_cli_bash_autocomplete() {
local cur opts base;
COMPREPLY=();
cur="${COMP_WORDS[COMP_CWORD]}";
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-completion );
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) );
return 0;
};
complete -F _cli_bash_autocomplete %s`
return fmt.Sprintf(template, progName)
}
func zshCompletionCode(progName string) string {
var template = `autoload -U compinit && compinit;
autoload -U bashcompinit && bashcompinit;`
return template + "\n" + bashCompletionCode(progName)
}