From 88a3d6eaea65971b3e4884b5abbcccc4d7a79f72 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 11 Apr 2020 14:06:56 -0400 Subject: [PATCH] feat(comp): Move custom completions to Cobra 1.0 Cobra 1.0 introduces custom Go completions. This commit replaces Helm's own solution to use Cobra's solution. This allows to completely remove Helm's internal "completion" package. Signed-off-by: Marc Khouzam --- cmd/helm/flags.go | 13 +- cmd/helm/get_all.go | 25 +- cmd/helm/get_hooks.go | 28 +- cmd/helm/get_manifest.go | 28 +- cmd/helm/get_notes.go | 25 +- cmd/helm/get_values.go | 26 +- cmd/helm/history.go | 21 +- cmd/helm/install.go | 13 +- cmd/helm/list.go | 9 +- cmd/helm/load_plugins.go | 22 +- cmd/helm/plugin_uninstall.go | 16 +- cmd/helm/plugin_update.go | 16 +- cmd/helm/pull.go | 15 +- cmd/helm/release_testing.go | 15 +- cmd/helm/repo_remove.go | 10 +- cmd/helm/rollback.go | 15 +- cmd/helm/root.go | 40 +- cmd/helm/search_repo.go | 23 +- cmd/helm/show.go | 44 +- cmd/helm/status.go | 26 +- cmd/helm/template.go | 9 +- cmd/helm/testdata/output/output-comp.txt | 1 + cmd/helm/testdata/output/plugin_args_comp.txt | 1 + .../testdata/output/plugin_args_flag_comp.txt | 1 + .../output/plugin_args_many_args_comp.txt | 1 + .../testdata/output/plugin_args_ns_comp.txt | 1 + .../output/plugin_echo_bad_directive.txt | 1 + .../output/plugin_echo_no_directive.txt | 1 + cmd/helm/testdata/output/revision-comp.txt | 1 + .../output/revision-wrong-args-comp.txt | 1 + cmd/helm/testdata/output/status-comp.txt | 1 + .../output/status-wrong-args-comp.txt | 1 + cmd/helm/uninstall.go | 15 +- cmd/helm/upgrade.go | 21 +- internal/completion/complete.go | 399 ------------------ 35 files changed, 235 insertions(+), 650 deletions(-) delete mode 100644 internal/completion/complete.go diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 6b9c3050b..d4cd88fef 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -18,12 +18,12 @@ package main import ( "fmt" + "log" "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/values" @@ -55,19 +55,22 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { // bindOutputFlag will add the output flag to the given command and bind the // value to the given format pointer func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { - f := cmd.Flags() - flag := f.VarPF(newOutputValue(output.Table, varRef), outputFlag, "o", + cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o", fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", "))) - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var formatNames []string for _, format := range output.Formats() { if strings.HasPrefix(format, toComplete) { formatNames = append(formatNames, format) } } - return formatNames, completion.BashCompDirectiveDefault + return formatNames, cobra.ShellCompDirectiveDefault }) + + if err != nil { + log.Fatal(err) + } } type outputValue output.Format diff --git a/cmd/helm/get_all.go b/cmd/helm/get_all.go index 7d893d7e0..1ebc7e387 100644 --- a/cmd/helm/get_all.go +++ b/cmd/helm/get_all.go @@ -18,11 +18,11 @@ package main import ( "io" + "log" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" ) @@ -41,6 +41,12 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download all information for a named release", Long: getAllHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { @@ -57,24 +63,19 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + f.StringVar(&template, "template", "", "go template for formatting the output, eg: {{.Release.Name}}") return cmd diff --git a/cmd/helm/get_hooks.go b/cmd/helm/get_hooks.go index c2087b1ba..6453c30eb 100644 --- a/cmd/helm/get_hooks.go +++ b/cmd/helm/get_hooks.go @@ -19,11 +19,11 @@ package main import ( "fmt" "io" + "log" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -41,6 +41,12 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download all hooks for a named release", Long: getHooksHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { @@ -53,23 +59,17 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - - f := cmd.Flags() - f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + return cmd } diff --git a/cmd/helm/get_manifest.go b/cmd/helm/get_manifest.go index f332befd9..048bf36b0 100644 --- a/cmd/helm/get_manifest.go +++ b/cmd/helm/get_manifest.go @@ -19,11 +19,11 @@ package main import ( "fmt" "io" + "log" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -43,6 +43,12 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Short: "download the manifest for a named release", Long: getManifestHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { @@ -53,23 +59,17 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - - f := cmd.Flags() - f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + return cmd } diff --git a/cmd/helm/get_notes.go b/cmd/helm/get_notes.go index 4491bd9ba..88d494fc4 100644 --- a/cmd/helm/get_notes.go +++ b/cmd/helm/get_notes.go @@ -19,11 +19,11 @@ package main import ( "fmt" "io" + "log" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -39,6 +39,12 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download the notes for a named release", Long: getNotesHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { @@ -51,23 +57,18 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + return cmd } diff --git a/cmd/helm/get_values.go b/cmd/helm/get_values.go index a8c5acc5e..fa7b10fed 100644 --- a/cmd/helm/get_values.go +++ b/cmd/helm/get_values.go @@ -19,11 +19,11 @@ package main import ( "fmt" "io" + "log" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" ) @@ -46,6 +46,12 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download the values file for a named release", Long: getValuesHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { vals, err := client.Run(args[0]) if err != nil { @@ -55,23 +61,19 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + + if err != nil { + log.Fatal(err) + } + f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values") bindOutputFlag(cmd, &outfmt) diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 3ef542e58..b49c52990 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/cli/output" @@ -61,6 +60,12 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "fetch release history", Aliases: []string{"hist"}, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { history, err := getHistory(client, args[0]) if err != nil { @@ -71,14 +76,6 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history") bindOutputFlag(cmd, &outfmt) @@ -187,7 +184,7 @@ func min(x, y int) int { return y } -func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, completion.BashCompDirective) { +func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) { client := action.NewHistory(cfg) var revisions []string @@ -195,7 +192,7 @@ func compListRevisions(cfg *action.Configuration, releaseName string) ([]string, for _, release := range hist { revisions = append(revisions, strconv.Itoa(release.Version)) } - return revisions, completion.BashCompDirectiveDefault + return revisions, cobra.ShellCompDirectiveDefault } - return nil, completion.BashCompDirectiveError + return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 44f7336c0..b489c262d 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/pflag" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -114,6 +113,9 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "install a chart", Long: installDesc, Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstall(args, toComplete, client) + }, RunE: func(_ *cobra.Command, args []string) error { rel, err := runInstall(args, client, valueOpts, out) if err != nil { @@ -124,11 +126,6 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - return compInstall(args, toComplete, client) - }) - addInstallFlags(cmd.Flags(), client, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) @@ -242,7 +239,7 @@ func isChartInstallable(ch *chart.Chart) (bool, error) { } // Provide dynamic auto-completion for the install and template commands -func compInstall(args []string, toComplete string, client *action.Install) ([]string, completion.BashCompDirective) { +func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) { requiredArgs := 1 if client.GenerateName { requiredArgs = 0 @@ -250,5 +247,5 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st if len(args) == requiredArgs { return compListCharts(toComplete, true) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 08d6beb79..18e5ffe45 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/release" @@ -186,8 +185,8 @@ func (r *releaseListWriter) WriteYAML(out io.Writer) error { } // Provide dynamic auto-completion for release names -func compListReleases(toComplete string, cfg *action.Configuration) ([]string, completion.BashCompDirective) { - completion.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete)) +func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) { + cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug) client := action.NewList(cfg) client.All = true @@ -197,7 +196,7 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c client.SetStateMask() results, err := client.Run() if err != nil { - return nil, completion.BashCompDirectiveDefault + return nil, cobra.ShellCompDirectiveDefault } var choices []string @@ -205,5 +204,5 @@ func compListReleases(toComplete string, cfg *action.Configuration) ([]string, c choices = append(choices, res.Name) } - return choices, completion.BashCompDirectiveNoFileComp + return choices, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index 36de50135..e439c8407 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -32,7 +32,6 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/yaml" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/plugin" ) @@ -97,6 +96,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { // This passes all the flags to the subcommand. DisableFlagParsing: true, } + // TODO: Make sure a command with this name does not already exist. baseCmd.AddCommand(c) @@ -105,7 +105,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { // We only do this when necessary (for the "completion" and "__complete" commands) to avoid the // risk of a rogue plugin affecting Helm's normal behavior. subCmd, _, err := baseCmd.Find(os.Args[1:]) - if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == completion.CompRequestCmd)) || + if (err == nil && (subCmd.Name() == "completion" || subCmd.Name() == cobra.ShellCompRequestCmd)) || /* for the tests */ subCmd == baseCmd.Root() { loadCompletionForPlugin(c, plug) } @@ -247,9 +247,9 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug // Only setup dynamic completion if there are no sub-commands. This avoids // calling plugin.complete at every completion, which greatly simplifies // development of plugin.complete for plugin developers. - completion.RegisterValidArgsFunc(baseCmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + baseCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return pluginDynamicComp(plugin, cmd, args, toComplete) - }) + } } // Create fake flags. @@ -322,12 +322,12 @@ func loadFile(path string) (*pluginCommand, error) { // pluginDynamicComp call the plugin.complete script of the plugin (if available) // to obtain the dynamic completion choices. It must pass all the flags and sub-commands // specified in the command-line to the plugin.complete executable (except helm's global flags) -func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { +func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { md := plug.Metadata u, err := processParent(cmd, args) if err != nil { - return nil, completion.BashCompDirectiveError + return nil, cobra.ShellCompDirectiveError } // We will call the dynamic completion script of the plugin @@ -343,12 +343,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t } plugin.SetupPluginEnv(settings, md.Name, plug.Dir) - completion.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv)) + cobra.CompDebugln(fmt.Sprintf("calling %s with args %v", main, argv), settings.Debug) buf := new(bytes.Buffer) if err := callPluginExecutable(md.Name, main, argv, buf); err != nil { // The dynamic completion file is optional for a plugin, so this error is ok. - completion.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error())) - return nil, completion.BashCompDirectiveDefault + cobra.CompDebugln(fmt.Sprintf("Unable to call %s: %v", main, err.Error()), settings.Debug) + return nil, cobra.ShellCompDirectiveDefault } var completions []string @@ -361,12 +361,12 @@ func pluginDynamicComp(plug *plugin.Plugin, cmd *cobra.Command, args []string, t // Check if the last line of output is of the form :, which // indicates the BashCompletionDirective. - directive := completion.BashCompDirectiveDefault + directive := cobra.ShellCompDirectiveDefault if len(completions) > 0 { lastLine := completions[len(completions)-1] if len(lastLine) > 1 && lastLine[0] == ':' { if strInt, err := strconv.Atoi(lastLine[1:]); err == nil { - directive = completion.BashCompDirective(strInt) + directive = cobra.ShellCompDirective(strInt) completions = completions[:len(completions)-1] } } diff --git a/cmd/helm/plugin_uninstall.go b/cmd/helm/plugin_uninstall.go index 66cdaccdc..b2290fb9b 100644 --- a/cmd/helm/plugin_uninstall.go +++ b/cmd/helm/plugin_uninstall.go @@ -24,7 +24,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/plugin" ) @@ -39,6 +38,12 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command { Use: "uninstall ...", Aliases: []string{"rm", "remove"}, Short: "uninstall one or more Helm plugins", + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp + }, PreRunE: func(cmd *cobra.Command, args []string) error { return o.complete(args) }, @@ -46,15 +51,6 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command { return o.run(out) }, } - - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp - }) - return cmd } diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go index 23f840b4a..c46444e0d 100644 --- a/cmd/helm/plugin_update.go +++ b/cmd/helm/plugin_update.go @@ -24,7 +24,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v3/pkg/plugin/installer" ) @@ -40,6 +39,12 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command { Use: "update ...", Aliases: []string{"up"}, Short: "update one or more Helm plugins", + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListPlugins(toComplete), cobra.ShellCompDirectiveNoFileComp + }, PreRunE: func(cmd *cobra.Command, args []string) error { return o.complete(args) }, @@ -47,15 +52,6 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command { return o.run(out) }, } - - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListPlugins(toComplete), completion.BashCompDirectiveNoFileComp - }) - return cmd } diff --git a/cmd/helm/pull.go b/cmd/helm/pull.go index 16cd10467..d10c629db 100644 --- a/cmd/helm/pull.go +++ b/cmd/helm/pull.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -51,6 +50,12 @@ func newPullCmd(out io.Writer) *cobra.Command { Aliases: []string{"fetch"}, Long: pullDesc, Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListCharts(toComplete, false) + }, RunE: func(cmd *cobra.Command, args []string) error { client.Settings = settings if client.Version == "" && client.Devel { @@ -69,14 +74,6 @@ func newPullCmd(out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListCharts(toComplete, false) - }) - f := cmd.Flags() f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it") diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 036d96794..37cac6186 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/cli/output" ) @@ -46,6 +45,12 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Short: "run tests for a release", Long: releaseTestHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() rel, runErr := client.Run(args[0]) @@ -72,14 +77,6 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") diff --git a/cmd/helm/repo_remove.go b/cmd/helm/repo_remove.go index 5dad4e5e0..e6e9cb681 100644 --- a/cmd/helm/repo_remove.go +++ b/cmd/helm/repo_remove.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/repo" ) @@ -45,6 +44,9 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command { Aliases: []string{"rm"}, Short: "remove one or more chart repositories", Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache @@ -52,12 +54,6 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command { return o.run(out) }, } - - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - return compListRepos(toComplete, args), completion.BashCompDirectiveNoFileComp - }) - return cmd } diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 745e910b2..3b336d0ff 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -25,7 +25,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -47,6 +46,12 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "roll back a release to a previous revision", Long: rollbackDesc, Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 1 { ver, err := strconv.Atoi(args[1]) @@ -65,14 +70,6 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 143745f29..69f5855d9 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "io" + "log" "strings" "github.com/spf13/cobra" @@ -27,7 +28,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/pkg/action" ) @@ -70,24 +70,22 @@ By default, the default directories depend on the Operating System. The defaults func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command { cmd := &cobra.Command{ - Use: "helm", - Short: "The Helm package manager for Kubernetes.", - Long: globalUsage, - SilenceUsage: true, - BashCompletionFunction: completion.GetBashCustomFunction(), + Use: "helm", + Short: "The Helm package manager for Kubernetes.", + Long: globalUsage, + SilenceUsage: true, } flags := cmd.PersistentFlags() settings.AddFlags(flags) // Setup shell completion for the namespace flag - flag := flags.Lookup("namespace") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if client, err := actionConfig.KubernetesClientSet(); err == nil { // Choose a long enough timeout that the user notices somethings is not working // but short enough that the user is not made to wait very long to := int64(3) - completion.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to)) + cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug) nsNames := []string{} if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil { @@ -96,16 +94,19 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string nsNames = append(nsNames, ns.Name) } } - return nsNames, completion.BashCompDirectiveNoFileComp + return nsNames, cobra.ShellCompDirectiveNoFileComp } } - return nil, completion.BashCompDirectiveDefault + return nil, cobra.ShellCompDirectiveDefault }) + if err != nil { + log.Fatal(err) + } + // Setup shell completion for the kube-context flag - flag = flags.Lookup("kube-context") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - completion.CompDebugln("About to get the different kube-contexts") + err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cobra.CompDebugln("About to get the different kube-contexts", settings.Debug) loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() if len(settings.KubeConfig) > 0 { @@ -120,11 +121,15 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string ctxs = append(ctxs, name) } } - return ctxs, completion.BashCompDirectiveNoFileComp + return ctxs, cobra.ShellCompDirectiveNoFileComp } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + // We can safely ignore any errors that flags.Parse encounters since // those errors will be caught later during the call to cmd.Execution. // This call is required to gather configuration information prior to @@ -164,9 +169,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string // Hidden documentation generator command: 'helm docs' newDocsCmd(out), - - // Setup the special hidden __complete command to allow for dynamic auto-completion - completion.NewCompleteCmd(settings, out), ) // Add *experimental* subcommands diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index c7f0da974..dd530379b 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -32,7 +32,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/search" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/repo" @@ -290,8 +289,8 @@ func compListChartsOfRepo(repoName string, prefix string) []string { // Provide dynamic auto-completion for commands that operate on charts (e.g., helm show) // When true, the includeFiles argument indicates that completion should include local files (e.g., local charts) -func compListCharts(toComplete string, includeFiles bool) ([]string, completion.BashCompDirective) { - completion.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete)) +func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.ShellCompDirective) { + cobra.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete), settings.Debug) noSpace := false noFile := false @@ -312,7 +311,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion. noSpace = true } } - completion.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions)) + cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug) // Now handle completions for url prefixes for _, url := range []string{"https://", "http://", "file://"} { @@ -328,7 +327,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion. noSpace = true } } - completion.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions)) + cobra.CompDebugln(fmt.Sprintf("Completions after urls: %v", completions), settings.Debug) // Finally, provide file completion if we need to. // We only do this if: @@ -347,22 +346,22 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, completion. } } } - completion.CompDebugln(fmt.Sprintf("Completions after files: %v", completions)) + cobra.CompDebugln(fmt.Sprintf("Completions after files: %v", completions), settings.Debug) // If the user didn't provide any input to completion, // we provide a hint that a path can also be used if includeFiles && len(toComplete) == 0 { completions = append(completions, "./", "/") } - completion.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions)) + cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug) - directive := completion.BashCompDirectiveDefault + directive := cobra.ShellCompDirectiveDefault if noFile { - directive = directive | completion.BashCompDirectiveNoFileComp + directive = directive | cobra.ShellCompDirectiveNoFileComp } if noSpace { - directive = directive | completion.BashCompDirectiveNoSpace - // The completion.BashCompDirective flags do not work for zsh right now. + directive = directive | cobra.ShellCompDirectiveNoSpace + // The cobra.ShellCompDirective flags do not work for zsh right now. // We handle it ourselves instead. completions = compEnforceNoSpace(completions) } @@ -380,7 +379,7 @@ func compEnforceNoSpace(completions []string) []string { // We only do this if there is a single choice for completion. if len(completions) == 1 { completions = append(completions, completions[0]+".") - completion.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions)) + cobra.CompDebugln(fmt.Sprintf("compEnforceNoSpace: completions now are %v", completions), settings.Debug) } return completions } diff --git a/cmd/helm/show.go b/cmd/helm/show.go index 91c35a53c..c78069a09 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -63,18 +62,19 @@ func newShowCmd(out io.Writer) *cobra.Command { } // Function providing dynamic auto-completion - validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp } return compListCharts(toComplete, true) } all := &cobra.Command{ - Use: "all [CHART]", - Short: "show all information of the chart", - Long: showAllDesc, - Args: require.ExactArgs(1), + Use: "all [CHART]", + Short: "show all information of the chart", + Long: showAllDesc, + Args: require.ExactArgs(1), + ValidArgsFunction: validArgsFunc, RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll output, err := runShow(args, client) @@ -87,10 +87,11 @@ func newShowCmd(out io.Writer) *cobra.Command { } valuesSubCmd := &cobra.Command{ - Use: "values [CHART]", - Short: "show the chart's values", - Long: showValuesDesc, - Args: require.ExactArgs(1), + Use: "values [CHART]", + Short: "show the chart's values", + Long: showValuesDesc, + Args: require.ExactArgs(1), + ValidArgsFunction: validArgsFunc, RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues output, err := runShow(args, client) @@ -103,10 +104,11 @@ func newShowCmd(out io.Writer) *cobra.Command { } chartSubCmd := &cobra.Command{ - Use: "chart [CHART]", - Short: "show the chart's definition", - Long: showChartDesc, - Args: require.ExactArgs(1), + Use: "chart [CHART]", + Short: "show the chart's definition", + Long: showChartDesc, + Args: require.ExactArgs(1), + ValidArgsFunction: validArgsFunc, RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart output, err := runShow(args, client) @@ -119,10 +121,11 @@ func newShowCmd(out io.Writer) *cobra.Command { } readmeSubCmd := &cobra.Command{ - Use: "readme [CHART]", - Short: "show the chart's README", - Long: readmeChartDesc, - Args: require.ExactArgs(1), + Use: "readme [CHART]", + Short: "show the chart's README", + Long: readmeChartDesc, + Args: require.ExactArgs(1), + ValidArgsFunction: validArgsFunc, RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme output, err := runShow(args, client) @@ -138,9 +141,6 @@ func newShowCmd(out io.Writer) *cobra.Command { for _, subCmd := range cmds { addShowFlags(subCmd, client) showCommand.AddCommand(subCmd) - - // Register the completion function for each subcommand - completion.RegisterValidArgsFunc(subCmd, validArgsFunc) } return showCommand diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 2efb2006c..5913d08df 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -19,13 +19,13 @@ package main import ( "fmt" "io" + "log" "strings" "time" "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli/output" @@ -53,6 +53,12 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "display the status of the named release", Long: statusHelp, Args: require.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { rel, err := client.Run(args[0]) if err != nil { @@ -66,25 +72,21 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision") - flag := f.Lookup("revision") - completion.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { + + err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(cfg, args[0]) } - return nil, completion.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp }) + if err != nil { + log.Fatal(err) + } + bindOutputFlag(cmd, &outfmt) return cmd diff --git a/cmd/helm/template.go b/cmd/helm/template.go index a4438b50c..0fc29e189 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -28,7 +28,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli/values" @@ -56,6 +55,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "locally render templates", Long: templateDesc, Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstall(args, toComplete, client) + }, RunE: func(_ *cobra.Command, args []string) error { client.DryRun = true client.ReleaseName = "RELEASE-NAME" @@ -136,11 +138,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - return compInstall(args, toComplete, client) - }) - f := cmd.Flags() addInstallFlags(f, client, valueOpts) f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") diff --git a/cmd/helm/testdata/output/output-comp.txt b/cmd/helm/testdata/output/output-comp.txt index de5f16f1d..be574756b 100644 --- a/cmd/helm/testdata/output/output-comp.txt +++ b/cmd/helm/testdata/output/output-comp.txt @@ -2,3 +2,4 @@ table json yaml :0 +Completion ended with directive: ShellCompDirectiveDefault diff --git a/cmd/helm/testdata/output/plugin_args_comp.txt b/cmd/helm/testdata/output/plugin_args_comp.txt index 8fb01cc23..007112d31 100644 --- a/cmd/helm/testdata/output/plugin_args_comp.txt +++ b/cmd/helm/testdata/output/plugin_args_comp.txt @@ -3,3 +3,4 @@ Namespace: default Num args received: 1 Args received: :4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/plugin_args_flag_comp.txt b/cmd/helm/testdata/output/plugin_args_flag_comp.txt index 92f0e58a8..c7a09e3fa 100644 --- a/cmd/helm/testdata/output/plugin_args_flag_comp.txt +++ b/cmd/helm/testdata/output/plugin_args_flag_comp.txt @@ -3,3 +3,4 @@ Namespace: default Num args received: 2 Args received: --myflag :4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt index 86fa768bb..f3c386b6d 100644 --- a/cmd/helm/testdata/output/plugin_args_many_args_comp.txt +++ b/cmd/helm/testdata/output/plugin_args_many_args_comp.txt @@ -3,3 +3,4 @@ Namespace: mynamespace Num args received: 2 Args received: --myflag start :2 +Completion ended with directive: ShellCompDirectiveNoSpace diff --git a/cmd/helm/testdata/output/plugin_args_ns_comp.txt b/cmd/helm/testdata/output/plugin_args_ns_comp.txt index e12867daa..26cd79b98 100644 --- a/cmd/helm/testdata/output/plugin_args_ns_comp.txt +++ b/cmd/helm/testdata/output/plugin_args_ns_comp.txt @@ -3,3 +3,4 @@ Namespace: mynamespace Num args received: 1 Args received: :2 +Completion ended with directive: ShellCompDirectiveNoSpace diff --git a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt index f4b86cd47..8038b9525 100644 --- a/cmd/helm/testdata/output/plugin_echo_bad_directive.txt +++ b/cmd/helm/testdata/output/plugin_echo_bad_directive.txt @@ -3,3 +3,4 @@ Namespace: default Num args received: 1 Args received: :0 +Completion ended with directive: ShellCompDirectiveDefault diff --git a/cmd/helm/testdata/output/plugin_echo_no_directive.txt b/cmd/helm/testdata/output/plugin_echo_no_directive.txt index 6266dd4d9..7001be0e9 100644 --- a/cmd/helm/testdata/output/plugin_echo_no_directive.txt +++ b/cmd/helm/testdata/output/plugin_echo_no_directive.txt @@ -3,3 +3,4 @@ Namespace: mynamespace Num args received: 1 Args received: :0 +Completion ended with directive: ShellCompDirectiveDefault diff --git a/cmd/helm/testdata/output/revision-comp.txt b/cmd/helm/testdata/output/revision-comp.txt index b4799f059..d06947d9d 100644 --- a/cmd/helm/testdata/output/revision-comp.txt +++ b/cmd/helm/testdata/output/revision-comp.txt @@ -3,3 +3,4 @@ 10 11 :0 +Completion ended with directive: ShellCompDirectiveDefault diff --git a/cmd/helm/testdata/output/revision-wrong-args-comp.txt b/cmd/helm/testdata/output/revision-wrong-args-comp.txt index b6f867176..8d9fad576 100644 --- a/cmd/helm/testdata/output/revision-wrong-args-comp.txt +++ b/cmd/helm/testdata/output/revision-wrong-args-comp.txt @@ -1 +1,2 @@ :4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/status-comp.txt b/cmd/helm/testdata/output/status-comp.txt index c97882964..8d4d21df7 100644 --- a/cmd/helm/testdata/output/status-comp.txt +++ b/cmd/helm/testdata/output/status-comp.txt @@ -1,3 +1,4 @@ aramis athos :4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/testdata/output/status-wrong-args-comp.txt b/cmd/helm/testdata/output/status-wrong-args-comp.txt index b6f867176..8d9fad576 100644 --- a/cmd/helm/testdata/output/status-wrong-args-comp.txt +++ b/cmd/helm/testdata/output/status-wrong-args-comp.txt @@ -1 +1,2 @@ :4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/cmd/helm/uninstall.go b/cmd/helm/uninstall.go index 85fa822bd..509918e53 100644 --- a/cmd/helm/uninstall.go +++ b/cmd/helm/uninstall.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" ) @@ -48,6 +47,12 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "uninstall a release", Long: uninstallDesc, Args: require.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compListReleases(toComplete, cfg) + }, RunE: func(cmd *cobra.Command, args []string) error { for i := 0; i < len(args); i++ { @@ -65,14 +70,6 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) != 0 { - return nil, completion.BashCompDirectiveNoFileComp - } - return compListReleases(toComplete, cfg) - }) - f := cmd.Flags() f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index f8103b485..1db899eae 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -25,7 +25,6 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/cli/output" @@ -72,6 +71,15 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "upgrade a release", Long: upgradeDesc, Args: require.ExactArgs(2), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return compListReleases(toComplete, cfg) + } + if len(args) == 1 { + return compListCharts(toComplete, true) + } + return nil, cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() @@ -155,17 +163,6 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - // Function providing dynamic auto-completion - completion.RegisterValidArgsFunc(cmd, func(cmd *cobra.Command, args []string, toComplete string) ([]string, completion.BashCompDirective) { - if len(args) == 0 { - return compListReleases(toComplete, cfg) - } - if len(args) == 1 { - return compListCharts(toComplete, true) - } - return nil, completion.BashCompDirectiveNoFileComp - }) - f := cmd.Flags() f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") diff --git a/internal/completion/complete.go b/internal/completion/complete.go deleted file mode 100644 index 545f5b0dd..000000000 --- a/internal/completion/complete.go +++ /dev/null @@ -1,399 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package completion - -import ( - "errors" - "fmt" - "io" - "log" - "os" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/cli" -) - -// ================================================================================== -// The below code supports dynamic shell completion in Go. -// This should ultimately be pushed down into Cobra. -// ================================================================================== - -// CompRequestCmd Hidden command to request completion results from the program. -// Used by the shell completion script. -const CompRequestCmd = "__complete" - -// Global map allowing to find completion functions for commands or flags. -var validArgsFunctions = map[interface{}]func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective){} - -// BashCompDirective is a bit map representing the different behaviors the shell -// can be instructed to have once completions have been provided. -type BashCompDirective int - -const ( - // BashCompDirectiveError indicates an error occurred and completions should be ignored. - BashCompDirectiveError BashCompDirective = 1 << iota - - // BashCompDirectiveNoSpace indicates that the shell should not add a space - // after the completion even if there is a single completion provided. - BashCompDirectiveNoSpace - - // BashCompDirectiveNoFileComp indicates that the shell should not provide - // file completion even when no completion is provided. - // This currently does not work for zsh or bash < 4 - BashCompDirectiveNoFileComp - - // BashCompDirectiveDefault indicates to let the shell perform its default - // behavior after completions have been provided. - BashCompDirectiveDefault BashCompDirective = 0 -) - -// GetBashCustomFunction returns the bash code to handle custom go completion -// This should eventually be provided by Cobra -func GetBashCustomFunction() string { - return fmt.Sprintf(` -__helm_custom_func() -{ - __helm_debug "${FUNCNAME[0]}: c is $c, words[@] is ${words[@]}, #words[@] is ${#words[@]}" - __helm_debug "${FUNCNAME[0]}: cur is ${cur}, cword is ${cword}, words is ${words}" - - local out requestComp lastParam lastChar - requestComp="${words[0]} %[1]s ${words[@]:1}" - - lastParam=${words[$((${#words[@]}-1))]} - lastChar=${lastParam:$((${#lastParam}-1)):1} - __helm_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" - - if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then - # If the last parameter is complete (there is a space following it) - # We add an extra empty parameter so we can indicate this to the go method. - __helm_debug "${FUNCNAME[0]}: Adding extra empty parameter" - requestComp="${requestComp} \"\"" - fi - - __helm_debug "${FUNCNAME[0]}: calling ${requestComp}" - # Use eval to handle any environment variables and such - out=$(eval ${requestComp} 2>/dev/null) - - # Extract the directive int at the very end of the output following a : - directive=${out##*:} - # Remove the directive - out=${out%%:*} - if [ "${directive}" = "${out}" ]; then - # There is not directive specified - directive=0 - fi - __helm_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" - __helm_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" - - if [ $((${directive} & %[2]d)) -ne 0 ]; then - __helm_debug "${FUNCNAME[0]}: received error, completion failed" - else - if [ $((${directive} & %[3]d)) -ne 0 ]; then - if [[ $(type -t compopt) = "builtin" ]]; then - __helm_debug "${FUNCNAME[0]}: activating no space" - compopt -o nospace - fi - fi - if [ $((${directive} & %[4]d)) -ne 0 ]; then - if [[ $(type -t compopt) = "builtin" ]]; then - __helm_debug "${FUNCNAME[0]}: activating no file completion" - compopt +o default - fi - fi - - while IFS='' read -r comp; do - COMPREPLY+=("$comp") - done < <(compgen -W "${out[*]}" -- "$cur") - fi -} -`, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp) -} - -// RegisterValidArgsFunc should be called to register a function to provide argument completion for a command -func RegisterValidArgsFunc(cmd *cobra.Command, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) { - if _, exists := validArgsFunctions[cmd]; exists { - log.Fatal(fmt.Sprintf("RegisterValidArgsFunc: command '%s' already registered", cmd.Name())) - } - validArgsFunctions[cmd] = f -} - -// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag -func RegisterFlagCompletionFunc(flag *pflag.Flag, f func(cmd *cobra.Command, args []string, toComplete string) ([]string, BashCompDirective)) { - if _, exists := validArgsFunctions[flag]; exists { - log.Fatal(fmt.Sprintf("RegisterFlagCompletionFunc: flag '%s' already registered", flag.Name)) - } - validArgsFunctions[flag] = f - - // Make sure the completion script call the __helm_custom_func for the registered flag. - // This is essential to make the = form work. E.g., helm -n= or helm status --output= - if flag.Annotations == nil { - flag.Annotations = map[string][]string{} - } - flag.Annotations[cobra.BashCompCustom] = []string{"__helm_custom_func"} -} - -var debug = true - -// Returns a string listing the different directive enabled in the specified parameter -func (d BashCompDirective) string() string { - var directives []string - if d&BashCompDirectiveError != 0 { - directives = append(directives, "BashCompDirectiveError") - } - if d&BashCompDirectiveNoSpace != 0 { - directives = append(directives, "BashCompDirectiveNoSpace") - } - if d&BashCompDirectiveNoFileComp != 0 { - directives = append(directives, "BashCompDirectiveNoFileComp") - } - if len(directives) == 0 { - directives = append(directives, "BashCompDirectiveDefault") - } - - if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { - return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d) - } - return strings.Join(directives, ", ") -} - -// NewCompleteCmd add a special hidden command that an be used to request completions -func NewCompleteCmd(settings *cli.EnvSettings, out io.Writer) *cobra.Command { - debug = settings.Debug - return &cobra.Command{ - Use: fmt.Sprintf("%s [command-line]", CompRequestCmd), - DisableFlagsInUseLine: true, - Hidden: true, - DisableFlagParsing: true, - Args: require.MinimumNArgs(1), - Short: "Request shell completion choices for the specified command-line", - Long: fmt.Sprintf("%s is a special command that is used by the shell completion logic\n%s", - CompRequestCmd, "to request completion choices for the specified command-line."), - Run: func(cmd *cobra.Command, args []string) { - CompDebugln(fmt.Sprintf("%s was called with args %v", cmd.Name(), args)) - - // The last argument, which is not complete, should not be part of the list of arguments - toComplete := args[len(args)-1] - trimmedArgs := args[:len(args)-1] - - // Find the real command for which completion must be performed - finalCmd, finalArgs, err := cmd.Root().Find(trimmedArgs) - if err != nil { - // Unable to find the real command. E.g., helm invalidCmd - CompDebugln(fmt.Sprintf("Unable to find a command for arguments: %v", trimmedArgs)) - return - } - - CompDebugln(fmt.Sprintf("Found final command '%s', with finalArgs %v", finalCmd.Name(), finalArgs)) - - var flag *pflag.Flag - if !finalCmd.DisableFlagParsing { - // We only do flag completion if we are allowed to parse flags - // This is important for helm plugins which need to do their own flag completion. - flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) - if err != nil { - // Error while attempting to parse flags - CompErrorln(err.Error()) - return - } - } - - // Parse the flags and extract the arguments to prepare for calling the completion function - if err = finalCmd.ParseFlags(finalArgs); err != nil { - CompErrorln(fmt.Sprintf("Error while parsing flags from args %v: %s", finalArgs, err.Error())) - return - } - - // We only remove the flags from the arguments if DisableFlagParsing is not set. - // This is important for helm plugins, which need to receive all flags. - // The plugin completion code will do its own flag parsing. - if !finalCmd.DisableFlagParsing { - finalArgs = finalCmd.Flags().Args() - CompDebugln(fmt.Sprintf("Args without flags are '%v' with length %d", finalArgs, len(finalArgs))) - } - - // Find completion function for the flag or command - var key interface{} - var keyStr string - if flag != nil { - key = flag - keyStr = flag.Name - } else { - key = finalCmd - keyStr = finalCmd.Name() - } - completionFn, ok := validArgsFunctions[key] - if !ok { - CompErrorln(fmt.Sprintf("Dynamic completion not supported/needed for flag or command: %s", keyStr)) - return - } - - CompDebugln(fmt.Sprintf("Calling completion method for subcommand '%s' with args '%v' and toComplete '%s'", finalCmd.Name(), finalArgs, toComplete)) - completions, directive := completionFn(finalCmd, finalArgs, toComplete) - for _, comp := range completions { - // Print each possible completion to stdout for the completion script to consume. - fmt.Fprintln(out, comp) - } - - if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { - directive = BashCompDirectiveDefault - } - - // As the last printout, print the completion directive for the - // completion script to parse. - // The directive integer must be that last character following a single : - // The completion script expects :directive - fmt.Fprintf(out, ":%d\n", directive) - - // Print some helpful info to stderr for the user to understand. - // Output from stderr should be ignored from the completion script. - fmt.Fprintf(os.Stderr, "Completion ended with directive: %s\n", directive.string()) - }, - } -} - -func isFlag(arg string) bool { - return len(arg) > 0 && arg[0] == '-' -} - -func checkIfFlagCompletion(finalCmd *cobra.Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { - var flagName string - trimmedArgs := args - flagWithEqual := false - if isFlag(lastArg) { - if index := strings.Index(lastArg, "="); index >= 0 { - flagName = strings.TrimLeft(lastArg[:index], "-") - lastArg = lastArg[index+1:] - flagWithEqual = true - } else { - return nil, nil, "", errors.New("Unexpected completion request for flag") - } - } - - if len(flagName) == 0 { - if len(args) > 0 { - prevArg := args[len(args)-1] - if isFlag(prevArg) { - // If the flag contains an = it means it has already been fully processed - if index := strings.Index(prevArg, "="); index < 0 { - flagName = strings.TrimLeft(prevArg, "-") - - // Remove the uncompleted flag or else Cobra could complain about - // an invalid value for that flag e.g., helm status --output j - trimmedArgs = args[:len(args)-1] - } - } - } - } - - if len(flagName) == 0 { - // Not doing flag completion - return nil, trimmedArgs, lastArg, nil - } - - flag := findFlag(finalCmd, flagName) - if flag == nil { - // Flag not supported by this command, nothing to complete - err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName) - return nil, nil, "", err - } - - if !flagWithEqual { - if len(flag.NoOptDefVal) != 0 { - // We had assumed dealing with a two-word flag but the flag is a boolean flag. - // In that case, there is no value following it, so we are not really doing flag completion. - // Reset everything to do argument completion. - trimmedArgs = args - flag = nil - } - } - - return flag, trimmedArgs, lastArg, nil -} - -func findFlag(cmd *cobra.Command, name string) *pflag.Flag { - flagSet := cmd.Flags() - if len(name) == 1 { - // First convert the short flag into a long flag - // as the cmd.Flag() search only accepts long flags - if short := flagSet.ShorthandLookup(name); short != nil { - CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found flag '%s' which we will change to '%s'", name, short.Name)) - name = short.Name - } else { - set := cmd.InheritedFlags() - if short = set.ShorthandLookup(name); short != nil { - CompDebugln(fmt.Sprintf("checkIfFlagCompletion: found inherited flag '%s' which we will change to '%s'", name, short.Name)) - name = short.Name - } else { - return nil - } - } - } - return cmd.Flag(name) -} - -// CompDebug prints the specified string to the same file as where the -// completion script prints its logs. -// Note that completion printouts should never be on stdout as they would -// be wrongly interpreted as actual completion choices by the completion script. -func CompDebug(msg string) { - msg = fmt.Sprintf("[Debug] %s", msg) - - // Such logs are only printed when the user has set the environment - // variable BASH_COMP_DEBUG_FILE to the path of some file to be used. - if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" { - f, err := os.OpenFile(path, - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err == nil { - defer f.Close() - f.WriteString(msg) - } - } - - if debug { - // Must print to stderr for this not to be read by the completion script. - fmt.Fprintln(os.Stderr, msg) - } -} - -// CompDebugln prints the specified string with a newline at the end -// to the same file as where the completion script prints its logs. -// Such logs are only printed when the user has set the environment -// variable BASH_COMP_DEBUG_FILE to the path of some file to be used. -func CompDebugln(msg string) { - CompDebug(fmt.Sprintf("%s\n", msg)) -} - -// CompError prints the specified completion message to stderr. -func CompError(msg string) { - msg = fmt.Sprintf("[Error] %s", msg) - - CompDebug(msg) - - // If not already printed by the call to CompDebug(). - if !debug { - // Must print to stderr for this not to be read by the completion script. - fmt.Fprintln(os.Stderr, msg) - } -} - -// CompErrorln prints the specified completion message to stderr with a newline at the end. -func CompErrorln(msg string) { - CompError(fmt.Sprintf("%s\n", msg)) -}