Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,14 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
shellComplete, completionDetected, arguments := checkShellCompleteFlag(a, arguments)

// If the completion flag was present but shell completion is disabled
// (e.g., because "--" was on the command line), do not execute the
// action. The flag has already been stripped from arguments.
if completionDetected && !shellComplete {
return nil
}

cCtx := NewContext(a, nil, &Context{Context: ctx})
cCtx.shellComplete = shellComplete
Expand Down
16 changes: 11 additions & 5 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,28 +435,34 @@ func checkHelp(cCtx *Context) bool {
return found
}

func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
func checkShellCompleteFlag(a *App, arguments []string) (bool, bool, []string) {
if !a.EnableBashCompletion {
return false, arguments
return false, false, arguments
}

pos := len(arguments) - 1
lastArg := arguments[pos]

if lastArg != "--generate-bash-completion" {
return false, arguments
return false, false, arguments
}

// The completion flag was detected — strip it from arguments regardless
// of "--" presence, so that it never leaks into the action's arguments.
stripped := make([]string, 0, len(arguments)-1)
stripped = append(stripped, arguments[:pos]...)
stripped = append(stripped, arguments[pos+1:]...)

for _, arg := range arguments {
// If arguments include "--", shell completion is disabled
// because after "--" only positional arguments are accepted.
// https://unix.stackexchange.com/a/11382
if arg == "--" {
return false, arguments
return false, true, stripped
}
}

return true, arguments[:pos]
return true, true, stripped
}

func checkCompletions(cCtx *Context) bool {
Expand Down
43 changes: 26 additions & 17 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1716,57 +1716,66 @@ GLOBAL OPTIONS:
func Test_checkShellCompleteFlag(t *testing.T) {
t.Parallel()
tests := []struct {
name string
app *App
arguments []string
wantShellCompletion bool
wantArgs []string
name string
app *App
arguments []string
wantShellCompletion bool
wantCompletionDetected bool
wantArgs []string
}{
{
name: "disable bash completion",
arguments: []string{"--generate-bash-completion"},
app: &App{},
wantShellCompletion: false,
wantArgs: []string{"--generate-bash-completion"},
name: "disable bash completion",
arguments: []string{"--generate-bash-completion"},
app: &App{},
wantShellCompletion: false,
wantCompletionDetected: false,
wantArgs: []string{"--generate-bash-completion"},
},
{
name: "--generate-bash-completion isn't used",
arguments: []string{"foo"},
app: &App{
EnableBashCompletion: true,
},
wantShellCompletion: false,
wantArgs: []string{"foo"},
wantShellCompletion: false,
wantCompletionDetected: false,
wantArgs: []string{"foo"},
},
{
name: "arguments include double dash",
arguments: []string{"--", "foo", "--generate-bash-completion"},
app: &App{
EnableBashCompletion: true,
},
wantShellCompletion: false,
wantArgs: []string{"--", "foo", "--generate-bash-completion"},
wantShellCompletion: false,
wantCompletionDetected: true,
wantArgs: []string{"--", "foo"},
},
{
name: "--generate-bash-completion",
arguments: []string{"foo", "--generate-bash-completion"},
app: &App{
EnableBashCompletion: true,
},
wantShellCompletion: true,
wantArgs: []string{"foo"},
wantShellCompletion: true,
wantCompletionDetected: true,
wantArgs: []string{"foo"},
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
shellCompletion, args := checkShellCompleteFlag(tt.app, tt.arguments)
shellCompletion, completionDetected, args := checkShellCompleteFlag(tt.app, tt.arguments)
if tt.wantShellCompletion != shellCompletion {
t.Errorf("Unexpected shell completion, got:\n%v\nexpected: %v",
shellCompletion, tt.wantShellCompletion)
}
if tt.wantCompletionDetected != completionDetected {
t.Errorf("Unexpected completion detected, got:\n%v\nexpected: %v",
completionDetected, tt.wantCompletionDetected)
}
if !reflect.DeepEqual(tt.wantArgs, args) {
t.Errorf("Unexpected arguments, got:\n%v\nexpected: %v",
args, tt.wantArgs)
Expand Down
Loading