file_evented_update_checker.rb 2.2 KB
Newer Older
1
require 'listen'
2 3
require 'set'
require 'pathname'
4 5 6

module ActiveSupport
  class FileEventedUpdateChecker
7 8 9 10 11 12
    def initialize(files, dirs={}, &block)
      @files = files.map {|f| expand_path(f)}.to_set

      @dirs = {}
      dirs.each do |dir, exts|
        @dirs[expand_path(dir)] = Array(exts).map(&:to_s)
13
      end
14

15 16
      @block = block
      @modified = false
17 18

      if (watch_dirs = base_directories).any?
19
        Listen.to(*watch_dirs, &method(:changed)).start
20
      end
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
    end

    def updated?
      @modified
    end

    def execute
      @block.call
    ensure
      @modified = false
    end

    def execute_if_updated
      if updated?
        execute
        true
      end
    end

    private

42 43
    def expand_path(fname)
      File.expand_path(fname)
44 45 46 47
    end

    def changed(modified, added, removed)
      return if updated?
48 49

      if (modified + added + removed).any? {|f| watching?(f)}
50 51 52 53
        @modified = true
      end
    end

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
    def watching?(file)
      file = expand_path(file)
      return true if @files.member?(file)

      file = Pathname.new(file)
      return false if file.directory?

      ext = file.extname.sub(/\A\./, '')
      dir = file.dirname

      loop do
        if @dirs.fetch(dir.to_path, []).include?(ext)
          break true
        else
          if dir.root? # TODO: find a common parent directory in initialize
            break false
          end
          dir = dir.parent
        end
      end
    end

    # TODO: Better return a list of non-nested directories.
77
    def base_directories
78 79 80 81 82 83
      [].tap do |bd|
        bd.concat @files.map {|f| existing_parent(File.dirname(f))}
        bd.concat @dirs.keys.map {|dir| existing_parent(dir)}
        bd.compact!
        bd.uniq!
      end
84 85
    end

86
    def existing_parent(dir)
87
      dir = Pathname.new(expand_path(dir))
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

      loop do
        if dir.directory?
          break dir.to_path
        else
          if dir.root?
            # Edge case in which not even the root exists. For example, Windows
            # paths could have a non-existing drive letter. Since the parent of
            # root is root, we need to break to prevent an infinite loop.
            break
          else
            dir = dir.parent
          end
        end
      end
103 104 105
    end
  end
end