Syd-sinatra 0.3.2 → 0.9.0.2

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 (82) hide show
  1. data/AUTHORS +40 -0
  2. data/CHANGES +189 -0
  3. data/README.rdoc +148 -119
  4. data/Rakefile +34 -10
  5. data/{test → compat}/app_test.rb +11 -10
  6. data/{test → compat}/application_test.rb +21 -5
  7. data/compat/builder_test.rb +101 -0
  8. data/compat/erb_test.rb +136 -0
  9. data/{test → compat}/events_test.rb +16 -3
  10. data/compat/filter_test.rb +30 -0
  11. data/compat/haml_test.rb +233 -0
  12. data/compat/helper.rb +30 -0
  13. data/compat/mapped_error_test.rb +72 -0
  14. data/{test → compat}/pipeline_test.rb +9 -4
  15. data/compat/sass_test.rb +57 -0
  16. data/{test → compat}/streaming_test.rb +4 -1
  17. data/lib/sinatra/base.rb +843 -0
  18. data/lib/sinatra/compat.rb +239 -0
  19. data/lib/sinatra/main.rb +48 -0
  20. data/lib/sinatra/test/bacon.rb +17 -0
  21. data/lib/sinatra/test/rspec.rb +7 -8
  22. data/lib/sinatra/test/spec.rb +3 -4
  23. data/lib/sinatra/test/unit.rb +3 -5
  24. data/lib/sinatra/test.rb +114 -0
  25. data/lib/sinatra.rb +6 -1468
  26. data/sinatra.gemspec +68 -35
  27. data/test/base_test.rb +68 -0
  28. data/test/builder_test.rb +50 -87
  29. data/test/data/reload_app_file.rb +3 -0
  30. data/test/erb_test.rb +38 -124
  31. data/test/filter_test.rb +65 -20
  32. data/test/haml_test.rb +51 -216
  33. data/test/helper.rb +23 -5
  34. data/test/helpers_test.rb +361 -0
  35. data/test/mapped_error_test.rb +137 -49
  36. data/test/middleware_test.rb +58 -0
  37. data/test/options_test.rb +97 -0
  38. data/test/reload_test.rb +61 -0
  39. data/test/request_test.rb +18 -0
  40. data/test/result_test.rb +88 -0
  41. data/test/routing_test.rb +391 -0
  42. data/test/sass_test.rb +27 -48
  43. data/test/sinatra_test.rb +13 -0
  44. data/test/static_test.rb +57 -0
  45. data/test/templates_test.rb +88 -0
  46. data/test/views/hello.builder +1 -0
  47. data/test/views/hello.erb +1 -0
  48. data/test/views/hello.haml +1 -0
  49. data/test/views/hello.sass +2 -0
  50. data/test/views/hello.test +1 -0
  51. data/test/views/layout2.builder +3 -0
  52. data/test/views/layout2.erb +2 -0
  53. data/test/views/layout2.haml +2 -0
  54. data/test/views/layout2.test +1 -0
  55. metadata +79 -47
  56. data/ChangeLog +0 -78
  57. data/lib/sinatra/test/methods.rb +0 -76
  58. data/test/event_context_test.rb +0 -15
  59. /data/{test → compat}/custom_error_test.rb +0 -0
  60. /data/{test → compat}/public/foo.xml +0 -0
  61. /data/{test → compat}/sessions_test.rb +0 -0
  62. /data/{test → compat}/sym_params_test.rb +0 -0
  63. /data/{test → compat}/template_test.rb +0 -0
  64. /data/{test → compat}/use_in_file_templates_test.rb +0 -0
  65. /data/{test → compat}/views/foo.builder +0 -0
  66. /data/{test → compat}/views/foo.erb +0 -0
  67. /data/{test → compat}/views/foo.haml +0 -0
  68. /data/{test → compat}/views/foo.sass +0 -0
  69. /data/{test → compat}/views/foo_layout.erb +0 -0
  70. /data/{test → compat}/views/foo_layout.haml +0 -0
  71. /data/{test → compat}/views/layout_test/foo.builder +0 -0
  72. /data/{test → compat}/views/layout_test/foo.erb +0 -0
  73. /data/{test → compat}/views/layout_test/foo.haml +0 -0
  74. /data/{test → compat}/views/layout_test/foo.sass +0 -0
  75. /data/{test → compat}/views/layout_test/layout.builder +0 -0
  76. /data/{test → compat}/views/layout_test/layout.erb +0 -0
  77. /data/{test → compat}/views/layout_test/layout.haml +0 -0
  78. /data/{test → compat}/views/layout_test/layout.sass +0 -0
  79. /data/{test → compat}/views/no_layout/no_layout.builder +0 -0
  80. /data/{test → compat}/views/no_layout/no_layout.haml +0 -0
  81. /data/{images → lib/sinatra/images}/404.png +0 -0
  82. /data/{images → lib/sinatra/images}/500.png +0 -0
