diff options
Diffstat (limited to 'activesupport/lib/active_support')
-rw-r--r-- | activesupport/lib/active_support/file_evented_update_checker.rb | 145 | ||||
-rw-r--r-- | activesupport/lib/active_support/file_update_checker.rb | 2 |
2 files changed, 146 insertions, 1 deletions
diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb new file mode 100644 index 0000000000..ae500d697f --- /dev/null +++ b/activesupport/lib/active_support/file_evented_update_checker.rb @@ -0,0 +1,145 @@ +require 'listen' +require 'set' +require 'pathname' + +module ActiveSupport + class FileEventedUpdateChecker #:nodoc: all + def initialize(files, dirs = {}, &block) + @ph = PathHelper.new + @files = files.map { |f| @ph.xpath(f) }.to_set + + @dirs = {} + dirs.each do |dir, exts| + @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) } + end + + @block = block + @updated = false + @lcsp = @ph.longest_common_subpath(@dirs.keys) + + if (dtw = directories_to_watch).any? + Listen.to(*dtw, &method(:changed)).start + end + end + + def updated? + @updated + end + + def execute + @block.call + ensure + @updated = false + end + + def execute_if_updated + if updated? + execute + true + end + end + + private + + def changed(modified, added, removed) + unless updated? + @updated = (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = @ph.xpath(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = @ph.normalize_extension(file.extname) + + file.dirname.ascend do |dir| + if @dirs.fetch(dir, []).include?(ext) + break true + elsif dir == @lcsp || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) } + dtw.compact! + dtw.uniq! + + @ph.filter_out_descendants(dtw) + end + + class PathHelper + using Module.new { + refine Pathname do + def ascendant_of?(other) + other.to_s =~ /\A#{Regexp.quote(to_s)}#{Pathname::SEPARATOR_PAT}?/ + end + end + } + + def xpath(path) + Pathname.new(path).expand_path + end + + def normalize_extension(ext) + ext.to_s.sub(/\A\./, '') + end + + # Given a collection of Pathname objects returns the longest subpath + # common to all of them, or +nil+ if there is none. + def longest_common_subpath(paths) + return if paths.empty? + + lcsp = Pathname.new(paths[0]) + + paths[1..-1].each do |path| + until lcsp.ascendant_of?(path) + if lcsp.root? + # If we get here a root directory is not an ascendant of path. + # This may happen if there are paths in different drives on + # Windows. + return + else + lcsp = lcsp.parent + end + end + end + + lcsp + end + + # Returns the deepest existing ascendant, which could be the argument itself. + def existing_parent(dir) + dir.ascend do |ascendant| + break ascendant if ascendant.directory? + end + end + + # Filters out directories which are descendants of others in the collection (stable). + def filter_out_descendants(directories) + return directories if directories.length < 2 + + sorted_by_nparts = directories.sort_by { |dir| dir.each_filename.to_a.length } + descendants = [] + + until sorted_by_nparts.empty? + dir = sorted_by_nparts.shift + + descendants.concat sorted_by_nparts.select { |possible_descendant| + dir.ascendant_of?(possible_descendant) + } + end + + # Array#- preserves order. + directories - descendants + end + end + end +end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 78b627c286..1fa9335080 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -35,7 +35,7 @@ module ActiveSupport # This method must also receive a block that will be called once a path # changes. The array of files and list of directories cannot be changed # after FileUpdateChecker has been initialized. - def initialize(files, dirs={}, &block) + def initialize(files, dirs = {}, &block) @files = files.freeze @glob = compile_glob(dirs) @block = block |