|
#!/usr/bin/ruby -rubygems |
|
|
|
# Replacement for "livereload" gem for working with Rails |
|
# |
|
# Dependencies: |
|
# $ sudo /usr/bin/gem install mislav-rspactor em-websocket json haml |
|
|
|
# uncomment to force loading the correct rspactor version |
|
# gem 'mislav-rspactor', '~> 0.4.0' |
|
require 'rspactor' |
|
require 'em-websocket' |
|
require 'json' |
|
require 'sass/plugin' |
|
require 'fileutils' |
|
|
|
API_VERSION = '1.3' |
|
|
|
web_sockets = [] |
|
debug = !!ARGV.delete('-D') |
|
dirs = ARGV.empty?? [Dir.pwd] : ARGV |
|
|
|
# Compass support |
|
compass_config = "./config/compass.rb" |
|
|
|
if File.exists? compass_config |
|
# $ sudo /usr/bin/gem install compass |
|
require 'compass' |
|
Compass.add_project_configuration(compass_config) |
|
Compass.configure_sass_plugin! |
|
Compass.handle_configuration_change! |
|
else |
|
Sass::Plugin.add_template_location(Dir.pwd + '/app/styles') |
|
end |
|
|
|
# Jekyll support |
|
jekyll_mode = %w[_config.yml _site].any? { |file| File.exists? file } |
|
|
|
if jekyll_mode |
|
require 'jekyll' |
|
require 'webrick' |
|
|
|
options = Jekyll.configuration({}) |
|
# Get source and destination directories (possibly set by config file) |
|
source = options['source'] |
|
destination = options['destination'] |
|
# Create the Site |
|
jekyll_site = Jekyll::Site.new(options) |
|
|
|
FileUtils.mkdir_p(destination) |
|
|
|
mime_types = WEBrick::HTTPUtils::DefaultMimeTypes |
|
mime_types.store 'js', 'application/javascript' |
|
|
|
jekyll_server = WEBrick::HTTPServer.new( |
|
:Port => options['server_port'], |
|
:DocumentRoot => destination, |
|
:MimeTypes => mime_types |
|
) |
|
end |
|
|
|
sinatra_mode = File.exists? 'app.rb' |
|
|
|
extensions = %w[html erb haml sass scss css js rb yml] |
|
extensions.concat %w[md markdown textile xml] if jekyll_mode |
|
extensions << 'mustache' if sinatra_mode |
|
|
|
ws_push = lambda do |file| |
|
data = ['refresh', { :path => file, :apply_js_live => false, :apply_css_live => true }].to_json |
|
puts data if debug |
|
# send it to the browser! |
|
web_sockets.each { |ws| ws.send(data) } |
|
end |
|
|
|
process_file = if sinatra_mode |
|
lambda do |file| |
|
case file |
|
when %r{/(views|templates)/}, %r{/public/.+\.(css|js|html)$} |
|
file = file.sub(%r{\.s[ac]ss$}, '.css') |
|
ws_push[file] |
|
true |
|
else |
|
false |
|
end |
|
end |
|
else |
|
lambda do |file| |
|
case file |
|
when %r{/app/.+\.(erb|haml)$}, %r{/app/helpers/.+\.rb$}, # application view code |
|
%r{/public/.+\.(css|js|html)$}, # static assets |
|
%r{/config/locales/.+\.yml$}, # translation files |
|
%r{/_site/.+\.css$} # styles in jekyll |
|
ws_push[file] |
|
true |
|
when %r{\.s[ac]ss$} |
|
puts "Regenerating CSS stylesheets..." if debug |
|
Sass::Plugin.update_stylesheets |
|
true |
|
else |
|
false |
|
end |
|
end |
|
end |
|
|
|
listener = RSpactor::Listener.new(:extensions => extensions, :relative_paths => false) { |files| |
|
for file in files |
|
done = process_file[file] |
|
puts "Unhandled change: #{file}" if not done and debug and not jekyll_mode |
|
end |
|
|
|
if jekyll_mode and not files.any? { |file| file.index(destination) == 0 } |
|
# puts "Regenerating site..." |
|
# jekyll_site.process |
|
|
|
files.select { |file| file =~ /\.css/ }.each do |file| |
|
path = file.sub(Dir.pwd + '/', '') |
|
target_file = File.expand_path(path, destination) |
|
listener.force_changed << target_file |
|
FileUtils.cp file, target_file |
|
end |
|
end |
|
} |
|
|
|
Sass::Plugin.on_updating_stylesheet do |template, css| |
|
# We notify the listener that the stylesheet file changed for sure. |
|
# Because the filesystem can't know modification time in milliseconds, |
|
# this prevents FSEvents handler from thinking the file didn't change. |
|
stylesheet_file = File.expand_path(css, Dir.pwd) |
|
puts "generating #{stylesheet_file}" if debug |
|
listener.force_changed << stylesheet_file |
|
end |
|
|
|
Sass::Plugin.on_compilation_error do |e, template, css| |
|
warn "error compiling #{template}" |
|
end |
|
|
|
puts "Starting file watcher for directories: #{dirs.inspect}" |
|
listener.start(dirs) |
|
|
|
# Disable the RubyCocoa thread hook as apparently Laurent did not apply the |
|
# thread patches to the OS X system Ruby |
|
ENV['RUBYCOCOA_THREAD_HOOK_DISABLE'] = 'kampai' |
|
|
|
Thread.new { OSX.CFRunLoopRun } |
|
|
|
if jekyll_mode |
|
Thread.new { jekyll_server.start } |
|
trap("INT") { jekyll_server.shutdown } |
|
end |
|
|
|
trap("INT") { EventMachine.stop } |
|
|
|
EventMachine.run do |
|
puts "LiveReload is waiting for a browser to connect." |
|
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => '10083', :debug => debug) do |ws| |
|
ws.onopen do |
|
begin |
|
puts "Browser connected." |
|
ws.send "!!ver:#{API_VERSION}" |
|
web_sockets << ws |
|
rescue |
|
puts $! |
|
puts $!.backtrace |
|
end |
|
end |
|
|
|
ws.onmessage do |msg| |
|
puts "Browser URL: #{msg}" |
|
end |
|
|
|
ws.onclose do |
|
web_sockets.delete ws |
|
puts "Browser disconnected." |
|
end |
|
end |
|
end |
@andreyvit Have you look how RSpactor (http://github.com/thibaudgg/rspactor) handle FSEvents (without RubyCocoa & EM) ? Feel free to contact me if you have any questions.