jellyfish 0.6.0 → 0.8.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.
@@ -8,8 +8,13 @@ Bacon.summary_on_exit
8
8
  shared :jellyfish do
9
9
  %w[options get head post put delete patch].each do |method|
10
10
  instance_eval <<-RUBY
11
- def #{method} path='/', app=app
12
- app.call('PATH_INFO' => path, 'REQUEST_METHOD' => '#{method}'.upcase)
11
+ def #{method} path='/', app=app, env={}
12
+ File.open(File::NULL) do |input|
13
+ app.call({'PATH_INFO' => path ,
14
+ 'REQUEST_METHOD' => '#{method}'.upcase,
15
+ 'SCRIPT_NAME' => '' ,
16
+ 'rack.input' => input }.merge(env))
17
+ end
13
18
  end
14
19
  RUBY
15
20
  end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Jellyfish
3
- VERSION = '0.6.0'
3
+ VERSION = '0.8.0'
4
4
  end
data/task/gemgem.rb CHANGED
@@ -14,6 +14,7 @@ module Gemgem
14
14
 
15
15
  s.description = description.join
16
16
  s.summary = description.first
17
+ s.license = readme['LICENSE'].sub(/.+\n\n/, '').lines.first.strip
17
18
 
18
19
  s.rubygems_version = Gem::VERSION
19
20
  s.date = Time.now.strftime('%Y-%m-%d')
@@ -33,8 +34,8 @@ module Gemgem
33
34
  @readme ||=
34
35
  if path
35
36
  ps = "##{File.read(path)}".
