英文:
How to put cobra sub commands sources into separate folders
问题
我是Go的初学者,我正在尝试使用Cobra创建一个CLI。为了启动项目,我使用了Cobra Generator,生成了一个命令、一个子命令,一切都正常工作。
现在我的项目布局如下:
cli
├── cmd
│ ├── command.go
│ ├── root.go
│ ├── subcommand.go
├── go.mod
├── go.sum
└── main.go
这个布局对我来说不太合适,假设我的项目有很多命令,或者有很多命令的命名空间,每个命名空间由不同的团队拥有,那将变得非常混乱和难以维护。我更喜欢这样的布局:
cli
├── cmd
│ ├── command
│ │ ├── command.go
│ │ └── subcommand.go
│ └── root.go
├── go.mod
├── go.sum
└── main.go
现在,我对Go中的包和导入方式缺乏一些知识(即使在阅读了这里和那里的文档之后),但据我所了解,只要它们属于同一个包,一个资源可以在多个Go源文件中访问。但正如文档中所说,“实现可能要求一个包的所有源文件都位于同一个目录中。”,所以为了实现我想要的布局,我需要有多个包,即每个命令命名空间一个包,我认为这是可以的(或者至少,我不明白有什么问题)。所以这是我尝试过的:
- 在
cmd目录下创建一个command目录 - 将
command.go文件移动到command目录中 - 将
command.go文件中的package语句改为command - 对
subcommand.go做同样的操作
这样构建是可以的,但是找不到命令(Error: unknown command "command" for "cli")。我认为这是因为我从未导入过新的command包,所以我在main.go文件中导入了它,其中已经导入了cmd包。
构建失败,告诉我command.go文件中的undefined: rootCmd。我猜想command包无法看到cmd包中的资源,所以我在command.go文件中导入了cmd包,并将rootCmd替换为cmd.rootCmd(rootCmd是在cli/cmd/root.go文件中创建的变量,是Cobra Generator提供的文件的一部分)。这次我真的很有希望,但结果还是一样(undefined: cmd.rootCmd),现在我迷失了。
我是不是在尝试一些Cobra不支持的东西?
我是不是在尝试一些Cobra可以做到的事情,但没有使用Cobra Generator?
我是不是在尝试一些根本不应该做的事情(比如一种糟糕的设计,Go试图保护我免受这种设计的影响)?
如果不是,我应该如何继续?
英文:
I'm a Go beginner, and I'm trying to create a CLI with Cobra. To bootstrap the project, I used the Cobra Generator, generated a command, a subcommand, and everything works fine.
I now have this type of layout :
cli
├── cmd
│   ├── command.go
│   ├── root.go
│   ├── subcommand.go
├── go.mod
├── go.sum
└── main.go
This doesn't suit me, let's say my project is intendend to have lots of commands, or lots of command namespaces, each owned by a different team, it would become very messy and hard to maintain. I would prefer a layout like this :
cli
├── cmd
│   ├── command
│   │   ├── command.go
│   │   └── subcommand.go
│   └── root.go
├── go.mod
├── go.sum
└── main.go
Now, I lack some knowledge about the way packages and imports are done in Go (even after reading the doc here and there), but as far as I understand, a resource can be accessed accross multiple go source files as long as they belong to the same package. But as said in the documentation, "An implementation may require that all source files for a package inhabit the same directory.", so to achieve a layout such as I would like, I would need to have multiple packages, ie one for each command namespace, and I think this is fine (or at least, I don't understand what would be wrong with that). So this is what I've tried :
- Create a
commanddirectory inside thecmdone - Move the
command.gofile inside thecommanddirectory - Change the
packageclause from thecommand.gofile tocommand - Do the same for
subcommand.go
And this builds fine, but the command is not found(Error: unknown command "command" for "cli"). I thought it was because I never imported that new command package, so I imported it in the main.go file, where the cmd package is imported.
The build fails, telling me undefined: rootCmd in the command.go file. I guess the command package is unable to see the resources from the cmd one, so I imported the cmd package in the command.go file, and replaced rootCmd with cmd.rootCmd (rootCmd being a variable created in the cli/cmd/root.go file, part of the Cobra Generator provided files). I really had hope this time, but the result is still the same (undefined: cmd.rootCmd), and now I'm lost.
Am I trying to do something that is not possible with Cobra?
Am I trying to do something that is possible with Cobra, but not using the Cobra Generator?
Am I trying to do something that should not be done at all (like a bad design from which Go is trying to protect me)?
If not, how should I proceed?
答案1
得分: 17
你可以使用cobra-cli命令无法获得所需的布局,但你可以手动设置。让我们从cobra-cli布局开始:
$ go mod init example
$ cobra-cli init
然后添加一些命令:
$ cobra-cli add foo
$ cobra-cli add bar
这样我们就得到了:
.
├── cmd
│ ├── bar.go
│ ├── foo.go
│ └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
让我们首先将命令移动到子目录中,以便获得所需的布局。这样我们就得到了:
.
├── cmd
│ ├── bar
│ │ └── bar.go
│ ├── foo
│ │ └── foo.go
│ └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
现在我们需要对代码进行一些更改,以使其正常工作。
-
因为我们的子命令现在位于单独的包中,所以我们需要重命名
rootCmd以使其可导出。find cmd -name '*.go' -print | xargs sed -i 's/rootCmd/RootCmd/g' -
我们需要将每个子命令放在自己的包中,所以我们将
cmd/foo/foo.go顶部的package cmd替换为package foo,将cmd/bar/bar.go中的package cmd替换为package bar。 -
我们需要子命令导入根命令,以便它们可以调用
RootCmd.AddCommand。这意味着在cmd/foo/foo.go和cmd/bar/bar.go中,我们需要在import部分添加example/cmd(回想一下,我们通过go mod init命令将顶级包命名为example)。这意味着每个文件将以以下方式开始:package foo import ( "fmt" "example/cmd" "github.com/spf13/cobra" )作为这个更改的一部分,我们还需要更新对
AddCommand的引用,以使用显式的包名:func init() { cmd.RootCmd.AddCommand(fooCmd) } -
最后,我们需要在某个地方导入子命令。现在我们从未导入
foo或bar包,所以代码实际上是不可见的(尝试在其中一个文件中引入一个严重的语法错误,你会发现go build会成功,因为这些文件没有被任何地方引用)。我们可以在顶级的main.go文件中做到这一点:package main import ( "example/cmd" _ "example/cmd/bar" _ "example/cmd/foo" ) func main() { cmd.Execute() }
现在,如果我们构建并运行代码,我们会看到我们的foo和bar命令可用:
$ go build
$ ./example
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
example [command]
Available Commands:
bar A brief description of your command
completion Generate the autocompletion script for the specified shell
foo A brief description of your command
help Help about any command
Flags:
-h, --help help for example
-t, --toggle Help message for toggle
Use "example [command] --help" for more information about a command.
英文:
You can't get the layout you want by using the cobra-cli command, but you can certainly set things up manually. Let's start with the cobra-cli layou:
$ go mod init example
$ cobra-cli init
And add a couple of commands:
$ cobra-cli add foo
$ cobra-cli add bar
This gets us:
.
├── cmd
│   ├── bar.go
│   ├── foo.go
│   └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
Let's first move the commands into subdirectories so we have the desired layout. That gets us:
.
├── cmd
│   ├── bar
│   │   └── bar.go
│   ├── foo
│   │   └── foo.go
│   └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
Now we need to make some changes to our code so that this works.
-
Because our subcommands are now in separate packages, we'll need to rename
rootCmdso that it is exportable.find cmd -name '*.go' -print | xargs sed -i 's/rootCmd/RootCmd/g' -
We need each subcommand to be in its own package, so we'll replace
package cmdat the top ofcmd/foo/foo.gowithpackage foo, and withpackage barincmd/bar/bar.go. -
We need the subcommands to import the root command so that they can call
RootCmd.AddCommand. That means incmd/foo/foo.goandcmd/bar/bar.gowe need to addexample/cmdto ourimportsection (recall that we named our top-level packageexamplevia thego mod initcommand). That means each file will start with:package foo import ( "fmt" "example/cmd" "github.com/spf13/cobra" )As part of this change, we will also need to update the reference to
AddCommandto use an explicit package name:func init() { cmd.RootCmd.AddCommand(fooCmd) } -
Lastly, we need to import the subcommands somewhere. Right now we never import the
fooorbarpackages, so the code is effectively invisible (try introducing an egregious syntax error in one of those files -- you'll see thatgo buildwill succeed because those files aren't referenced anywhere).We can do this in our top level
main.gofile:package main import ( "example/cmd" _ "example/cmd/bar" _ "example/cmd/foo" ) func main() { cmd.Execute() }
Now if we build and run the code, we see that our foo and bar commands are available:
$ go build
$ ./example
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
example [command]
Available Commands:
bar A brief description of your command
completion Generate the autocompletion script for the specified shell
foo A brief description of your command
help Help about any command
Flags:
-h, --help help for example
-t, --toggle Help message for toggle
Use "example [command] --help" for more information about a command.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论