bundle exec を打たなくて良くなる zsh プラグイン書いた


本エントリでは僕が初めてつくった zsh プラグインを紹介します．
Ruby を使っている人なら bundler のお世話になっている方が多いと思いますが，
その bundler をより便利に使うためのプラグインになります．Ruby を使わない方
には直接は関係ないですが，アイデアは応用できるかもしれません．

# Bundler の問題点

Bundler は gem の依存関係をローカルな範囲で管理できる gem で，Rails を始め色々
なところで使われています．この bundler を使う上で，面倒くさいことの1つが `bundle exec` です．
bundler でインストールされた gem をコマンドラインから実行する場合，`$PATH` が通っていないため，
実行する場合は `bundle exec` を使う必要があります．

例えば，rails を bundler でインストールした場合，コマンドはお馴染み，

```zsh
bundle exec rails
```

となります．しかし，これには

1. `bundle exec` を毎回打つのが面倒
2. alias が適用されないため `rails` と打つのが面倒

という問題があります．

これに対して，いくつか解決案を考えてみましょう．

# 解決策1：エイリアス

`alias be='bundle exec'` というようにエイリアスを張ると，入力は若干楽になります．
しかし，これだと bundler でインストールしたコマンドを意識する必要がありますし，上記の 2. はまったく解決されていません．

# 解決策2：binstubs を使う

`bundle install` 時に `--binstubs` オプションを付けておくことで，gem の実行ファイルのインストール先を指定できます．
従って，`.zshenv` などで `export PATH=./vendor/bin:$PATH` とし，下記のようにすると bundler でインストールした gem に直接パスを
通すことができます．

```zsh
bundle install --path=vendor/bundle --binstubs=vendor/bin
```

これだと `bundle exec` を打つ必要は無く，エイリアスも効くため，これで良いように見えます．

しかし，これだと

1. `$PATH` に相対パスを入れていると思わぬディレクトリにパスが通ってしまうトラブルが起きる可能性があり，また純粋に気持ち悪い
2. 実行しているコマンドが bundler でインストールしたものなのか，システムにインストールされているものなのか見分けがつかない
3. プロジェクトのルートディレクトリでしか bundle install したコマンドを実行できない

といった問題があります．

# 解決策3：zsh-bundle-exec を使う

既存の方法ではどれもいまいちであることが分かっていただけたかと思います．
そこで，今回はこの問題を zsh で解決するために，[zsh-bundle-exec](https://github.com/rhysd/zsh-bundle-exec)という zsh プラグインを作りました．

[rhysd/zsh-bundle-exec](https://github.com/rhysd/zsh-bundle-exec)

zsh-bundle-exec は入力されたコマンドが bundler で管理されているものだった場合に，自動で bundle exec を挿入し，よしなにエイリアスを展開してくれます．
リポジトリ内にある `zsh-bundle-exec.zsh` を zshenv などから読みこめば使えます．

![screenshot](http://gifzo.net/BAUT7u7dr0U.gif)

このスクリーンショットでは，bundler で管理されているプロジェクト下に居るときに `r -v` を入力し Enter を押すと `alias r=rails` が展開されて `bundle exec rails -v` になっているのが分かります．
コマンドは Enter を押した時点で展開されるため，bundler 経由で実行されているのが分かり，解決策2のような `bundle install` し忘れていて実はシステムの gem のほうを使っていたといった事態を防げます．また，bundler で管理しているプロジェクト内であれば，どこでも利用可能です．

### カスタマイズする

環境変数を使うことで zsh-bundle-exec の設定を行うことができます．

- `$BUNDLE_EXEC_GEMFILE_CURRENT_DIR_ONLY` : 空でない値を指定すると Gemfile があるディレクトリにいるときのみ zsh-bundle-exec が有効になります．
- `$BUNDLE_EXEC_COMMANDS` : 空白区切りの文字列で複数のコマンドを指定すると，それらのコマンドを入力時のみ zsh-bundle-exec が有効になります．
- `$BUNDLE_EXEC_RUBY_COMMAND` : 利用している Ruby のコマンドです．デフォルトは `"ruby"` です．

### 仕組み

zsh-bundle-exec では，`^M` と `^J` のキーバインドを上書きして `accept-line` の前に入力されたコマンドが bundler で管理されているものかを確認しています．
まず必要に応じて `alias` コマンドでエイリアスを展開し，Ruby のスクリプトを実行して bundler を呼び出してコマンドが bundler が管理しているものであるかどうかを調べた後，入力したコマンドが bundler で管理されているものであれば `bundle exec` を挿入しています．

### 若干はまったところとか

今までシェルスクリプトは `/bin/sh` で簡単な処理の自動化や zshrc でしか書いたことが無かったため，いくつかハマったポイントがあったので，書いておきます．

- `local` 組み込みコマンド

```zsh
local hoge=$(false)
(( $? != 0 )) && echo hoge
```

このコードでは，`$?` に `local` の終了コードが入るため，`"hoge"` は表示されません．
次のようにする必要があります．

```zsh
local hoge
hoge=$(false)
(( $? != 0 )) && echo hoge
```

- `sed` コマンド

BSD の sed の正規表現では `\s` や `+` が使えません．
Mac 環境でうまく動かなくて困りました．`+` の代わりに `*` を，`\s` の代わりに `[	 ]` をそれぞれ使いました．

# まとめ

入力されたコマンドが bundler で管理されているものだった場合に，自動で bundle exec を挿入し，よしなにエイリアスを展開してくれる zsh プラグイン，zsh-bundle-exec を作成しました．
毎度 `bundle exec` を打つのが面倒くさいなぁと思っている人の役に立てば良いなと思います．