Goでsubcommandsを使う
git
や kubectl
みたいなサブコマンドを実装したい時。
たぶん一番楽なのはsubcommandsライブラリを使う方法だと思います。
subcommands package · pkg.go.dev
mainは後回しにして、subcommand側から。 Command
型を作って、subcommands.Command
インタフェースを実装します。
package subcmd import ( "context" "flag" "fmt" "github.com/golang/subcommands" ) type Command struct {} // これがサブコマンド名になる func (c *Command) Name() string { return "sbcmd" } // コマンド一覧で出てくるサブコマンドの説明 func (c *Command) Synopsis() string { return "subcommands example" } // helpとかで出てくる使い方 func (c *Command) Usage() string { return "subcmd [args]" } // flagライブラリでオプションの処理をするやつ func (c *Command) SetFlags(f *flag.FlagSet) { } // 本体 func (c *Command) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { fmt.Println("ノ˘ω˘) You just don't have enough fight in you!") return subcommands.ExitSuccess }
mainでこのCommand
を登録します。
package main import ( "context" "flag" "os" "github.com/google/subcommands" "yunomu.net/subcmdex/subcmd" // Commandを定義したパッケージ ) // 別にmain()でもよい func init() { // commands, flags, helpというサブコマンドはプリセットがあるのでせっかくだから登録する subcommands.Register(subcommands.CommandsCommand(), "help") subcommands.Register(subcommands.FlagsCommand(), "help") subcommands.Register(subcommands.HelpCommand(), "help") subcommands.Register(&subcmd.Command{}, "") // subcommands.Registerの後に実行する // 全体で1回だけ実行しないとおかしなことになる flag.Parse() } func main() { ctx := context.Background() subcommands.Execute(ctx) }
これでサブコマンドが実行できる。
% go run main.go subcmd ノ˘ω˘) You just don't have enough fight in you!
さらにsubcmd
のサブコマンドも作れる。Command
インタフェースのSetFlag
を以下のように書き換える。
// (省略) type Command struct { cdr *subcommands.Commander // Commanderを保存できるようにする。 } // (省略) func (c *Command) SetFlags(f *flag.FlagSet) { // SetFlagsはCommandがRegisterで登録された時に呼び出されるのでCommanderはここで初期化する cdr = subcommands.NewCommander(f, "") cdr.Register(&subsubcmd.Command{}) // サブコマンドにもcommands, flags, helpがあるのでついでに登録しておくといい cdr.Register(cdr.CommandsCommand(), "help") cdr.Register(cdr.FlagsCommand(), "help") cdr.Register(cdr.HelpCommand(), "help") c.cdr = cdr } func (c *Command) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { // Commanderを実行する return c.cdr.Execute(ctx, args...) } // (省略)
これで何段階でも階層化したコマンドを作ることができる。
それはそうと kubectl
や gcloud
コマンドはsubcommandsではなくCobraを使っています。ソースコードを生成するタイプなのでそれなりの命名規則などのクセはあるもののこれはこれで便利。
GitHub - spf13/cobra: A Commander for modern Go CLI interactions