2012-05-26 03:18:51 +02:00
|
|
|
require "pathname"
|
2012-05-20 01:49:44 +02:00
|
|
|
require "set"
|
|
|
|
|
2011-12-22 06:52:38 +01:00
|
|
|
# API for watching file change events
|
2012-05-07 23:41:39 +02:00
|
|
|
module Middleman
|
|
|
|
module CoreExtensions
|
|
|
|
module FileWatcher
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-20 01:49:44 +02:00
|
|
|
IGNORE_LIST = [
|
2012-05-26 03:18:51 +02:00
|
|
|
/^\.bundle\//,
|
2012-05-20 01:49:44 +02:00
|
|
|
/^\.sass-cache\//,
|
|
|
|
/^\.git\//,
|
|
|
|
/^\.gitignore$/,
|
|
|
|
/^\.DS_Store$/,
|
|
|
|
/^build\//,
|
|
|
|
/^\.rbenv-.*$/,
|
|
|
|
/^Gemfile$/,
|
|
|
|
/^Gemfile\.lock$/,
|
2012-07-09 04:01:06 +02:00
|
|
|
/~$/,
|
|
|
|
/(^|\/)\.?#/
|
2012-05-20 01:49:44 +02:00
|
|
|
]
|
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Setup extension
|
|
|
|
class << self
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Once registered
|
|
|
|
def registered(app)
|
|
|
|
app.send :include, InstanceMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Before parsing config, load the data/ directory
|
|
|
|
app.before_configuration do
|
2012-07-19 10:17:50 +02:00
|
|
|
files.reload_path(data_dir)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# After config, load everything else
|
|
|
|
app.ready do
|
2012-07-19 10:17:50 +02:00
|
|
|
files.reload_path('.')
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
alias :included :registered
|
2011-11-18 01:02:07 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Instance methods
|
|
|
|
module InstanceMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Access the file api
|
|
|
|
# @return [Middleman::CoreExtensions::FileWatcher::API]
|
|
|
|
def files
|
2012-05-20 01:49:44 +02:00
|
|
|
@_files_api ||= API.new(self)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Core File Change API class
|
|
|
|
class API
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-07-15 20:04:45 +02:00
|
|
|
attr_reader :app
|
|
|
|
delegate :logger, :to => :app
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Initialize api and internal path cache
|
2012-05-20 01:49:44 +02:00
|
|
|
def initialize(app)
|
|
|
|
@app = app
|
|
|
|
@known_paths = Set.new
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-20 01:49:44 +02:00
|
|
|
@_changed = []
|
|
|
|
@_deleted = []
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Add callback to be run on file change
|
|
|
|
#
|
|
|
|
# @param [nil,Regexp] matcher A Regexp to match the change path against
|
|
|
|
# @return [Array<Proc>]
|
|
|
|
def changed(matcher=nil, &block)
|
|
|
|
@_changed << [block, matcher] if block_given?
|
|
|
|
@_changed
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Add callback to be run on file deletion
|
|
|
|
#
|
|
|
|
# @param [nil,Regexp] matcher A Regexp to match the deleted path against
|
|
|
|
# @return [Array<Proc>]
|
|
|
|
def deleted(matcher=nil, &block)
|
|
|
|
@_deleted << [block, matcher] if block_given?
|
|
|
|
@_deleted
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Notify callbacks that a file changed
|
|
|
|
#
|
2012-05-26 03:18:51 +02:00
|
|
|
# @param [Pathname] path The file that changed
|
2012-05-07 23:41:39 +02:00
|
|
|
# @return [void]
|
|
|
|
def did_change(path)
|
2012-05-26 03:18:51 +02:00
|
|
|
return if ignored?(path)
|
2012-07-19 10:17:50 +02:00
|
|
|
logger.debug "== File Change: #{path}"
|
2012-05-20 01:49:44 +02:00
|
|
|
@known_paths << path
|
2012-05-07 23:41:39 +02:00
|
|
|
self.run_callbacks(path, :changed)
|
|
|
|
end
|
2011-11-18 01:02:07 +01:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Notify callbacks that a file was deleted
|
|
|
|
#
|
2012-05-26 03:18:51 +02:00
|
|
|
# @param [Pathname] path The file that was deleted
|
2012-05-07 23:41:39 +02:00
|
|
|
# @return [void]
|
|
|
|
def did_delete(path)
|
2012-05-26 03:18:51 +02:00
|
|
|
return if ignored?(path)
|
2012-07-19 10:17:50 +02:00
|
|
|
logger.debug "== File Deletion: #{path}"
|
2012-05-20 01:49:44 +02:00
|
|
|
@known_paths.delete(path)
|
2012-05-07 23:41:39 +02:00
|
|
|
self.run_callbacks(path, :deleted)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Manually trigger update events
|
|
|
|
#
|
2012-05-26 03:18:51 +02:00
|
|
|
# @param [Pathname] path The path to reload
|
|
|
|
# @param [Boolean] only_new Whether we only look for new files
|
2012-05-07 23:41:39 +02:00
|
|
|
# @return [void]
|
2012-05-26 03:18:51 +02:00
|
|
|
def reload_path(path, only_new=false)
|
2012-07-19 10:17:50 +02:00
|
|
|
# chdir into the root directory so Pathname can work with relative paths
|
|
|
|
Dir.chdir @app.root_path do
|
|
|
|
path = Pathname(path)
|
|
|
|
return unless path.exist?
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-07-19 10:17:50 +02:00
|
|
|
glob = (path + "**/*").to_s
|
|
|
|
subset = @known_paths.select { |p| p.fnmatch(glob) }
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-07-19 10:17:50 +02:00
|
|
|
::Middleman::Util.all_files_under(path).each do |filepath|
|
|
|
|
if only_new
|
|
|
|
next if subset.include?(filepath)
|
|
|
|
else
|
|
|
|
subset.delete(filepath)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-07-19 10:17:50 +02:00
|
|
|
self.did_change(filepath)
|
2012-05-26 03:18:51 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-07-19 10:17:50 +02:00
|
|
|
subset.each(&method(:did_delete)) unless only_new
|
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-04-23 10:12:52 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Like reload_path, but only triggers events on new files
|
|
|
|
#
|
2012-05-26 03:18:51 +02:00
|
|
|
# @param [Pathname] path The path to reload
|
2012-05-07 23:41:39 +02:00
|
|
|
# @return [void]
|
|
|
|
def find_new_files(path)
|
2012-05-26 03:18:51 +02:00
|
|
|
reload_path(path, true)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
protected
|
2012-05-26 03:18:51 +02:00
|
|
|
# Whether this path is ignored
|
|
|
|
# @param [Pathname] path
|
|
|
|
# @return [Boolean]
|
|
|
|
def ignored?(path)
|
|
|
|
IGNORE_LIST.any? { |r| path.to_s.match(r) }
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Notify callbacks for a file given an array of callbacks
|
|
|
|
#
|
2012-05-26 03:18:51 +02:00
|
|
|
# @param [Pathname] path The file that was changed
|
2012-05-07 23:41:39 +02:00
|
|
|
# @param [Symbol] callbacks_name The name of the callbacks method
|
|
|
|
# @return [void]
|
|
|
|
def run_callbacks(path, callbacks_name)
|
2012-07-19 10:17:50 +02:00
|
|
|
path = path.to_s
|
2012-05-07 23:41:39 +02:00
|
|
|
self.send(callbacks_name).each do |callback, matcher|
|
|
|
|
next unless matcher.nil? || path.match(matcher)
|
2012-05-20 01:49:44 +02:00
|
|
|
@app.instance_exec(path, &callback)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
|
|
|
end
|
2011-11-18 01:02:07 +01:00
|
|
|
end
|
|
|
|
end
|
2011-11-17 22:21:05 +01:00
|
|
|
end
|
2012-07-09 04:01:06 +02:00
|
|
|
end
|