API叩いて云々するようなアプリを作りたいと思うと、やっぱりサブコマンドを実装したCLIツールを 自作したいと思うのはエンジニアの嵯峨ですよね?w
いろいろと調べていたらmitchellh/cli(以下cliパッケージ)という”Go CLI Library”を発見したので触ってみました。 このcliパッケージですが、Packer・Serf・Consul・Vault・Terraform・Nomadなどで使用されているそうです。

使い方

ライブラリのインストール

$ go get github.com/mitchellh/cli

書き方

cliパッケージにはtype Command interface {}というインターフェースが用意されており、 Help() stringRun(args []string) intSynopsis() stringのメソッドを 実装することで簡単に使用できます。

とりあえず、ここではisayという名前で以下のようなアプリを作成します。

$ ./isay --help
Usage: isay [--version] [--help] <command> [<args>]

Available commands are:
    goodbye    Say Hello.
    hello      Say Goodbye.

実装編

とりあえず、main.goという名前で以下のコードを記述してみます。

package main

import (
	"github.com/mitchellh/cli"
	"os"
	"fmt"
)

// helloサブコマンドの実装
type HelloCommand struct {}
func (c *HelloCommand) Run(args []string) int {
	fmt.Println("You say Hello, I say Goodbye.")
	return 0
}
func (c *HelloCommand) Synopsis() string {
	return "Say Goodbye."
}
func (c *HelloCommand) Help() string {
	return "Usage: isay hello"
}

// goodbye サブコマンドの実装
type GoodbyeCommand struct {}
func (c *GoodbyeCommand) Run(args []string) int {
	fmt.Println("You say Goodbye, I say Hello.")
	return 0
}
func (c *GoodbyeCommand) Synopsis() string {
	return "Say Hello."
}
func (c *GoodbyeCommand) Help() string {
	return "Usage: isay goodbye"
}

func main() {
	// コマンド(アプリ)名とバージョン定義
	c := cli.NewCLI("isay", "0.0.1")

	// サブコマンドの名前と実装の関連付け
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"hello": func() (cli.Command, error) {
			return &HelloCommand{}, nil
		},
		"goodbye": func() (cli.Command, error) {
			return &GoodbyeCommand{}, nil
		},
	}

	// サブコマンドの実行と終了処理
	exitStatus, err := c.Run()
	if err != nil {
		fmt.Println(err)
	}
	os.Exit(exitStatus)
}

ビルド

$ go build -o isay main.go

動作確認と解説

とりあえず、実行してみましょう。

$ ./isay hello
You say Hello, I say Goodbye.

$ ./isay goodbye
You say Goodbye, I say Hello.

func Run(args []string) int {}で実装した結果が表示されましたね。

続いて、何も指定せず実行してみます。

$ ./isay

するとUsageが表示されます。なお./isay --helpも同様の出力を得る事ができます。

Usage: isay [--version] [--help] <command> [<args>]

Available commands are:
    goodbye    Say Hello.
    hello      Say Goodbye.

このあたりは、cliパッケージが自動生成してくれており、コマンド名及び--versionの値は cli.NewCLI("isay", "0.0.1")で指定したものになります。

$ ./isay --version
0.0.1

なお、Available commands are:の部分についてはfunc Synopsis() string {}の実装(返り値)が表示されます。

最後に、func Help() string {}について。お気づきかもしれませんがサブコマンドの--helpオプションの結果として表示することができます。

./isay hello --help
Usage: isay hello

$ ./isay goodbye --help
Usage: isay goodbye

コードを貼ると大分記事が長くなってしまうので今回はここまで。

触ってみて非常に便利だなと思いつつ、コマンド(アプリ)名とバージョンがハードコーティングになるところはうまくやりくりしたいですね。 ちなみに参考にpackerのソースをほんの少しだけ読みかじってみましたが、Help()の部分はハードコーティングされていました。

参考

以上。