Skip to content

Commit

Permalink
Improve the command for printing completion scripts (#1998)
Browse files Browse the repository at this point in the history
  • Loading branch information
bartekpacia authored Nov 24, 2024
1 parent 0a1c772 commit 588ada5
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 53 deletions.
13 changes: 6 additions & 7 deletions autocomplete/bash_autocomplete
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#! /bin/bash
#!/bin/bash

: ${PROG:=$(basename ${BASH_SOURCE})}
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.

# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
_cli_init_completion() {
__%[1]s_init_completion() {
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}

_cli_bash_autocomplete() {
__%[1]s_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base words
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n "=:" || return
else
_cli_init_completion -n "=:" || return
__%[1]s_init_completion -n "=:" || return
fi
words=("${words[@]:0:$cword}")
if [[ "$cur" == "-"* ]]; then
Expand All @@ -31,5 +31,4 @@ _cli_bash_autocomplete() {
fi
}

complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
unset PROG
complete -o bashdefault -o default -o nospace -F __%[1]s_bash_autocomplete %[1]s
2 changes: 1 addition & 1 deletion autocomplete/powershell_autocomplete.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
}
31 changes: 15 additions & 16 deletions autocomplete/zsh_autocomplete
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
#compdef program
compdef _program program
#compdef %[1]s
compdef _%[1]s %[1]s

# Replace all occurrences of "program" in this file with the actual name of your
# CLI program. We recommend using Find+Replace feature of your editor. Let's say
# your CLI program is called "acme", then replace like so:
# * program => acme
# * _program => _acme
# This is a shell completion script auto-generated by https://github.com/urfave/cli for zsh.

_program() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
_%[1]s() {
local -a opts # Declare a local array
local current
current=${words[-1]} # -1 means "the last element"
if [[ "$current" == "-"* ]]; then
# Current word starts with a hyphen, so complete flags/options
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${current} --generate-shell-completion)}")
else
# Current word does not start with a hyphen, so complete subcommands
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi

Expand All @@ -24,7 +22,8 @@ _program() {
fi
}

# don't run the completion function when being source-ed or eval-ed
if [ "$funcstack[1]" = "_program" ]; then
_program
# Don't run the completion function when being source-ed or eval-ed.
# See https://github.com/urfave/cli/issues/1874 for discussion.
if [ "$funcstack[1]" = "_%[1]s" ]; then
_%[1]s
fi
2 changes: 1 addition & 1 deletion command.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func (cmd *Command) setupDefaults(osArgs []string) {
}

if cmd.EnableShellCompletion || cmd.Root().shellCompletion {
completionCommand := buildCompletionCommand()
completionCommand := buildCompletionCommand(osArgs[0])

if cmd.ShellCompletionCommandName != "" {
tracef(
Expand Down
61 changes: 36 additions & 25 deletions completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,48 @@ import (
)

const (
completionCommandName = "generate-completion"
completionFlagName = "generate-shell-completion"
completionFlag = "--" + completionFlagName
completionCommandName = "completion"

// This flag is supposed to only be used by the completion script itself to generate completions on the fly.
completionFlag = "--generate-shell-completion"
)

type renderCompletion func(cmd *Command, appName string) (string, error)

var (
//go:embed autocomplete
autoCompleteFS embed.FS

shellCompletions = map[string]renderCompletion{
"bash": getCompletion("autocomplete/bash_autocomplete"),
"ps": getCompletion("autocomplete/powershell_autocomplete.ps1"),
"zsh": getCompletion("autocomplete/zsh_autocomplete"),
"fish": func(c *Command) (string, error) {
"bash": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/bash_autocomplete")
return fmt.Sprintf(string(b), appName), err
},
"zsh": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/zsh_autocomplete")
return fmt.Sprintf(string(b), appName), err
},
"fish": func(c *Command, appName string) (string, error) {
return c.ToFishCompletion()
},
"pwsh": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/powershell_autocomplete.ps1")
return string(b), err
},
}
)

type renderCompletion func(*Command) (string, error)

func getCompletion(s string) renderCompletion {
return func(c *Command) (string, error) {
b, err := autoCompleteFS.ReadFile(s)
return string(b), err
}
}

func buildCompletionCommand() *Command {
func buildCompletionCommand(appName string) *Command {
return &Command{
Name: completionCommandName,
Hidden: true,
Action: completionCommandAction,
Action: func(ctx context.Context, cmd *Command) error {
return printShellCompletion(ctx, cmd, appName)
},
}
}

func completionCommandAction(ctx context.Context, cmd *Command) error {
func printShellCompletion(_ context.Context, cmd *Command, appName string) error {
var shells []string
for k := range shellCompletions {
shells = append(shells, k)
Expand All @@ -57,14 +62,20 @@ func completionCommandAction(ctx context.Context, cmd *Command) error {
}
s := cmd.Args().First()

if rc, ok := shellCompletions[s]; !ok {
renderCompletion, ok := shellCompletions[s]
if !ok {
return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
} else if c, err := rc(cmd); err != nil {
}

completionScript, err := renderCompletion(cmd, appName)
if err != nil {
return Exit(err, 1)
} else {
if _, err = cmd.Writer.Write([]byte(c)); err != nil {
return Exit(err, 1)
}
}

_, err = cmd.Writer.Write([]byte(completionScript))
if err != nil {
return Exit(err, 1)
}

return nil
}
2 changes: 1 addition & 1 deletion completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func TestCompletionInvalidShell(t *testing.T) {
assert.ErrorContains(t, err, "unknown shell junky-sheell")

enableError := true
shellCompletions[unknownShellName] = func(c *Command) (string, error) {
shellCompletions[unknownShellName] = func(c *Command, appName string) (string, error) {
if enableError {
return "", fmt.Errorf("cant do completion")
}
Expand Down
4 changes: 2 additions & 2 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ func cliArgContains(flagName string, args []string) bool {
}

func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
cur := strings.TrimPrefix(lastArg, "-")
cur = strings.TrimPrefix(cur, "-")
// Trim to handle both "-short" and "--long" flags.
cur := strings.TrimLeft(lastArg, "-")
for _, flag := range flags {
if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
continue
Expand Down

0 comments on commit 588ada5

Please sign in to comment.