Goのflagパッケージを使ってサブコマンドを作る
GoでPythonのargparseみたいなサブコマンドを処理したい場合はflag.NewFlagSetというやつを使えば良いらしい。
Goでのやり方はこのページでドン!なんだけど。忘れないようにメモ。
Golang: Implementing subcommands for command line applications · Software adventures and thoughts
FlagSetを別々に作ってParseする
サブコマンドを作る場合はflag.NewFlagSetを呼び出すとFlagSetが返ってくるのでそれぞれ引数、オプションの解析をすれば良い。
今回は
- help: ヘルプを表示する. -vオプションでより詳しく表示する.
- money: 入力したふたつの通貨の為替レートを表示する.
上記2つのサブコマンドを持つコマンドを作る。
helpコマンドにはオプションを付けるので、以下のようにする。
help := flag.NewFlagSet("help", flag.ExitOnError) verbosity := help.Int("verbosity", 0, "") // または // var verbosity int // help.IntVar(&verbosity, "verbosity", 0, "") money := flag.NewFlagSet("money", flag.ExitOnError)
必須引数はFlagSet.Args()で参照できる
moneyコマンドは通貨をふたつ入力するので、以下のようにする。
if len(money.Args()) < 2 { // e.g. $ <cmd> money USD fmt.Println("エラー: 通貨をふたつ入力してください") return }
verbosityの数でメッセージの詳細度を上げたい
例えば「-v」よりも「-vv」、「-vvv」の方が詳細なメッセージを表示可能としたい。
最初、安易に以下のようにやってしまったが、
help := flag.NewFlagSet("help", flag.ExitOnError) v1 := help.Bool("v", false, "") v2 := help.Bool("vv", false, "") v3 := help.Bool("vvv", false, "")
これだと、「-vvvv」などが解析エラーになってしまう。
なので、help.Args()をループで回して最大値を取るよう書き直した(以下)。
// 引数からメッセージ詳細度を算出する。算出に使わなかった引数は戻り値のスライスに返却する。 var regex = regexp.MustCompile("^-v+$") func Verbosity(xs []string) (int, []string) { var v float64 others := make([]string, 0, len(xs)) for _, x := range xs { if regex.MatchString(x) { v = math.Max(v, float64(strings.Count(x, "v"))) } else { others = append(others, x) } } return int(v), others } help := flag.NewFlagSet("help", flag.ExitOnError) var verbosity int help.IntVar(&verbosity, "verbosity", 0, "") v, others := Verbosity(os.Args[2:]) help.Parse(others) if verbosity < v { // e.g. <cmd> help -verbosity=0 -vvv help.Set("verbosity", strconv.Itoa(v)) }
サンプルコード
プログラム全体は以下のようになった。
package main import ( "flag" "fmt" "math" "os" "regexp" "strconv" "strings" ) var regex = regexp.MustCompile("^-v+$") // 引数からメッセージ詳細度を算出する。算出に使わなかった引数は戻り値のスライスに返却する。 func Verbosity(xs []string) (int, []string) { var v float64 others := make([]string, 0, len(xs)) for _, x := range xs { if regex.MatchString(x) { v = math.Max(v, float64(strings.Count(x, "v"))) } else { others = append(others, x) } } return int(v), others } func main() { help := flag.NewFlagSet( "help", flag.ExitOnError) var verbosity int help.IntVar(&verbosity, "verbosity", 0, "") money := flag.NewFlagSet("money", flag.ExitOnError) if len(os.Args) == 1 { fmt.Println("usage: argparse <command> [<args>]") fmt.Println("\thelp: ヘルプをプリントします") fmt.Println("\tmoney: 為替レートを調べます") return } switch os.Args[1] { case "help": v, others := Verbosity(os.Args[2:]) help.Parse(others) if verbosity < v { // e.g. <cmd> help -verbosity=0 -vvv help.Set("verbosity", strconv.Itoa(v)) } case "money": money.Parse(os.Args[2:]) default: fmt.Printf("%q is not valid command.\n", os.Args[1]) os.Exit(2) } if help.Parsed() { message := "Help me." if verbosity == 1 { message = "Help me!" } if verbosity == 2 { message = "Help me!!" } if verbosity >= 3 { message = "HELP ME!!" } fmt.Println(message) } if money.Parsed() { if len(money.Args()) < 2 { // e.g. $ <cmd> money USD fmt.Println("エラー: 通貨をふたつ入力してください") return } from, to := money.Arg(0), money.Arg(1) if from == "" || to == "" { // e.g. $ <cmd> money USD '' fmt.Println("エラー: 通貨をふたつ入力してください") return } fmt.Printf("%v/%v: 100円/$\n", from, to) // 適当 } }
動かすとこのような結果が出る。
$ go run foo.go help -verbosity=0 -vvv # => HELP ME!! $ go run foo.go help -vv # => Help me!! $ go run foo.go money USD JPY # => USD/JPY: 100円/$
参考リンク
ポインタを渡すときはIntVar, XXXVarを使う旨、説明されている。
Go言語のflagパッケージを使う - uragami note
Golang: Implementing subcommands for command line applications · Software adventures and thoughts