jellyfish 0.3.0 → 0.4.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.
- data/.gitignore +2 -0
- data/CHANGES.md +60 -0
- data/README.md +98 -3
- data/{config.ru → example/config.ru} +33 -3
- data/example/rainbows.rb +4 -0
- data/example/server.sh +3 -0
- data/jellyfish.gemspec +7 -3
- data/lib/jellyfish.rb +16 -14
- data/lib/jellyfish/sinatra.rb +37 -0
- data/lib/jellyfish/version.rb +1 -1
- metadata +7 -3
data/.gitignore
ADDED
data/CHANGES.md
CHANGED
@@ -1,5 +1,65 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
+
## Jellyfish 0.4.0 -- 2012-10-14
|
4
|
+
|
5
|
+
* Now you can define your own custom controller like:
|
6
|
+
|
7
|
+
``` ruby
|
8
|
+
require 'jellyfish'
|
9
|
+
class Heater
|
10
|
+
include Jellyfish
|
11
|
+
get '/status' do
|
12
|
+
temperature
|
13
|
+
end
|
14
|
+
|
15
|
+
def controller; Controller; end
|
16
|
+
class Controller < Jellyfish::Controller
|
17
|
+
def temperature
|
18
|
+
"30\u{2103}\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
use Rack::ContentLength
|
23
|
+
use Rack::ContentType, 'text/plain'
|
24
|
+
run Heater.new
|
25
|
+
```
|
26
|
+
|
27
|
+
* Now it's possible to use a custom matcher instead of regular expression:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
require 'jellyfish'
|
31
|
+
class Tank
|
32
|
+
include Jellyfish
|
33
|
+
class Matcher
|
34
|
+
def match path
|
35
|
+
path.reverse == 'match/'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
get Matcher.new do |match|
|
39
|
+
"#{match}\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
use Rack::ContentLength
|
43
|
+
use Rack::ContentType, 'text/plain'
|
44
|
+
run Tank.new
|
45
|
+
```
|
46
|
+
|
47
|
+
* Added a Sinatra flavor controller
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
require 'jellyfish'
|
51
|
+
class Tank
|
52
|
+
include Jellyfish
|
53
|
+
def controller; Jellyfish::Sinatra; end
|
54
|
+
get %r{^/(?<id>\d+)$} do
|
55
|
+
"Jelly ##{params[:id]}\n"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
use Rack::ContentLength
|
59
|
+
use Rack::ContentType, 'text/plain'
|
60
|
+
run Tank.new
|
61
|
+
```
|
62
|
+
|
3
63
|
## Jellyfish 0.3.0 -- 2012-10-13
|
4
64
|
|
5
65
|
* Birthday!
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ Rack applications or Rack middlewares. Under 200 lines of code.
|
|
16
16
|
## DESIGN:
|
17
17
|
|
18
18
|
* Learn the HTTP way instead of using some pointless helpers
|
19
|
-
* Learn the Rack way instead of wrapping Rack
|
19
|
+
* Learn the Rack way instead of wrapping Rack functionalities, again
|
20
20
|
* Learn regular expression for routes instead of custom syntax
|
21
21
|
* Embrace simplicity over convenience
|
22
22
|
* Don't make things complicated only for _some_ convenience, but
|
@@ -28,8 +28,10 @@ Rack applications or Rack middlewares. Under 200 lines of code.
|
|
28
28
|
* Simple
|
29
29
|
* No templates
|
30
30
|
* No ORM
|
31
|
+
* No `dup` in `call`
|
31
32
|
* Regular expression routes, e.g. `get %r{/(\d+)}`
|
32
33
|
* String routes, e.g. `get '/'`
|
34
|
+
* Custom routes, e.g. `get Matcher.new`
|
33
35
|
* Build for either Rack applications or Rack middlewares
|
34
36
|
|
35
37
|
## WHY?
|
@@ -57,6 +59,7 @@ class Tank
|
|
57
59
|
end
|
58
60
|
end
|
59
61
|
use Rack::ContentLength
|
62
|
+
use Rack::ContentType, 'text/plain'
|
60
63
|
run Tank.new
|
61
64
|
```
|
62
65
|
|
@@ -71,10 +74,31 @@ class Tank
|
|
71
74
|
end
|
72
75
|
end
|
73
76
|
use Rack::ContentLength
|
77
|
+
use Rack::ContentType, 'text/plain'
|
74
78
|
run Tank.new
|
75
79
|
```
|
76
80
|
|
77
|
-
###
|
81
|
+
### Custom matcher routes
|
82
|
+
|
83
|
+
``` ruby
|
84
|
+
require 'jellyfish'
|
85
|
+
class Tank
|
86
|
+
include Jellyfish
|
87
|
+
class Matcher
|
88
|
+
def match path
|
89
|
+
path.reverse == 'match/'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
get Matcher.new do |match|
|
93
|
+
"#{match}\n"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
use Rack::ContentLength
|
97
|
+
use Rack::ContentType, 'text/plain'
|
98
|
+
run Tank.new
|
99
|
+
```
|
100
|
+
|
101
|
+
### Different HTTP status and custom headers
|
78
102
|
|
79
103
|
``` ruby
|
80
104
|
require 'jellyfish'
|
@@ -85,10 +109,11 @@ class Tank
|
|
85
109
|
headers_merge 'X-Jellyfish-Mana' => '200'
|
86
110
|
body "Jellyfish 100/200\n"
|
87
111
|
status 201
|
88
|
-
'return is ignored
|
112
|
+
'return is ignored if body has already been set'
|
89
113
|
end
|
90
114
|
end
|
91
115
|
use Rack::ContentLength
|
116
|
+
use Rack::ContentType, 'text/plain'
|
92
117
|
run Tank.new
|
93
118
|
```
|
94
119
|
|
@@ -103,6 +128,7 @@ class Tank
|
|
103
128
|
end
|
104
129
|
end
|
105
130
|
use Rack::ContentLength
|
131
|
+
use Rack::ContentType, 'text/plain'
|
106
132
|
run Tank.new
|
107
133
|
```
|
108
134
|
|
@@ -117,6 +143,7 @@ class Tank
|
|
117
143
|
end
|
118
144
|
end
|
119
145
|
use Rack::ContentLength
|
146
|
+
use Rack::ContentType, 'text/plain'
|
120
147
|
run Tank.new
|
121
148
|
```
|
122
149
|
|
@@ -135,6 +162,49 @@ class Tank
|
|
135
162
|
end
|
136
163
|
end
|
137
164
|
use Rack::ContentLength
|
165
|
+
use Rack::ContentType, 'text/plain'
|
166
|
+
run Tank.new
|
167
|
+
```
|
168
|
+
|
169
|
+
### Custom controller
|
170
|
+
|
171
|
+
``` ruby
|
172
|
+
require 'jellyfish'
|
173
|
+
class Heater
|
174
|
+
include Jellyfish
|
175
|
+
get '/status' do
|
176
|
+
temperature
|
177
|
+
end
|
178
|
+
|
179
|
+
def controller; Controller; end
|
180
|
+
class Controller < Jellyfish::Controller
|
181
|
+
def temperature
|
182
|
+
"30\u{2103}\n"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
use Rack::ContentLength
|
187
|
+
use Rack::ContentType, 'text/plain'
|
188
|
+
run Heater.new
|
189
|
+
```
|
190
|
+
|
191
|
+
### Sinatra flavor controller
|
192
|
+
|
193
|
+
Currently support:
|
194
|
+
|
195
|
+
* Indifferent params
|
196
|
+
|
197
|
+
``` ruby
|
198
|
+
require 'jellyfish'
|
199
|
+
class Tank
|
200
|
+
include Jellyfish
|
201
|
+
def controller; Jellyfish::Sinatra; end
|
202
|
+
get %r{^/(?<id>\d+)$} do
|
203
|
+
"Jelly ##{params[:id]}\n"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
use Rack::ContentLength
|
207
|
+
use Rack::ContentType, 'text/plain'
|
138
208
|
run Tank.new
|
139
209
|
```
|
140
210
|
|
@@ -157,6 +227,7 @@ class Tank
|
|
157
227
|
end
|
158
228
|
|
159
229
|
use Rack::ContentLength
|
230
|
+
use Rack::ContentType, 'text/plain'
|
160
231
|
use Heater
|
161
232
|
run Tank.new
|
162
233
|
```
|
@@ -181,6 +252,7 @@ end
|
|
181
252
|
|
182
253
|
HugeTank = Rack::Builder.new do
|
183
254
|
use Rack::ContentLength
|
255
|
+
use Rack::ContentType, 'text/plain'
|
184
256
|
use Heater
|
185
257
|
run Tank.new
|
186
258
|
end
|
@@ -209,10 +281,33 @@ class Tank
|
|
209
281
|
end
|
210
282
|
|
211
283
|
use Rack::ContentLength
|
284
|
+
use Rack::ContentType, 'text/plain'
|
212
285
|
use Protector
|
213
286
|
run Tank.new
|
214
287
|
```
|
215
288
|
|
289
|
+
### Chunked transfer encoding (streaming)
|
290
|
+
|
291
|
+
You would need a proper server setup.
|
292
|
+
Here's an example with Rainbows and fibers:
|
293
|
+
|
294
|
+
``` ruby
|
295
|
+
class Tank
|
296
|
+
include Jellyfish
|
297
|
+
class Body
|
298
|
+
def each
|
299
|
+
(0..4).each{ |i| yield "#{i}\n"; Rainbows.sleep(0.1) }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
get '/chunked' do
|
303
|
+
Body.new
|
304
|
+
end
|
305
|
+
end
|
306
|
+
use Rack::Chunked
|
307
|
+
use Rack::ContentType, 'text/plain'
|
308
|
+
run Tank.new
|
309
|
+
```
|
310
|
+
|
216
311
|
## CONTRIBUTORS:
|
217
312
|
|
218
313
|
* Lin Jen-Shin (@godfat)
|
@@ -18,8 +18,7 @@ class Tank
|
|
18
18
|
headers_merge 'X-Jellyfish-Mana' => '200'
|
19
19
|
body "Jellyfish 100/200\n"
|
20
20
|
status 201
|
21
|
-
|
22
|
-
'return is ignored in this case'
|
21
|
+
'return is ignored if body has already been set'
|
23
22
|
end
|
24
23
|
|
25
24
|
get '/env' do
|
@@ -42,17 +41,48 @@ class Tank
|
|
42
41
|
get '/yell' do
|
43
42
|
yell
|
44
43
|
end
|
44
|
+
|
45
|
+
class Matcher
|
46
|
+
def match path
|
47
|
+
path.reverse == 'match/'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
get Matcher.new do |match|
|
51
|
+
"#{match}\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
class Body
|
55
|
+
def each
|
56
|
+
if Object.const_defined?(:Rainbows)
|
57
|
+
(0..4).each{ |i| yield "#{i}\n"; Rainbows.sleep(0.1) }
|
58
|
+
else
|
59
|
+
yield "You need Rainbows + FiberSpawn (or so) for this\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
get '/chunked' do
|
64
|
+
Body.new
|
65
|
+
end
|
45
66
|
end
|
46
67
|
|
47
68
|
class Heater
|
48
69
|
include Jellyfish
|
49
70
|
get '/status' do
|
50
|
-
|
71
|
+
temperature
|
72
|
+
end
|
73
|
+
|
74
|
+
def controller; Controller; end
|
75
|
+
class Controller < Jellyfish::Controller
|
76
|
+
def temperature
|
77
|
+
"30\u{2103}\n"
|
78
|
+
end
|
51
79
|
end
|
52
80
|
end
|
53
81
|
|
54
82
|
HugeTank = Rack::Builder.new do
|
83
|
+
use Rack::Chunked
|
55
84
|
use Rack::ContentLength
|
85
|
+
use Rack::ContentType, 'text/plain'
|
56
86
|
use Heater
|
57
87
|
run Tank.new
|
58
88
|
end
|
data/example/rainbows.rb
ADDED
data/example/server.sh
ADDED
data/jellyfish.gemspec
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "jellyfish"
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.4.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-14"
|
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 = [
|
13
|
+
".gitignore",
|
13
14
|
".gitmodules",
|
14
15
|
".travis.yml",
|
15
16
|
"CHANGES.md",
|
@@ -17,12 +18,15 @@ Gem::Specification.new do |s|
|
|
17
18
|
"README.md",
|
18
19
|
"Rakefile",
|
19
20
|
"TODO.md",
|
20
|
-
"config.ru",
|
21
|
+
"example/config.ru",
|
22
|
+
"example/rainbows.rb",
|
23
|
+
"example/server.sh",
|
21
24
|
"jellyfish.gemspec",
|
22
25
|
"lib/jellyfish.rb",
|
23
26
|
"lib/jellyfish/public/302.html",
|
24
27
|
"lib/jellyfish/public/404.html",
|
25
28
|
"lib/jellyfish/public/500.html",
|
29
|
+
"lib/jellyfish/sinatra.rb",
|
26
30
|
"lib/jellyfish/version.rb",
|
27
31
|
"task/.gitignore",
|
28
32
|
"task/gemgem.rb"]
|
data/lib/jellyfish.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
module Jellyfish
|
3
3
|
autoload :VERSION, 'jellyfish/version'
|
4
|
+
autoload :Sinatra, 'jellyfish/sinatra'
|
4
5
|
|
5
6
|
REQUEST_METHOD = 'REQUEST_METHOD'
|
6
7
|
PATH_INFO = 'PATH_INFO'
|
@@ -19,8 +20,8 @@ module Jellyfish
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
22
|
-
class NotFound < Respond; def status; 404; end; end
|
23
23
|
class InternalError < Respond; def status; 500; end; end
|
24
|
+
class NotFound < Respond; def status; 404; end; end
|
24
25
|
class Found < Respond
|
25
26
|
attr_reader :url
|
26
27
|
def initialize url; @url = url ; end
|
@@ -94,11 +95,11 @@ module Jellyfish
|
|
94
95
|
def dispatch
|
95
96
|
actions.find{ |(route, block)|
|
96
97
|
case route
|
97
|
-
when Regexp
|
98
|
-
match = route.match(path_info)
|
99
|
-
break match, block if match
|
100
98
|
when String
|
101
99
|
break route, block if route == path_info
|
100
|
+
else#Regexp, using else allows you to use custom matcher
|
101
|
+
match = route.match(path_info)
|
102
|
+
break match, block if match
|
102
103
|
end
|
103
104
|
} || raise(NotFound.new)
|
104
105
|
end
|
@@ -132,39 +133,40 @@ module Jellyfish
|
|
132
133
|
# -----------------------------------------------------------------
|
133
134
|
|
134
135
|
def initialize app=nil; @app = app; end
|
136
|
+
def controller ; Controller; end
|
135
137
|
|
136
138
|
def call env
|
137
|
-
|
138
|
-
|
139
|
+
ctrl = controller.new(self.class.routes)
|
140
|
+
ctrl.call(env)
|
139
141
|
rescue NotFound => e # forward
|
140
142
|
if app
|
141
|
-
protect(
|
143
|
+
protect(ctrl, env){ app.call(env) }
|
142
144
|
else
|
143
|
-
handle(
|
145
|
+
handle(ctrl, e)
|
144
146
|
end
|
145
147
|
rescue Exception => e
|
146
|
-
handle(
|
148
|
+
handle(ctrl, e, env[RACK_ERRORS])
|
147
149
|
end
|
148
150
|
|
149
|
-
def protect
|
151
|
+
def protect ctrl, env
|
150
152
|
yield
|
151
153
|
rescue Exception => e
|
152
|
-
handle(
|
154
|
+
handle(ctrl, e, env[RACK_ERRORS])
|
153
155
|
end
|
154
156
|
|
155
157
|
private
|
156
|
-
def handle
|
158
|
+
def handle ctrl, e, stderr=nil
|
157
159
|
raise e unless self.class.handle_exceptions
|
158
160
|
handler = self.class.handlers.find{ |klass, block|
|
159
161
|
break block if e.kind_of?(klass)
|
160
162
|
}
|
161
163
|
if handler
|
162
|
-
|
164
|
+
ctrl.block_call(e, handler)
|
163
165
|
elsif e.kind_of?(Respond) # InternalError ends up here if no handlers
|
164
166
|
[e.status, e.headers, e.body]
|
165
167
|
else # fallback and see if there's any InternalError handler
|
166
168
|
log_error(e, stderr)
|
167
|
-
handle(
|
169
|
+
handle(ctrl, InternalError.new)
|
168
170
|
end
|
169
171
|
end
|
170
172
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
require 'jellyfish'
|
3
|
+
require 'rack/request'
|
4
|
+
|
5
|
+
module Jellyfish
|
6
|
+
class Sinatra < Controller
|
7
|
+
attr_reader :request, :params
|
8
|
+
def block_call argument, block
|
9
|
+
@request = Rack::Request.new(env)
|
10
|
+
@params = indifferent_params(if argument.kind_of?(MatchData)
|
11
|
+
then # merge captured data from matcher into params as sinatra
|
12
|
+
request.params.merge(Hash[argument.names.zip(argument.captures)])
|
13
|
+
else
|
14
|
+
request.params
|
15
|
+
end)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
# stolen from sinatra
|
22
|
+
# Enable string or symbol key access to the nested params hash.
|
23
|
+
def indifferent_params(params)
|
24
|
+
params = indifferent_hash.merge(params)
|
25
|
+
params.each do |key, value|
|
26
|
+
next unless value.is_a?(Hash)
|
27
|
+
params[key] = indifferent_params(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# stolen from sinatra
|
32
|
+
# Creates a Hash with indifferent access.
|
33
|
+
def indifferent_hash
|
34
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/jellyfish/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jellyfish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -36,6 +36,7 @@ executables: []
|
|
36
36
|
extensions: []
|
37
37
|
extra_rdoc_files: []
|
38
38
|
files:
|
39
|
+
- .gitignore
|
39
40
|
- .gitmodules
|
40
41
|
- .travis.yml
|
41
42
|
- CHANGES.md
|
@@ -43,12 +44,15 @@ files:
|
|
43
44
|
- README.md
|
44
45
|
- Rakefile
|
45
46
|
- TODO.md
|
46
|
-
- config.ru
|
47
|
+
- example/config.ru
|
48
|
+
- example/rainbows.rb
|
49
|
+
- example/server.sh
|
47
50
|
- jellyfish.gemspec
|
48
51
|
- lib/jellyfish.rb
|
49
52
|
- lib/jellyfish/public/302.html
|
50
53
|
- lib/jellyfish/public/404.html
|
51
54
|
- lib/jellyfish/public/500.html
|
55
|
+
- lib/jellyfish/sinatra.rb
|
52
56
|
- lib/jellyfish/version.rb
|
53
57
|
- task/.gitignore
|
54
58
|
- task/gemgem.rb
|