Skip to content

Commit

Permalink
Merge pull request #808 from yogeshlonkar/master
Browse files Browse the repository at this point in the history
Add support for flags bash completion
  • Loading branch information
AudriusButkevicius committed Aug 6, 2019
2 parents 26945c5 + 1db0496 commit 93392d1
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 8 deletions.
83 changes: 83 additions & 0 deletions app_test.go
Expand Up @@ -221,6 +221,89 @@ 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
}

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
}
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
os.Args = []string{"greet", "--st", "--generate-bash-completion"}

app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
IntFlag{
Name: "int-flag,i",
},
StringFlag{
Name: "string,s",
},
StringFlag{
Name: "string-flag-2",
},
StringFlag{
Name: "similar-flag",
},
StringFlag{
Name: "some-flag",
},
}

app.Run(os.Args)
// Output:
// --string
// --string-flag-2
}

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]}" != "source" ]]; then
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 )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
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
unset PROG
85 changes: 80 additions & 5 deletions help.go
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"text/tabwriter"
"text/template"
"unicode/utf8"
)

// AppHelpTemplate is the text template for the Default help topic.
Expand Down Expand Up @@ -157,19 +158,88 @@ func ShowAppHelp(c *Context) (err error) {

// 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)
}

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.TrimSpace(name)
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 := strings.TrimPrefix(lastArg, "-")
cur = strings.TrimPrefix(cur, "-")
for _, flag := range flags {
if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
continue
}
for _, name := range strings.Split(flag.GetName(), ",") {
name = strings.TrimSpace(name)
// this will get total count utf8 letters in flag name
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2 // resuse this count to generate single - or -- in flag completion
}
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
// skip flag completion for short flags example -v or -x
if strings.HasPrefix(lastArg, "--") && count == 1 {
continue
}
// match if last argument matches this flag and it is not repeated
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
fmt.Fprintln(writer, flagCompletion)
}
}
}
}

func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
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)
}
}
}

Expand Down Expand Up @@ -231,9 +301,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

0 comments on commit 93392d1

Please sign in to comment.