start reimplementation of sinatra-reloader

This commit is contained in:
Gabriel Andretta 2011-04-10 23:57:22 -03:00
parent 8190013f0b
commit 05755545bc
4 changed files with 241 additions and 0 deletions

View File

@ -7,6 +7,7 @@ module Sinatra
# or breaks if external dependencies are missing. Will extend
# Sinatra::Application by default.
module Common
register :Reloader
register :ConfigFile
register :Namespace
register :RespondWith

View File

@ -0,0 +1,145 @@
require 'pathname'
module Sinatra
module Reloader
class Route
attr_accessor :app, :source_location, :verb, :signature
def initialize(attrs={})
self.app = attrs[:app]
self.source_location = attrs[:source_location]
self.verb = attrs[:verb]
self.signature = attrs[:signature]
end
end
class Watcher
@path_watcher_map ||= Hash.new { |hash, key| hash[key] = new(key) }
def self.watcher_for(path)
@path_watcher_map[Pathname.new(path).expand_path.to_s]
end
def self.watch_file(path)
watcher_for(path)
end
def self.watch_route(route)
watcher_for(route.source_location).routes << route
end
def self.watch_inline_templates(path, app)
watcher_for(path).inline_templates(app)
end
def self.ignore(path)
watcher_for(path).ignore
end
def self.watchers
@path_watcher_map.values
end
def self.updated
watchers.find_all(&:updated?)
end
attr_reader :path, :routes, :mtime
attr_writer :app
def initialize(path)
@path, @routes = path, []
update
end
def updated?
!ignored? && mtime != File.mtime(path)
end
def update
@mtime = File.mtime(path)
end
def inline_templates(app)
@inline_templates = true
@app = app
end
def inline_templates?
!!@inline_templates
end
def ignore
@ignore = true
end
def ignored?
!!@ignore
end
def app
@app || (routes.first.app unless routes.empty?) || Sinatra::Application
end
end
def self.registered(klass)
klass.extend BaseMethods
klass.extend ExtensionMethods
klass.enable :reload_templates
klass.before { Reloader.perform }
end
def self.perform
Watcher.updated.each do |watcher|
if watcher.inline_templates?
watcher.app.set(:inline_templates, watcher.path)
end
watcher.routes.each do |route|
watcher.app.deactivate_route(route.verb, route.signature)
end
$LOADED_FEATURES.delete(watcher.path)
require watcher.path
watcher.update
end
end
module BaseMethods
def route(verb, path, options={}, &block)
source_location = block.respond_to?(:source_location) ?
block.source_location.first : caller_files.first
super.tap do |signature|
Watcher.watch_route Route.new(
:app => self,
:source_location => source_location,
:verb => verb,
:signature => signature
)
end
end
def iniline_templates=(file=nil)
file = (file.nil? || file == true) ?
(caller_files.first || File.expand_path($0)) : file
Watcher.watch_inline_templates(file, self)
super
end
end
module ExtensionMethods
def deactivate_route(verb, signature)
(routes[verb] ||= []).delete(signature)
end
def also_reload(glob)
Dir[glob].each { |path| Watcher.watch_file(path) }
end
def dont_reload(glob)
Dir[glob].each { |path| Watcher.ignore(path) }
end
end
end
register Reloader
Delegator.delegate :also_reload, :dont_reload
end

View File

@ -0,0 +1,19 @@
class App < Sinatra::Base
register Sinatra::Reloader
<% unless inline_templates.nil? %>
enable :inline_templates
<% end %>
<% routes.each do |route| %>
<%= route %>
<% end %>
end
<% unless inline_templates.nil? %>
__END__
<% inline_templates.each_pair do |name, content| %>
@@<%= name %>
<%= content %>
<% end %>
<% end %>

View File

@ -0,0 +1,76 @@
require 'backports'
require_relative 'spec_helper'
require 'fileutils'
describe Sinatra::Reloader do
def tmp_dir
File.expand_path('../../tmp', __FILE__)
end
def app_file_path
File.join(tmp_dir, 'app.rb')
end
def write_app_file(options={})
options[:routes] ||= ['get("/foo") { erb :foo }']
options[:inline_templates] ||= nil
File.open(app_file_path, 'w') do |f|
template_path = File.expand_path('../reloader/app.rb.erb', __FILE__)
template = Tilt.new(template_path, nil, :trim => '<>')
f.write template.render(Object.new, options)
end
end
def update_app_file(options={})
original_mtime = File.mtime(app_file_path)
begin
write_app_file(options)
sleep 0.1
end until original_mtime != File.mtime(app_file_path)
end
before(:each) do
FileUtils.rm_rf(tmp_dir)
FileUtils.mkdir_p(tmp_dir)
write_app_file(
:routes => ['get("/foo") { erb :foo }'],
:inline_templates => { :foo => 'foo' }
)
$LOADED_FEATURES.delete app_file_path
require app_file_path
self.app = App
end
after(:all) { FileUtils.rm_rf(tmp_dir) }
it "doesn't mess up the application" do
get('/foo').body.should == 'foo'
end
it "knows when a route has been modified" do
update_app_file(:routes => ['get("/foo") { "bar" }'])
get('/foo').body.should == 'bar'
end
it "knows when a route has been added" do
update_app_file(
:routes => ['get("/foo") { "foo" }', 'get("/bar") { "bar" }']
)
get('/foo').body.should == 'foo'
get('/bar').body.should == 'bar'
end
it "knows when a route has been removed" do
update_app_file(:routes => ['get("/bar") { "bar" }'])
get('/foo').status.should == 404
end
it "reloads inline templates" do
update_app_file(
:routes => ['get("/foo") { erb :foo }'],
:inline_templates => { :foo => 'bar' }
)
get('/foo').body.should == 'bar'
end
end