【Guard】自動生成されるGuardfileで監視・実行されるファイルは結局どこなのか、ソースコードから読み解いてみた

## はじめに
RailsアプリでRSpecを自動で走らせるためにGuardを導入したのですが、その設定ファイルである`Guardfile`について理解を深めたかったのでまとめてみました。

踏み込んでいくうちにソースコードまで読むことになり、メタプログラミングの勉強にもなりました。

この理解で`guard-rspec`や`guard-rubocop`で自動生成される内容にアレンジが加えられるようになったので、生産性が上がりそうです:point_up:

ベースとして、こちらの記事を参考にさせて頂きました。
[rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ](https://blog.s0014.com/posts/2017-05-24-guard/)

## この記事が役に立つ方
- 自動生成されるGuardfileを自分ではアレンジ出来ない方

## この記事のメリット
- 自動生成されるGuardfileではどういう設定がされているのかが分かる
- Guardfileを自分用にアレンジ出来るようになる。

## 環境
- macOS Mojave バージョン10.14.6
- シェル：zsh
- Ruby:2.6.5
- Rails:5.2.2
- RSpec:3.9
- Guard:2.16.1
　

## 【準備】Guardのインストールから起動まで
RSpecを使う場合を例にしますので、gemは`guard-rspec`にしています。

```ruby:Gemfile
group :development, :test do
  gem 'guard-rspec', require:false
end
```
Gemfileに上記gemを記載。
↓

```shell
$ bundle install
```
gemをbundlerでインストールする。
↓

```shell
$ bundle exec guard init
```
guardの設定ファイル（Guardfile）を作成。
↓

```shell
$ bundle exec guard
```
guardを起動。
これで`Guardfile`に沿って動く状態になります。

次はデフォルトで生成される`Guardfile`を見てみます。

## デフォルトのGuardfile
生成された`Guardfile`は以下のようになっています。

```ruby:Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  require "guard/rspec/dsl"
  dsl = Guard::RSpec::Dsl.new(self)

  # Feel free to open issues for suggestions and improvements

  # RSpec files
  rspec = dsl.rspec
  watch(rspec.spec_helper) { rspec.spec_dir }
  watch(rspec.spec_support) { rspec.spec_dir }
  watch(rspec.spec_files)

  # Ruby files
  ruby = dsl.ruby
  dsl.watch_spec_files_for(ruby.lib_files)

  # Rails files
  rails = dsl.rails(view_extensions: %w(erb haml slim))
  dsl.watch_spec_files_for(rails.app_files)
  dsl.watch_spec_files_for(rails.views)

  watch(rails.controllers) do |m|
    [
      rspec.spec.call("routing/#{m[1]}_routing"),
      rspec.spec.call("controllers/#{m[1]}_controller"),
      rspec.spec.call("acceptance/#{m[1]}")
    ]
  end

  # Rails config changes
  watch(rails.spec_helper)     { rspec.spec_dir }
  watch(rails.routes)          { "#{rspec.spec_dir}/routing" }
  watch(rails.app_controller)  { "#{rspec.spec_dir}/controllers" }

  # Capybara features specs
  watch(rails.view_dirs)     { |m| rspec.spec.call("features/#{m[1]}") }
  watch(rails.layouts)       { |m| rspec.spec.call("features/#{m[1]}") }

  # Turnip features and steps
  watch(%r{^spec/acceptance/(.+)\.feature$})
  watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
    Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
  end
end
```

見ると、rspecのテストがある`spec`、railsの`controller`や`routes`などのディレクトリ配下のファイルを監視してくれていて、それに対応するテストを走らせてくれていそうです。

ただ、ほとんど変数なので、**具体的にどのディレクトリやファイルを指しているのか**は分かりません。

ただ、最初から自動でここまでやってくれるのはありがたいんですが、自分でもアレンジ出来るようになりたかったので中身を読み解いてみます。

## Guardfileの基本構文

```ruby:Guardfile
guard :プラグイン名 オプション do
  watch(監視対象) {実行対象}
end
```
監視対象・実行対象はディレクトリでもファイルでも可です。
`{}`内は別の書き方をすると、以下と同義です。

```ruby:Guardfile
guard :プラグイン名　オプション do
  watch(監視対象) do
    実行対象
  end
end
```

また、以下のようにすると`監視対象`自身が`実行対象`になります。

```ruby:Guardfile
guard :プラグイン名 オプション do
  watch(監視対象)
end
```

`監視対象`の中で正規表現キャプチャを使っている場合、ブロック内で変数として代入することが出来ます。（キャプチャについては、以下記事が非常に参考になります）
[正規表現で名前付きキャプチャを使う - Qiita](https://qiita.com/jnchito/items/cceb669cb06fc044f411)

## 【例】Railsでコントローラーをいじったら自動でテストが走るようにしたい

Railsでコントローラーを変更したとき、対象となるテストを走らせたいという場合を想定すると、`Guardfile`には以下のように書きます。

```ruby:Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  watch("%r{app/controllers/(.*?).rb}") {|m| "spec/controllers/#{m[1]}.rb"}
end
```
もしくは以下のようになります。

```ruby:Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  watch("%r{app/controllers/(.*?).rb}") do |m|
    "spec/controllers/#{m[1]}.rb"
  end
end
```

キャプチャされた内容は[0]でなく、[1]から始まることに注意です。

このコードの意味を日本語で書くと、

- `guard-rspec`プラグインを使用して
- `app/controllers配下のRubyファイル`を監視して
- 動きがあればコマンドラインで`bundl exec rspec` `spec/controllers配下のRubyファイル`を実行する

ということですね。

次は自動生成された`Guardfile`を読み解いてみます。

## 自動生成された`Guardfile`を読み解く
自動生成される`Guardfile`については、メタプログラミングで簡潔に書かれていますが、以下ソースコードを見ると中身が分かるので、理解が可能です。
[guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub](https://github.com/guard/guard-rspec/blob/master/lib/guard/rspec/dsl.rb)

以下一文を例にします。

```ruby
watch(rspec.spec_helper) { rspec.spec_dir }
```
ここでは
監視対象：`rspec.spec_helper`
実行対象：`rspec.spec_dir`
です。

それぞれ変数に代入されているので具体的にどこを指すのかは書かれていませんが、ソースコードを見ると以下のように書かれています。

```ruby
...略
def rspec
  @rspec ||= OpenStruct.new(to_s: "spec").tap do |rspec|
    rspec.spec_dir = "spec"
    rspec.spec = ->(m) { Dsl.detect_spec_file_for(rspec, m) }
    rspec.spec_helper = "#{rspec.spec_dir}/spec_helper.rb"
    rspec.spec_files = %r{^#{rspec.spec_dir}/.+_spec\.rb$}
    rspec.spec_support = %r{^#{rspec.spec_dir}/support/(.+)\.rb$}
  end
end
...略
```

式展開していくと、監視対象は、

```ruby
  rspec.spec_helper = "spec/spec_helper.rb"
```
実行対象は、

```ruby
  rspec.spec_dir = "spec"
```
ということが分かります。

つまり、元の一文

```ruby
watch(rspec.spec_helper) { rspec.spec_dir }
```
は、

```ruby
watch("spec/spec_helper.rb") { "spec" }
```
に変換することができますね！

## おわりに
gemのソースコードをまともに見るのは初めてでしたが、この深堀りの仕方は他にも応用が効くなと感じました。

やっぱりgemとかプラグインを作る方の書くコードは**キレイ**ですね:relaxed:

## 参考にさせて頂いたサイト（いつもありがとうございます）
[rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ](https://blog.s0014.com/posts/2017-05-24-guard/)
[正規表現で名前付きキャプチャを使う - Qiita](https://qiita.com/jnchito/items/cceb669cb06fc044f411)
[guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub](https://github.com/guard/guard-rspec/blob/master/lib/guard/rspec/dsl.rb)