hub-pr を作るとき、(ghq などで使っていた)codegangsta/cli ではなく新しいライブラリを試してみようと思って mitchellh/cli を使ってみたけど、何かしっくりこないものがあったので、せっかくだし、と自作してみた。今回の要件は以下のとおり。
- (
go
やgit
のように)サブコマンドがある - コマンドラインオプションの解析には標準の
flag
パッケージをつかう - コマンドを追加するのが面倒でない
コマンドの実装
hub-pr のソースコードが一番のサンプルだけど、コマンドは以下のような関数で表現される。
func doCheckout(flags *flag.FlagSet, args []string) error {
各コマンドの実装は初期化された *flag.FlagSet
とコマンドの引数(プログラムの第1引数がコマンド名になるので os.Args[2:]
)を受けとる。フラグの管理と引数の処理をおこなうのはそれぞれの関数の責任。まったくおこなわなくてもよい。
flags.StringVar(&tmpl, "f", tmpl, "branch name format") flags.Parse(args)
また、各関数は error を返すことで異常終了するよう cli に指示できる。とくに cli.ErrUsage
は使用方法がおかしいことを示し、flags
を利用しつつ呼び出し側でいい感じにエラーを表示してくれる。
% hub-pr checkout Usage: checkout [-f TEMPLATE] PULL_REQUEST_NUMBER Checks out a branch corresponding to given pull request number. The branch name is based on the template, which defaults to '#{{.Number}}-{{.Head.Repo.Owner.Login}}/{{.Head.Ref}}'. Options: -f="#{{.Number}}-{{.Head.Repo.Owner.Login}}/{{.Head.Ref}}": branch name format
コマンドの登録・実行
コマンドを登録し、実行するには以下のようにする。
cli.Use( &cli.Command{ Name: "checkout", Action: doCheckout, Short: "Checkout a branch for a pull request", Long: "checkout [-f TEMPLATE] ...", }, ) cli.Run(os.Args[1:])
で、この Name やら Long やら、ヘルプを書くのが面倒なんですよ……。とくに文字列リテラルとして書かなければならないあたりになんとなくストレスを感じる。ドキュメントなのだから普通に書きたい。そこでコメントを使う。
// +command checkout - Checkout a branch for a pull request // // checkout [-f TEMPLATE] PULL_REQUEST_NUMBER // // Checks out a branch corresponding to given pull request // ... func doCheckout(flags *flag.FlagSet, args []string) error {
以上のように、コマンドに対応する関数のドキュメントに "+command" で始まるヘルプを含めてやるというルールにする。普通の文字列リテラルにくらべて、コメントでドキュメントを書くほうがメンテしやすいしコードを書くときにも参照しやすい。はず。
コメントからコマンドの登録コードを生成する
このコメントどう使うのって話だけど、ここから先に挙げたような cli.Use()
の形のコードを生成するための(雑な) API を go-cli/gen として提供している。
hub-pr では _tools/gen_commands.go から利用している。ドキュメントを更新したら go run _tools/gen_commands.go
すれば commands.go が自動生成される。実際には go generatable にしている。プロジェクトにあわせて自由に使えるように、ツールの形では提供していない。