class Middleman::Sources
data. The ‘locales` type represents localization YAML, and so on.
the sitemap uses to build a project. The `data` type represents YAML
queried. For example, the `source` type represents all content that
a Middleman project. They are separated by `type` which can then be
Sources handle multiple on-disk collections of files which make up
def bump_count
def bump_count @update_count += 1 end
def by_type(type)
def by_type(type) self.class.new @app, @options, watchers.select { |d| d.type == type } end
def changed(matcher=nil, &block)
-
matcher
(nil, Regexp
) -- A Regexp to match the change path against
def changed(matcher=nil, &block) on_change :source do |updated, _removed| updated.select { |f| matcher.nil? ? true : matches?(matcher, f) }.each do |f| block.call(f[:relative_path]) end end end
def deleted(matcher=nil, &block)
-
matcher
(nil, Regexp
) -- A Regexp to match the change path against
def deleted(matcher=nil, &block) on_change :source do |_updated, removed| removed.select { |f| matcher.nil? ? true : matches?(matcher, f) }.each do |f| block.call(f[:relative_path]) end end end
def did_change(updated_files, removed_files, watcher)
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
def exists?(type, path)
def exists?(type, path) watchers .lazy .select { |d| d.type == type } .any? { |d| d.exists?(path) } end
def files
def files watchers.flat_map(&:files).uniq { |f| f[:relative_path] } end
def find(type, path, glob=false)
def find(type, path, glob=false) watchers .lazy .select { |d| d.type == type } .map { |d| d.find(path, glob) } .reject(&:nil?) .first end
def find_new_files!
def find_new_files! return unless @update_count != @last_update_count @last_update_count = @update_count watchers.each(&:poll_once!) end
def globally_ignored?(file)
def globally_ignored?(file) @ignores.values.any? do |descriptor| ((descriptor[:type] == :all) || file[:types].include?(descriptor[:type])) && matches?(descriptor[:validator], file) end end
def ignore(name, type, regex=nil, &block)
def ignore(name, type, regex=nil, &block) @ignores[name] = { type: type, validator: (block_given? ? block : regex) } bump_count find_new_files! if @running end
def ignored?(path)
def ignored?(path) descriptor = find(:source, path) !descriptor || globally_ignored?(descriptor) end
def initialize(app, options={}, watchers=[])
def initialize(app, options={}, watchers=[]) @app = app @watchers = watchers @sorted_watchers = @watchers.dup.freeze @options = options # Set of procs wanting to be notified of changes @on_change_callbacks = [] # Global ignores @ignores = {} # 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
def matches?(validator, file)
def matches?(validator, file) path = file[:relative_path] if validator.is_a? Regexp !!validator.match(path.to_s) else !!validator.call(path, @app) end end
def on_change(type, &block)
def on_change(type, &block) @on_change_callbacks << CallbackDescriptor.new(type, block) @on_change_callbacks end
def run_callbacks(callback_descriptors, updated_files, removed_files)
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
def start!
def start! watchers.each(&:listen!) @running = true end
def stop!
def stop! watchers.each(&:stop_listener!) @running = false end
def unwatch(watcher)
def unwatch(watcher) @watchers.delete(watcher) watcher.unwatch bump_count end
def watch(type_or_handler, options={})
def watch(type_or_handler, options={}) handler = if type_or_handler.is_a? Symbol SourceWatcher.new(self, type_or_handler, options.delete(: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
def watcher_for_path(types, path)
def watcher_for_path(types, path) watchers .select { |d| types.include?(d.type) } .find { |d| d.exists?(path) } end
def watchers
def watchers @sorted_watchers end