英文:
Cobra: Providing context to subcommands without using package globals?
问题
我使用cobra和viper编写了一个简单的命令行工具。最近,我对它进行了重构,以避免使用包全局变量,主要是因为使用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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论