@@ -0,0 +1,391 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe "Routing" do
4
+ %w[get put post delete head].each do |verb|
5
+ it "defines #{verb.upcase} request handlers with #{verb}" do
6
+ mock_app {
7
+ send verb, '/hello' do
8
+ 'Hello World'
9
+ end
10
+ }
11
+
12
+ request = Rack::MockRequest.new(@app)
13
+ response = request.request(verb.upcase, '/hello', {})
14
+ assert response.ok?
15
+ assert_equal 'Hello World', response.body
16
+ end
17
+ end
18
+
19
+ it "404s when no route satisfies the request" do
20
+ mock_app {
21
+ get('/foo') { }
22
+ }
23
+ get '/bar'
24
+ assert_equal 404, status
25
+ end
26
+
27
+ it "exposes params with indifferent hash" do
28
+ mock_app {
29
+ get '/:foo' do
30
+ assert_equal 'bar', params['foo']
31
+ assert_equal 'bar', params[:foo]
32
+ 'well, alright'
33
+ end
34
+ }
35
+ get '/bar'
36
+ assert_equal 'well, alright', body
37
+ end
38
+
39
+ it "merges named params and query string params in params" do
40
+ mock_app {
41
+ get '/:foo' do
42
+ assert_equal 'bar', params['foo']
43
+ assert_equal 'biz', params['baz']
44
+ end
45
+ }
46
+ get '/bar?baz=biz'
47
+ assert ok?
48
+ end
49
+
50
+ it "supports named params like /hello/:person" do
51
+ mock_app {
52
+ get '/hello/:person' do
53
+ "Hello #{params['person']}"
54
+ end
55
+ }
56
+ get '/hello/Frank'
57
+ assert_equal 'Hello Frank', body
58
+ end
59
+
60
+ it "supports optional named params like /?:foo?/?:bar?" do
61
+ mock_app {
62
+ get '/?:foo?/?:bar?' do
63
+ "foo=#{params[:foo]};bar=#{params[:bar]}"
64
+ end
65
+ }
66
+
67
+ get '/hello/world'
68
+ assert ok?
69
+ assert_equal "foo=hello;bar=world", body
70
+
71
+ get '/hello'
72
+ assert ok?
73
+ assert_equal "foo=hello;bar=", body
74
+
75
+ get '/'
76
+ assert ok?
77
+ assert_equal "foo=;bar=", body
78
+ end
79
+
80
+ it "supports single splat params like /*" do
81
+ mock_app {
82
+ get '/*' do
83
+ assert params['splat'].kind_of?(Array)
84
+ params['splat'].join "\n"
85
+ end
86
+ }
87
+
88
+ get '/foo'
89
+ assert_equal "foo", body
90
+
91
+ get '/foo/bar/baz'
92
+ assert_equal "foo/bar/baz", body
93
+ end
94
+
95
+ it "supports mixing multiple splat params like /*/foo/*/*" do
96
+ mock_app {
97
+ get '/*/foo/*/*' do
98
+ assert params['splat'].kind_of?(Array)
99
+ params['splat'].join "\n"
100
+ end
101
+ }
102
+
103
+ get '/bar/foo/bling/baz/boom'
104
+ assert_equal "bar\nbling\nbaz/boom", body
105
+
106
+ get '/bar/foo/baz'
107
+ assert not_found?
108
+ end
109
+
110
+ it "supports mixing named and splat params like /:foo/*" do
111
+ mock_app {
112
+ get '/:foo/*' do
113
+ assert_equal 'foo', params['foo']
114
+ assert_equal ['bar/baz'], params['splat']
115
+ end
116
+ }
117
+
118
+ get '/foo/bar/baz'
119
+ assert ok?
120
+ end
121
+
122
+ it "supports basic nested params" do
123
+ mock_app {
124
+ get '/hi' do
125
+ params["person"]["name"]
126
+ end
127
+ }
128
+
129
+ get "/hi?person[name]=John+Doe"
130
+ assert ok?
131
+ assert_equal "John Doe", body
132
+ end
133
+
134
+ it "exposes nested params with indifferent hash" do
135
+ mock_app {
136
+ get '/testme' do
137
+ assert_equal 'baz', params['bar']['foo']
138
+ assert_equal 'baz', params['bar'][:foo]
139
+ 'well, alright'
140
+ end
141
+ }
142
+ get '/testme?bar[foo]=baz'
143
+ assert_equal 'well, alright', body
144
+ end
145
+
146
+ it "supports deeply nested params" do
147
+ input = {
148
+ 'browser[chrome][engine][name]' => 'V8',
149
+ 'browser[chrome][engine][version]' => '1.0',
150
+ 'browser[firefox][engine][name]' => 'spidermonkey',
151
+ 'browser[firefox][engine][version]' => '1.7.0',
152
+ 'emacs[map][goto-line]' => 'M-g g',
153
+ 'emacs[version]' => '22.3.1',
154
+ 'paste[name]' => 'hello world',
155
+ 'paste[syntax]' => 'ruby'
156
+ }
157
+ expected = {
158
+ "emacs" => {
159
+ "map" => { "goto-line" => "M-g g" },
160
+ "version" => "22.3.1"
161
+ },
162
+ "browser" => {
163
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
164
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
165
+ },
166
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
167
+ }
168
+ mock_app {
169
+ get '/foo' do
170
+ assert_equal expected, params
171
+ 'looks good'
172
+ end
173
+ }
174
+ get "/foo?#{param_string(input)}"
175
+ assert ok?
176
+ assert_equal 'looks good', body
177
+ end
178
+
179
+ it "supports paths that include spaces" do
180
+ mock_app {
181
+ get '/path with spaces' do
182
+ 'looks good'
183
+ end
184
+ }
185
+
186
+ get '/path%20with%20spaces'
187
+ assert ok?
188
+ assert_equal 'looks good', body
189
+ end
190
+
191
+ it "URL decodes named parameters and splats" do
192
+ mock_app {
193
+ get '/:foo/*' do
194
+ assert_equal 'hello world', params['foo']
195
+ assert_equal ['how are you'], params['splat']
196
+ nil
197
+ end
198
+ }
199
+
200
+ get '/hello%20world/how%20are%20you'
201
+ assert ok?
202
+ end
203
+
204
+ it 'supports regular expressions' do
205
+ mock_app {
206
+ get(/^\/foo...\/bar$/) do
207
+ 'Hello World'
208
+ end
209
+ }
210
+
211
+ get '/foooom/bar'
212
+ assert ok?
213
+ assert_equal 'Hello World', body
214
+ end
215
+
216
+ it 'makes regular expression captures available in params[:captures]' do
217
+ mock_app {
218
+ get(/^\/fo(.*)\/ba(.*)/) do
219
+ assert_equal ['orooomma', 'f'], params[:captures]
220
+ 'right on'
221
+ end
222
+ }
223
+
224
+ get '/foorooomma/baf'
225
+ assert ok?
226
+ assert_equal 'right on', body
227
+ end
228
+
229
+ it "returns response immediately on halt" do
230
+ mock_app {
231
+ get '/' do
232
+ halt 'Hello World'
233
+ 'Boo-hoo World'
234
+ end
235
+ }
236
+
237
+ get '/'
238
+ assert ok?
239
+ assert_equal 'Hello World', body
240
+ end
241
+
242
+ it "transitions to the next matching route on pass" do
243
+ mock_app {
244
+ get '/:foo' do
245
+ pass
246
+ 'Hello Foo'
247
+ end
248
+
249
+ get '/*' do
250
+ assert !params.include?('foo')
251
+ 'Hello World'
252
+ end
253
+ }
254
+
255
+ get '/bar'
256
+ assert ok?
257
+ assert_equal 'Hello World', body
258
+ end
259
+
260
+ it "transitions to 404 when passed and no subsequent route matches" do
261
+ mock_app {
262
+ get '/:foo' do
263
+ pass
264
+ 'Hello Foo'
265
+ end
266
+ }
267
+
268
+ get '/bar'
269
+ assert not_found?
270
+ end
271
+
272
+ it "passes when matching condition returns false" do
273
+ mock_app {
274
+ condition { params[:foo] == 'bar' }
275
+ get '/:foo' do
276
+ 'Hello World'
277
+ end
278
+ }
279
+
280
+ get '/bar'
281
+ assert ok?
282
+ assert_equal 'Hello World', body
283
+
284
+ get '/foo'
285
+ assert not_found?
286
+ end
287
+
288
+ it "does not pass when matching condition returns nil" do
289
+ mock_app {
290
+ condition { nil }
291
+ get '/:foo' do
292
+ 'Hello World'
293
+ end
294
+ }
295
+
296
+ get '/bar'
297
+ assert ok?
298
+ assert_equal 'Hello World', body
299
+ end
300
+
301
+ it "passes to next route when condition calls pass explicitly" do
302
+ mock_app {
303
+ condition { pass unless params[:foo] == 'bar' }
304
+ get '/:foo' do
305
+ 'Hello World'
306
+ end
307
+ }
308
+
309
+ get '/bar'
310
+ assert ok?
311
+ assert_equal 'Hello World', body
312
+
313
+ get '/foo'
314
+ assert not_found?
315
+ end
316
+
317
+ it "passes to the next route when host_name does not match" do
318
+ mock_app {
319
+ host_name 'example.com'
320
+ get '/foo' do
321
+ 'Hello World'
322
+ end
323
+ }
324
+ get '/foo'
325
+ assert not_found?
326
+
327
+ get '/foo', :env => { 'HTTP_HOST' => 'example.com' }
328
+ assert_equal 200, status
329
+ assert_equal 'Hello World', body
330
+ end
331
+
332
+ it "passes to the next route when user_agent does not match" do
333
+ mock_app {
334
+ user_agent(/Foo/)
335
+ get '/foo' do
336
+ 'Hello World'
337
+ end
338
+ }
339
+ get '/foo'
340
+ assert not_found?
341
+
342
+ get '/foo', :env => { 'HTTP_USER_AGENT' => 'Foo Bar' }
343
+ assert_equal 200, status
344
+ assert_equal 'Hello World', body
345
+ end
346
+
347
+ it "makes captures in user agent pattern available in params[:agent]" do
348
+ mock_app {
349
+ user_agent(/Foo (.*)/)
350
+ get '/foo' do
351
+ 'Hello ' + params[:agent].first
352
+ end
353
+ }
354
+ get '/foo', :env => { 'HTTP_USER_AGENT' => 'Foo Bar' }
355
+ assert_equal 200, status
356
+ assert_equal 'Hello Bar', body
357
+ end
358
+
359
+ it "filters by accept header" do
360
+ mock_app {
361
+ get '/', :provides => :xml do
362
+ request.env['HTTP_ACCEPT']
363
+ end
364
+ }
365
+
366
+ get '/', :env => { :accept => 'application/xml' }
367
+ assert ok?
368
+ assert_equal 'application/xml', body
369
+ assert_equal 'application/xml', response.headers['Content-Type']
370
+
371
+ get '/', :env => { :accept => 'text/html' }
372
+ assert !ok?
373
+ end
374
+
375
+ it "allows multiple mime types for accept header" do
376
+ types = ['image/jpeg', 'image/pjpeg']
377
+
378
+ mock_app {
379
+ get '/', :provides => types do
380
+ request.env['HTTP_ACCEPT']
381
+ end
382
+ }
383
+
384
+ types.each do |type|
385
+ get '/', :env => { :accept => type }
386
+ assert ok?
387
+ assert_equal type, body
388
+ assert_equal type, response.headers['Content-Type']
389
+ end
390
+ end
391
+ end
data/test/sass_test.rb CHANGED
@@ -1,57 +1,36 @@
1
1
  require File.dirname(__FILE__) + '/helper'
