From 7495b140e2359c47f5991bc2863674bb02dec9ea Mon Sep 17 00:00:00 2001 From: phm07 <22707808+phm07@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:41:03 +0200 Subject: [PATCH] feat: allow auto-completing context flag (#861) See #853 for more information. Closes #853 --- internal/cli/root.go | 12 +++++++ internal/state/config/options.go | 56 +++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/internal/cli/root.go b/internal/cli/root.go index 18fb655e..4a43571d 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -22,6 +22,18 @@ func NewRootCommand(s state.State) *cobra.Command { cmd.PersistentFlags().AddFlagSet(s.Config().FlagSet()) + for _, opt := range config.Options { + f := opt.GetFlagCompletionFunc() + if !opt.HasFlags(config.OptionFlagPFlag) || f == nil { + continue + } + // opt.FlagName() is prefixed with -- + flagName := opt.FlagName()[2:] + _ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return f(s.Client(), s.Config(), cmd, args, toComplete) + }) + } + cmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { var err error out := os.Stdout diff --git a/internal/state/config/options.go b/internal/state/config/options.go index 453cec81..7fd26ac4 100644 --- a/internal/state/config/options.go +++ b/internal/state/config/options.go @@ -6,9 +6,11 @@ import ( "time" "github.com/spf13/cast" + "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/hetznercloud/cli/internal/cmd/util" + "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) @@ -33,6 +35,8 @@ const ( DefaultPreferenceFlags = OptionFlagPreference | OptionFlagConfig | OptionFlagPFlag | OptionFlagEnv ) +type FlagCompletionFunc func(client hcapi2.Client, cfg Config, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) + type IOption interface { // addToFlagSet adds the option to the provided flag set addToFlagSet(fs *pflag.FlagSet) @@ -40,6 +44,9 @@ type IOption interface { GetName() string // GetDescription returns the description of the option GetDescription() string + // GetFlagCompletionFunc returns the completion function for this option's flag. + // If it doesn't exist it returns nil. + GetFlagCompletionFunc() FlagCompletionFunc // ConfigKey returns the key used in the config file. If the option is not configurable via the config file, an empty string is returned ConfigKey() string // EnvVar returns the name of the environment variable. If the option is not configurable via an environment variable, an empty string is returned @@ -80,6 +87,7 @@ var ( DefaultConfigPath(), OptionFlagPFlag|OptionFlagEnv, nil, + nil, ) OptionToken = newOpt( @@ -88,6 +96,7 @@ var ( "", OptionFlagConfig|OptionFlagEnv|OptionFlagSensitive, nil, + nil, ) OptionContext = newOpt( @@ -95,6 +104,14 @@ var ( "Currently active context", "", OptionFlagConfig|OptionFlagEnv|OptionFlagPFlag, + func(_ hcapi2.Client, cfg Config, _ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + ctxs := cfg.Contexts() + ctxNames := make([]string, 0, len(ctxs)) + for _, ctx := range ctxs { + ctxNames = append(ctxNames, ctx.Name()) + } + return ctxNames, cobra.ShellCompDirectiveDefault + }, &overrides{configKey: "active_context"}, ) @@ -104,6 +121,7 @@ var ( hcloud.Endpoint, DefaultPreferenceFlags, nil, + nil, ) OptionDebug = newOpt( @@ -112,6 +130,7 @@ var ( false, DefaultPreferenceFlags, nil, + nil, ) OptionDebugFile = newOpt( @@ -120,6 +139,7 @@ var ( "", DefaultPreferenceFlags, nil, + nil, ) OptionPollInterval = newOpt( @@ -128,6 +148,7 @@ var ( 500*time.Millisecond, DefaultPreferenceFlags, nil, + nil, ) OptionQuiet = newOpt( @@ -136,6 +157,7 @@ var ( false, DefaultPreferenceFlags, nil, + nil, ) OptionDefaultSSHKeys = newOpt( @@ -144,6 +166,7 @@ var ( []string{}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice, nil, + nil, ) OptionSortCertificate = newOpt( @@ -152,6 +175,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortDatacenter = newOpt( @@ -160,6 +184,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortFirewall = newOpt( @@ -168,6 +193,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortFloatingIP = newOpt( @@ -176,6 +202,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortImage = newOpt( @@ -184,6 +211,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortLoadBalancer = newOpt( @@ -192,6 +220,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortLocation = newOpt( @@ -200,6 +229,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortPlacementGroup = newOpt( @@ -208,6 +238,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortPrimaryIP = newOpt( @@ -216,6 +247,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortServer = newOpt( @@ -224,6 +256,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortSSHKey = newOpt( @@ -232,6 +265,7 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) OptionSortVolume = newOpt( @@ -240,15 +274,17 @@ var ( []string{"id:asc"}, (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, nil, + nil, ) ) type Option[T any] struct { - Name string - Description string - Default T - Flags OptionFlag - overrides *overrides + Name string + Description string + Default T + Flags OptionFlag + FlagCompletionFunc FlagCompletionFunc + overrides *overrides } func (o *Option[T]) Get(c Config) (T, error) { @@ -322,6 +358,10 @@ func (o *Option[T]) GetDescription() string { return o.Description } +func (o *Option[T]) GetFlagCompletionFunc() FlagCompletionFunc { + return o.FlagCompletionFunc +} + func (o *Option[T]) ConfigKey() string { if !o.HasFlags(OptionFlagConfig) { return "" @@ -423,15 +463,15 @@ func (o *Option[T]) addToFlagSet(fs *pflag.FlagSet) { } } -func newOpt[T any](name, description string, def T, flags OptionFlag, ov *overrides) *Option[T] { - o := &Option[T]{Name: name, Description: description, Default: def, Flags: flags, overrides: ov} +func newOpt[T any](name, description string, def T, flags OptionFlag, f FlagCompletionFunc, ov *overrides) *Option[T] { + o := &Option[T]{Name: name, Description: description, Default: def, Flags: flags, FlagCompletionFunc: f, overrides: ov} Options[name] = o return o } // NewTestOption is a helper function to create an option for testing purposes func NewTestOption[T any](name, description string, def T, flags OptionFlag, ov *overrides) (*Option[T], func()) { - opt := newOpt(name, description, def, flags, ov) + opt := newOpt(name, description, def, flags, nil, ov) return opt, func() { delete(Options, name) }