pirj-sinatra-contrib 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +61 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +42 -0
  6. data/lib/sinatra/config_file.rb +151 -0
  7. data/lib/sinatra/content_for.rb +111 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +45 -0
  12. data/lib/sinatra/decompile.rb +113 -0
  13. data/lib/sinatra/engine_tracking.rb +96 -0
  14. data/lib/sinatra/extension.rb +95 -0
  15. data/lib/sinatra/json.rb +134 -0
  16. data/lib/sinatra/link_header.rb +132 -0
  17. data/lib/sinatra/namespace.rb +282 -0
  18. data/lib/sinatra/reloader.rb +384 -0
  19. data/lib/sinatra/respond_with.rb +245 -0
  20. data/lib/sinatra/streaming.rb +267 -0
  21. data/lib/sinatra/test_helpers.rb +87 -0
  22. data/sinatra-contrib.gemspec +121 -0
  23. data/spec/capture_spec.rb +80 -0
  24. data/spec/config_file/key_value.yml +6 -0
  25. data/spec/config_file/missing_env.yml +4 -0
  26. data/spec/config_file/with_envs.yml +7 -0
  27. data/spec/config_file/with_nested_envs.yml +11 -0
  28. data/spec/config_file_spec.rb +44 -0
  29. data/spec/content_for/different_key.erb +1 -0
  30. data/spec/content_for/different_key.erubis +1 -0
  31. data/spec/content_for/different_key.haml +2 -0
  32. data/spec/content_for/different_key.slim +2 -0
  33. data/spec/content_for/layout.erb +1 -0
  34. data/spec/content_for/layout.erubis +1 -0
  35. data/spec/content_for/layout.haml +1 -0
  36. data/spec/content_for/layout.slim +1 -0
  37. data/spec/content_for/multiple_blocks.erb +4 -0
  38. data/spec/content_for/multiple_blocks.erubis +4 -0
  39. data/spec/content_for/multiple_blocks.haml +8 -0
  40. data/spec/content_for/multiple_blocks.slim +8 -0
  41. data/spec/content_for/multiple_yields.erb +3 -0
  42. data/spec/content_for/multiple_yields.erubis +3 -0
  43. data/spec/content_for/multiple_yields.haml +3 -0
  44. data/spec/content_for/multiple_yields.slim +3 -0
  45. data/spec/content_for/passes_values.erb +1 -0
  46. data/spec/content_for/passes_values.erubis +1 -0
  47. data/spec/content_for/passes_values.haml +1 -0
  48. data/spec/content_for/passes_values.slim +1 -0
  49. data/spec/content_for/same_key.erb +1 -0
  50. data/spec/content_for/same_key.erubis +1 -0
  51. data/spec/content_for/same_key.haml +2 -0
  52. data/spec/content_for/same_key.slim +2 -0
  53. data/spec/content_for/takes_values.erb +1 -0
  54. data/spec/content_for/takes_values.erubis +1 -0
  55. data/spec/content_for/takes_values.haml +3 -0
  56. data/spec/content_for/takes_values.slim +3 -0
  57. data/spec/content_for_spec.rb +201 -0
  58. data/spec/decompile_spec.rb +44 -0
  59. data/spec/extension_spec.rb +33 -0
  60. data/spec/json_spec.rb +115 -0
  61. data/spec/link_header_spec.rb +100 -0
  62. data/spec/namespace/foo.erb +1 -0
  63. data/spec/namespace/nested/foo.erb +1 -0
  64. data/spec/namespace_spec.rb +623 -0
  65. data/spec/okjson.rb +581 -0
  66. data/spec/reloader/app.rb.erb +40 -0
  67. data/spec/reloader_spec.rb +441 -0
  68. data/spec/respond_with/bar.erb +1 -0
  69. data/spec/respond_with/bar.json.erb +1 -0
  70. data/spec/respond_with/foo.html.erb +1 -0
  71. data/spec/respond_with/not_html.sass +2 -0
  72. data/spec/respond_with_spec.rb +289 -0
  73. data/spec/spec_helper.rb +6 -0
  74. data/spec/streaming_spec.rb +436 -0
  75. metadata +252 -0
