|
#!/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 |
Ah, I see now what you meant about fsevent_watch. Interesting stuff. I'll try 0.7, maybe even merge some of my own ideas into it. But for now, I'm sticking to my forks of both livereload and rspactor. When my livereload gets more solid (meaning tested against real world projects) we can think about having it in rspactor.