Golangのcliツールジェネレータ兼開発補助パッケージcobraのtips

GolangのCLI補助パッケージcobraの導入や使用方法、フラグや引数外部呼び出し方法など実務で使いそうな物を纏めてみました。柔軟すぎてドキュメントが大量にあるけど簡単なことを理解するだけでも非常に便利に作っていけるのでオススメです。

Hugoをいじっている時にGo熱が再燃してしまったのでGoネタです。
GoといったらやっぱCLIからやるのが基本だろうと思い、色々思い出しながら微妙にアングラなツールを書いています。

GoのCLIジェネレータはいくつかありますが、現在はcobraが人気みたいですね。

このcobraの作者はHugoと同じ作者です。
cobraを使用しているツールも錚々たるメンバーが名を連ねています。

  • Kubernetes
  • rkt
  • etcd
  • Docker (distribution)
  • OpenShift
  • ProjectAtomic (enterprise)

などなど。

今回はこのcobraを使っている時に個人的に気になった点やあんまり日本語で紹介されていない点を備忘録がてら書いていきます。

cobraのインストール

各地で紹介されているけどとりあえずインストール。

go get -v github.com/spf13/cobra
go install github.com/spf13/cobra/cobra

初期設定

いきなりジェネレータ使うって記事が多いけど、cobraには設定ファイルがあります。
これをやっとかないと生成ファイルのヘッダコメントを編集するのが面倒くさい。
設定ファイルは~/.cobra.yamlに書きます。

author: geeks-dev <[email protected]>
license: MIT

cobraジェネレータを使う

パッケージとしてだけでなくジェネレータにもなるcobraさん。
initサブコマンドでテンプレ的な必要ファイルを自動で展開してくれます。

cobra init github.com/アカウント名/リポジトリ名

addサブコマンドはこれから自分が作るコマンドにサブコマンドを追加する為に必要なファイルを追加してくれます。
上記の例だと$GOPATH/src/github.com/アカウント名/リポジトリ名cdで移動して使います。

cobra add 追加したいサブコマンド

-pオプションが使えるらしいけど何かよくわからん。
とりあえずinitaddだけ覚えていれば不都合ないです。

メインコマンドとサブコマンド

cobraはメインコマンドとサブコマンドどちらも作っていけます。
デフォルトでcmd/root.goが作られているはずですが、こちらがメインコマンドの挙動です。
メインコマンドは以下の行をアンコメントして使用します。

Run: func(cmd *cobra.Command, args []string) {
    //何か処理を書く
}

メインコマンドに特に役割を与えない場合はコメントのままで良いです。
ヘルプを表示したいだけなら-hオプションが勝手に全体で有効になっています。

サブコマンド

例えばcobra add testと実行すると以下のようなファイルがcmd/test.goとして作成されます。
コメントやテキストは説明がてら変えてます。


// オーサーやらライセンスやらが書いてある

package cmd

import (
    "github.com/spf13/cobra"
)

// testCmd represents the test command
var testCmd = &cobra.Command{
    Use:   "test",
    Short: "メインコマンドでヘルプを表示したときのテキスト",
    Long: `サブコマンドでヘルプを表示したときのテキスト
ヒアドキュメントなので改行も気にせず書けばいい`,
    Run: func(cmd *cobra.Command, args []string) {
    // この辺にクールな処理を書いていく
    // argsは引数。フラグを除去したスライスになっている。
    },
}

func init() {
    RootCmd.AddCommand(testCmd)

    //フラグ関係の処理はここに書いていく
}

フラグ

コマンドラインオプションのことですが、フラグつったりオプションつったり何なんですかね。
フラグっていうのが一般的で日本に入ってきた時わかりづらいからオプションって浸透させたのかな・・・。
勝手な妄想ですんません。

ちょっとゴチャゴチャしてるんですけど、まず大別してコマンド全体で使用できるグローバルフラグとサブコマンド専用のローカルフラグと2種類あると思います。
そして、--toggleみたいにフラグ指定だけで使用するものと--toggle afterのように値を取る物があります。
さらに--flagみたいに-が2個で使用されるのが通常のフラグ。
-fみたいに-が1個で使用されるのがショートハンドと呼ばれるものになります。

とりあえずサンプル。


// グローバル変数を作っておく
var birthday string
var name string
var view bool
var save bool
var color string
var size string
var sleep bool
var skip bool