@@ -0,0 +1,40 @@
1
+ class <%= name %> < <%= parent %>
2
+ <% if enable_reloader %>
3
+ register Sinatra::Reloader
4
+ enable :reloader
5
+ <% end %>
6
+ <% unless inline_templates.nil? %>
7
+ enable :inline_templates
8
+ <% end %>
9
+
10
+ <% extensions.each do |extension| %>
11
+ register <%= extension %>
12
+ <% end %>
13
+
14
+ <% middlewares.each do |middleware| %>
15
+ use <%= middleware %>
16
+ <% end %>
17
+
18
+ <% filters.each do |filter| %>
19
+ <%= filter %>
20
+ <% end %>
21
+
22
+ <% errors.each do |number, code| %>
23
+ error <%= number %> do
24
+ <%= code %>
25
+ end
26
+ <% end %>
27
+
28
+ <% routes.each do |route| %>
29
+ <%= route %>
30
+ <% end %>
31
+ end
32
+
33
+ <% unless inline_templates.nil? %>
34
+ __END__
35
+
36
+ <% inline_templates.each_pair do |name, content| %>
37
+ @@<%= name %>
38
+ <%= content %>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,441 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+ require 'fileutils'
4
+
5
+ describe Sinatra::Reloader do
6
+ # Returns the temporary directory.
7
+ def tmp_dir
8
+ File.expand_path('../../tmp', __FILE__)
9
+ end
10
+
11
+ # Returns the path of the Sinatra application file created by
12
+ # +setup_example_app+.
13
+ def app_file_path
14
+ File.join(tmp_dir, "example_app_#{@@example_app_counter}.rb")
15
+ end
16
+
17
+ # Returns the name of the Sinatra application created by
18
+ # +setup_example_app+: 'ExampleApp1' for the first application,
19
+ # 'ExampleApp2' fo the second one, and so on...
20
+ def app_name
21
+ "ExampleApp#{@@example_app_counter}"
22
+ end
23
+
24
+ # Returns the (constant of the) Sinatra application created by
25
+ # +setup_example_app+.
26
+ def app_const
27
+ Module.const_get(app_name)
28
+ end
29
+
30
+ # Writes a file with a Sinatra application using the template
31
+ # located at <tt>specs/reloader/app.rb.erb</tt>. It expects an
32
+ # +options+ hash, with an array of strings containing the
33
+ # application's routes (+:routes+ key), a hash with the inline
34
+ # template's names as keys and the bodys as values
35
+ # (+:inline_templates+ key) and an optional application name
36
+ # (+:name+) otherwise +app_name+ is used.
37
+ #
38
+ # It ensures to change the written file's mtime when it already
39
+ # exists.
40
+ def write_app_file(options={})
41
+ options[:routes] ||= ['get("/foo") { erb :foo }']
42
+ options[:inline_templates] ||= nil
43
+ options[:extensions] ||= []
44
+ options[:middlewares] ||= []
45
+ options[:filters] ||= []
46
+ options[:errors] ||= {}
47
+ options[:name] ||= app_name
48
+ options[:enable_reloader] = true unless options[:enable_reloader] === false
49
+ options[:parent] ||= 'Sinatra::Base'
50
+
51
+ update_file(app_file_path) do |f|
52
+ template_path = File.expand_path('../reloader/app.rb.erb', __FILE__)
53
+ template = Tilt.new(template_path, nil, :trim => '<>')
54
+ f.write template.render(Object.new, options)
55
+ end
56
+ end
57
+
58
+ alias update_app_file write_app_file
59
+
60
+ # It calls <tt>File.open(path, 'w', &block)</tt> all the times
61
+ # needed to change the file's mtime.
62
+ def update_file(path, &block)
63
+ original_mtime = File.exist?(path) ? File.mtime(path) : Time.at(0)
64
+ new_time = original_mtime + 1
65
+ File.open(path, 'w', &block)
66
+ File.utime(new_time, new_time, path)
67
+ end
68
+
69
+ # Writes a Sinatra application to a file, requires the file, sets
70
+ # the new application as the one being tested and enables the
71
+ # reloader.
72
+ def setup_example_app(options={})
73
+ @@example_app_counter ||= 0
74
+ @@example_app_counter += 1
75
+
76
+ FileUtils.mkdir_p(tmp_dir)
77
+ write_app_file(options)
78
+ $LOADED_FEATURES.delete app_file_path
79
+ require app_file_path
80
+ self.app = app_const
81
+ app_const.enable :reloader
82
+ end
83
+
84
+ after(:all) { FileUtils.rm_rf(tmp_dir) }
85
+
86
+ describe "default route reloading mechanism" do
87
+ before(:each) do
88
+ setup_example_app(:routes => ['get("/foo") { "foo" }'])
89
+ end
90
+
91
+ it "doesn't mess up the application" do
92
+ get('/foo').body.should == 'foo'
93
+ end
94
+
95
+ it "knows when a route has been modified" do
96
+ update_app_file(:routes => ['get("/foo") { "bar" }'])
97
+ get('/foo').body.should == 'bar'
98
+ end
99
+
100
+ it "knows when a route has been added" do
101
+ update_app_file(
102
+ :routes => ['get("/foo") { "foo" }', 'get("/bar") { "bar" }']
103
+ )
104
+ get('/foo').body.should == 'foo'
105
+ get('/bar').body.should == 'bar'
106
+ end
107
+
108
+ it "knows when a route has been removed" do
109
+ update_app_file(:routes => ['get("/bar") { "bar" }'])
110
+ get('/foo').status.should == 404
111
+ end
112
+
113
+ it "doesn't try to reload a removed file" do
114
+ update_app_file(:routes => ['get("/foo") { "i shall not be reloaded" }'])
115
+ FileUtils.rm app_file_path
116
+ get('/foo').body.strip.should == 'foo'
117
+ end
118
+ end
119
+
120
+ describe "default inline templates reloading mechanism" do
121
+ before(:each) do
122
+ setup_example_app(
123
+ :routes => ['get("/foo") { erb :foo }'],
124
+ :inline_templates => { :foo => 'foo' }
125
+ )
126
+ end
127
+
128
+ it "doesn't mess up the application" do
129
+ get('/foo').body.strip.should == 'foo'
130
+ end
131
+
132
+ it "reloads inline templates in the app file" do
133
+ update_app_file(
134
+ :routes => ['get("/foo") { erb :foo }'],
135
+ :inline_templates => { :foo => 'bar' }
136
+ )
137
+ get('/foo').body.strip.should == 'bar'
138
+ end
139
+
140
+ it "reloads inline templates in other file" do
141
+ setup_example_app(:routes => ['get("/foo") { erb :foo }'])
142
+ template_file_path = File.join(tmp_dir, 'templates.rb')
143
+ File.open(template_file_path, 'w') do |f|
144
+ f.write "__END__\n\n@@foo\nfoo"
145
+ end
146
+ require template_file_path
147
+ app_const.inline_templates= template_file_path
148
+ get('/foo').body.strip.should == 'foo'
149
+ update_file(template_file_path) do |f|
150
+ f.write "__END__\n\n@@foo\nbar"
151
+ end
152
+ get('/foo').body.strip.should == 'bar'
153
+ end
154
+ end
155
+
156
+ describe "default middleware reloading mechanism" do
157
+ it "knows when a middleware has been added" do
158
+ setup_example_app(:routes => ['get("/foo") { "foo" }'])
159
+ update_app_file(
160
+ :routes => ['get("/foo") { "foo" }'],
161
+ :middlewares => [Rack::Head]
162
+ )
163
+ get('/foo') # ...to perform the reload
164
+ app_const.middleware.should_not be_empty
165
+ end
166
+
167
+ it "knows when a middleware has been removed" do
168
+ setup_example_app(
169
+ :routes => ['get("/foo") { "foo" }'],
170
+ :middlewares => [Rack::Head]
171
+ )
172
+ update_app_file(:routes => ['get("/foo") { "foo" }'])
173
+ get('/foo') # ...to perform the reload
174
+ app_const.middleware.should be_empty
175
+ end
176
+ end
177
+
178
+ describe "default filter reloading mechanism" do
179
+ it "knows when a before filter has been added" do
180
+ setup_example_app(:routes => ['get("/foo") { "foo" }'])
181
+ expect {
182
+ update_app_file(
183
+ :routes => ['get("/foo") { "foo" }'],
184
+ :filters => ['before { @hi = "hi" }']
185
+ )
186
+ get('/foo') # ...to perform the reload
187
+ }.to change { app_const.filters[:before].size }.by(1)
188
+ end
189
+
190
+ it "knows when an after filter has been added" do
191
+ setup_example_app(:routes => ['get("/foo") { "foo" }'])
192
+ expect {
193
+ update_app_file(
194
+ :routes => ['get("/foo") { "foo" }'],
195
+ :filters => ['after { @bye = "bye" }']
196
+ )
197
+ get('/foo') # ...to perform the reload
198
+ }.to change { app_const.filters[:after].size }.by(1)
199
+ end
200
+
201
+ it "knows when a before filter has been removed" do
202
+ setup_example_app(
203
+ :routes => ['get("/foo") { "foo" }'],
204
+ :filters => ['before { @hi = "hi" }']
205
+ )
206
+ expect {
207
+ update_app_file(:routes => ['get("/foo") { "foo" }'])
208
+ get('/foo') # ...to perform the reload
209
+ }.to change { app_const.filters[:before].size }.by(-1)
210
+ end
211
+
212
+ it "knows when an after filter has been removed" do
213
+ setup_example_app(
214
+ :routes => ['get("/foo") { "foo" }'],
215
+ :filters => ['after { @bye = "bye" }']
216
+ )
217
+ expect {
218
+ update_app_file(:routes => ['get("/foo") { "foo" }'])
219
+ get('/foo') # ...to perform the reload
220
+ }.to change { app_const.filters[:after].size }.by(-1)
221
+ end
222
+ end
223
+
224
+ describe "error reloading" do
225
+ before do
226
+ setup_example_app(
227
+ :routes => ['get("/secret") { 403 }'],
228
+ :errors => { 403 => "'Access forbiden'" }
229
+ )
230
+ end
231
+
232
+ it "doesn't mess up the application" do
233
+ get('/secret').should be_client_error
234
+ get('/secret').body.strip.should == 'Access forbiden'
235
+ end
236
+
237
+ it "knows when a error has been added" do
238
+ update_app_file(:errors => { 404 => "'Nowhere'" })
239
+ get('/nowhere').should be_not_found
240
+ get('/nowhere').body.should == 'Nowhere'
241
+ end
242
+
243
+ it "knows when a error has been removed" do
244
+ update_app_file(:routes => ['get("/secret") { 403 }'])
245
+ get('/secret').should be_client_error
246
+ get('/secret').body.should_not == 'Access forbiden'
247
+ end
248
+
249
+ it "knows when a error has been modified" do
250
+ update_app_file(
251
+ :routes => ['get("/secret") { 403 }'],
252
+ :errors => { 403 => "'What are you doing here?'" }
253
+ )
254
+ get('/secret').should be_client_error
255
+ get('/secret').body.should == 'What are you doing here?'
256
+ end
257
+ end
258
+
259
+ describe "extension reloading" do
260
+ it "doesn't duplicate routes with every reload" do
261
+ module ::RouteExtension
262
+ def self.registered(klass)
263
+ klass.get('/bar') { 'bar' }
264
+ end
265
+ end
266
+
267
+ setup_example_app(
268
+ :routes => ['get("/foo") { "foo" }'],
269
+ :extensions => ['RouteExtension']
270
+ )
271
+
272
+ expect {
273
+ update_app_file(
274
+ :routes => ['get("/foo") { "foo" }'],
275
+ :extensions => ['RouteExtension']
276
+ )
277
+ get('/foo') # ...to perform the reload
278
+ }.to_not change { app_const.routes['GET'].size }
279
+ end
280
+
281
+ it "doesn't duplicate middleware with every reload" do
282
+ module ::MiddlewareExtension
283
+ def self.registered(klass)
284
+ klass.use Rack::Head
285
+ end
286
+ end
287
+
288
+ setup_example_app(
289
+ :routes => ['get("/foo") { "foo" }'],
290
+ :extensions => ['MiddlewareExtension']
291
+ )
292
+
293
+ expect {
294
+ update_app_file(
295
+ :routes => ['get("/foo") { "foo" }'],
296
+ :extensions => ['MiddlewareExtension']
297
+ )
298
+ get('/foo') # ...to perform the reload
299
+ }.to_not change { app_const.middleware.size }
300
+ end
301
+
302
+ it "doesn't duplicate before filters with every reload" do
303
+ module ::BeforeFilterExtension
304
+ def self.registered(klass)
305
+ klass.before { @hi = 'hi' }
306
+ end
307
+ end
308
+
309
+ setup_example_app(
310
+ :routes => ['get("/foo") { "foo" }'],
311
+ :extensions => ['BeforeFilterExtension']
312
+ )
313
+
314
+ expect {
315
+ update_app_file(
316
+ :routes => ['get("/foo") { "foo" }'],
317
+ :extensions => ['BeforeFilterExtension']
318
+ )
319
+ get('/foo') # ...to perform the reload
320
+ }.to_not change { app_const.filters[:before].size }
321
+ end
322
+
323
+ it "doesn't duplicate after filters with every reload" do
324
+ module ::AfterFilterExtension
325
+ def self.registered(klass)
326
+ klass.after { @bye = 'bye' }
327
+ end
328
+ end
329
+
330
+ setup_example_app(
331
+ :routes => ['get("/foo") { "foo" }'],
332
+ :extensions => ['AfterFilterExtension']
333
+ )
334
+
335
+ expect {
336
+ update_app_file(
337
+ :routes => ['get("/foo") { "foo" }'],
338
+ :extensions => ['AfterFilterExtension']
339
+ )
340
+ get('/foo') # ...to perform the reload
341
+ }.to_not change { app_const.filters[:after].size }
342
+ end
343
+ end
344
+
345
+ describe ".dont_reload" do
346
+ before(:each) do
347
+ setup_example_app(
348
+ :routes => ['get("/foo") { erb :foo }'],
349
+ :inline_templates => { :foo => 'foo' }
350
+ )
351
+ end
352
+
353
+ it "allows to specify a file to stop from being reloaded" do
354
+ app_const.dont_reload app_file_path
355
+ update_app_file(:routes => ['get("/foo") { "bar" }'])
356
+ get('/foo').body.strip.should == 'foo'
357
+ end
358
+
359
+ it "allows to specify a glob to stop matching files from being reloaded" do
360
+ app_const.dont_reload '**/*.rb'
361
+ update_app_file(:routes => ['get("/foo") { "bar" }'])
362
+ get('/foo').body.strip.should == 'foo'
363
+ end
364
+
365
+ it "doesn't interfere with other application's reloading policy" do
366
+ app_const.dont_reload '**/*.rb'
367
+ setup_example_app(:routes => ['get("/foo") { "foo" }'])
368
+ update_app_file(:routes => ['get("/foo") { "bar" }'])
369
+ get('/foo').body.strip.should == 'bar'
370
+ end
371
+ end
372
+
373
+ describe ".also_reload" do
374
+ before(:each) do
375
+ setup_example_app(:routes => ['get("/foo") { Foo.foo }'])
376
+ @foo_path = File.join(tmp_dir, 'foo.rb')
377
+ update_file(@foo_path) do |f|
378
+ f.write 'class Foo; def self.foo() "foo" end end'
379
+ end
380
+ $LOADED_FEATURES.delete @foo_path
381
+ require @foo_path
382
+ app_const.also_reload @foo_path
383
+ end
384
+
385
+ it "allows to specify a file to be reloaded" do
386
+ get('/foo').body.strip.should == 'foo'
387
+ update_file(@foo_path) do |f|
388
+ f.write 'class Foo; def self.foo() "bar" end end'
389
+ end
390
+ get('/foo').body.strip.should == 'bar'
391
+ end
392
+
393
+ it "allows to specify glob to reaload matching files" do
394
+ get('/foo').body.strip.should == 'foo'
395
+ update_file(@foo_path) do |f|
396
+ f.write 'class Foo; def self.foo() "bar" end end'
397
+ end
398
+ get('/foo').body.strip.should == 'bar'
399
+ end
400
+
401
+ it "doesn't try to reload a removed file" do
402
+ update_file(@foo_path) do |f|
403
+ f.write 'class Foo; def self.foo() "bar" end end'
404
+ end
405
+ FileUtils.rm @foo_path
406
+ get('/foo').body.strip.should == 'foo'
407
+ end
408
+
409
+ it "doesn't interfere with other application's reloading policy" do
410
+ app_const.also_reload '**/*.rb'
411
+ setup_example_app(:routes => ['get("/foo") { Foo.foo }'])
412
+ get('/foo').body.strip.should == 'foo'
413
+ update_file(@foo_path) do |f|
414
+ f.write 'class Foo; def self.foo() "bar" end end'
415
+ end
416
+ get('/foo').body.strip.should == 'foo'
417
+ end
418
+ end
419
+
420
+ it "automatically registers the reloader in the subclasses" do
421
+ class ::Parent < Sinatra::Base
422
+ register Sinatra::Reloader
423
+ enable :reloader
424
+ end
425
+
426
+ setup_example_app(
427
+ :routes => ['get("/foo") { "foo" }'],
428
+ :enable_reloader => false,
429
+ :parent => 'Parent'
430
+ )
431
+
432
+ update_app_file(
433
+ :routes => ['get("/foo") { "bar" }'],
434
+ :enable_reloader => false,
435
+ :parent => 'Parent'
436
+ )
437
+
438
+ get('/foo').body.should == 'bar'
439
+ end
440
+
441
+ end