Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for flags bash completion #808

Merged
merged 13 commits into from Aug 6, 2019
54 changes: 54 additions & 0 deletions app_test.go
Expand Up @@ -221,6 +221,60 @@ func ExampleApp_Run_subcommandNoAction() {

}

func ExampleApp_Run_bashComplete_withShortFlag() {
os.Args = []string{"greet", "-", "--generate-bash-completion"}

app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
IntFlag{
Name: "other,o",
},
StringFlag{
Name: "xyz,x",
},
}

app.Run(os.Args)
// Output:
// --other
// -o
// --xyz
// -x
// --help
// -h
// --version
// -v
}
AudriusButkevicius marked this conversation as resolved.
Show resolved Hide resolved

func ExampleApp_Run_bashComplete_withLongFlag() {
os.Args = []string{"greet", "--s", "--generate-bash-completion"}

app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
IntFlag{
Name: "other,o",
},
StringFlag{
Name: "xyz,x",
},
StringFlag{
Name: "some-flag,s",
},
StringFlag{
Name: "similar-flag",
},
}

app.Run(os.Args)
// Output:
// --some-flag
// --similar-flag
}
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved

func ExampleApp_Run_bashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
Expand Down
11 changes: 8 additions & 3 deletions autocomplete/bash_autocomplete
Expand Up @@ -3,14 +3,19 @@
: ${PROG:=$(basename ${BASH_SOURCE})}

_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[@]:0:$COMP_CWORD}" != "source" ]]; then
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
if [[ $cur == -* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}

complete -F _cli_bash_autocomplete $PROG

complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
unset PROG
84 changes: 79 additions & 5 deletions help.go
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"io"
"os"
"regexp"
"strings"
"text/tabwriter"
"text/template"
"unicode/utf8"
)

// AppHelpTemplate is the text template for the Default help topic.
Expand Down Expand Up @@ -152,24 +154,91 @@ func ShowAppHelp(c *Context) (err error) {
return nil
}

var shortFlagRegex = regexp.MustCompile(`^-`)
saschagrunert marked this conversation as resolved.
Show resolved Hide resolved

// DefaultAppComplete prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
DefaultCompleteWithFlags(nil)(c)
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
}

func printCommandSuggestions(commands []Command, writer io.Writer) {
for _, command := range commands {
if command.Hidden {
continue
}
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
for _, name := range command.Names() {
fmt.Fprintf(c.App.Writer, "%s:%s\n", name, command.Usage)
fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
}
} else {
for _, name := range command.Names() {
fmt.Fprintf(c.App.Writer, "%s\n", name)
fmt.Fprintf(writer, "%s\n", name)
}
}
}
}

func cliArgContains(flagName string) bool {
for _, name := range strings.Split(flagName, ",") {
name = strings.Trim(name, " ")
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2
}
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
for _, a := range os.Args {
if a == flag {
return true
}
}
}
return false
}

func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
cur := shortFlagRegex.ReplaceAllString(lastArg, "")
cur = shortFlagRegex.ReplaceAllString(cur, "")
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
for _, flag := range flags {
if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
continue
}
for _, name := range strings.Split(flag.GetName(), ",") {
name = strings.Trim(name, " ")
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2
}
AudriusButkevicius marked this conversation as resolved.
Show resolved Hide resolved
if strings.HasPrefix(lastArg, "--") && count == 1 {
continue
}
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
fmt.Fprintln(writer, flagCompletion)
}
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
}
}
AudriusButkevicius marked this conversation as resolved.
Show resolved Hide resolved
}

func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
yogeshlonkar marked this conversation as resolved.
Show resolved Hide resolved
return func(c *Context) {
if len(os.Args) > 2 {
lastArg := os.Args[len(os.Args)-2]
if strings.HasPrefix(lastArg, "-") {
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
if cmd != nil {
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
}
return
}
}
if cmd != nil {
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
} else {
printCommandSuggestions(c.App.Commands, c.App.Writer)
}
}
}

// ShowCommandHelpAndExit - exits with code after showing help
func ShowCommandHelpAndExit(c *Context, command string, code int) {
ShowCommandHelp(c, command)
Expand Down Expand Up @@ -228,9 +297,14 @@ func ShowCompletions(c *Context) {
// ShowCommandCompletions 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)
if c != nil {
if c.BashComplete != nil {
c.BashComplete(ctx)
} else {
DefaultCompleteWithFlags(c)(ctx)
}
}

}

func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
Expand Down