mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
start reimplementation of sinatra-reloader
This commit is contained in:
parent
8190013f0b
commit
05755545bc
4 changed files with 241 additions and 0 deletions
|
@ -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
|
||||
|
|
145
sinatra-contrib/lib/sinatra/reloader.rb
Normal file
145
sinatra-contrib/lib/sinatra/reloader.rb
Normal 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
|
19
sinatra-contrib/spec/reloader/app.rb.erb
Normal file
19
sinatra-contrib/spec/reloader/app.rb.erb
Normal 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 %>
|
76
sinatra-contrib/spec/reloader_spec.rb
Normal file
76
sinatra-contrib/spec/reloader_spec.rb
Normal 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
|
Loading…
Reference in a new issue