英文:
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
command
directory inside thecmd
one - Move the
command.go
file inside thecommand
directory - Change the
package
clause from thecommand.go
file 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
rootCmd
so 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 cmd
at the top ofcmd/foo/foo.go
withpackage foo
, and withpackage bar
incmd/bar/bar.go
. -
We need the subcommands to import the root command so that they can call
RootCmd.AddCommand
. That means incmd/foo/foo.go
andcmd/bar/bar.go
we need to addexample/cmd
to ourimport
section (recall that we named our top-level packageexample
via thego mod init
command). 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
AddCommand
to 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
foo
orbar
packages, so the code is effectively invisible (try introducing an egregious syntax error in one of those files -- you'll see thatgo build
will succeed because those files aren't referenced anywhere).We can do this in our top level
main.go
file: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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论