36
- scan(/((#+)[^\n]+\n\n.+?(?=\n\n\2[^#\n]+\n))/m).map(&:first)
37
- ps.inject({'HEADER' => ps.first}){ |r, s, i|
37
+ scan(/((#+)[^\n]+\n\n.+?(?=(\n\n\2[^#\n]+\n)|\Z))/m).map(&:first)
38
+ ps.inject('HEADER' => ps.first){ |r, s, i|
38
39
  r[s[/\w+/]] = s
39
40
  r
40
41
  }
@@ -44,7 +45,7 @@ module Gemgem
44
45
  end
45
46
 
46
47
  def description
47
- @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines.to_a
48
+ @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines
48
49
  end
49
50
 
50
51
  def changes
@@ -104,9 +105,9 @@ module Gemgem
104
105
  end
105
106
 
106
107
  def split_lines ruby
107
- ruby.gsub(/(.+?)\[(.+?)\]/){ |s|
108
+ ruby.gsub(/(.+?)\s*=\s*\[(.+?)\]/){ |s|
108
109
  if $2.index(',')
109
- "#{$1}[\n #{$2.split(',').map(&:strip).join(",\n ")}]"
110
+ "#{$1} = [\n #{$2.split(',').map(&:strip).join(",\n ")}]"
110
111
  else
111
112
  s
112
113
  end
@@ -179,7 +180,7 @@ namespace :gem do
179
180
 
180
181
  desc 'Install gem'
181
182
  task :install => [:build] do
182
- sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}")
183
+ sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}.gem")
183
184
  end
184
185
 
185
186
  desc 'Build gem'
@@ -0,0 +1,110 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra base_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ should 'process requests with #call' do
9
+ app = Class.new{
10
+ include Jellyfish
11
+ get '/' do
12
+ 'Hello World'
13
+ end
14
+ }.new
15
+ app.respond_to?(:call).should.eq true
16
+ status, _, body = get('/', app)
17
+ status.should.eq 200
18
+ body .should.eq ['Hello World']
19
+ end
20
+
21
+ should 'not maintain state between requests' do
22
+ app = Class.new{
23
+ include Jellyfish
24
+ get '/state' do
25
+ @foo ||= 'new'
26
+ body = "Foo: #{@foo}"
27
+ @foo = 'discard'
28
+ body
29
+ end
30
+ }.new
31
+
32
+ 2.times do
33
+ status, _, body = get('/state', app)
34
+ status.should.eq 200
35
+ body .should.eq ['Foo: new']
36
+ end
37
+ end
38
+
39
+ describe 'Jellyfish as a Rack middleware' do
40
+ behaves_like :jellyfish
41
+
42
+ def app
43
+ @app ||= Class.new{
44
+ include Jellyfish
45
+ get '/' do
46
+ 'Hello from middleware'
47
+ end
48
+
49
+ get '/low-level-forward' do
50
+ status, headers, body = jellyfish.app.call(env)
51
+ self.status status
52
+ self.headers headers
53
+ body
54
+ end
55
+
56
+ get '/explicit-forward' do
57
+ headers_merge 'X-Middleware' => 'true'
58
+ status, headers, _ = jellyfish.app.call(env)
59
+ self.status status
60
+ self.headers headers
61
+ 'Hello after explicit forward'
62
+ end
63
+ }.new(inner_app)
64
+ end
65
+
66
+ def inner_app
67
+ @inner_app ||= lambda{ |env|
68
+ [210, {'X-Downstream' => 'true'}, ['Hello from downstream']]
69
+ }
70
+ end
71
+
72
+ should 'create a middleware that responds to #call with .new' do
73
+ app.respond_to?(:call).should.eq true
74
+ end
75
+
76
+ should 'expose the downstream app' do
77
+ app.app.object_id.should.eq inner_app.object_id
78
+ end
79
+
80
+ should 'intercept requests' do
81
+ status, _, body = get('/')
82
+ status.should.eq 200
83
+ body .should.eq ['Hello from middleware']
84
+ end
85
+
86
+ should 'forward requests downstream when no matching route found' do
87
+ status, headers, body = get('/missing')
88
+ status .should.eq 210
89
+ headers['X-Downstream'].should.eq 'true'
90
+ body .should.eq ['Hello from downstream']
91
+ end
92
+
93
+ should 'call the downstream app directly and return result' do
94
+ status, headers, body = get('/low-level-forward')
95
+ status .should.eq 210
96
+ headers['X-Downstream'].should.eq 'true'
97
+ body .should.eq ['Hello from downstream']
98
+ end
99
+
100
+ should 'forward the request and integrate the response' do
101
+ status, headers, body =
102
+ get('/explicit-forward', Rack::ContentLength.new(app))
103
+
104
+ status .should.eq 210
105
+ headers['X-Downstream'] .should.eq 'true'
106
+ headers['Content-Length'].should.eq '28'
107
+ body .should.eq ['Hello after explicit forward']
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,43 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra streaming_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ should 'return the concatinated body' do
9
+ app = Class.new{
10
+ include Jellyfish
11
+ get '/' do
12
+ Jellyfish::ChunkedBody.new{ |out|
13
+ out['Hello']
14
+ out[' ']
15
+ out['World!']
16
+ }
17
+ end
18
+ }.new
19
+ _, _, body = get('/', app)
20
+ body.to_a.join.should.eq 'Hello World!'
21
+ end
22
+
23
+ should 'postpone body generation' do
24
+ stream = Jellyfish::ChunkedBody.new{ |out|
25
+ 10.times{ |i| out[i] }
26
+ }
27
+
28
+ stream.each.with_index do |s, i|
29
+ s.should.eq i
30
+ end
31
+ end
32
+
33
+ should 'give access to route specific params' do
34
+ app = Class.new{
35
+ include Jellyfish
36
+ get(%r{/(?<name>\w+)}){ |m|
37
+ Jellyfish::ChunkedBody.new{ |o| o[m[:name]] }
38
+ }
39
+ }.new
40
+ _, _, body = get('/foo', app)
41
+ body.to_a.join.should.eq 'foo'
42
+ end
43
+ end
@@ -0,0 +1,145 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ describe 'Sinatra mapped_error_test.rb' do
5
+ behaves_like :jellyfish
6
+
7
+ exp = Class.new(RuntimeError)
8
+
9
+ should 'invoke handlers registered with handle when raised' do
10
+ app = Class.new{
11
+ include Jellyfish
12
+ handle(exp){ 'Foo!' }
13
+ get '/' do
14
+ raise exp
15
+ end
16
+ }.new
17
+
18
+ status, _, body = get('/', app)
19
+ status.should.eq 200
20
+ body .should.eq ['Foo!']
21
+ end
22
+
23
+ should 'pass the exception object to the error handler' do
24
+ app = Class.new{
25
+ include Jellyfish
26
+ handle(exp){ |e| e.should.kind_of?(exp) }
27
+ get('/'){ raise exp }
28
+ }.new
29
+ get('/', app)
30
+ end
31
+
32
+ should 'use the Exception handler if no matching handler found' do
33
+ app = Class.new{
34
+ include Jellyfish
35
+ handle(Exception){ 'Exception!' }
36
+ get('/'){ raise exp }
37
+ }.new
38
+
39
+ status, _, body = get('/', app)
40
+ status.should.eq 200
41
+ body .should.eq ['Exception!']
42
+ end
43
+
44
+ should 'favour subclass handler over superclass handler if available' do
45
+ app = Class.new{
46
+ include Jellyfish
47
+ handle(Exception) { 'Exception!' }
48
+ handle(RuntimeError){ 'RuntimeError!' }
49
+ get('/'){ raise exp }
50
+ }.new
51
+
52
+ status, _, body = get('/', app)
53
+ status.should.eq 200
54
+ body .should.eq ['RuntimeError!']
55
+
56
+ handlers = app.class.handlers
57
+ handlers.size.should.eq 3
58
+ handlers[exp].should.eq handlers[RuntimeError]
59
+ end
60
+
61
+ should 'pass the exception to the handler' do
62
+ app = Class.new{
63
+ include Jellyfish
64
+ handle(exp){ |e|
65
+ e.should.kind_of?(exp)
66
+ 'looks good'
67
+ }
68
+ get('/'){ raise exp }
69
+ }.new
70
+
71
+ _, _, body = get('/', app)
72
+ body.should.eq ['looks good']
73
+ end
74
+
75
+ should 'raise errors from the app when handle_exceptions is false' do
76
+ app = Class.new{
77
+ include Jellyfish
78
+ handle_exceptions false
79
+ get('/'){ raise exp }
80
+ }.new
81
+
82
+ lambda{ get('/', app) }.should.raise(exp)
83
+ end
84
+
85
+ should 'call error handlers even when handle_exceptions is false' do
86
+ app = Class.new{
87
+ include Jellyfish
88
+ handle_exceptions false
89
+ handle(exp){ "she's there." }
90
+ get('/'){ raise exp }
91
+ }.new
92
+
93
+ _, _, body = get('/', app)
94
+ body.should.eq ["she's there."]
95
+ end
96
+
97
+ should 'never raises Jellyfish::NotFound beyond the application' do
98
+ app = Class.new{
99
+ include Jellyfish
100
+ get('/'){ raise Jellyfish::NotFound }
101
+ }.new
102
+
103
+ status, _, _ = get('/', app)
104
+ status.should.eq 404
105
+ end
106
+
107
+ should 'cascade for subclasses of Jellyfish::NotFound' do
108
+ e = Class.new(Jellyfish::NotFound)
109
+ app = Class.new{
110
+ include Jellyfish
111
+ get('/'){ raise e }
112
+ }.new
113
+
114
+ status, _, body = get('/', app)
115
+ status.should.eq 404
116
+ end
117
+
118
+ should 'inherit error mappings from base class' do
119
+ sup = Class.new{
120
+ include Jellyfish
121
+ handle(exp){ 'sup' }
122
+ }
123
+ app = Class.new(sup){
124
+ get('/'){ raise exp }
125
+ }.new
126
+
127
+ _, _, body = get('/', app)
128
+ body.should.eq ['sup']
129
+ end
130
+
131
+ should 'override error mappings in base class' do
132
+ sup = Class.new{
133
+ include Jellyfish
134
+ handle(exp){ 'sup' }
135
+ }
136
+ app = Class.new(sup){
137
+ handle(exp){ 'sub' }
138
+ get('/'){ raise exp }
139
+ }.new
140
+
141
+
142
+ _, _, body = get('/', app)
143
+ body.should.eq ['sub']
144
+ end
145
+ end
@@ -0,0 +1,217 @@
1
+
2
+ require 'jellyfish/test'
3
+
4
+ # stolen from sinatra
5
+ describe 'Sinatra filter_test.rb' do
6
+ behaves_like :jellyfish
7
+
8
+ def new_app base=Object, &block
9
+ Class.new(base){
10
+ include Jellyfish
11
+ controller_include(Jellyfish::MultiActions)
12
+ instance_eval(&block)
13
+ }.new
14
+ end
15
+
16
+ should 'executes filters in the order defined' do
17
+ count = 0
18
+ app = new_app{
19
+ get { count.should.eq 0; count = 1 }
20
+ get { count.should.eq 1; count = 2 }
21
+ get('/'){ 'Hello World' }
22
+ }
23
+
24
+ status, _, body = get('/', app)
25
+ status.should.eq 200
26
+ count .should.eq 2
27
+ body .should.eq ['Hello World']
28
+ end
29
+
30
+ should 'modify env' do
31
+ app = new_app{
32
+ get{ env['BOO'] = 'MOO' }
33
+ get('/foo'){ env['BOO'] }
34
+ }
35
+
36
+ status, _, body = get('/foo', app)
37
+ status.should.eq 200
38
+ body .should.eq ['MOO']
39
+ end
40
+
41
+ should 'modify instance variables available to routes' do
42
+ app = new_app{
43
+ get{ @foo = 'bar' }
44
+ get('/foo') { @foo }
45
+ }
46
+
47
+ status, _, body = get('/foo', app)
48
+ status.should.eq 200
49
+ body .should.eq ['bar']
50
+ end
51
+
52
+ should 'allows redirects' do
53
+ app = new_app{
54
+ get{ found '/bar' }
55
+ get('/foo') do
56
+ fail 'before block should have halted processing'
57
+ 'ORLY?!'
58
+ end
59
+ }
60
+
61
+ status, headers, body = get('/foo', app)
62
+ status .should.eq 302
63
+ headers['Location'].should.eq '/bar'
64
+ body.join .should =~ %r{<h1>Jellyfish found: /bar</h1>}
65
+ end
66
+
67
+ should 'not modify the response with its return value' do
68
+ app = new_app{
69
+ get{ 'Hello World!' }
70
+ get '/foo' do
71
+ body.should.eq nil
72
+ 'cool'
73
+ end
74
+ }
75
+
76
+ status, _, body = get('/foo', app)
77
+ status.should.eq 200
78
+ body .should.eq ['cool']
79
+ end
80
+
81
+ should 'modify the response with halt' do
82
+ app = new_app{
83
+ get('/foo'){ halt [302, {}, ['Hi']] }
84
+ get('/foo'){ 'should not happen' }
85
+ get('/bar'){ status 402; body 'Ho'; halt }
86
+ get('/bar'){ 'should not happen' }
87
+ }
88
+
89
+ get('/foo', app).should.eq [302, {}, ['Hi']]
90
+ get('/bar', app).should.eq [402, {}, ['Ho']]
91
+ end
92
+
93
+ should 'give you access to params' do
94
+ app = new_app{
95
+ get{ @foo = Rack::Request.new(env).params['foo'] }
96
+ get('/foo'){ @foo.reverse }
97
+ }
98
+
99
+ status, _, body = get('/foo', app, 'QUERY_STRING' => 'foo=cool')
100
+ status.should.eq 200
101
+ body .should.eq ['looc']
102
+ end
103
+
104
+ should 'run filters defined in superclasses' do
105
+ sup = new_app{ get{ @foo = 'hello from superclass' } }.class
106
+ app = new_app(sup){ get('/foo'){ @foo } }
107
+
108
+ _, _, body = get('/foo', app)
109
+ body.should.eq ['hello from superclass']
110
+
111
+ sup .routes['get'].size.should.eq 1
112
+ app.class.routes['get'].size.should.eq 2
113
+ end
114
+
115
+ should 'take an optional route pattern' do
116
+ ran_filter = false
117
+ app = new_app{
118
+ get(%r{^/b}){ ran_filter = true }
119
+ get('/foo') {}
120
+ get('/bar') {}
121
+ }
122
+ get('/foo', app)
123
+ ran_filter.should.eq false
124
+ get('/bar', app)
125
+ ran_filter.should.eq true
126
+ end
127
+
128
+ should 'generate block arguments from route pattern' do
129
+ subpath = nil
130
+ app = new_app{
131
+ get(%r{^/foo/(\w+)}){ |m| subpath = m[1] }
132
+ }
133
+ get('/foo/bar', app)
134
+ subpath.should.eq 'bar'
135
+ end
136
+
137
+ should 'execute before and after filters in correct order' do
138
+ invoked = 0
139
+ app = new_app{
140
+ get { invoked = 2 }
141
+ get('/'){ invoked += 2; body 'hello' }
142
+ get { invoked *= 2 }
143
+ }
144
+
145
+ status, _, body = get('/', app)
146
+ status .should.eq 200
147
+ body .should.eq ['hello']
148
+ invoked.should.eq 8
149
+ end
150
+
151
+ should 'execute filters in the order defined' do
152
+ count = 0
153
+ app = new_app{
154
+ get('/'){ body 'Hello World' }
155
+ get{
156
+ count.should.eq 0
157
+ count = 1
158
+ }
159
+ get{
160
+ count.should.eq 1
161
+ count = 2
162
+ }
163
+ }
164
+
165
+ status, _, body = get('/', app)
166
+ status.should.eq 200
167
+ count .should.eq 2
168
+ body .should.eq ['Hello World']
169
+ end
170
+
171
+ should 'allow redirects' do
172
+ app = new_app{
173
+ get('/foo'){ 'ORLY' }
174
+ get { found '/bar' }
175
+ }
176
+
177
+ status, headers, body = get('/foo', app)
178
+ status .should.eq 302
179
+ headers['Location'].should.eq '/bar'
180
+ body.join .should =~ %r{<h1>Jellyfish found: /bar</h1>}
181
+ end
182
+
183
+ should 'not modify the response with its return value' do
184
+ app = new_app{
185
+ get('/foo'){ body 'cool' }
186
+ get { 'Hello World!' }
187
+ }
188
+
189
+ status, _, body = get('/foo', app)
190
+ status.should.eq 200
191
+ body .should.eq ['cool']
192
+ end
193
+
194
+ should 'modify the response with halt' do
195
+ app = new_app{
196
+ get('/foo'){ 'should not be returned' }
197
+ get{ halt [302, {}, ['Hi']] }
198
+ }
199
+
200
+ status, _, body = get('/foo', app)
201
+ status.should.eq 302
202
+ body .should.eq ['Hi']
203
+ end
204
+
205
+ should 'take an optional route pattern' do
206
+ ran_filter = false
207
+ app = new_app{
208
+ get('/foo') {}
209
+ get('/bar') {}
210
+ get(%r{^/b}){ ran_filter = true }
211
+ }
212
+ get('/foo', app)
213
+ ran_filter.should.eq false
214
+ get('/bar', app)
215
+ ran_filter.should.eq true
216
+ end
217
+ end