sinatra-contrib 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.md +135 -0
- data/Rakefile +75 -0
- data/ideas.md +29 -0
- data/lib/sinatra/capture.rb +42 -0
- data/lib/sinatra/config_file.rb +151 -0
- data/lib/sinatra/content_for.rb +111 -0
- data/lib/sinatra/contrib.rb +39 -0
- data/lib/sinatra/contrib/all.rb +2 -0
- data/lib/sinatra/contrib/setup.rb +53 -0
- data/lib/sinatra/contrib/version.rb +45 -0
- data/lib/sinatra/cookies.rb +331 -0
- data/lib/sinatra/decompile.rb +113 -0
- data/lib/sinatra/engine_tracking.rb +96 -0
- data/lib/sinatra/extension.rb +95 -0
- data/lib/sinatra/json.rb +134 -0
- data/lib/sinatra/link_header.rb +132 -0
- data/lib/sinatra/multi_route.rb +81 -0
- data/lib/sinatra/namespace.rb +282 -0
- data/lib/sinatra/reloader.rb +384 -0
- data/lib/sinatra/respond_with.rb +245 -0
- data/lib/sinatra/streaming.rb +267 -0
- data/lib/sinatra/test_helpers.rb +87 -0
- data/sinatra-contrib.gemspec +125 -0
- data/spec/capture_spec.rb +80 -0
- data/spec/config_file/key_value.yml +6 -0
- data/spec/config_file/missing_env.yml +4 -0
- data/spec/config_file/with_envs.yml +7 -0
- data/spec/config_file/with_nested_envs.yml +11 -0
- data/spec/config_file_spec.rb +44 -0
- data/spec/content_for/different_key.erb +1 -0
- data/spec/content_for/different_key.erubis +1 -0
- data/spec/content_for/different_key.haml +2 -0
- data/spec/content_for/different_key.slim +2 -0
- data/spec/content_for/layout.erb +1 -0
- data/spec/content_for/layout.erubis +1 -0
- data/spec/content_for/layout.haml +1 -0
- data/spec/content_for/layout.slim +1 -0
- data/spec/content_for/multiple_blocks.erb +4 -0
- data/spec/content_for/multiple_blocks.erubis +4 -0
- data/spec/content_for/multiple_blocks.haml +8 -0
- data/spec/content_for/multiple_blocks.slim +8 -0
- data/spec/content_for/multiple_yields.erb +3 -0
- data/spec/content_for/multiple_yields.erubis +3 -0
- data/spec/content_for/multiple_yields.haml +3 -0
- data/spec/content_for/multiple_yields.slim +3 -0
- data/spec/content_for/passes_values.erb +1 -0
- data/spec/content_for/passes_values.erubis +1 -0
- data/spec/content_for/passes_values.haml +1 -0
- data/spec/content_for/passes_values.slim +1 -0
- data/spec/content_for/same_key.erb +1 -0
- data/spec/content_for/same_key.erubis +1 -0
- data/spec/content_for/same_key.haml +2 -0
- data/spec/content_for/same_key.slim +2 -0
- data/spec/content_for/takes_values.erb +1 -0
- data/spec/content_for/takes_values.erubis +1 -0
- data/spec/content_for/takes_values.haml +3 -0
- data/spec/content_for/takes_values.slim +3 -0
- data/spec/content_for_spec.rb +201 -0
- data/spec/cookies_spec.rb +782 -0
- data/spec/decompile_spec.rb +44 -0
- data/spec/extension_spec.rb +33 -0
- data/spec/json_spec.rb +115 -0
- data/spec/link_header_spec.rb +100 -0
- data/spec/multi_route_spec.rb +45 -0
- data/spec/namespace/foo.erb +1 -0
- data/spec/namespace/nested/foo.erb +1 -0
- data/spec/namespace_spec.rb +623 -0
- data/spec/okjson.rb +581 -0
- data/spec/reloader/app.rb.erb +40 -0
- data/spec/reloader_spec.rb +441 -0
- data/spec/respond_with/bar.erb +1 -0
- data/spec/respond_with/bar.json.erb +1 -0
- data/spec/respond_with/foo.html.erb +1 -0
- data/spec/respond_with/not_html.sass +2 -0
- data/spec/respond_with_spec.rb +289 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/streaming_spec.rb +436 -0
- metadata +256 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
# = Sinatra::MultiRoute
|
5
|
+
#
|
6
|
+
# Create multiple routes with one statement.
|
7
|
+
#
|
8
|
+
# == Usage
|
9
|
+
#
|
10
|
+
# Use this extension to create a handler for multiple routes:
|
11
|
+
#
|
12
|
+
# get '/foo', '/bar' do
|
13
|
+
# # ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Or for multiple verbs:
|
17
|
+
#
|
18
|
+
# route :get, :post, '/' do
|
19
|
+
# # ...
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Or even for custom verbs:
|
23
|
+
#
|
24
|
+
# route 'LIST', '/' do
|
25
|
+
# # ...
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# === Classic Application
|
29
|
+
#
|
30
|
+
# To use the extension in a classic application all you need to do is require
|
31
|
+
# it:
|
32
|
+
#
|
33
|
+
# require "sinatra"
|
34
|
+
# require "sinatra/multi_route"
|
35
|
+
#
|
36
|
+
# # Your classic application code goes here...
|
37
|
+
#
|
38
|
+
# === Modular Application
|
39
|
+
#
|
40
|
+
# To use the extension in a modular application you need to require it, and
|
41
|
+
# then, tell the application you will use it:
|
42
|
+
#
|
43
|
+
# require "sinatra/base"
|
44
|
+
# require "sinatra/multi_route"
|
45
|
+
#
|
46
|
+
# class MyApp < Sinatra::Base
|
47
|
+
# register Sinatra::MultiRoute
|
48
|
+
#
|
49
|
+
# # The rest of your modular application code goes here...
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
module MultiRoute
|
53
|
+
def head(*args, &block) super(*route_args(args), &block) end
|
54
|
+
def delete(*args, &block) super(*route_args(args), &block) end
|
55
|
+
def get(*args, &block) super(*route_args(args), &block) end
|
56
|
+
def options(*args, &block) super(*route_args(args), &block) end
|
57
|
+
def patch(*args, &block) super(*route_args(args), &block) end
|
58
|
+
def post(*args, &block) super(*route_args(args), &block) end
|
59
|
+
def put(*args, &block) super(*route_args(args), &block) end
|
60
|
+
|
61
|
+
def route(*args, &block)
|
62
|
+
options = Hash === args.last ? args.pop : {}
|
63
|
+
routes = [*args.pop]
|
64
|
+
args.each do |verb|
|
65
|
+
verb = verb.to_s.upcase if Symbol === verb
|
66
|
+
routes.each do |route|
|
67
|
+
super(verb, route, options, &block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def route_args(args)
|
75
|
+
options = Hash === args.last ? args.pop : {}
|
76
|
+
[args, options]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
register MultiRoute
|
81
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'backports'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'sinatra/decompile'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
|
7
|
+
# = Sinatra::Namespace
|
8
|
+
#
|
9
|
+
# <tt>Sinatra::Namespace</tt> is an extension that adds namespaces to an
|
10
|
+
# application. This namespaces will allow you to share a path prefix for the
|
11
|
+
# routes within the namespace, and define filters, conditions and error
|
12
|
+
# handlers exclusively for them. Besides that, you can also register helpers
|
13
|
+
# and extensions that will be used only within the namespace.
|
14
|
+
#
|
15
|
+
# == Usage
|
16
|
+
#
|
17
|
+
# Once you have loaded the extension (see below), you use the +namespace+
|
18
|
+
# method to define namespaces in your application.
|
19
|
+
#
|
20
|
+
# You can define a namespace by a path prefix:
|
21
|
+
#
|
22
|
+
# namespace '/blog' do
|
23
|
+
# get() { haml :blog }
|
24
|
+
# get '/:entry_permalink' do
|
25
|
+
# @entry = Entry.find_by_permalink!(params[:entry_permalink])
|
26
|
+
# haml :entry
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # More blog routes...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# by a condition:
|
33
|
+
#
|
34
|
+
# namespace :host_name => 'localhost' do
|
35
|
+
# get('/admin/dashboard') { haml :dashboard }
|
36
|
+
# get('/admin/login') { haml :login }
|
37
|
+
#
|
38
|
+
# # More admin routes...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# or both:
|
42
|
+
#
|
43
|
+
# namespace '/admin', :host_name => 'localhost' do
|
44
|
+
# get('/dashboard') { haml :dashboard }
|
45
|
+
# get('/login') { haml :login }
|
46
|
+
# post('/login') { login_user }
|
47
|
+
#
|
48
|
+
# # More admin routes...
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# When you define a filter or an error handler, or register an extension or a
|
52
|
+
# set of helpers within a namespace, they only affect the routes defined in
|
53
|
+
# it. For instance, lets define a before filter to prevent the access of
|
54
|
+
# unauthorized users to the admin section of the application:
|
55
|
+
#
|
56
|
+
# namespace '/admin' do
|
57
|
+
# helpers AdminHelpers
|
58
|
+
# before { authenticate unless request.path_info == '/admin/login' }
|
59
|
+
#
|
60
|
+
# get '/dashboard' do
|
61
|
+
# # Only authenticated users can access here...
|
62
|
+
# haml :dashboard
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # More admin routes...
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# get '/' do
|
69
|
+
# # Any user can access here...
|
70
|
+
# haml :index
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Well, they actually also affect the nested namespaces:
|
74
|
+
#
|
75
|
+
# namespace '/admin' do
|
76
|
+
# helpers AdminHelpers
|
77
|
+
# before { authenticate unless request.path_info == '/admin/login' }
|
78
|
+
#
|
79
|
+
# namespace '/users' do
|
80
|
+
# get do
|
81
|
+
# # Only authenticated users can access here...
|
82
|
+
# @users = User.all
|
83
|
+
# haml :users
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # More user admin routes...
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# # More admin routes...
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# === Classic Application Setup
|
93
|
+
#
|
94
|
+
# To be able to use namespaces in a classic application all you need to do is
|
95
|
+
# require the extension:
|
96
|
+
#
|
97
|
+
# require "sinatra"
|
98
|
+
# require "sinatra/namespace"
|
99
|
+
#
|
100
|
+
# # The rest of your classic application code goes here...
|
101
|
+
#
|
102
|
+
# === Modular Application Setup
|
103
|
+
#
|
104
|
+
# To be able to use namespaces in a modular application all you need to do is
|
105
|
+
# require the extension, and then, register it:
|
106
|
+
#
|
107
|
+
# require "sinatra/base"
|
108
|
+
# require "sinatra/namespace"
|
109
|
+
#
|
110
|
+
# class MyApp < Sinatra::Base
|
111
|
+
# register Sinatra::Namespace
|
112
|
+
#
|
113
|
+
# # The rest of your modular application code goes here...
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
module Namespace
|
117
|
+
def self.new(base, pattern, conditions = {}, &block)
|
118
|
+
Module.new do
|
119
|
+
extend NamespacedMethods
|
120
|
+
include InstanceMethods
|
121
|
+
@base, @extensions = base, []
|
122
|
+
@pattern, @conditions = compile(pattern, conditions)
|
123
|
+
@templates = Hash.new { |h,k| @base.templates[k] }
|
124
|
+
namespace = self
|
125
|
+
before { extend(@namespace = namespace) }
|
126
|
+
class_eval(&block)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module InstanceMethods
|
131
|
+
def settings
|
132
|
+
@namespace
|
133
|
+
end
|
134
|
+
|
135
|
+
def template_cache
|
136
|
+
super.fetch(:nested, @namespace) { Tilt::Cache.new }
|
137
|
+
end
|
138
|
+
|
139
|
+
def error_block!(*keys)
|
140
|
+
if block = keys.inject(nil) { |b,k| b ||= @namespace.errors[k] }
|
141
|
+
instance_eval(&block)
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module SharedMethods
|
149
|
+
def namespace(pattern, conditions = {}, &block)
|
150
|
+
Sinatra::Namespace.new(self, pattern, conditions, &block)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
module NamespacedMethods
|
155
|
+
include SharedMethods
|
156
|
+
include Sinatra::Decompile
|
157
|
+
attr_reader :base, :templates
|
158
|
+
|
159
|
+
def self.prefixed(*names)
|
160
|
+
names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }}
|
161
|
+
end
|
162
|
+
|
163
|
+
prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
|
164
|
+
|
165
|
+
def helpers(*extensions, &block)
|
166
|
+
class_eval(&block) if block_given?
|
167
|
+
include(*extensions) if extensions.any?
|
168
|
+
end
|
169
|
+
|
170
|
+
def register(*extensions, &block)
|
171
|
+
extensions << Module.new(&block) if block_given?
|
172
|
+
@extensions += extensions
|
173
|
+
extensions.each do |extension|
|
174
|
+
extend extension
|
175
|
+
extension.registered(self) if extension.respond_to?(:registered)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def invoke_hook(name, *args)
|
180
|
+
@extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
|
181
|
+
end
|
182
|
+
|
183
|
+
def errors
|
184
|
+
@errors ||= {}
|
185
|
+
end
|
186
|
+
|
187
|
+
def not_found(&block)
|
188
|
+
error(404, &block)
|
189
|
+
end
|
190
|
+
|
191
|
+
def error(codes = Exception, &block)
|
192
|
+
[*codes].each { |c| errors[c] = block }
|
193
|
+
end
|
194
|
+
|
195
|
+
def respond_to(*args)
|
196
|
+
return @conditions[:provides] || base.respond_to if args.empty?
|
197
|
+
@conditions[:provides] = args
|
198
|
+
end
|
199
|
+
|
200
|
+
def set(key, value = self, &block)
|
201
|
+
raise ArgumentError, "may not set #{key}" if key != :views
|
202
|
+
return key.each { |k,v| set(k, v) } if block.nil? and value == self
|
203
|
+
block ||= proc { value }
|
204
|
+
singleton_class.send(:define_method, key, &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
def enable(*opts)
|
208
|
+
opts.each { |key| set(key, true) }
|
209
|
+
end
|
210
|
+
|
211
|
+
def disable(*opts)
|
212
|
+
opts.each { |key| set(key, false) }
|
213
|
+
end
|
214
|
+
|
215
|
+
def template(name, &block)
|
216
|
+
filename, line = caller_locations.first
|
217
|
+
templates[name] = [block, filename, line.to_i]
|
218
|
+
end
|
219
|
+
|
220
|
+
def layout(name=:layout, &block)
|
221
|
+
template name, &block
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def app
|
227
|
+
base.respond_to?(:base) ? base.base : base
|
228
|
+
end
|
229
|
+
|
230
|
+
def compile(pattern, conditions, default_pattern = nil)
|
231
|
+
if pattern.respond_to? :to_hash
|
232
|
+
conditions = conditions.merge pattern.to_hash
|
233
|
+
pattern = nil
|
234
|
+
end
|
235
|
+
base_pattern, base_conditions = @pattern, @conditions
|
236
|
+
pattern ||= default_pattern
|
237
|
+
base_pattern ||= base.pattern if base.respond_to? :pattern
|
238
|
+
base_conditions ||= base.conditions if base.respond_to? :conditions
|
239
|
+
[ prefixed_path(base_pattern, pattern),
|
240
|
+
(base_conditions || {}).merge(conditions) ]
|
241
|
+
end
|
242
|
+
|
243
|
+
def prefixed_path(a, b)
|
244
|
+
return a || b || // unless a and b
|
245
|
+
a, b = decompile(a), decompile(b) unless a.class == b.class
|
246
|
+
a, b = regexpify(a), regexpify(b) unless a.class == b.class
|
247
|
+
path = a.class.new "#{a}#{b}"
|
248
|
+
path = /^#{path}$/ if path.is_a? Regexp and base == app
|
249
|
+
path
|
250
|
+
end
|
251
|
+
|
252
|
+
def regexpify(pattern)
|
253
|
+
pattern = Sinatra::Base.send(:compile, pattern).first.inspect
|
254
|
+
pattern.gsub! /^\/(\^|\\A)?|(\$|\\Z)?\/$/, ''
|
255
|
+
Regexp.new pattern
|
256
|
+
end
|
257
|
+
|
258
|
+
def prefixed(method, pattern = nil, conditions = {}, &block)
|
259
|
+
default = '*' if method == :before or method == :after
|
260
|
+
pattern, conditions = compile pattern, conditions, default
|
261
|
+
result = base.send(method, pattern, conditions, &block)
|
262
|
+
invoke_hook :route_added, method.to_s.upcase, pattern, block
|
263
|
+
result
|
264
|
+
end
|
265
|
+
|
266
|
+
def method_missing(meth, *args, &block)
|
267
|
+
base.send(meth, *args, &block)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
module BaseMethods
|
272
|
+
include SharedMethods
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.extend_object(base)
|
276
|
+
base.extend BaseMethods
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
register Sinatra::Namespace
|
281
|
+
Delegator.delegate :namespace
|
282
|
+
end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
|
5
|
+
# = Sinatra::Reloader
|
6
|
+
#
|
7
|
+
# Extension to reload modified files. Useful during development,
|
8
|
+
# since it will automatically require files defining routes, filters,
|
9
|
+
# error handlers and inline templates, with every incoming request,
|
10
|
+
# but only if they have been updated.
|
11
|
+
#
|
12
|
+
# == Usage
|
13
|
+
#
|
14
|
+
# === Classic Application
|
15
|
+
#
|
16
|
+
# To enable the realoader in a classic application all you need to do is
|
17
|
+
# require it:
|
18
|
+
#
|
19
|
+
# require "sinatra"
|
20
|
+
# require "sinatra/reloader" if development?
|
21
|
+
#
|
22
|
+
# # Your classic application code goes here...
|
23
|
+
#
|
24
|
+
# === Modular Application
|
25
|
+
#
|
26
|
+
# To enable the realoader in a modular application all you need to do is
|
27
|
+
# require it, and then, register it:
|
28
|
+
#
|
29
|
+
# require "sinatra/base"
|
30
|
+
# require "sinatra/reloader"
|
31
|
+
#
|
32
|
+
# class MyApp < Sinatra::Base
|
33
|
+
# configure :development do
|
34
|
+
# register Sinatra::Reloader
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # Your modular application code goes here...
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# == Changing the Reloading Policy
|
41
|
+
#
|
42
|
+
# You can refine the reloading policy with +also_reload+ and
|
43
|
+
# +dont_reload+, to customize which files should, and should not, be
|
44
|
+
# reloaded, respectively.
|
45
|
+
#
|
46
|
+
# === Classic Application
|
47
|
+
#
|
48
|
+
# Simply call the methods:
|
49
|
+
#
|
50
|
+
# require "sinatra"
|
51
|
+
# require "sinatra/reloader" if development?
|
52
|
+
#
|
53
|
+
# also_reload '/path/to/some/file'
|
54
|
+
# dont_reload '/path/to/other/file'
|
55
|
+
#
|
56
|
+
# # Your classic application code goes here...
|
57
|
+
#
|
58
|
+
# === Modular Application
|
59
|
+
#
|
60
|
+
# Call the methods inside the +configure+ block:
|
61
|
+
#
|
62
|
+
# require "sinatra/base"
|
63
|
+
# require "sinatra/reloader"
|
64
|
+
#
|
65
|
+
# class MyApp < Sinatra::Base
|
66
|
+
# configure :development do
|
67
|
+
# register Sinatra::Reloader
|
68
|
+
# also_reload '/path/to/some/file'
|
69
|
+
# dont_reload '/path/to/other/file'
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# # Your modular application code goes here...
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
module Reloader
|
76
|
+
|
77
|
+
# Watches a file so it can tell when it has been updated, and what
|
78
|
+
# elements contains.
|
79
|
+
class Watcher
|
80
|
+
|
81
|
+
# Represents an element of a Sinatra application that may need to
|
82
|
+
# be reloaded. An element could be:
|
83
|
+
# * a route
|
84
|
+
# * a filter
|
85
|
+
# * an error handler
|
86
|
+
# * a middleware
|
87
|
+
# * inline templates
|
88
|
+
#
|
89
|
+
# Its +representation+ attribute is there to allow to identify the
|
90
|
+
# element within an application, that is, to match it with its
|
91
|
+
# Sinatra's internal representation.
|
92
|
+
class Element < Struct.new(:type, :representation)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Collection of file +Watcher+ that can be associated with a
|
96
|
+
# Sinatra application. That way, we can know which files belong
|
97
|
+
# to a given application and which files have been modified. It
|
98
|
+
# also provides a mechanism to inform a Watcher the elements
|
99
|
+
# defined in the file being watched and if it changes should be
|
100
|
+
# ignored.
|
101
|
+
class List
|
102
|
+
@app_list_map = Hash.new { |hash, key| hash[key] = new }
|
103
|
+
|
104
|
+
# Returns the +List+ for the application +app+.
|
105
|
+
def self.for(app)
|
106
|
+
@app_list_map[app]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Creates a new +List+ instance.
|
110
|
+
def initialize
|
111
|
+
@path_watcher_map = Hash.new do |hash, key|
|
112
|
+
hash[key] = Watcher.new(key)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Lets the +Watcher+ for the file localted at +path+ know that the
|
117
|
+
# +element+ is defined there, and adds the +Watcher+ to the +List+,
|
118
|
+
# if it isn't already there.
|
119
|
+
def watch(path, element)
|
120
|
+
watcher_for(path).elements << element
|
121
|
+
end
|
122
|
+
|
123
|
+
# Tells the +Watcher+ for the file located at +path+ to ignore
|
124
|
+
# the file changes, and adds the +Watcher+ to the +List+, if
|
125
|
+
# it isn't already there.
|
126
|
+
def ignore(path)
|
127
|
+
watcher_for(path).ignore
|
128
|
+
end
|
129
|
+
|
130
|
+
# Adds a +Watcher+ for the file located at +path+ to the
|
131
|
+
# +List+, if it isn't already there.
|
132
|
+
def watcher_for(path)
|
133
|
+
@path_watcher_map[File.expand_path(path)]
|
134
|
+
end
|
135
|
+
alias watch_file watcher_for
|
136
|
+
|
137
|
+
# Returns an array with all the watchers in the +List+.
|
138
|
+
def watchers
|
139
|
+
@path_watcher_map.values
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns an array with all the watchers in the +List+ that
|
143
|
+
# have been updated.
|
144
|
+
def updated
|
145
|
+
watchers.find_all(&:updated?)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
attr_reader :path, :elements, :mtime
|
150
|
+
|
151
|
+
# Creates a new +Watcher+ instance for the file located at +path+.
|
152
|
+
def initialize(path)
|
153
|
+
@path, @elements = path, []
|
154
|
+
update
|
155
|
+
end
|
156
|
+
|
157
|
+
# Indicates whether or not the file being watched has been modified.
|
158
|
+
def updated?
|
159
|
+
!ignore? && !removed? && mtime != File.mtime(path)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Updates the file being watched mtime.
|
163
|
+
def update
|
164
|
+
@mtime = File.mtime(path)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Indicates whether or not the file being watched has inline
|
168
|
+
# templates.
|
169
|
+
def inline_templates?
|
170
|
+
elements.any? { |element| element.type == :inline_templates }
|
171
|
+
end
|
172
|
+
|
173
|
+
# Informs that the modifications to the file being watched
|
174
|
+
# should be ignored.
|
175
|
+
def ignore
|
176
|
+
@ignore = true
|
177
|
+
end
|
178
|
+
|
179
|
+
# Indicates whether or not the modifications to the file being
|
180
|
+
# watched should be ignored.
|
181
|
+
def ignore?
|
182
|
+
!!@ignore
|
183
|
+
end
|
184
|
+
|
185
|
+
# Indicates whether or not the file being watched has been removed.
|
186
|
+
def removed?
|
187
|
+
!File.exist?(path)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# When the extension is registed it extends the Sinatra application
|
192
|
+
# +klass+ with the modules +BaseMethods+ and +ExtensionMethods+ and
|
193
|
+
# defines a before filter to +perform+ the reload of the modified files.
|
194
|
+
def self.registered(klass)
|
195
|
+
@reloader_loaded_in ||= {}
|
196
|
+
return if @reloader_loaded_in[klass]
|
197
|
+
|
198
|
+
@reloader_loaded_in[klass] = true
|
199
|
+
|
200
|
+
klass.extend BaseMethods
|
201
|
+
klass.extend ExtensionMethods
|
202
|
+
klass.set(:reloader) { klass.development? }
|
203
|
+
klass.set(:reload_templates) { klass.reloader? }
|
204
|
+
klass.before do
|
205
|
+
if klass.reloader?
|
206
|
+
if Reloader.thread_safe?
|
207
|
+
Thread.exclusive { Reloader.perform(klass) }
|
208
|
+
else
|
209
|
+
Reloader.perform(klass)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Reloads the modified files, adding, updating and removing the
|
216
|
+
# needed elements.
|
217
|
+
def self.perform(klass)
|
218
|
+
Watcher::List.for(klass).updated.each do |watcher|
|
219
|
+
klass.set(:inline_templates, watcher.path) if watcher.inline_templates?
|
220
|
+
watcher.elements.each { |element| klass.deactivate(element) }
|
221
|
+
$LOADED_FEATURES.delete(watcher.path)
|
222
|
+
require watcher.path
|
223
|
+
watcher.update
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Indicates whether or not we can and need to run thread-safely.
|
228
|
+
def self.thread_safe?
|
229
|
+
Thread and Thread.list.size > 1 and Thread.respond_to?(:exclusive)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Contains the methods defined in Sinatra::Base that are overriden.
|
233
|
+
module BaseMethods
|
234
|
+
# Does everything Sinatra::Base#route does, but it also tells the
|
235
|
+
# +Watcher::List+ for the Sinatra application to watch the defined
|
236
|
+
# route.
|
237
|
+
#
|
238
|
+
# Note: We are using #compile! so we don't interfere with extensions
|
239
|
+
# changing #route.
|
240
|
+
def compile!(verb, path, block, options = {})
|
241
|
+
source_location = block.respond_to?(:source_location) ?
|
242
|
+
block.source_location.first : caller_files[1]
|
243
|
+
signature = super
|
244
|
+
watch_element(
|
245
|
+
source_location, :route, { :verb => verb, :signature => signature }
|
246
|
+
)
|
247
|
+
signature
|
248
|
+
end
|
249
|
+
|
250
|
+
# Does everything Sinatra::Base#inline_templates= does, but it also
|
251
|
+
# tells the +Watcher::List+ for the Sinatra application to watch the
|
252
|
+
# inline templates in +file+ or the file who made the call to this
|
253
|
+
# method.
|
254
|
+
def inline_templates=(file=nil)
|
255
|
+
file = (file.nil? || file == true) ?
|
256
|
+
(caller_files[1] || File.expand_path($0)) : file
|
257
|
+
watch_element(file, :inline_templates)
|
258
|
+
super
|
259
|
+
end
|
260
|
+
|
261
|
+
# Does everything Sinatra::Base#use does, but it also tells the
|
262
|
+
# +Watcher::List+ for the Sinatra application to watch the middleware
|
263
|
+
# being used.
|
264
|
+
def use(middleware, *args, &block)
|
265
|
+
path = caller_files[1] || File.expand_path($0)
|
266
|
+
watch_element(path, :middleware, [middleware, args, block])
|
267
|
+
super
|
268
|
+
end
|
269
|
+
|
270
|
+
# Does everything Sinatra::Base#add_filter does, but it also tells
|
271
|
+
# the +Watcher::List+ for the Sinatra application to watch the defined
|
272
|
+
# filter.
|
273
|
+
def add_filter(type, path = nil, options = {}, &block)
|
274
|
+
source_location = block.respond_to?(:source_location) ?
|
275
|
+
block.source_location.first : caller_files[1]
|
276
|
+
result = super
|
277
|
+
watch_element(source_location, :"#{type}_filter", filters[type].last)
|
278
|
+
result
|
279
|
+
end
|
280
|
+
|
281
|
+
# Does everything Sinatra::Base#error does, but it also tells the
|
282
|
+
# +Watcher::List+ for the Sinatra application to watch the defined
|
283
|
+
# error handler.
|
284
|
+
def error(*codes, &block)
|
285
|
+
path = caller_files[1] || File.expand_path($0)
|
286
|
+
result = super
|
287
|
+
codes.each do |c|
|
288
|
+
watch_element(path, :error, :code => c, :handler => @errors[c])
|
289
|
+
end
|
290
|
+
result
|
291
|
+
end
|
292
|
+
|
293
|
+
# Does everything Sinatra::Base#register does, but it also lets the
|
294
|
+
# reloader know that an extension is being registered, because the
|
295
|
+
# elements defined in its +registered+ method need a special treatment.
|
296
|
+
def register(*extensions, &block)
|
297
|
+
start_registering_extension
|
298
|
+
result = super
|
299
|
+
stop_registering_extension
|
300
|
+
result
|
301
|
+
end
|
302
|
+
|
303
|
+
# Does everything Sinatra::Base#register does and then registers the
|
304
|
+
# reloader in the +subclass+.
|
305
|
+
def inherited(subclass)
|
306
|
+
result = super
|
307
|
+
subclass.register Sinatra::Reloader
|
308
|
+
result
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Contains the methods that the extension adds to the Sinatra application.
|
313
|
+
module ExtensionMethods
|
314
|
+
# Removes the +element+ from the Sinatra application.
|
315
|
+
def deactivate(element)
|
316
|
+
case element.type
|
317
|
+
when :route then
|
318
|
+
verb = element.representation[:verb]
|
319
|
+
signature = element.representation[:signature]
|
320
|
+
(routes[verb] ||= []).delete(signature)
|
321
|
+
when :middleware then
|
322
|
+
@middleware.delete(element.representation)
|
323
|
+
when :before_filter then
|
324
|
+
filters[:before].delete(element.representation)
|
325
|
+
when :after_filter then
|
326
|
+
filters[:after].delete(element.representation)
|
327
|
+
when :error then
|
328
|
+
code = element.representation[:code]
|
329
|
+
handler = element.representation[:handler]
|
330
|
+
@errors.delete(code) if @errors[code] == handler
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Indicates with a +glob+ which files should be reloaded if they
|
335
|
+
# have been modified. It can be called several times.
|
336
|
+
def also_reload(glob)
|
337
|
+
Dir[glob].each { |path| Watcher::List.for(self).watch_file(path) }
|
338
|
+
end
|
339
|
+
|
340
|
+
# Indicates with a +glob+ which files should not be reloaded even if
|
341
|
+
# they have been modified. It can be called several times.
|
342
|
+
def dont_reload(glob)
|
343
|
+
Dir[glob].each { |path| Watcher::List.for(self).ignore(path) }
|
344
|
+
end
|
345
|
+
|
346
|
+
private
|
347
|
+
|
348
|
+
attr_reader :register_path
|
349
|
+
|
350
|
+
# Indicates an extesion is being registered.
|
351
|
+
def start_registering_extension
|
352
|
+
@register_path = caller_files[2]
|
353
|
+
end
|
354
|
+
|
355
|
+
# Indicates the extesion has already been registered.
|
356
|
+
def stop_registering_extension
|
357
|
+
@register_path = nil
|
358
|
+
end
|
359
|
+
|
360
|
+
# Indicates whether or not an extension is being registered.
|
361
|
+
def registering_extension?
|
362
|
+
!register_path.nil?
|
363
|
+
end
|
364
|
+
|
365
|
+
# Builds a Watcher::Element from +type+ and +representation+ and
|
366
|
+
# tells the Watcher::List for the current application to watch it
|
367
|
+
# in the file located at +path+.
|
368
|
+
#
|
369
|
+
# If an extension is being registered, it also tells the list to
|
370
|
+
# watch it in the file where the extesion has been registered.
|
371
|
+
# This prevents the duplication of the elements added by the
|
372
|
+
# extension in its +registered+ method with every reload.
|
373
|
+
def watch_element(path, type, representation=nil)
|
374
|
+
list = Watcher::List.for(self)
|
375
|
+
element = Watcher::Element.new(type, representation)
|
376
|
+
list.watch(path, element)
|
377
|
+
list.watch(register_path, element) if registering_extension?
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
register Reloader
|
383
|
+
Delegator.delegate :also_reload, :dont_reload
|
384
|
+
end
|