フラグのカスタム補完がうまくできなかったので書きました
cobra の補完生成機能とは
みんな大好き cobra には bash、zsh 向けの補完生成機能が存在する
詳しくは cobra/bash_completions.md at master · spf13/cobra · GitHub を見てほしいけど、こんな感じ ↓ で簡単に補完の機能を生成し、出力させることが出来る
# https://qiita.com/minamijoyo/items/9dceb1d8a66e48ab45cd package cmd import ( "os" "github.com/spf13/cobra" ) func init() { RootCmd.AddCommand(newCompletionCmd()) } func newCompletionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "completion", Short: "Generates shell completion scripts", Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, } cmd.AddCommand( newCompletionBashCmd(), newCompletionZshCmd(), ) return cmd } func newCompletionBashCmd() *cobra.Command { cmd := &cobra.Command{ Use: "bash", Short: "Generates bash completion scripts", Run: func(cmd *cobra.Command, args []string) { RootCmd.GenBashCompletion(os.Stdout) }, } return cmd } func newCompletionZshCmd() *cobra.Command { cmd := &cobra.Command{ Use: "zsh", Short: "Generates zsh completion scripts", Run: func(cmd *cobra.Command, args []string) { RootCmd.GenZshCompletion(os.Stdout) }, } return cmd }
cobra.Command
に Gen{Ba,Z}shCompletion
という関数が生えているので、それを叩くだけ
例に示した通りに bash 用、zsh 用の関数があることがわかる
これを実装した後はこんな感じ ↓ で補完を読み込むようにして、再度 shell を開くなりして補完を利かすことが出来る
$ echo ". <(hogefuga completion bash)" >> ~/.bashrc
$ hogefuga completion zsh > /usr/local/share/zsh/site-functions/_hogefuga
すごくシンプルなものであればこれで結構事が足りて、用意したフラグ名についても正しく補完されるようになる
custom completion について
たとえば docker の補完の場合、 docker container rm <TAB>
と入力すると現在起動しているコンテナ一覧が表示され、容易に好きなコンテナを指定することが出来る
これは、補完のスクリプト内で docker のコンテナ一覧を取得する処理が走っており、任意の変数に代入されることで実現している
- https://github.com/docker/cli/blob/ea9ca25ca97bef372b12059cb35b63de0f4634da/contrib/completion/bash/docker#L120
- https://github.com/docker/cli/blob/ea9ca25ca97bef372b12059cb35b63de0f4634da/contrib/completion/zsh/_docker#L51
これを cobra で実現する場合、次のようなコードを書くことになる
const ( bash_completion_func = `__docker_get_container() { local docker_output out if docker_output=$(docker container ls -q 2>/dev/null); then out=($(echo "${docker_output}" | awk '{print $1}')) COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) fi } __docker_custom_func() { case ${last_command} in docker_container_rm) __docker_get_container return ;; *) ;; esac } `) // 省略 cmds := &cobra.Command{ Run: runSomething, BashCompletionFunction: bash_completion_func, }
詳しく説明しないし、↑ のコードは動かしてないから動作しないかもしれないけど簡単に言うと補完するコマンドが見つからない際に __docker_custom_func
が発火され、そこでなんというコマンドが実行されたか見て、ここで docker_container_rm
だった場合は __docker_get_container
が発火される
__docker_get_container
はいろいろやってるけど結局は COMPREPLY
にコンテナの id 一覧を代入している
ちなみにフラグにわたす場合はこんな感じ
func rootCmd() *cobra.Command { cmd := &cobra.Command{ RunE: func(c *cobra.Command, args []string) error { // something }, BashCompletionFunction: `__docker_get_container() { local docker_output out if docker_output=$(docker container ls -q 2>/dev/null); then out=($(echo "${docker_output}" | awk '{print $1}')) COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) fi } `, } # しょうりゃく cmd.Flags().StringVar(&opts.profile, "container", "fuga", "set container id") cmd.MarkFlagCustom("profile", "__docker_get_container") # しょうりゃく }
cmd.MarkFlagCustom
で指定のフラグに対する補完用のスクリプトを指定する
zsh では custom completion が効かない
他は大体いい感じにしてくれるのに、custom completion だけ使えない
公式にある zsh_completions.md にはこう書かれている
What's not yet Supported
なので、上の例に登場していた BashCompletionFunction
の zsh 版みたいなものは存在しない
つまり、素直に GenZshCompletion
を叩いたところで custom completion は全く効かない
じゃあどうすればよいのか
でも我々は cobra 製である kubectl、minikube(CLI)あたりに zsh 用の素晴らしい補完が用意されていることを知っている
あれはどうやって custom completion を実現しているのだろうか?
結論はこんなかんじ ↓ だった
まじかよ…
実際のコードはこんな感じ
- https://github.com/kubernetes/kubectl/blob/2b775f287352b3191b0e52cc4231941cd8e337bf/pkg/cmd/completion/completion.go#L145-L314
- https://github.com/kubernetes/minikube/blob/90bf0f8ec57f051ac014d3b0db6d9051475874d6/cmd/minikube/cmd/completion.go#l108-l281
bash to zsh 用テンプレートの中に GenBashCompletion
の結果を埋めていることがわかる
各自で開発している CLI にこのテンプレートを埋め込む場合は minikube の GenerateZshCompletion
を参考にするとよさそう ( __minikube
などの文字列を __hogefuga
に置き換えるだけで動いた)
おわりに
あまり zsh と補完に詳しくないからわからないけど、なんで cobra に ZshCompletionFunction
を実装しないの…?