jellyfish 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg
2
+ *.rbc
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 functionality, again
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
- ### Different HTTP status
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 in this case'
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
- "30\u{2103}\n"
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
@@ -0,0 +1,4 @@
1
+
2
+ Rainbows! do
3
+ use :FiberSpawn
4
+ end
data/example/server.sh ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ rainbows -Ilib -c example/rainbows.rb example/config.ru
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.3.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-13"
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
- controller = Controller.new(self.class.routes)
138
- controller.call(env)
139
+ ctrl = controller.new(self.class.routes)
140
+ ctrl.call(env)
139
141
  rescue NotFound => e # forward
140
142
  if app
141
- protect(controller, env){ app.call(env) }
143
+ protect(ctrl, env){ app.call(env) }
142
144
  else
143
- handle(controller, e)
145
+ handle(ctrl, e)
144
146
  end
145
147
  rescue Exception => e
146
- handle(controller, e, env[RACK_ERRORS])
148
+ handle(ctrl, e, env[RACK_ERRORS])
147
149
  end
148
150
 
149
- def protect controller, env
151
+ def protect ctrl, env
150
152
  yield
151
153
  rescue Exception => e
152
- handle(controller, e, env[RACK_ERRORS])
154
+ handle(ctrl, e, env[RACK_ERRORS])
153
155
  end
154
156
 
155
157
  private
156
- def handle controller, e, stderr=nil
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
- controller.block_call(e, handler)
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(controller, InternalError.new)
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
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Jellyfish
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
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.3.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-13 00:00:00.000000000 Z
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