Cobra:如何在不使用包全局变量的情况下为子命令提供上下文?

huangapple go评论93阅读模式
英文:

Cobra: Providing context to subcommands without using package globals?

问题

我使用cobraviper编写了一个简单的命令行工具。最近,我对它进行了重构,以避免使用包全局变量,主要是因为使用cobra init建议的布局进行测试变得困难。

不再使用以下方式...

var rootCmd = &cobra.Command{
  ...
}

func main() {
  rootCmd.Execute()
}

而是使用以下方式:

func NewCmdRoot() *cobra.Command {
  cmd := &cobra.Command{
    ...
  }

  return cmd
}

func main() {
  rootCmd := NewCmdRoot()
  rootCmd.Execute()
}

这实际上效果很好,使得测试更容易以干净的cli选项集开始。我在将Viper集成到新方案中遇到了一些困难。如果我只关心根命令,我可以在PersistentPreRun命令中设置一些东西,像这样:

func initConfig(cmd *cobra.Command) {
  config := viper.New()
  rootCmd := cmd.Root()

  config.BindPFlag("my-nifty-option", rootCmd.Flag("my-nifty-option"))

  // ...这里发生了一些事情...

  config.ReadInConfig()

  // 接下来该怎么办?
}

func NewCmdRoot() *cobra.Command {
  cmd := &cobra.Command{
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      initConfig(cmd)
    },
  }

这种方式有点奏效:只要我只关心与Cobra命令行选项对应的配置选项,一切都按预期工作。但是,如果我想要访问config变量本身呢?

我不确定如何在initConfig方法之外公开config变量,而又不将其变成包全局变量。我希望能够实例化多个命令树,每个命令树都有自己隔离的Viper配置对象,但我不清楚应该将其放在哪里。

英文:

I've written a simple CLI tool using cobra and viper. I've recently been refactoring it to avoid package global variables, largely because it turned out to be difficult to test using the layout suggested by e.g. cobra init.

Instead of...

var rootCmd = &cobra.Command{
  ...
}

func main() {
  rootCmd.Execute()
}

I've got something more like:

func NewCmdRoot() *cobra.Command {
  cmd := &cobra.Command{
    ...
  }

  return cmd
}

func main() {
  rootCmd := NewCmdRoot()
  rootCmd.Execute()
}

This has actually worked out great, and makes it much easier for tests
to start with a clean set of cli options. I'm running into some
difficulties integrating Viper into the new scheme. If I only care
about the root command, I can set things up in a PersistentPreRun
command, like this:

func initConfig(cmd *cobra.Command) {
  config := viper.New()
  rootCmd := cmd.Root()

  config.BindPFlag("my-nifty-option", rootCmd.Flag("my-nifty-option")); err != nil {

  // ...stuff happens here...

  config.ReadInConfig()

  // What happens next?
}

func NewCmdRoot() *cobra.Command {
  cmd := &cobra.Command{
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      initConfig(cmd)
    },
  }

This sort of works: as long as I'm only interested in config options
that correspond to Cobra command line options, things works as
expected. But what if I want access to the config variable itself?

I'm unsure how to expose the config variable outside of the
initConfig method without turning it into a package global. I'd like
the possibility of instantiating multiple command trees, each with
it's own isolated Viper config object, but I'm unclear where to put
it.

答案1

得分: 4

The cobra team recently did that, see https://github.com/spf13/cobra/pull/1551

func TestFoo(t *testing.T){
	root := &Command{
		Use: "root",
		PreRun: func(cmd *Command, args []string) {
			ctx := context.WithValue(cmd.Context(), key{}, val)
			cmd.SetContext(ctx)
		},
		Run: func(cmd *Command, args []string) {
			fmt.Println(cmd.Context().Value(key{}))
		},
	}
    ctx := context.WithValue(cmd.Context(), "key", "default")
	root.ExecuteContext(ctx)
}
英文:

The cobra team did that recently, see https://github.com/spf13/cobra/pull/1551

func TestFoo(t *testing.T){
	root := &Command{
		Use: "root",
		PreRun: func(cmd *Command, args []string) {
			ctx := context.WithValue(cmd.Context(), key{}, val)
			cmd.SetContext(ctx)
		},
		Run: func(cmd *Command, args []string) {
			fmt.Println(cmd.Context().Value(key{}))
		},
	}
    ctx := context.WithValue(cmd.Context(), "key", "default")
	root.ExecuteContext(ctx)
}

huangapple
  • 本文由 发表于 2021年5月22日 02:17:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/67642066.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定