jellyfish 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +23 -0
- data/Gemfile +7 -0
- data/README.md +2 -2
- data/example/config.ru +11 -0
- data/jellyfish.gemspec +48 -3
- data/lib/jellyfish.rb +21 -25
- data/lib/jellyfish/test.rb +22 -0
- data/lib/jellyfish/version.rb +1 -1
- data/sinatra/builder_test.rb +95 -0
- data/sinatra/coffee_test.rb +92 -0
- data/sinatra/contest.rb +98 -0
- data/sinatra/creole_test.rb +65 -0
- data/sinatra/delegator_test.rb +162 -0
- data/sinatra/encoding_test.rb +20 -0
- data/sinatra/erb_test.rb +104 -0
- data/sinatra/extensions_test.rb +100 -0
- data/sinatra/filter_test.rb +428 -0
- data/sinatra/haml_test.rb +101 -0
- data/sinatra/helper.rb +123 -0
- data/sinatra/helpers_test.rb +1783 -0
- data/sinatra/integration/app.rb +62 -0
- data/sinatra/integration_helper.rb +214 -0
- data/sinatra/integration_test.rb +85 -0
- data/sinatra/less_test.rb +67 -0
- data/sinatra/liquid_test.rb +59 -0
- data/sinatra/mapped_error_test.rb +259 -0
- data/sinatra/markaby_test.rb +80 -0
- data/sinatra/markdown_test.rb +81 -0
- data/sinatra/middleware_test.rb +68 -0
- data/sinatra/nokogiri_test.rb +69 -0
- data/sinatra/rack_test.rb +45 -0
- data/sinatra/radius_test.rb +59 -0
- data/sinatra/rdoc_test.rb +66 -0
- data/sinatra/readme_test.rb +136 -0
- data/sinatra/request_test.rb +45 -0
- data/sinatra/response_test.rb +61 -0
- data/sinatra/result_test.rb +98 -0
- data/sinatra/route_added_hook_test.rb +59 -0
- data/sinatra/routing_test.rb +1104 -0
- data/sinatra/sass_test.rb +116 -0
- data/sinatra/scss_test.rb +89 -0
- data/sinatra/server_test.rb +48 -0
- data/sinatra/settings_test.rb +538 -0
- data/sinatra/sinatra_test.rb +17 -0
- data/sinatra/slim_test.rb +88 -0
- data/sinatra/static_test.rb +178 -0
- data/sinatra/streaming_test.rb +140 -0
- data/sinatra/templates_test.rb +298 -0
- data/sinatra/textile_test.rb +65 -0
- data/test/sinatra/test_base.rb +123 -0
- metadata +48 -3
data/CHANGES.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
+
## Jellyfish 0.5.0 -- 2012-10-18
|
4
|
+
|
5
|
+
### Incompatible changes
|
6
|
+
|
7
|
+
* Some internal constants are removed.
|
8
|
+
* Renamed `Respond` to `Response`.
|
9
|
+
|
10
|
+
### Enhancements
|
11
|
+
|
12
|
+
* Now Jellyfish would always use the custom error handler to handle the
|
13
|
+
particular exception even if `handle_exceptions` set to false. That is,
|
14
|
+
now setting `handle_exceptions` to false would only disable default
|
15
|
+
error handling. This behavior makes more sense since if you want the
|
16
|
+
exception bubble out then you shouldn't define the custom error handler
|
17
|
+
in the first place. If you define it, you must mean you want to use it.
|
18
|
+
|
19
|
+
* Eliminated some uninitialized instance variable warnings.
|
20
|
+
|
21
|
+
* Now you can access the original app via `jellyfish` in the controller.
|
22
|
+
|
23
|
+
* `Jellyfish::Controller` no longer includes `Jellyfish`, which would remove
|
24
|
+
those `DSL` methods accidentally included in previous version (0.4.0-).
|
25
|
+
|
3
26
|
## Jellyfish 0.4.0 -- 2012-10-14
|
4
27
|
|
5
28
|
* Now you can define your own custom controller like:
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Jellyfish
|
1
|
+
# Jellyfish[![Build Status](https://secure.travis-ci.org/godfat/jellyfish.png?branch=master)](http://travis-ci.org/godfat/jellyfish)
|
2
2
|
|
3
3
|
by Lin Jen-Shin ([godfat](http://godfat.org))
|
4
4
|
|
@@ -29,7 +29,7 @@ Rack applications or Rack middlewares. Under 200 lines of code.
|
|
29
29
|
* No templates
|
30
30
|
* No ORM
|
31
31
|
* No `dup` in `call`
|
32
|
-
* Regular expression routes, e.g. `get %r{
|
32
|
+
* Regular expression routes, e.g. `get %r{^/(?<id>\d+)$}`
|
33
33
|
* String routes, e.g. `get '/'`
|
34
34
|
* Custom routes, e.g. `get Matcher.new`
|
35
35
|
* Build for either Rack applications or Rack middlewares
|
data/example/config.ru
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
require 'jellyfish'
|
3
3
|
|
4
|
+
Overheat = Class.new(RuntimeError)
|
5
|
+
|
4
6
|
class Tank
|
5
7
|
include Jellyfish
|
6
8
|
handle_exceptions false
|
@@ -63,10 +65,19 @@ class Tank
|
|
63
65
|
get '/chunked' do
|
64
66
|
Body.new
|
65
67
|
end
|
68
|
+
|
69
|
+
get '/overheat' do
|
70
|
+
raise Overheat
|
71
|
+
end
|
66
72
|
end
|
67
73
|
|
68
74
|
class Heater
|
69
75
|
include Jellyfish
|
76
|
+
handle Overheat do
|
77
|
+
status 500
|
78
|
+
"It's overheated\n"
|
79
|
+
end
|
80
|
+
|
70
81
|
get '/status' do
|
71
82
|
temperature
|
72
83
|
end
|
data/jellyfish.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "jellyfish"
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.5.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Lin Jen-Shin (godfat)"]
|
9
|
-
s.date = "2012-10-
|
9
|
+
s.date = "2012-10-18"
|
10
10
|
s.description = "Pico web framework for building API-centric web applications, either\nRack applications or Rack middlewares. Under 200 lines of code."
|
11
11
|
s.email = ["godfat (XD) godfat.org"]
|
12
12
|
s.files = [
|
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
".gitmodules",
|
15
15
|
".travis.yml",
|
16
16
|
"CHANGES.md",
|
17
|
+
"Gemfile",
|
17
18
|
"LICENSE",
|
18
19
|
"README.md",
|
19
20
|
"Rakefile",
|
@@ -27,13 +28,57 @@ Gem::Specification.new do |s|
|
|
27
28
|
"lib/jellyfish/public/404.html",
|
28
29
|
"lib/jellyfish/public/500.html",
|
29
30
|
"lib/jellyfish/sinatra.rb",
|
31
|
+
"lib/jellyfish/test.rb",
|
30
32
|
"lib/jellyfish/version.rb",
|
33
|
+
"sinatra/builder_test.rb",
|
34
|
+
"sinatra/coffee_test.rb",
|
35
|
+
"sinatra/contest.rb",
|
36
|
+
"sinatra/creole_test.rb",
|
37
|
+
"sinatra/delegator_test.rb",
|
38
|
+
"sinatra/encoding_test.rb",
|
39
|
+
"sinatra/erb_test.rb",
|
40
|
+
"sinatra/extensions_test.rb",
|
41
|
+
"sinatra/filter_test.rb",
|
42
|
+
"sinatra/haml_test.rb",
|
43
|
+
"sinatra/helper.rb",
|
44
|
+
"sinatra/helpers_test.rb",
|
45
|
+
"sinatra/integration/app.rb",
|
46
|
+
"sinatra/integration_helper.rb",
|
47
|
+
"sinatra/integration_test.rb",
|
48
|
+
"sinatra/less_test.rb",
|
49
|
+
"sinatra/liquid_test.rb",
|
50
|
+
"sinatra/mapped_error_test.rb",
|
51
|
+
"sinatra/markaby_test.rb",
|
52
|
+
"sinatra/markdown_test.rb",
|
53
|
+
"sinatra/middleware_test.rb",
|
54
|
+
"sinatra/nokogiri_test.rb",
|
55
|
+
"sinatra/rack_test.rb",
|
56
|
+
"sinatra/radius_test.rb",
|
57
|
+
"sinatra/rdoc_test.rb",
|
58
|
+
"sinatra/readme_test.rb",
|
59
|
+
"sinatra/request_test.rb",
|
60
|
+
"sinatra/response_test.rb",
|
61
|
+
"sinatra/result_test.rb",
|
62
|
+
"sinatra/route_added_hook_test.rb",
|
63
|
+
"sinatra/routing_test.rb",
|
64
|
+
"sinatra/sass_test.rb",
|
65
|
+
"sinatra/scss_test.rb",
|
66
|
+
"sinatra/server_test.rb",
|
67
|
+
"sinatra/settings_test.rb",
|
68
|
+
"sinatra/sinatra_test.rb",
|
69
|
+
"sinatra/slim_test.rb",
|
70
|
+
"sinatra/static_test.rb",
|
71
|
+
"sinatra/streaming_test.rb",
|
72
|
+
"sinatra/templates_test.rb",
|
73
|
+
"sinatra/textile_test.rb",
|
31
74
|
"task/.gitignore",
|
32
|
-
"task/gemgem.rb"
|
75
|
+
"task/gemgem.rb",
|
76
|
+
"test/sinatra/test_base.rb"]
|
33
77
|
s.homepage = "https://github.com/godfat/jellyfish"
|
34
78
|
s.require_paths = ["lib"]
|
35
79
|
s.rubygems_version = "1.8.24"
|
36
80
|
s.summary = "Pico web framework for building API-centric web applications, either"
|
81
|
+
s.test_files = ["test/sinatra/test_base.rb"]
|
37
82
|
|
38
83
|
if s.respond_to? :specification_version then
|
39
84
|
s.specification_version = 3
|
data/lib/jellyfish.rb
CHANGED
@@ -3,14 +3,9 @@ module Jellyfish
|
|
3
3
|
autoload :VERSION, 'jellyfish/version'
|
4
4
|
autoload :Sinatra, 'jellyfish/sinatra'
|
5
5
|
|
6
|
-
REQUEST_METHOD = 'REQUEST_METHOD'
|
7
|
-
PATH_INFO = 'PATH_INFO'
|
8
|
-
LOCATION = 'Location'
|
9
|
-
RACK_ERRORS = 'rack.errors'
|
10
|
-
|
11
6
|
# -----------------------------------------------------------------
|
12
7
|
|
13
|
-
class
|
8
|
+
class Response < RuntimeError
|
14
9
|
def headers
|
15
10
|
@headers ||= {'Content-Type' => 'text/html'}
|
16
11
|
end
|
@@ -20,23 +15,23 @@ module Jellyfish
|
|
20
15
|
end
|
21
16
|
end
|
22
17
|
|
23
|
-
class InternalError <
|
24
|
-
class NotFound <
|
25
|
-
class Found <
|
18
|
+
class InternalError < Response; def status; 500; end; end
|
19
|
+
class NotFound < Response; def status; 404; end; end
|
20
|
+
class Found < Response # this would be raised in redirect
|
26
21
|
attr_reader :url
|
27
22
|
def initialize url; @url = url ; end
|
28
23
|
def status ; 302 ; end
|
29
|
-
def headers ; super.merge(
|
24
|
+
def headers ; super.merge('Location' => url) ; end
|
30
25
|
def body ; super.map{ |b| b.gsub('VAR_URL', url) }; end
|
31
26
|
end
|
32
27
|
|
33
28
|
# -----------------------------------------------------------------
|
34
29
|
|
35
30
|
class Controller
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@
|
31
|
+
attr_reader :routes, :jellyfish, :env
|
32
|
+
def initialize routes, jellyfish
|
33
|
+
@routes, @jellyfish = routes, jellyfish
|
34
|
+
@status, @headers, @body = nil
|
40
35
|
end
|
41
36
|
|
42
37
|
def call env
|
@@ -50,12 +45,12 @@ module Jellyfish
|
|
50
45
|
[status || 200, headers || {}, body]
|
51
46
|
end
|
52
47
|
|
53
|
-
def forward ; raise(NotFound.new)
|
54
|
-
def found url; raise(Found.new(url)); end
|
48
|
+
def forward ; raise(Jellyfish::NotFound.new) ; end
|
49
|
+
def found url; raise(Jellyfish:: Found.new(url)); end
|
55
50
|
alias_method :redirect, :found
|
56
51
|
|
57
|
-
def path_info ; env[PATH_INFO] || '/' ; end
|
58
|
-
def request_method; env[REQUEST_METHOD] || 'GET'; end
|
52
|
+
def path_info ; env['PATH_INFO'] || '/' ; end
|
53
|
+
def request_method; env['REQUEST_METHOD'] || 'GET'; end
|
59
54
|
|
60
55
|
%w[status headers].each do |field|
|
61
56
|
module_eval <<-RUBY
|
@@ -89,7 +84,7 @@ module Jellyfish
|
|
89
84
|
|
90
85
|
private
|
91
86
|
def actions
|
92
|
-
routes[request_method.downcase] || raise(NotFound.new)
|
87
|
+
routes[request_method.downcase] || raise(Jellyfish::NotFound.new)
|
93
88
|
end
|
94
89
|
|
95
90
|
def dispatch
|
@@ -101,7 +96,7 @@ module Jellyfish
|
|
101
96
|
match = route.match(path_info)
|
102
97
|
break match, block if match
|
103
98
|
end
|
104
|
-
} || raise(NotFound.new)
|
99
|
+
} || raise(Jellyfish::NotFound.new)
|
105
100
|
end
|
106
101
|
end
|
107
102
|
|
@@ -136,7 +131,7 @@ module Jellyfish
|
|
136
131
|
def controller ; Controller; end
|
137
132
|
|
138
133
|
def call env
|
139
|
-
ctrl = controller.new(self.class.routes)
|
134
|
+
ctrl = controller.new(self.class.routes, self)
|
140
135
|
ctrl.call(env)
|
141
136
|
rescue NotFound => e # forward
|
142
137
|
if app
|
@@ -145,24 +140,25 @@ module Jellyfish
|
|
145
140
|
handle(ctrl, e)
|
146
141
|
end
|
147
142
|
rescue Exception => e
|
148
|
-
handle(ctrl, e, env[
|
143
|
+
handle(ctrl, e, env['rack.errors'])
|
149
144
|
end
|
150
145
|
|
151
146
|
def protect ctrl, env
|
152
147
|
yield
|
153
148
|
rescue Exception => e
|
154
|
-
handle(ctrl, e, env[
|
149
|
+
handle(ctrl, e, env['rack.errors'])
|
155
150
|
end
|
156
151
|
|
157
152
|
private
|
158
153
|
def handle ctrl, e, stderr=nil
|
159
|
-
raise e unless self.class.handle_exceptions
|
160
154
|
handler = self.class.handlers.find{ |klass, block|
|
161
155
|
break block if e.kind_of?(klass)
|
162
156
|
}
|
163
157
|
if handler
|
164
158
|
ctrl.block_call(e, handler)
|
165
|
-
elsif
|
159
|
+
elsif !self.class.handle_exceptions
|
160
|
+
raise e
|
161
|
+
elsif e.kind_of?(Response) # InternalError ends up here if no handlers
|
166
162
|
[e.status, e.headers, e.body]
|
167
163
|
else # fallback and see if there's any InternalError handler
|
168
164
|
log_error(e, stderr)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
require 'bacon'
|
3
|
+
require 'rack'
|
4
|
+
require 'jellyfish'
|
5
|
+
|
6
|
+
Bacon.summary_on_exit
|
7
|
+
|
8
|
+
shared :jellyfish do
|
9
|
+
%w[options get head post put delete patch].each do |method|
|
10
|
+
instance_eval <<-RUBY
|
11
|
+
def #{method} path='/', app=app
|
12
|
+
app.call('PATH_INFO' => path, 'REQUEST_METHOD' => '#{method}'.upcase)
|
13
|
+
end
|
14
|
+
RUBY
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Kernel
|
19
|
+
def eq? rhs
|
20
|
+
self == rhs
|
21
|
+
end
|
22
|
+
end
|
data/lib/jellyfish/version.rb
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'builder'
|
5
|
+
|
6
|
+
class BuilderTest < Test::Unit::TestCase
|
7
|
+
def builder_app(options = {}, &block)
|
8
|
+
mock_app {
|
9
|
+
set :views, File.dirname(__FILE__) + '/views'
|
10
|
+
set options
|
11
|
+
get '/', &block
|
12
|
+
}
|
13
|
+
get '/'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'renders inline Builder strings' do
|
17
|
+
builder_app { builder 'xml.instruct!' }
|
18
|
+
assert ok?
|
19
|
+
assert_equal %{<?xml version="1.0" encoding="UTF-8"?>\n}, body
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'defaults content type to xml' do
|
23
|
+
builder_app { builder 'xml.instruct!' }
|
24
|
+
assert ok?
|
25
|
+
assert_equal "application/xml;charset=utf-8", response['Content-Type']
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'defaults allows setting content type per route' do
|
29
|
+
builder_app do
|
30
|
+
content_type :html
|
31
|
+
builder 'xml.instruct!'
|
32
|
+
end
|
33
|
+
assert ok?
|
34
|
+
assert_equal "text/html;charset=utf-8", response['Content-Type']
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'defaults allows setting content type globally' do
|
38
|
+
builder_app(:builder => { :content_type => 'html' }) do
|
39
|
+
builder 'xml.instruct!'
|
40
|
+
end
|
41
|
+
assert ok?
|
42
|
+
assert_equal "text/html;charset=utf-8", response['Content-Type']
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'renders inline blocks' do
|
46
|
+
builder_app {
|
47
|
+
@name = "Frank & Mary"
|
48
|
+
builder do |xml|
|
49
|
+
xml.couple @name
|
50
|
+
end
|
51
|
+
}
|
52
|
+
assert ok?
|
53
|
+
assert_equal "<couple>Frank & Mary</couple>\n", body
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'renders .builder files in views path' do
|
57
|
+
builder_app {
|
58
|
+
@name = "Blue"
|
59
|
+
builder :hello
|
60
|
+
}
|
61
|
+
assert ok?
|
62
|
+
assert_equal %(<exclaim>You're my boy, Blue!</exclaim>\n), body
|
63
|
+
end
|
64
|
+
|
65
|
+
it "renders with inline layouts" do
|
66
|
+
mock_app {
|
67
|
+
layout do
|
68
|
+
%(xml.layout { xml << yield })
|
69
|
+
end
|
70
|
+
get('/') { builder %(xml.em 'Hello World') }
|
71
|
+
}
|
72
|
+
get '/'
|
73
|
+
assert ok?
|
74
|
+
assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
|
75
|
+
end
|
76
|
+
|
77
|
+
it "renders with file layouts" do
|
78
|
+
builder_app {
|
79
|
+
builder %(xml.em 'Hello World'), :layout => :layout2
|
80
|
+
}
|
81
|
+
assert ok?
|
82
|
+
assert_equal "<layout>\n<em>Hello World</em>\n</layout>\n", body
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises error if template not found" do
|
86
|
+
mock_app {
|
87
|
+
get('/') { builder :no_such_template }
|
88
|
+
}
|
89
|
+
assert_raise(Errno::ENOENT) { get('/') }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
rescue LoadError
|
94
|
+
warn "#{$!.to_s}: skipping builder tests"
|
95
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'coffee-script'
|
5
|
+
require 'execjs'
|
6
|
+
|
7
|
+
begin
|
8
|
+
ExecJS.compile '1'
|
9
|
+
rescue Exception
|
10
|
+
raise LoadError, 'unable to execute JavaScript'
|
11
|
+
end
|
12
|
+
|
13
|
+
class CoffeeTest < Test::Unit::TestCase
|
14
|
+
def coffee_app(options = {}, &block)
|
15
|
+
mock_app {
|
16
|
+
set :views, File.dirname(__FILE__) + '/views'
|
17
|
+
set(options)
|
18
|
+
get '/', &block
|
19
|
+
}
|
20
|
+
get '/'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'renders inline Coffee strings' do
|
24
|
+
coffee_app { coffee "alert 'Aye!'\n" }
|
25
|
+
assert ok?
|
26
|
+
assert body.include?("alert('Aye!');")
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'defaults content type to javascript' do
|
30
|
+
coffee_app { coffee "alert 'Aye!'\n" }
|
31
|
+
assert ok?
|
32
|
+
assert_equal "application/javascript;charset=utf-8", response['Content-Type']
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'defaults allows setting content type per route' do
|
36
|
+
coffee_app do
|
37
|
+
content_type :html
|
38
|
+
coffee "alert 'Aye!'\n"
|
39
|
+
end
|
40
|
+
assert ok?
|
41
|
+
assert_equal "text/html;charset=utf-8", response['Content-Type']
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'defaults allows setting content type globally' do
|
45
|
+
coffee_app(:coffee => { :content_type => 'html' }) do
|
46
|
+
coffee "alert 'Aye!'\n"
|
47
|
+
end
|
48
|
+
assert ok?
|
49
|
+
assert_equal "text/html;charset=utf-8", response['Content-Type']
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'renders .coffee files in views path' do
|
53
|
+
coffee_app { coffee :hello }
|
54
|
+
assert ok?
|
55
|
+
assert_include body, "alert(\"Aye!\");"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'ignores the layout option' do
|
59
|
+
coffee_app { coffee :hello, :layout => :layout2 }
|
60
|
+
assert ok?
|
61
|
+
assert_include body, "alert(\"Aye!\");"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "raises error if template not found" do
|
65
|
+
mock_app {
|
66
|
+
get('/') { coffee :no_such_template }
|
67
|
+
}
|
68
|
+
assert_raise(Errno::ENOENT) { get('/') }
|
69
|
+
end
|
70
|
+
|
71
|
+
it "passes coffee options to the coffee engine" do
|
72
|
+
coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true }
|
73
|
+
assert ok?
|
74
|
+
assert_body "alert('Aye!');"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "passes default coffee options to the coffee engine" do
|
78
|
+
mock_app do
|
79
|
+
set :coffee, :no_wrap => true # default coffee style is :nested
|
80
|
+
get '/' do
|
81
|
+
coffee "alert 'Aye!'\n"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
get '/'
|
85
|
+
assert ok?
|
86
|
+
assert_body "alert('Aye!');"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
rescue LoadError
|
91
|
+
warn "#{$!.to_s}: skipping coffee tests"
|
92
|
+
end
|