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 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