Provide a dir => extension API to file update checker.

This commit is contained in:
José Valim
2011-12-12 20:26:04 +01:00
parent b04e2d86df
commit 62cda03fa8
2 changed files with 67 additions and 20 deletions

View File

@@ -1,3 +1,6 @@
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/array/extract_options"
module ActiveSupport
# This class is responsible to track files and invoke the given block
# whenever one of these files are changed. For example, this class
@@ -15,15 +18,34 @@ module ActiveSupport
class FileUpdateChecker
attr_reader :paths, :last_update_at
# It accepts two parameters on initialization. The first is
# the *paths* and the second is *calculate*, a boolean.
#
# paths must be an array of file paths but can contain a hash as
# last argument. The hash must have directories as keys and the
# value is an array of extensions to be watched under that directory.
#
# If *calculate* is true, the latest updated at will calculated
# on initialization, therefore, the first call to execute_if_updated
# will only evaluate the block if something really changed.
#
# This method must also receive a block that will be the block called
# once a file changes.
#
# This particular implementation checks for added files and updated files,
# but not removed files. Directories lookup are compiled to a glob for
# performance.
def initialize(paths, calculate=false, &block)
@paths = paths
@glob = compile_glob(@paths.extract_options!)
@block = block
@last_update_at = calculate ? updated_at : nil
end
def updated_at
# TODO: Use Enumerable check once we get rid of 1.8.7
all = paths.is_a?(Array) ? paths : Dir[paths]
all = []
all.concat @paths
all.concat Dir[@glob] if @glob
all.map { |path| File.mtime(path) }.max
end
@@ -37,5 +59,22 @@ module ActiveSupport
false
end
end
private
def compile_glob(hash)
return if hash.empty?
globs = []
hash.each do |key, value|
globs << "#{key}/**/*#{compile_ext(value)}"
end
"{#{globs.join(",")}}"
end
def compile_ext(array)
array = Array.wrap(array)
return if array.empty?
".{#{array.join(",")}}"
end
end
end

View File

@@ -4,21 +4,19 @@ require 'fileutils'
MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
module FileUpdateCheckerSuite
class FileUpdateCheckerWithEnumerableTest < Test::Unit::TestCase
FILES = %w(1.txt 2.txt 3.txt)
def setup
FileUtils.mkdir_p("tmp_watcher")
FileUtils.touch(FILES)
end
def teardown
FileUtils.rm_rf("tmp_watcher")
FileUtils.rm(FILES)
end
def args
raise NotImplementedError
end
def test_should_not_execute_the_block_if_no_paths_are_given
i = 0
checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
@@ -28,41 +26,51 @@ module FileUpdateCheckerSuite
def test_should_invoke_the_block_on_first_call_if_it_does_not_calculate_last_updated_at_on_load
i = 0
checker = ActiveSupport::FileUpdateChecker.new(args){ i += 1 }
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
checker.execute_if_updated
assert_equal 1, i
end
def test_should_not_invoke_the_block_on_first_call_if_it_calculates_last_updated_at_on_load
i = 0
checker = ActiveSupport::FileUpdateChecker.new(args, true){ i += 1 }
checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
checker.execute_if_updated
assert_equal 0, i
end
def test_should_not_invoke_the_block_if_no_file_has_changed
i = 0
checker = ActiveSupport::FileUpdateChecker.new(args, true){ i += 1 }
checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
5.times { assert !checker.execute_if_updated }
assert_equal 0, i
end
def test_should_invoke_the_block_if_a_file_has_changed
i = 0
checker = ActiveSupport::FileUpdateChecker.new(args, true){ i += 1 }
checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
sleep(1)
FileUtils.touch(FILES)
assert checker.execute_if_updated
assert_equal 1, i
end
end
class FileUpdateCheckerWithEnumerableTest < Test::Unit::TestCase
include FileUpdateCheckerSuite
def args; FILES; end
end
def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
i = 0
checker = ActiveSupport::FileUpdateChecker.new([{"tmp_watcher" => [:txt]}], true){ i += 1 }
FileUtils.cd "tmp_watcher" do
FileUtils.touch(FILES)
end
assert checker.execute_if_updated
assert_equal 1, i
end
class FileUpdateCheckerWithStringTest < Test::Unit::TestCase
include FileUpdateCheckerSuite
def args; "{1,2,3}.txt"; end
end
def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
i = 0
checker = ActiveSupport::FileUpdateChecker.new([{"tmp_watcher" => :rb}], true){ i += 1 }
FileUtils.cd "tmp_watcher" do
FileUtils.touch(FILES)
end
assert !checker.execute_if_updated
assert_equal 0, i
end
end