nasa9084
· 2 min read

jessevdk/go-flagsでサブコマンドを実装する

jessevdk/go-flagsでサブコマンドを実装する

Go言語を用いてコマンドラインツールを開発する際、皆さんはフラグのパースやサブコマンドの実装にどんなパッケージを使用していますか?標準のflagパッケージのほか、、spf13/cobraalecthomas/kingpinなどもよく使われているようです。
私は専ら、jessevdk/go-flags(以下go-flags)を使用しています。
go-flagsはその名の通り、基本的にはオプション/フラグの解析用パッケージで、ドキュメントもフラグ解析に関するものがほとんどです。
しかし、go-flagsでは、サブコマンドの実装も可能です。今回はこれに焦点を当ててご紹介していきます。

go-flagsでは、親コマンドにサブコマンドを登録する、という形でサブコマンドを実装していきます。サブコマンドは構造体として実装し、それぞれがオプションを格納する構造体を兼ねる形となります。
終端の、実際に何かの動作をするコマンドは Commander interfaceを実装している必要がありますが、中間のサブコマンド(docker containerのような、グルーピングのためのサブコマンド)はこれを実装していなくても構いません。
Commander interfaceの定義は次のようになっています。

type Commander interface {
    Execute(args []string) error
}

非常に単純ですね。argsにはコマンドでパースされなかったあまりの引数が渡されます。
実際の実装例を見てみましょう。

type subcommand struct {
    verbose bool `short:"v" long:"verbose"`
}

func (cmd *subcommand) Execute(args []string) error {
    // some exec
    return nil
}

サブコマンドを実装したら、親のコマンドにサブコマンドとして登録します。
ドキュメントを見ると、Command構造体に登録する関数があること、Parser構造体はCommand構造体が埋め込まれていること、がわかります。
通常、go-flagsパッケージを使用する場合はパッケージグローバルのParse関数を使用することが多いと思うのですが、サブコマンドを実装する場合はトップレベルのパーサーを自分で作る必要があります。

type options struct {
    // ...
}

var opts options // global option
var parser = flags.NewParser(&opts, flags.Default)
var subcmd subcommand

func init() {
    parser.AddCommand("subcmd",
        "subcommand",
        "",
        &subcmd,
    )
}

func main() {
    if _, err := parser.Parse(); err != nil {
        if fe, ok := err.(*flags.Error); ok && fe.Type == flags.ErrHelp {
            os.Exit(0)
        }
        log.Print(err)
        os.Exit(1)
    }
}

このように登録することで、subcmdという名前のサブコマンドが使用できるようになりました。go run main.go subcmdなどとすると、subcommand.Execute関数が実行されます。
実際にはParser.AddSubCommandのエラーをハンドリングしたりなど、もう少しやらなければならないことはあると思いますが、基本的には以上です。