class Middleman::Sources

Sources handle multiple on-disk collections of files which make up a Middleman project. They are separated by ‘type` which can then be queried. For example, the `source` type represents all content that the sitemap uses to build a project. The `data` type represents YAML data. The `locales` type represents localization YAML, and so on.

Constants

CODE_TYPES

Types which require a reload to eval ruby

CallbackDescriptor

A callback requires a type and the proc to execute.

HANDLER

Duck-typed definition of a valid source watcher

Matcher
OUTPUT_TYPES

Types which could cause output to change.

Attributes

app[R]
options[R]

Public Class Methods

new(app, _options={}, watchers=[]) click to toggle source
# File lib/middleman-core/sources.rb, line 59
def initialize(app, _options={}, watchers=[])
  @app = app
  @watchers = watchers
  @sorted_watchers = @watchers.dup.freeze

  ::Middleman::Sources.file_cache = {}

  # Set of procs wanting to be notified of changes
  @on_change_callbacks = ::Hamster::Vector.empty

  # Global ignores
  @ignores = ::Hamster::Hash.empty

  # Whether we're "running", which means we're in a stable
  # watch state after all initialization and config.
  @running = false

  @update_count = 0
  @last_update_count = -1

  # When the app is about to shut down, stop our watchers.
  @app.before_shutdown(&method(:stop!))
end

Public Instance Methods

by_type(type) click to toggle source
# File lib/middleman-core/sources.rb, line 172
def by_type(type)
  self.class.new @app, nil, watchers.select { |d| d.type == type }
end
changed(matcher=nil) { |f| ... } click to toggle source
# File lib/middleman-core/sources.rb, line 280
def changed(matcher=nil, &_block)
  on_change OUTPUT_TYPES do |updated, _removed|
    updated
      .select { |f| matcher.nil? ? true : matches?(matcher, f) }
      .each { |f| yield f[:relative_path] }
  end
end
deleted(matcher=nil) { |f| ... } click to toggle source
# File lib/middleman-core/sources.rb, line 292
def deleted(matcher=nil, &_block)
  on_change OUTPUT_TYPES do |_updated, removed|
    removed
      .select { |f| matcher.nil? ? true : matches?(matcher, f) }
      .each { |f| yield f[:relative_path] }
  end
end
exists?(types, path) click to toggle source
# File lib/middleman-core/sources.rb, line 208
def exists?(types, path)
  watchers.any? { |d| Array(types).include?(d.type) && d.exists?(path) }
end
files() click to toggle source
# File lib/middleman-core/sources.rb, line 180
def files
  watchers.flat_map(&:files).uniq { |f| f[:relative_path] }
end
find(types, path, glob=false) click to toggle source
# File lib/middleman-core/sources.rb, line 191
def find(types, path, glob=false)
  array_of_types = Array(types)

  watchers
    .lazy
    .select { |d| array_of_types.include?(d.type) }
    .map { |d| d.find(path, glob) }
    .reject(&:nil?)
    .first
end
find_new_files!() click to toggle source
# File lib/middleman-core/sources.rb, line 226
def find_new_files!
  return [] unless @update_count != @last_update_count

  @last_update_count = @update_count
  watchers.reduce([]) { |sum, w| sum + w.find_new_files! }
end
globally_ignored?(file) click to toggle source
# File lib/middleman-core/sources.rb, line 104
def globally_ignored?(file)
  @ignores.values.any? do |descriptor|
    ((descriptor[:type] == :all) || file[:types].include?(descriptor[:type])) &&
      matches?(descriptor[:validator], file)
  end
end
ignore(name, type, regex=nil, &block) click to toggle source
# File lib/middleman-core/sources.rb, line 91
def ignore(name, type, regex=nil, &block)
  @ignores = @ignores.put(name, type: type,
                                validator: (block_given? ? block : regex))

  bump_count
  poll_once! if @running
end
ignored?(path) click to toggle source
# File lib/middleman-core/sources.rb, line 304
def ignored?(path)
  descriptor = find(OUTPUT_TYPES, path)
  !descriptor || globally_ignored?(descriptor)
end
on_change(types, &block) click to toggle source
# File lib/middleman-core/sources.rb, line 270
def on_change(types, &block)
  Array(types).each do |type|
    @on_change_callbacks = @on_change_callbacks.push(CallbackDescriptor.new(type, block))
  end
end
poll_once!() click to toggle source
# File lib/middleman-core/sources.rb, line 237
def poll_once!
  return [] unless @update_count != @last_update_count

  @last_update_count = @update_count
  watchers.reduce([]) { |sum, w| sum + w.poll_once! }
end
start!() click to toggle source
# File lib/middleman-core/sources.rb, line 248
def start!
  watchers.each(&:listen!)
  @running = true
end
stop!() click to toggle source
# File lib/middleman-core/sources.rb, line 257
def stop!
  watchers.each(&:stop_listener!)
  @running = false
end
unwatch(watcher) click to toggle source
# File lib/middleman-core/sources.rb, line 159
def unwatch(watcher)
  @watchers.delete(watcher)

  watcher.unwatch

  bump_count
end
watch(type_or_handler, options={}) click to toggle source
# File lib/middleman-core/sources.rb, line 119
def watch(type_or_handler, options={})
  handler = if type_or_handler.is_a? Symbol
    path = File.expand_path(options.delete(:path), app.root)
    SourceWatcher.new(self, type_or_handler, path, options)
  else
    type_or_handler
  end

  @watchers << handler

  # The index trick is used so that the sort is stable - watchers with the same priority
  # will always be ordered in the same order as they were registered.
  n = 0
  @sorted_watchers = @watchers.sort_by do |w|
    priority = w.options.fetch(:priority, 50)
    n += 1
    [priority, n]
  end.reverse.freeze

  handler.on_change(&method(:did_change))

  if @running
    handler.poll_once!
    handler.listen!
  end

  handler
end
watcher_for_path(types, path) click to toggle source
# File lib/middleman-core/sources.rb, line 218
def watcher_for_path(types, path)
  watchers.detect { |d| Array(types).include?(d.type) && d.exists?(path) }
end
watchers() click to toggle source
# File lib/middleman-core/sources.rb, line 150
def watchers
  @sorted_watchers
end

Protected Instance Methods

bump_count() click to toggle source
# File lib/middleman-core/sources.rb, line 330
def bump_count
  @update_count += 1
end
did_change(updated_files, removed_files, watcher) click to toggle source
# File lib/middleman-core/sources.rb, line 339
def did_change(updated_files, removed_files, watcher)
  valid_updated = updated_files.select do |file|
    watcher_for_path(file[:types], file[:relative_path].to_s) == watcher
  end

  valid_removed = removed_files.select do |file|
    watcher_for_path(file[:types], file[:relative_path].to_s).nil?
  end

  return if valid_updated.empty? && valid_removed.empty?

  bump_count
  run_callbacks(@on_change_callbacks, valid_updated, valid_removed)
end
matches?(validator, file) click to toggle source
# File lib/middleman-core/sources.rb, line 317
def matches?(validator, file)
  path = file[:relative_path]
  if validator.is_a? Regexp
    !!(path.to_s =~ validator)
  else
    !!validator.call(path, @app)
  end
end
run_callbacks(callback_descriptors, updated_files, removed_files) click to toggle source
# File lib/middleman-core/sources.rb, line 360
def run_callbacks(callback_descriptors, updated_files, removed_files)
  callback_descriptors.each do |callback|
    if callback[:type] == :all
      callback[:proc].call(updated_files, removed_files)
    else
      valid_updated = updated_files.select { |f| f[:types].include?(callback[:type]) }
      valid_removed = removed_files.select { |f| f[:types].include?(callback[:type]) }

      callback[:proc].call(valid_updated, valid_removed) unless valid_updated.empty? && valid_removed.empty?
    end
  end
end