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.
Files changed (79) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +75 -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/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +113 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +134 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +81 -0
  19. data/lib/sinatra/namespace.rb +282 -0
  20. data/lib/sinatra/reloader.rb +384 -0
  21. data/lib/sinatra/respond_with.rb +245 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +125 -0
  25. data/spec/capture_spec.rb +80 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/missing_env.yml +4 -0
  28. data/spec/config_file/with_envs.yml +7 -0
  29. data/spec/config_file/with_nested_envs.yml +11 -0
  30. data/spec/config_file_spec.rb +44 -0
  31. data/spec/content_for/different_key.erb +1 -0
  32. data/spec/content_for/different_key.erubis +1 -0
  33. data/spec/content_for/different_key.haml +2 -0
  34. data/spec/content_for/different_key.slim +2 -0
  35. data/spec/content_for/layout.erb +1 -0
  36. data/spec/content_for/layout.erubis +1 -0
  37. data/spec/content_for/layout.haml +1 -0
  38. data/spec/content_for/layout.slim +1 -0
  39. data/spec/content_for/multiple_blocks.erb +4 -0
  40. data/spec/content_for/multiple_blocks.erubis +4 -0
  41. data/spec/content_for/multiple_blocks.haml +8 -0
  42. data/spec/content_for/multiple_blocks.slim +8 -0
  43. data/spec/content_for/multiple_yields.erb +3 -0
  44. data/spec/content_for/multiple_yields.erubis +3 -0
  45. data/spec/content_for/multiple_yields.haml +3 -0
  46. data/spec/content_for/multiple_yields.slim +3 -0
  47. data/spec/content_for/passes_values.erb +1 -0
  48. data/spec/content_for/passes_values.erubis +1 -0
  49. data/spec/content_for/passes_values.haml +1 -0
  50. data/spec/content_for/passes_values.slim +1 -0
  51. data/spec/content_for/same_key.erb +1 -0
  52. data/spec/content_for/same_key.erubis +1 -0
  53. data/spec/content_for/same_key.haml +2 -0
  54. data/spec/content_for/same_key.slim +2 -0
  55. data/spec/content_for/takes_values.erb +1 -0
  56. data/spec/content_for/takes_values.erubis +1 -0
  57. data/spec/content_for/takes_values.haml +3 -0
  58. data/spec/content_for/takes_values.slim +3 -0
  59. data/spec/content_for_spec.rb +201 -0
  60. data/spec/cookies_spec.rb +782 -0
  61. data/spec/decompile_spec.rb +44 -0
  62. data/spec/extension_spec.rb +33 -0
  63. data/spec/json_spec.rb +115 -0
  64. data/spec/link_header_spec.rb +100 -0
  65. data/spec/multi_route_spec.rb +45 -0
  66. data/spec/namespace/foo.erb +1 -0
  67. data/spec/namespace/nested/foo.erb +1 -0
  68. data/spec/namespace_spec.rb +623 -0
  69. data/spec/okjson.rb +581 -0
  70. data/spec/reloader/app.rb.erb +40 -0
  71. data/spec/reloader_spec.rb +441 -0
  72. data/spec/respond_with/bar.erb +1 -0
  73. data/spec/respond_with/bar.json.erb +1 -0
  74. data/spec/respond_with/foo.html.erb +1 -0
  75. data/spec/respond_with/not_html.sass +2 -0
  76. data/spec/respond_with_spec.rb +289 -0
  77. data/spec/spec_helper.rb +6 -0
  78. data/spec/streaming_spec.rb +436 -0
  79. metadata +256 -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