yunomuのブログ

趣味のこと

Goでsubcommandsを使う

gitkubectl みたいなサブコマンドを実装したい時。

 たぶん一番楽なのは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...)
}
// (省略)

 これで何段階でも階層化したコマンドを作ることができる。

 それはそうと kubectlgcloud コマンドはsubcommandsではなくCobraを使っています。ソースコードを生成するタイプなのでそれなりの命名規則などのクセはあるもののこれはこれで便利。

GitHub - spf13/cobra: A Commander for modern Go CLI interactions