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, nil, watchers.select { |d| d.type == type } end
def changed(matcher=nil, &_block)
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
def deleted(matcher=nil, &_block)
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
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?(types, path)
def exists?(types, path) watchers.any? { |d| Array(types).include?(d.type) && d.exists?(path) } end
def files
def files watchers.flat_map(&:files).uniq { |f| f[:relative_path] } end
def find(types, path, glob=false)
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
def find_new_files!
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
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 = @ignores.put(name, type: type, validator: (block_given? ? block : regex)) bump_count poll_once! if @running end
def ignored?(path)
def ignored?(path) descriptor = find(OUTPUT_TYPES, 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 ::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
def matches?(validator, file)
def matches?(validator, file) path = file[:relative_path] if validator.is_a? Regexp !!(path.to_s =~ validator) else !!validator.call(path, @app) end end
def on_change(types, &block)
def on_change(types, &block) Array(types).each do |type| @on_change_callbacks = @on_change_callbacks.push(CallbackDescriptor.new(type, block)) end end
def poll_once!
def poll_once! return [] unless @update_count != @last_update_count @last_update_count = @update_count watchers.reduce([]) { |sum, w| sum + w.poll_once! } 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 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
def watcher_for_path(types, path)
def watcher_for_path(types, path) watchers.detect { |d| Array(types).include?(d.type) && d.exists?(path) } end
def watchers
def watchers @sorted_watchers end