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

Let the commands store flagComp functions internally (and avoid global state) #2012

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
7 changes: 4 additions & 3 deletions bash_completions.go
Expand Up @@ -534,9 +534,9 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {

// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags
func prepareCustomAnnotationsForFlags(cmd *Command) {
flagCompletionMutex.RLock()
defer flagCompletionMutex.RUnlock()
for flag := range flagCompletionFunctions {
cmd.flagCompletionMutex.RLock()
defer cmd.flagCompletionMutex.RUnlock()
for flag := range cmd.flagCompletionFunctions {
// Make sure the completion script calls the __*_go_custom_completion function for
// every registered flag. We need to do this here (and not when the flag was registered
// for completion) so that we can know the root command name for the prefix
Expand Down Expand Up @@ -644,6 +644,7 @@ func writeCmdAliases(buf io.StringWriter, cmd *Command) {
WriteStringAndCheck(buf, ` fi`)
WriteStringAndCheck(buf, "\n")
}

func writeArgAliases(buf io.StringWriter, cmd *Command) {
WriteStringAndCheck(buf, " noun_aliases=()\n")
sort.Strings(cmd.ArgAliases)
Expand Down
8 changes: 8 additions & 0 deletions command.go
Expand Up @@ -26,6 +26,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"

flag "github.com/spf13/pflag"
)
Expand Down Expand Up @@ -158,6 +159,13 @@ type Command struct {
// that we can use on every pflag set and children commands
globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName

// flagsCompletions contrains completions for arbitrary lists of flags.
// Those flags may or may not actually strictly belong to the command in the function,
// but registering completions for them through the command allows for garbage-collecting.
flagCompletionFunctions map[*flag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
maxlandon marked this conversation as resolved.
Show resolved Hide resolved
// lock for reading and writing from flagCompletionFunctions
flagCompletionMutex *sync.RWMutex
maxlandon marked this conversation as resolved.
Show resolved Hide resolved

// usageFunc is usage func defined by user.
usageFunc func(*Command) error
// usageTemplate is usage template defined by user.
Expand Down
24 changes: 11 additions & 13 deletions completions.go
Expand Up @@ -18,7 +18,6 @@ import (
"fmt"
"os"
"strings"
"sync"

"github.com/spf13/pflag"
)
Expand All @@ -33,10 +32,10 @@ const (
)

// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}

// lock for reading and writing from flagCompletionFunctions
var flagCompletionMutex = &sync.RWMutex{}
// var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
maxlandon marked this conversation as resolved.
Show resolved Hide resolved
//
// // lock for reading and writing from flagCompletionFunctions
// var flagCompletionMutex = &sync.RWMutex{}

// ShellCompDirective is a bit map representing the different behaviors the shell
// can be instructed to have once completions have been provided.
Expand Down Expand Up @@ -135,13 +134,13 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman
if flag == nil {
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
}
flagCompletionMutex.Lock()
defer flagCompletionMutex.Unlock()
c.flagCompletionMutex.Lock()
defer c.flagCompletionMutex.Unlock()

if _, exists := flagCompletionFunctions[flag]; exists {
if _, exists := c.flagCompletionFunctions[flag]; exists {
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
}
flagCompletionFunctions[flag] = f
c.flagCompletionFunctions[flag] = f
return nil
}

Expand Down Expand Up @@ -479,9 +478,9 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// Find the completion function for the flag or command
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
if flag != nil && flagCompletion {
flagCompletionMutex.RLock()
completionFn = flagCompletionFunctions[flag]
flagCompletionMutex.RUnlock()
finalCmd.flagCompletionMutex.RLock()
maxlandon marked this conversation as resolved.
Show resolved Hide resolved
completionFn = finalCmd.flagCompletionFunctions[flag]
finalCmd.flagCompletionMutex.RUnlock()
} else {
completionFn = finalCmd.ValidArgsFunction
}
Expand Down Expand Up @@ -805,7 +804,6 @@ to your powershell profile.
return cmd.Root().GenPowerShellCompletion(out)
}
return cmd.Root().GenPowerShellCompletionWithDesc(out)

},
}
if haveNoDescFlag {
Expand Down