前回の記事で、 cliパッケージを利用してサブコマンドを実装してみましたが、 今回はサブコマンドの引数(args)周りについて記載したいと思います。

サブコマンドの[args]はどのように受け取るのか。

とりあえず、今回は以下のようなサンプルコードから始めたいと思います。
今回取り上げるのは、Usageにある<command> [<args>]args の部分です。

$ example --help
Usage: example [--version] [--help] <command> [<args>]

Available commands are:
    foo    Foo Sub-Command.

$ example foo
Foo sub-command execute

コード

package main

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

type FooCommand struct {}
func (c *FooCommand) Run(args []string) int {
	fmt.Printf("Foo sub-command execute\n")
	return 0
}
func (c *FooCommand) Synopsis() string {
	return "Foo Sub-Command."
}
func (c *FooCommand) Help() string {
	return "Usage: example foo"
}

func main() {
	c := cli.NewCLI("example", "0.0.1")

	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"foo": func() (cli.Command, error) {
			return &FooCommand{}, nil
		},
	}

	exitStatus, err := c.Run()
	if err != nil {
		fmt.Println(err)
	}
	os.Exit(exitStatus)
}

サブコマンドでのargsの取り扱い

cliパッケージのFooCommandの実装をみるとだいたいお気づきになるかと思いますが、 func Run(args []string)args []stringにサブコマンドへの引数が渡されます。

試しにRun()を以下のように記述し、出力を確認してみます。

func (c *FooCommand) Run(args []string) int {
	fmt.Printf("args => %s\n", strings.Join(args, ", "))
	return 0
}

実行結果

./example foo a b c
args => a, b, c

$ ./example foo 1 2 3 4 5
args => 1, 2, 3, 4, 5

$ ./example foo
args =>

args []stringにサブコマンド以降の引数が自動的に渡されたことことが確認できますね。

Packerのコードをみつつflagパッケージとの併用

packerのコードを参考にしながら、flagパッケージとの併用を試してみました。

func (c *FooCommand) Run(args []string) int {
	var verboseFlag bool
	flags := flag.NewFlagSet("foo", flag.ExitOnError)
	flags.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", c.Help() ) }
	flags.BoolVar(&verboseFlag, "v", false, "verbose flag")
	if err := flags.Parse(args); err != nil {
		return 1
	}

	if len(flags.Args()) != 1 {
		flags.Usage()
		return 1
	}

	if verboseFlag {
		fmt.Printf("Verbose mode true\n")
	}
	fmt.Printf("Foo sub-command execute... message: %s\n", flags.Args()[0])
	return 0
}

func (c *FooCommand) Help() string {
	return "Usage: example foo [-v] message"
}

実行結果

$ ./example foo --help
Usage: example foo [-v] message

$ ./example foo
Usage: example foo [-v] message

./example foo hello
Foo sub-command execute... message: hello

$ ./example foo -v hello
Verbose mode true
Foo sub-command execute... message: hello

flagパッケージの詳細はここでは触れませんが、flags.Usage = func() {fmt.Printf("%s\n", c.Help())}と定義することで --helpオプション指定時にcliパッケージのHelp()を呼び出すことができるようになります。
なお、print文などで出力を指定しておかないとflags.Usage()と呼び出した時にヘルプがコンソールに表示されませんので注意が必要です。 (ヘルプ表示はstderrに吐かれるため、自分はfmt.Fprintf()を利用しました)

個人的にはflags.Usage()の呼び出しでヘルプ表示ができず結構はまってしまいましたが、 cliパッケージflagパッケージを使いこなせればお手軽に自作コマンドが作成できるかなと感じました。

参考

以上。