class Middleman::SourceWatcher

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

Constants

IGNORED_DIRECTORIES

Attributes

directory[R]
listener[R]

Reference to lower level listener

options[R]
type[R]

Public Class Methods

new(parent, type, directory, options={}) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 62
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

Public Instance Methods

exists?(path) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 158
def exists?(path)
  !find(path).nil?
end
files() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 127
def files
  @files.values
end
find(path, glob=false) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 137
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
find_new_files!() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 197
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
inspect()
Alias for: to_s
listen!() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 166
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
poll_once!() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 208
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
stop_listener!() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 189
def stop_listener!
  return unless @listener

  @listener.stop
  @listener = nil
end
to_s() click to toggle source

Work around this bug: bugs.ruby-lang.org/issues/4521 where Ruby will call to_s/inspect while printing exception messages, which can take a long time (minutes at full CPU) if the object is huge or has cyclic references, like this.

# File lib/middleman-core/sources/source_watcher.rb, line 226
def to_s
  "#<Middleman::SourceWatcher:0x#{object_id} type=#{@type.inspect} directory=#{@directory.inspect}>"
end
Also aliased as: inspect
unwatch() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 119
def unwatch
  stop_listener!
end
update_config(options={}) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 106
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
update_path(directory) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 96
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

Protected Instance Methods

on_listener_change(modified, added, removed) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 246
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
partial?(relative_path) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 316
def partial?(relative_path)
  relative_path.split(::File::SEPARATOR).any? { |p| p.start_with?('_') }
end
path_to_source_file(path, directory, type, destination_dir) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 303
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
record_file_change(f) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 321
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
remove_file_from_cache(f) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 331
def remove_file_from_cache(f)
  @files.delete(f[:full_path])
  @extensionless_files.delete(strip_extensions(f[:full_path]))
end
should_not_recurse?(p) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 234
def should_not_recurse?(p)
  relative_path = p.relative_path_from(@directory).to_s
  IGNORED_DIRECTORIES.include?(relative_path)
end
strip_extensions(p) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 337
def strip_extensions(p)
  p = p.sub_ext('') while ::Tilt[p.to_s] || p.extname == '.html'
  Pathname(p.to_s + '.*')
end
update(updated_paths, removed_paths) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 259
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
valid?(file) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 347
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

Private Instance Methods

without_listener_running() { || ... } click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 359
def without_listener_running
  listener_running = @listener && @listener.processing?

  stop_listener! if listener_running

  yield

  if listener_running
    poll_once!
    listen!
  end
end