Apexのzsh補完関数を書いてみた / ついでにzsh補完関数をゆるふわに理解する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、中山です。

みなさんApexを使っていますか。「Apexって何。というかアペックス?エイペックス?」という方は弊社八幡がまとめた以下の記事を参照してください。Apexの概要から基本的な使用方法まで分かりやすくまとまっています。

私は最近業務の中でLambdaを利用する機会がありました。Lambdaの管理にApexを利用したのですが、かなり便利だなぁと思った次第です。私が感じた主な便利ポイントを以下にまとめます。

  • 作業がほぼ全てターミナルで完結できるので、マネジメントコンソールポチポチ苦行から開放される
  • Terraformを利用できる
  • ライフサイクルフック機能が地味に便利

などなどいろいろと便利ポイントがあります。

しかし、便利ではあるのですが、Apexはサブコマンドがそこそこ多いです。たまに使用するサブコマンドやオプションをいちいち記憶しておくには私の頭のキャパが不足しています。また、いちいちhelpを叩いて確認するのも苦行です。

という訳で、こういった面倒な作業はコードを書いて解決しようと思い、Apexのzsh補完関数を自分で書いてみました。今回は私が書いた補完関数についてブログエントリを書いてみます。

コード

コードはGistに上げておきました。本エントリで参照するコミットハッシュ値は37f126699617e30d2aea29b13579e490d206aea3です。HEADが移動した場合このエントリで書かれている情報と差異が発生している可能性があります。ご注意ください。

zsh補完関数の使い方

いくつかやり方はあるかと思いますが、一例を以下に書きます。

  • コードをcloneする
$ git clone https://gist.github.com/002e3f5af837df385776.git
  • ~/.zshrc を編集する
$ $EDITOR ~/.zshrc
  • 以下の内容を追記する
fpath=(path/to/apex/zsh/completion/dir(N-/) $fpath)
autoload -U compinit && compinit
  • ~/.zshrc を再読込する
$ source ~/.zshrc

コードの解説

以下ではコードの内容について解説をします。と言いたいところですが、正直コード書いた自分でも内容よく分かってないです。敗北感ある。

zshの補完関数を正確に理解するのは難しいですし、正直あまり理解する価値があるとも思っていません。しかし、補完関数の書き方はほとんど形式が決まっていて、一度書き方を覚えてしまえば作成するのにそこまで苦労はしません。

という訳で、以下では「ここを抑えておけば大体の仕組みを理解できるよ」というポイントを解説します。

一番大切なポイント

1行目と最終行の138行目です。この指定をすることにより apex というコマンドの補完関数を登録しています。イディオムなので覚えておきましょう。

4行目から6行目で変数の定義を行います。zshの補完関数は要は単なる関数なので、変数のスコープは小さくしましょう。 local を付けてください。

curcontext はなにやら便利な仕組みのようです。ありがたく使わせてもらいましょう。

グローバルオプション

Apexのオプションにはどのコマンドにも指定可能(グローバル)なものと、あるコマンドのみ指定可能(ローカル)なもの、2種類があります。グローバルなオプションはまとめて指定したいので、 case コマンド(後述)で分岐させる前、8行目で指定しています。こうすることにより、全てのサブコマンドでオプションが補完されます。

ローカルオプション

ではサブコマンド特有のローカルオプションはどうやって補完させるのでしょうか。それには case を使います。17行目から22行目のコードはイディオムです。こうすることにより、例えば、 delete というサブコマンドが入力された場合このオプションを補完する、という動作になります。

サブコマンドを補完する

サブコマンドの補完で一番中核となる機能が16行目'1: :__apex_sub_commands' です。このコードの意味は、一番目の引数が入力された時に __apex_sub_commands 関数を実行させるという意味になります。で、肝心の関数の中身は82行目です。この関数によApexのサブコマンドを補完させています。

サブコマンドの補完にはいくつか方法があります。静的な補完と、動的な補完です。静的な補完の例としては、例えばこちらのコードが参考になります。このコードの4行目です。見るとわかると思うのですが、つまり静的な補完とは、補完したいサブコマンドをハードコードする方法のことです。この方法にはメリット、デメリットがあります。

  • メリット
    • 補完が早い
    • 補完対象のコマンドが無くても補完可能
  • デメリット
    • サブコマンドの修正が行われた場合コードを修正する必要がある

動的な補完とは __apex_sub_commands のように、補完中にコマンドを実行させて補完候補を取得する仕組みのことです。この関数では、 apex --help を実行して、サブコマンドを grepsed を駆使し、抜き出しています。

この方法におけるメリット/デメリットは以下のとおりです。

  • メリット
    • 補完候補に変更が加えられてもコードを修正する必要がない
  • デメリット
    • コマンドそのものがpathに含まれてない場合エラーが出る
    • awscliのように多数のサブコマンドがある場合、補完されるまで時間がかかる

個人的にはApexのように開発速度が早く、頻繁に新しいサブコマンドが追加されるような場合は動的な補完が適していると思っています。

profileを補完する

Apexにはprofileを指定できるオプションがあります。弊社のように多数のAWSアカウントを業務中に利用するような場合、どんどんprofileが膨れ上がってしまい、とてもじゃないですが覚えきれません。これも補完しましょう。profileの補完には14行目__profiles 関数を呼びだしています。125行目がその実態の関数です。こちらも同じように ~/.aws/credentials からprofileを動的に取得しています。

Terraformのサブコマンドを補完する

Apexには apex infra でTerraformのサブコマンドを指定する機能があります。ApexからTerraformを呼び出しているコードはこちらです。で、Terraformもサブコマンドが多いので補完したいじゃないですか。だったら補完しましょう。という訳で37行目で分岐させて、 infra が入力されたら、 __apex_infra_sub_commands 関数が呼び出されるようにしています。こっちも terraform help コマンドから動的に補完候補を取得しています。

まとめ

いかがだったでしょうか。

zshの補完関数は一度そのお作法を理解すればほとんど同じように書くことができます。また、補完関数の作成を通じてサブコマンドやオプションを理解する助けにもなります。そこまで正確に理解しなくてもいいんです。お作法だけ覚えればだいたい意図した動作をしてくれます。みなさんもお気に入りのコマンドラインツールがあったらぜひ一度補完関数を書いてみてください。

本エントリがみなさんの参考になったら幸いに思います。

参考リンク