func init() {
    RootCmd.AddCommand(testCmd)
    //フラグ関係の処理はここに書いていく

    //
    testCmd.PersistentFlags().StringVar(&birtyday, "birthday", "デフォルト値", "何かしらのボキャブラリー溢れる説明のテキスト")
    //ショートハンド無しの値を取るグローバルフラグの書き方

    testCmd.PersistentFlags().StringVarP(&name, "name", "n", "デフォルト値", "何かしらのボキャブラリー溢れる説明")
    //ショートハンド有りの値を取るグローバルフラグの書き方

    testCmd.PersistentFlags().BoolVar(&view, "view", false, "何かしらのボキャブラリー溢れるテキスト")
    //フラグ指定だけで機能するショートハンド無しのグローバルフラグの書き方

    testCmd.PersistentFlags().BoolVarP(&save, "save", "s", false, "ボキャブラリー溢れる説明のテキスト")
    //フラグ指定だけで機能するショートハンド有りのグローバルフラグの書き方

    testCmd.Flags().StringVar(&color, "color", "デフォルト値", "ボキャブラリー溢れる説明")
    //ショートハンド無しの値を取るローカルフラグの書き方

    testCmd.Flags().StringVarP(&size, "size", "s", "デフォルト値", "何かの説明")
    //ショートハンド有りの値を取るローカルフラグの書き方

    testCmd.Flags().BoolVar(&sleep, "sleep", false, "ボキャブラリー")
    //フラグ指定だけで機能するショートハンド無しのローカルフラグの書き方

    testCmd.Flags().BoolVarP(&skip, "skip", "k", false, "ボ")
    //フラグ指定だけで機能するショートハンド有りのローカルフラグの書き方

}

これだけサンプルを書いてもまだまだ説明が足りないくらい柔軟 ややこしい ですが、とりあえずroot.goRootCmd.PersistentFlags()と書けばコマンド全体のフラグとなります。
サブコマンドのファイル(ここではtest.go)にtestCmd.PersistentFlags()と書けばtestサブコマンドとさらに子の、所謂サブサブコマンドでも共通するフラグとなります。
PersistentFlags()ではなくFlags()と書けばそのコマンドファイル専用のローカルフラグになります。

StringVarがショートハンド無しでStringVarPがショートハンド有り。
BoolVarBoolVarPも同様ですが、デフォルト値はfalseにしとかないとフラグを指定するだけでtrueになってしまうので機能しなくなります。

とりあえず上記だけ覚えとけば不都合ないんじゃないでしょうか。

見てわかる通りグローバル変数を参照渡しで書き換えるんですが、グローバル変数なんで命名に気をつけてください。 goの特性上他のサブコマンドファイルでも同じ変数名は使えません。

参照は普通にこんな感じ。

var testCmd = &cobra.Command{
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println(birthday)
        fmt.Println(name)
        fmt.Println(view)
        fmt.Println(save)
        fmt.Println(color)
        fmt.Println(size)
        fmt.Println(sleep)
        fmt.Println(skip)
    },
}

実はcobraでは他にもInt型やFloat型を取ったりできるんですが、詳しく知りたい方はGoDoc参照してください。

引数

Run: func(cmd *cobra.Command, args []string) {}の中のargsはフラグが除去されているスライスです。

入力された順番になっています。
説明いらんので端折ります。

サブサブコマンド

そんな複雑なの作っちゃう?
設計に問題あるんちゃう?

え、ないの?
ん〜でも記事が長くなりすぎるからまた今度ね。

別のサブコマンドを実行する

これが書きたかった。
似たような名前のメソッドがあるから私の頭じゃ絶対忘れる。
基本的に2個のメソッドを使用します。

testCmd.ParseFlags([]string{"-o", "-oのフラグ値だよ〜"})
testCmd.Run(cmd, []string{"引数1だよ〜","引数2だよ〜"})

フラグが不要なら1行目はいりません。
cobra.Commandが構造体になっていてRunの他にもPreRunPostRunといったRunの前後の処理も記述できます。
だから他のサブコマンドでも同じことやるけど全部じゃないって時はPreRunRunPostRunとあらかじめ段階を分けて書いておくと良いです。
そうすればtestCmd.PreRun(cmd, []string{"引数だよ〜"})のように処理の一部だけを他のコマンドでも再利用することがでます。

順番はPreRunRunPostRunですね。

他にもPersistentPreRunPersistentPostRunがありますが、別のサブコマンドを実行するという場面では必要な機会は無いと思います。

Persistentって単語から察するにroot.goに書けば全体の前後共通処理でサブコマンドに書けばサブコマンド、サブサブコマンドに波及する前後共通処理ってとこでしょうか。

設定ファイル

cobraは同者作のviperという設定ファイルのパッケージを利用することができます。
viperもかなり柔軟で、設定ファイル読み取りに必要な機能はこれ一つでほぼ賄えるんじゃないでしょうか。
読み取りだけなら。

うん・・・Github見た感じissueには上がってるけど現時点ではファイルに書き込む機能はないんですよね。
設定値変更するメソッドまで実装しといて何でじゃ。
つまりRSSリーダーやツイッタークライアントみたいに取得先を保存するコマンドを作りたかったらその辺は自前実装になります。
というかGolangの記事って設定ファイルの読み取り系は多いけど設定の保存に関する記事はほぼ無いです。
まぁtomlなりjsonなりを書き込む処理を入れろってことなんでしょうか。

まとめ

cobraはコマンドラインツール作りに必要な機能をほぼ備えているんじゃないでしょうか。
table出力のパッケージやインタラクティブ入力系のパッケージを併用すれば思い通りのコマンドラインツールがさっと書けるようになると思います。

寒くなってきたのでコタツにノーパソ広げてcobraで遊んでみましょう。