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? || @parent.options.fetch(:disable_watcher, false)
  @force_polling = @parent.options.fetch(:force_polling, false)
  @latency = @parent.options.fetch(:latency, 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 156
def exists?(path)
  !find(path).nil?
end
files() click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 119
def files
  @files.values
end
find(path, glob=false) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 129
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)

  destination_dir = @options[:destination_dir]
  if destination_dir.present? && p.to_s.start_with?(destination_dir)
    path_without_destination_dir = p.to_s[destination_dir.to_s.length + 1 .. -1]
    p = Pathname(path_without_destination_dir)
  end

  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 195
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 164
def listen!
  return if @disable_watcher || @listener || @waiting_for_existence

  config = {
    force_polling: @force_polling,
    wait_for_delay: 0.5
  }

  config[:latency] = @latency.to_i 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 206
def poll_once!
  updated = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
  removed = @files.keys.reject { |p| updated.include?(p) }

  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 187
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 224
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 111
def unwatch
  stop_listener!
end
update_path(directory) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 95
def update_path(directory)
  @directory = Pathname(File.expand_path(directory, app.root))

  stop_listener! if @listener

  update([], @files.values.map { |source_file| source_file[:full_path] })

  poll_once!

  listen! unless @disable_watcher
end

Protected Instance Methods

on_listener_change(modified, added, removed) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 244
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 312
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 299
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 317
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 327
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 232
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 333
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 257
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

  execute_callbacks(:on_change, [
                      valid_updates,
                      valid_removes,
                      self
                    ]) unless valid_updates.empty? && valid_removes.empty?

  [valid_updates, valid_removes]
end
valid?(file) click to toggle source
# File lib/middleman-core/sources/source_watcher.rb, line 343
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