class Middleman::SourceWatcher

and responds to events on changes.
The default source watcher implementation. Watches a directory on disk

def exists?(path)

def exists?(path)
  !find(path).nil?
end

def files

def files
  @files.values
end

def find(path, glob=false)

def find(path, glob=false)
  path = path.to_s.encode!('UTF-8', 'UTF-8-MAC') if RUBY_PLATFORM =~ /darwin/
  p = Pathname(path)
  return nil if p.absolute? && !p.to_s.start_with?(@directory.to_s)
  p = @directory + p if p.relative?
  if glob
    @extensionless_files[p]
  else
    @files[p]
  end
end

def find_new_files!

def find_new_files!
  new_files = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
                               .reject { |p| @files.key?(p) }
  update(new_files, []).flatten.map { |s| s[:full_path] }
end

def initialize(parent, type, directory, options={})

def initialize(parent, type, directory, options={})
  @parent = parent
  @options = options
  @type = type
  @directory = Pathname(directory)
  @files = {}
  @extensionless_files = {}
  @frontmatter = options.fetch(:frontmatter, true)
  @binary = options.fetch(:binary, false)
  @validator = options.fetch(:validator, proc { true })
  @ignored = options.fetch(:ignored, proc { false })
  @only = Array(options.fetch(:only, []))
  @disable_watcher = app.build?
  @force_polling = false
  @latency = nil
  @wait_for_delay = nil
  @listener = nil
  @callbacks = ::Middleman::CallbackManager.new
  @callbacks.install_methods!(self, [:on_change])
  @waiting_for_existence = !@directory.exist?
end

def listen!

def listen!
  return if @disable_watcher || @listener || @waiting_for_existence
  config = {
    force_polling: @force_polling
  }
  config[:wait_for_delay] = @wait_for_delay.try(:to_f) || 0.5
  config[:latency] = @latency.to_f if @latency
  @listener = ::Listen.to(@directory.to_s, config, &method(:on_listener_change))
  @listener.ignore(/^\.sass-cache/)
  @listener.ignore(/^node_modules/)
  @listener.ignore(/^vendor\/bundle/)
  @listener.start
end

def on_listener_change(modified, added, removed)

def on_listener_change(modified, added, removed)
  updated = (modified + added)
  return if updated.empty? && removed.empty?
  update(updated.map { |s| Pathname(s) }, removed.map { |s| Pathname(s) })
end

def partial?(relative_path)

def partial?(relative_path)
  relative_path.split(::File::SEPARATOR).any? { |p| p.start_with?('_') }
end

def path_to_source_file(path, directory, type, destination_dir)

def path_to_source_file(path, directory, type, destination_dir)
  types = Set.new([type])
  types << :no_frontmatter unless @frontmatter
  types << :binary if @binary
  relative_path = path.relative_path_from(directory)
  relative_path = File.join(destination_dir, relative_path) if destination_dir
  types << :no_frontmatter if partial?(relative_path.to_s)
  ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types, 0)
end

def poll_once!

def poll_once!
  updated = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
  removed = @files.keys - updated
  result = update(updated, removed)
  if @waiting_for_existence && @directory.exist?
    @waiting_for_existence = false
    listen!
  end
  result.flatten.map { |s| s[:full_path] }
end

def record_file_change(f)

def record_file_change(f)
  if @files[f[:full_path]]
    @files[f[:full_path]][:version] += 1
  else
    @files[f[:full_path]] = f
    @extensionless_files[strip_extensions(f[:full_path])] = f
  end
end

def remove_file_from_cache(f)

def remove_file_from_cache(f)
  @files.delete(f[:full_path])
  @extensionless_files.delete(strip_extensions(f[:full_path]))
end

def should_not_recurse?(p)

def should_not_recurse?(p)
  relative_path = p.relative_path_from(@directory).to_s
  IGNORED_DIRECTORIES.include?(relative_path)
end

def stop_listener!

def stop_listener!
  return unless @listener
  @listener.stop
  @listener = nil
end

def strip_extensions(p)

def strip_extensions(p)
  p = p.sub_ext('') while Middleman::Util.tilt_class(p.to_s) || p.extname == '.html'
  Pathname(p.to_s + '.*')
end

def to_s

if the object is huge or has cyclic references, like this.
messages, which can take a long time (minutes at full CPU)
where Ruby will call to_s/inspect while printing exception
Work around this bug: http://bugs.ruby-lang.org/issues/4521
def to_s
  "#<Middleman::SourceWatcher:0x#{object_id} type=#{@type.inspect} directory=#{@directory.inspect}>"
end

def unwatch

def unwatch
  stop_listener!
end

def update(updated_paths, removed_paths)

def update(updated_paths, removed_paths)
  valid_updates = updated_paths
                  .map { |p| @files[p] || path_to_source_file(p, @directory, @type, @options[:destination_dir]) }
                  .select(&method(:valid?))
  valid_updates.each do |f|
    record_file_change(f)
    logger.debug "== Change (#{f[:types].inspect}): #{f[:relative_path]}"
  end
  related_sources = valid_updates.map { |u| u[:full_path] } + removed_paths
  related_updates = ::Middleman::Util.find_related_files(app, related_sources).select(&method(:valid?))
  related_updates.each do |f|
    logger.debug "== Possible Change (#{f[:types].inspect}): #{f[:relative_path]}"
  end
  valid_updates |= related_updates
  valid_removes = removed_paths
                  .select(&@files.method(:key?))
                  .map(&@files.method(:[]))
                  .select(&method(:valid?))
                  .each do |f|
                    remove_file_from_cache(f)
                    logger.debug "== Deletion (#{f[:types].inspect}): #{f[:relative_path]}"
                  end
  unless valid_updates.empty? && valid_removes.empty?
    execute_callbacks(:on_change, [
                        valid_updates,
                        valid_removes,
                        self
                      ])
  end
  [valid_updates, valid_removes]
end

def update_config(options={})

def update_config(options={})
  without_listener_running do
    @disable_watcher = options.fetch(:disable_watcher, false)
    @force_polling = options.fetch(:force_polling, false)
    @latency = options.fetch(:latency, nil)
    @wait_for_delay = options.fetch(:wait_for_delay, nil)
  end
end

def update_path(directory)

def update_path(directory)
  @directory = Pathname(File.expand_path(directory, app.root))
  without_listener_running do
    update([], @files.values.map { |source_file| source_file[:full_path] })
  end
  poll_once!
end

def valid?(file)

def valid?(file)
  return false unless @validator.call(file) && !globally_ignored?(file)
  if @only.empty?
    !@ignored.call(file)
  else
    @only.any? { |reg| file[:relative_path].to_s =~ reg }
  end
end

def without_listener_running

def without_listener_running
  listener_running = @listener && @listener.processing?
  stop_listener! if listener_running
  yield
  if listener_running
    poll_once!
    listen!
  end
end