2
2
 
3
- context "Sass" do
4
-
5
- setup do
6
- Sinatra.application = nil
3
+ describe "Sass Templates" do
4
+ def sass_app(&block)
5
+ mock_app {
6
+ set :views, File.dirname(__FILE__) + '/views'
7
+ get '/', &block
8
+ }
9
+ get '/'
7
10
  end
8
11
 
9
- context "Templates (in general)" do
10
-
11
- setup do
12
- Sinatra.application = nil
13
- end
14
-
15
- specify "are read from files if Symbols" do
16
-
17
- get '/from_file' do
18
- sass :foo, :views_directory => File.dirname(__FILE__) + "/views"
19
- end
20
-
21
- get_it '/from_file'
22
- should.be.ok
23
- body.should.equal "#sass {\n background_color: #FFF; }\n"
24
-
25
- end
26
-
27
- specify "raise an error if template not found" do
28
- get '/' do
29
- sass :not_found
30
- end
31
-
32
- lambda { get_it '/' }.should.raise(Errno::ENOENT)
33
- end
34
-
35
- specify "ignore default layout file with .sass extension" do
36
- get '/' do
37
- sass :foo, :views_directory => File.dirname(__FILE__) + "/views/layout_test"
38
- end
39
-
40
- get_it '/'
41
- should.be.ok
42
- body.should.equal "#sass {\n background_color: #FFF; }\n"
43
- end
44
-
45
- specify "ignore explicitly specified layout file" do
46
- get '/' do
47
- sass :foo, :layout => :layout, :views_directory => File.dirname(__FILE__) + "/views/layout_test"
48
- end
12
+ it 'renders inline Sass strings' do
13
+ sass_app { sass "#sass\n :background-color #FFF\n" }
14
+ assert ok?
15
+ assert_equal "#sass {\n background-color: #FFF; }\n", body
16
+ end
49
17
 
