如何将 cobra 子命令源代码放入单独的文件夹中?

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

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.rootCmdrootCmd是在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 the cmd one
  • Move the command.go file inside the command directory
  • Change the package clause from the command.go file to command
  • 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

现在我们需要对代码进行一些更改,以使其正常工作。

  1. 因为我们的子命令现在位于单独的包中,所以我们需要重命名rootCmd以使其可导出。

    find cmd -name '*.go' -print | xargs sed -i 's/rootCmd/RootCmd/g'
    
  2. 我们需要将每个子命令放在自己的包中,所以我们将cmd/foo/foo.go顶部的package cmd替换为package foo,将cmd/bar/bar.go中的package cmd替换为package bar

  3. 我们需要子命令导入根命令,以便它们可以调用RootCmd.AddCommand。这意味着在cmd/foo/foo.gocmd/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)
    }
    
  4. 最后,我们需要在某个地方导入子命令。现在我们从未导入foobar包,所以代码实际上是不可见的(尝试在其中一个文件中引入一个严重的语法错误,你会发现go build会成功,因为这些文件没有被任何地方引用)。我们可以在顶级的main.go文件中做到这一点:

    package main
    
    import (
      "example/cmd"
      _ "example/cmd/bar"
      _ "example/cmd/foo"
    )
    
    func main() {
      cmd.Execute()
    }
    

现在,如果我们构建并运行代码,我们会看到我们的foobar命令可用:

$ 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.

  1. 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'
    
  2. We need each subcommand to be in its own package, so we'll replace package cmd at the top of cmd/foo/foo.go with package foo, and with package bar in cmd/bar/bar.go.

  3. We need the subcommands to import the root command so that they can call RootCmd.AddCommand. That means in cmd/foo/foo.go and cmd/bar/bar.go we need to add example/cmd to our import section (recall that we named our top-level package example via the go 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)
    }
    
  4. Lastly, we need to import the subcommands somewhere. Right now we never import the foo or bar packages, so the code is effectively invisible (try introducing an egregious syntax error in one of those files -- you'll see that go 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.

huangapple
  • 本文由 发表于 2022年10月7日 19:27:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/73986477.html
匿名

发表评论

匿名网友

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

确定