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