50
- get_it '/'
51
- should.be.ok
52
- body.should.equal "#sass {\n background_color: #FFF; }\n"
53
- end
18
+ it 'renders .sass files in views path' do
19
+ sass_app { sass :hello }
20
+ assert ok?
21
+ assert_equal "#sass {\n background-color: #FFF; }\n", body
22
+ end
54
23
 
24
+ it 'ignores the layout option' do
25
+ sass_app { sass :hello, :layout => :layout2 }
26
+ assert ok?
27
+ assert_equal "#sass {\n background-color: #FFF; }\n", body
55
28
  end
56
29
 
30
+ it "raises error if template not found" do
31
+ mock_app {
32
+ get('/') { sass :no_such_template }
33
+ }
34
+ assert_raise(Errno::ENOENT) { get('/') }
35
+ end
57
36
  end
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'Sinatra' do
4
+ it 'creates a new Sinatra::Base subclass on new' do
5
+ app =
6
+ Sinatra.new do
7
+ get '/' do
8
+ 'Hello World'
9
+ end
10
+ end
11
+ assert_same Sinatra::Base, app.superclass
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'Static' do
4
+ F = ::File
5
+
6
+ before do
7
+ mock_app {
8
+ set :static, true
9
+ set :public, F.dirname(__FILE__)
10
+ }
11
+ end
12
+
13
+ it 'serves GET requests for files in the public directory' do
14
+ get "/#{F.basename(__FILE__)}"
15
+ assert ok?
16
+ assert_equal File.read(__FILE__), body
17
+ assert_equal File.size(__FILE__).to_s, response['Content-Length']
18
+ assert response.headers.include?('Last-Modified')
19
+ end
20
+
21
+ it 'serves HEAD requests for files in the public directory' do
22
+ head "/#{F.basename(__FILE__)}"
23
+ assert ok?
24
+ assert_equal '', body
25
+ assert_equal File.size(__FILE__).to_s, response['Content-Length']
26
+ assert response.headers.include?('Last-Modified')
27
+ end
28
+
29
+ it 'serves files in preference to custom routes' do
30
+ @app.get("/#{F.basename(__FILE__)}") { 'Hello World' }
31
+ get "/#{F.basename(__FILE__)}"
32
+ assert ok?
33
+ assert body != 'Hello World'
34
+ end
35
+
36
+ it 'does not serve directories' do
37
+ get "/"
38
+ assert not_found?
39
+ end
40
+
41
+ it 'passes to the next handler when the static option is disabled' do
42
+ @app.set :static, false
43
+ get "/#{F.basename(__FILE__)}"
44
+ assert not_found?
45
+ end
46
+
47
+ it 'passes to the next handler when the public option is nil' do
48
+ @app.set :public, nil
49
+ get "/#{F.basename(__FILE__)}"
50
+ assert not_found?
51
+ end
52
+
53
+ it '404s when a file is not found' do
54
+ get "/foobarbaz.txt"
55
+ assert not_found?
56
+ end
57
+ end
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe 'Templating' do
4
+ def render_app(&block)
5
+ mock_app {
6
+ def render_test(template, data, options, &block)
7
+ inner = block ? block.call : ''
8
+ data + inner
9
+ end
10
+ set :views, File.dirname(__FILE__) + '/views'
11
+ get '/', &block
12
+ template(:layout3) { "Layout 3!\n" }
13
+ }
14
+ get '/'
15
+ end
16
+
17
+ def with_default_layout
18
+ layout = File.dirname(__FILE__) + '/views/layout.test'
19
+ File.open(layout, 'wb') { |io| io.write "Layout!\n" }
20
+ yield
21
+ ensure
22
+ File.unlink(layout) rescue nil
23
+ end
24
+
25
+ it 'renders String templates directly' do
26
+ render_app { render :test, 'Hello World' }
27
+ assert ok?
28
+ assert_equal 'Hello World', body
29
+ end
30
+
31
+ it 'renders Proc templates using the call result' do
32
+ render_app { render :test, Proc.new {'Hello World'} }
33
+ assert ok?
34
+ assert_equal 'Hello World', body
35
+ end
36
+
37
+ it 'looks up Symbol templates in views directory' do
38
+ render_app { render :test, :hello }
39
+ assert ok?
40
+ assert_equal "Hello World!\n", body
41
+ end
42
+
43
+ it 'uses the default layout template if not explicitly overridden' do
44
+ with_default_layout do
45
+ render_app { render :test, :hello }
46
+ assert ok?
47
+ assert_equal "Layout!\nHello World!\n", body
48
+ end
49
+ end
50
+
51
+ it 'uses the default layout template if not really overriden' do
52
+ with_default_layout do
53
+ render_app { render :test, :hello, :layout => true }
54
+ assert ok?
55
+ assert_equal "Layout!\nHello World!\n", body
56
+ end
57
+ end
58
+
59
+ it 'uses the layout template specified' do
60
+ render_app { render :test, :hello, :layout => :layout2 }
61
+ assert ok?
62
+ assert_equal "Layout 2!\nHello World!\n", body
63
+ end
64
+
65
+ it 'uses layout templates defined with the #template method' do
66
+ render_app { render :test, :hello, :layout => :layout3 }
67
+ assert ok?
68
+ assert_equal "Layout 3!\nHello World!\n", body
69
+ end
70
+
71
+ it 'loads templates from source file with use_in_file_templates!' do
72
+ mock_app {
73
+ use_in_file_templates!
74
+ }
75
+ assert_equal "this is foo\n\n", @app.templates[:foo]
76
+ assert_equal "X\n= yield\nX\n", @app.templates[:layout]
77
+ end
78
+ end
79
+
80
+ __END__
81
+
82
+ @@ foo
83
+ this is foo
84
+
85
+ @@ layout
86
+ X
87
+ = yield
88
+ X
@@ -0,0 +1 @@
1
+ xml.exclaim "You're my boy, #{@name}!"
@@ -0,0 +1 @@
1
+ Hello <%= 'World' %>
@@ -0,0 +1 @@
1
+ %h1 Hello From Haml
@@ -0,0 +1,2 @@
1
+ #sass
2
+ :background-color #FFF
@@ -0,0 +1 @@
1
+ Hello World!
@@ -0,0 +1,3 @@
1
+ xml.layout do
2
+ xml << yield
3
+ end
@@ -0,0 +1,2 @@
1
+ ERB Layout!
2
+ <%= yield %>
@@ -0,0 +1,2 @@
1
+ %h1 HAML Layout!
2
+ %p= yield
@@ -0,0 +1 @@
1
+ Layout 2!