WebエンジニアのLoL日記

LoLをプレイしたりLJLの試合を見たりするのが好きなエンジニア。LoLのイベントやパッチノートなど気になった点を記事にしたり、LJLについの記事をかいたりしています。某社でWeb系のエンジニアとして働いているので、技術系の記事もたまに書きます。コンタクトを取りたい場合はtwitterまで。

Emacsで独自スクリプトを定義して開発をスムーズにする

f:id:yoshiki_utakata:20181203094202p:plain

はじめに

Emacsアドベントカレンダー1日目にもかかわらず出遅れてしまって申し訳ありません。*1 普段Emacsを使ってPHPで開発をしております。

Emacsを使っているのは、

  • 自分の手に馴染んでいる
  • IntelliJのようにお金を払わなくてもすべての機能が使える
  • コンソール上で起動でき、サーバーに入ってファイルを弄るときにも使える
  • 軽い

などの理由があるのですが、開発中にIntelliJではなくEmacsを使っているからには、Emacsを使っている意味を見出していかなければなりません。

カスタマイズ性の高いEmacs。しかし、Emacsをカスタマイズして便利に使っていくには、Emacs Lispは避けて通れません。ただし、Emacs Lispを自分で書くのは抵抗のある方も多いと思います。*2

そこで今回は、僕が開発中に実際に利用している簡単で便利なEmacs Lispを紹介し、皆さんへのEmacs Lispに対する抵抗感をなくし、そしてもっと便利にEmacsを使えるようにしたいと思います。

基礎編: Emacsからローカル開発環境のVagrantを操作する

実はIntelliJにもローカルのVagrantを立ち上げたりする機能があります。今回は、

  • EmacsからVagrantを立ち上げたり再起動させたりする
  • EmacsからVagrant上のファイルのログを見る

をしてみたいと思います。

といっても超簡単で、Elispからシェルのコマンドを呼び出すだけです。以下の関数を定義します。

(defun my-vagrant-reload()
  (interactive)
(async-shell-command "cd ~/myvagrant && vagrant reload"))

僕はこれを my-vagrant.el のようなファイルに定義し、init.el から読むようにしています。

簡単に解説します。

  • defun : 関数定義を表します
  • my-vagrant-reload() 関数名です。引数なしであることを表しています
  • interactive : これを書くことで、Emacs上から M-x my-vagrant-reload のように呼ぶことができるようになります
  • async-shell-command シェルのコマンドを実行します

試しに Emacs を起動し、M-x my-vagrant-reload とすると、Vagrantが再起動します。*3 コードを書いていると動作確認の際にVagrantを再起動したくなることがあるので、非常に重宝しています。

基礎編: Vagrant上のログをtailする

動作確認の際にログをtailすることがよくあります。しかし、いちいちVagrantSSH→ログをtailとするのが面倒なので、これもEmacsの関数として定義してしまいましょう。

(defun my-vagrant-tail-info-log()
  (interactive)
  (async-shell-command "cd ~/myvagrant && vagrant ssh && tail -f /var/log/myapp/info.log"))

これも先程の vagrant reload とほぼ変わりません。非常にシンプルです。

応用編: バッファ内の文字列を置き換える

PHPのバージョンで関数が使えなくなったので置き換えなければならない!といった場面があるかと思います。規則性のある置換ならEmacs Lispを書いて一発置換できます。

たとえば、PHPUnitgetMockクラスがPHPUnit 6から削除されました。そこで、新しく Mockery というモックフレームワークを導入し、$this->getMock(...) メソッドを \Mockery::mock(...) メソッドに置き換えていきたいです。

以下のような関数を定義します。

(defun replace-mockery-getmock ()
  (interactive)
  (beginning-of-buffer)
  (replace-regexp "$this->getMock('\\([^()]+\\)')" "\Mockery::mock(\\1::class)"))

この関数は

$this->getMock('HogeClass')

のような文字列を

\Mockery::mock(HogeClass::class)

のように置き換えています。手順としては

  • beginning-of-buffer: バッファの先頭にカーソルを移動します。replace-regexpが現在のカーソルの位置から下に向かって検索するため、最初にカーソルをバッファの先頭に移動しておく必要があります
  • replace-regexp正規表現での置き換えです。正規表現に関しては詳しく説明しませんが、
    • \\1: \\(...\\) でマッチした1番目のぶぶんを取り出します

置き換えの正規表現さえ書いてしまえば、バッファ内の関数などを M-x replace-mockery-getmock で一発変換できて便利です。

応用編: 選択中のテストを回す

すこし応用編です。

PHPUnit

phpunit --filter="hoge" NanikaTest.php

とコマンドを叩くと、 NanikaTest.php の中にある、hoge という文字列を含むテストだけを実行できます。これを利用し、現在カーソルがある部分のテストを実行します。

まず、現在カーソルがある関数の関数名を取得します。

;; 今カーソルがいるpublic methodの名前を取得する
(defun php-current-public-method-name ()
  ;; カーソルから一番近い"public function"を探す
  (search-backward "public function")
  ;; " function " の後ろの部分にカーソルを移動
  (search-forward " function ")
  ;; ここをメソッド名のスタートとする
  (setq method-name-start (point))
  ;; "(" を探す
  (search-forward "(") 
  ;; "(" の一つ前にカーソルを移動
  (left-char) 
  ;; ここがメソッド名の終わりとする
  (setq method-name-end (point)) 
  ;; 最後に文字列を切り出した返す
  (string-trim (buffer-substring-no-properties method-name-start method-name-end))
)

かなり複雑ですが、Emacs Lispの関数を駆使し、現在のカーソルから一番近い public function の名前を取得できます。PHPUnitのテストはすべて public function なので、これで取得したメソッド名に対してテストを実行すれば問題ありません。

(defun phpunit-current-function ()
  (interactive)
  (async-shell-command
   (concat
    "phpunit "
    "--filter=\""
    (php-current-public-method-name)
    "\" "
    buffer-file-name)))
  • concat 関数で文字列結合できるので、長いコマンドなどは適当に分割して書くと見やすいです
  • buffer-file-name で今開いているファイル名を取得できます。

複雑ですが、なんとなくわかってもらえれば大丈夫です。結局はEmacs Lispの関数とシェルのコマンドだけで、そんなに難しいことはしていません。

まとめ

僕が実際に定義して利用しているEmacs Lispの関数をいくつか紹介しました。

僕のやり方よりももっと効率的な方法もあるかもしれませんし、僕の定義した関数があらゆる人に役に立つとも限りません。

しかし、よく使うスクリプトなどをEmacsで関数を定義することにより、かなりの効率化がはかれます。とりあえずよく使うコマンドを定義してみるなど、簡単なところから始めてみてはいかがでしょうか。

*1:いつの間にか12月になっていて気づきませんでした...

*2:僕も最初はそうでした

*3:Vagrantのコマンド等の詳細については省略します