From 1fd9d978a737d36cf7cca698f0fcbfc6fcdbed98 Mon Sep 17 00:00:00 2001 From: wycats Date: Sun, 6 Feb 2011 13:41:56 -0800 Subject: [PATCH] Add initial FileWatcher implementation. The Backend is just an abstract implementation, which will be inherited by backends that do the heavy lifting. --- activesupport/lib/active_support.rb | 1 + .../lib/active_support/file_watcher.rb | 41 ++++++++++++ activesupport/test/file_watcher_test.rb | 65 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 activesupport/lib/active_support/file_watcher.rb create mode 100644 activesupport/test/file_watcher_test.rb diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 6b87774978..6b662ac660 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -42,6 +42,7 @@ module ActiveSupport autoload :DescendantsTracker autoload :FileUpdateChecker + autoload :FileWatcher autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/lib/active_support/file_watcher.rb b/activesupport/lib/active_support/file_watcher.rb new file mode 100644 index 0000000000..046a5a0309 --- /dev/null +++ b/activesupport/lib/active_support/file_watcher.rb @@ -0,0 +1,41 @@ +module ActiveSupport + class FileWatcher + class Backend + def initialize(path, watcher) + @watcher = watcher + @path = path + end + + def trigger(files) + @watcher.trigger(files) + end + end + + def initialize + @regex_matchers = {} + end + + def watch(path, &block) + return watch_regex(path, &block) if path.is_a?(Regexp) + raise "Paths must be regular expressions. #{path.inspect} is a #{path.class}" + end + + def watch_regex(regex, &block) + @regex_matchers[regex] = block + end + + def trigger(files) + trigger_files = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } } + + files.each do |file, state| + @regex_matchers.each do |regex, block| + trigger_files[block][state] << file if file =~ regex + end + end + + trigger_files.each do |block, payload| + block.call payload + end + end + end +end diff --git a/activesupport/test/file_watcher_test.rb b/activesupport/test/file_watcher_test.rb new file mode 100644 index 0000000000..54aa9f92f1 --- /dev/null +++ b/activesupport/test/file_watcher_test.rb @@ -0,0 +1,65 @@ +require 'abstract_unit' + +class FileWatcherTest < ActiveSupport::TestCase + class DumbBackend < ActiveSupport::FileWatcher::Backend + end + + def setup + @watcher = ActiveSupport::FileWatcher.new + + # In real life, the backend would take the path and use it to observe the file + # system. In our case, we will manually trigger the events for unit testing, + # so we can pass any path. + @backend = DumbBackend.new("RAILS_WOOT", @watcher) + + @payload = [] + @watcher.watch %r{^app/assets/.*\.scss$} do |pay| + pay.each do |status, files| + files.sort! + end + @payload << pay + end + end + + def test_one_change + @backend.trigger("app/assets/main.scss" => :changed) + assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) + end + + def test_multiple_changes + @backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) + assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) + end + + def test_multiple_changes_match + @backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) + assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload) + end + + def test_multiple_state_changes + @backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed) + assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload) + end + + def test_more_blocks + payload = [] + @watcher.watch %r{^config/routes\.rb$} do |pay| + payload << pay + end + + @backend.trigger "config/routes.rb" => :changed + assert_equal [:changed => ["config/routes.rb"]], payload + assert_equal [], @payload + end + + def test_overlapping_watchers + payload = [] + @watcher.watch %r{^app/assets/main\.scss$} do |pay| + payload << pay + end + + @backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed + assert_equal [:changed => ["app/assets/main.scss"]], payload + assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload + end +end