Rakeでgruntやgulpのようなファイル変更を監視してビルドしてMacで通知を出す

やりたいこと

  • 特定のディレクトリ以下のファイルに変更があったときに、rakeが走るようにしたい
  • rakeが走ったあとに、Macで通知を出したい
  • 複数のファイルが一挙もしくは短い間に変更された際に、ファイル1個ごとでビルドが走らないようにしたい
  • .git/index.lockとかが変更されたときに、ビルドが走らないようにしたい
  • Rubyのfswatchをわざわざ入れなくても、コマンドのfswatchが入っていれば走るようにしたい
  • ビルド中にまたファイルに変更があったら、ビルドを即中止して新しいビルドを走らせたい

既存のもの

gulpにもmakeにも不満なWebデベロッパーためのRake(コンパイル・パターンマッチ・ファイル監視・通知)というものがある。

  • 複数のファイルが一挙もしくは短い間に変更された際に、ファイル1個ごとでビルドが走ってしまう
  • .git/index.lockとかが変更されたときに、ビルドが走ってしまう
  • ビルドを中止してくれない
  • notify-sendがHomebrewにない

改造したヤツ

Rakefileこうなった。あんまりかっこよくない。パスとかの空白には弱そうだから適宜対処して欲しい。pid一周誤爆kill気にしないねー。

require 'mkmf'

APP_NAME = 'Super Slow Build Project'

IGNORE_REGEX = %r{/\.git/}
WAIT_BUFFER_SECOND = 2.0

task :watch do |t|
  mac = (RbConfig::CONFIG['host_os'] =~ /\Adarwin[\d.]+\z/)
  unless mac
    puts 'Cannot use rake watch except MacOSX'
    exit(1)
  end
  unless find_executable('fswatch')
    `brew install fswatch`
  end
  IO.popen("fswatch TARGET_PATH1 TARGET_PATH2", 'r') {|io|
    buffer = []
    pid = nil
    last_load = 0
    loop {
      begin
        r = io.read_nonblock(1024)
        buffer << [r]
        last_load = Time.now.to_f
      rescue IO::WaitReadable
        if Time.now.to_f - last_load < WAIT_BUFFER_SECOND
          sleep 0.5
          retry
        end

        lines, cr, rest = buffer.join('').rpartition("\n")
        buffer = [rest]
        build = lines.split("\n").any? {|line|
          IGNORE_REGEX.match(line).nil?
        }
        if build
          if pid
            Process.kill(9, pid)
          end
          pid = fork {
            sh %{rake} do |ok, res|
              if ok
                notify('Build succeed')
              else
                notify('Build ERROR')
              end
            end
          }
        end

        IO.select([io])
      end
    }
  }
end

def notify(message)
  puts message
  `osascript -e 'display notification "#{message}" with title "#{APP_NAME}"'`
end