diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index abb26e603eb..e55c6bc9e38 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -231,6 +231,34 @@ var _ = Describe("CLI", func() { }) }) + Describe("completionPluginsFlag", func() { + It("should suggest available plugins and filter out entered/deprecated ones", func() { + p1 := newMockPlugin("p1.io", "v1", projectVersion) + p2 := newMockPlugin("p2.io", "v1", projectVersion) + p3 := newMockDeprecatedPlugin("p3.io", "v1-alpha", "deprecated", projectVersion) + + c.plugins = makeMapFor(p1, p2, p3) + + k1 := plugin.KeyFor(p1) + k2 := plugin.KeyFor(p2) + + c.cmd = c.newRootCmd() + c.cmd.Flags().StringSlice(pluginsFlag, []string{}, "test usage") + + err := c.cmd.Flags().Set(pluginsFlag, k1) + Expect(err).NotTo(HaveOccurred()) + + got, directive := c.completionPluginsFlag(c.cmd, []string{}, "") + + Expect(directive).To(Equal(cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace)) + + expected := fmt.Sprintf("%s\tExternal or custom plugin", k2) + + Expect(got).To(HaveLen(1)) + Expect(got).To(ContainElement(expected)) + }) + }) + Context("buildCmd", func() { var projectFile string diff --git a/pkg/cli/root.go b/pkg/cli/root.go index b1fa1c77bc5..e9eab193b69 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -103,6 +103,7 @@ func (c CLI) newRootCmd() *cobra.Command { // Global flags for all subcommands. cmd.PersistentFlags().StringSlice(pluginsFlag, nil, "plugin keys to be used for this subcommand execution") + cobra.CheckErr(cmd.RegisterFlagCompletionFunc(pluginsFlag, c.completionPluginsFlag)) // Register --project-version on the root command so that it shows up in help. cmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), "project version") @@ -252,3 +253,48 @@ func (c CLI) getPluginTableFilteredWithOptions(filter func(plugin.Plugin) bool, return strings.Join(lines, "\n") } + +// completionPluginsFlag implements cobra.CompletionFunc and is registered +// as the flag completion function for --plugins +// We should note that flag completion does not work for comma-chained values +// but works fine when repeating the flag +func (c CLI) completionPluginsFlag( + cmd *cobra.Command, + _ []string, + _ string, +) ([]string, cobra.ShellCompDirective) { + // We filter strings that the user already passed to --plugins, + // in case the user chains the --plugins flag multiple times, + alreadyEntered, err := cmd.Flags().GetStringSlice(pluginsFlag) + if err != nil { + cobra.CheckErr(err) + } + + comps := make([]string, 0, len(c.plugins)) + + for pluginKey, p := range c.plugins { + if slices.Contains(alreadyEntered, pluginKey) { + continue + } + + // We also omit deprecated plugins from completion + if deprecated, ok := p.(plugin.Deprecated); ok { + if deprecated.DeprecationWarning() != "" { + continue + } + } + + // If the plugin provides a description, we show that + // otherwise, we show the default description + desc := "" + if describable, ok := p.(plugin.Describable); ok { + desc = describable.Description() + } else { + desc = getPluginDescription(pluginKey) + } + + comps = append(comps, cobra.CompletionWithDesc(pluginKey, desc)) + } + + return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace +}