cuba 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Michel Martens and Damian Janowski
1
+ Copyright (c) 2010 Michel Martens, Damian Janowski and Cyril David
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
@@ -8,13 +8,17 @@ Rum based microframework for web development.
8
8
  Description
9
9
  -----------
10
10
 
11
- Cuba is a light wrapper around [Rum](http://github.com/chneukirchen/rum),
12
- a tiny but powerful mapper for [Rack](http://github.com/chneukirchen/rack)
13
- applications.
11
+ Cuba is a microframework for web development heavily inspired by [Rum][rum],
12
+ a tiny but powerful mapper for [Rack][rack] applications.
14
13
 
15
- It integrates many templates via [Tilt](http://github.com/rtomayko/tilt),
16
- and testing via [Cutest](http://github.com/djanowski/cutest) and
17
- [Capybara](http://github.com/jnicklas/capybara).
14
+ It integrates many templates via [Tilt][tilt], and testing via
15
+ [Cutest][cutest] and [Capybara][capybara].
16
+
17
+ [rum]: http://github.com/chneukirchen/rum
18
+ [rack]: http://github.com/chneukirchen/rack
19
+ [tilt]: http://github.com/rtomayko/tilt
20
+ [cutest]: http://github.com/djanowski/cutest
21
+ [capybara]: http://github.com/jnicklas/capybara
18
22
 
19
23
  Usage
20
24
  -----
@@ -32,7 +36,7 @@ Here's a simple application:
32
36
  res.write "Hello world!"
33
37
  end
34
38
 
35
- on default do
39
+ on true do
36
40
  res.redirect "/hello"
37
41
  end
38
42
  end
@@ -56,40 +60,77 @@ To run it, you can create a `config.ru`:
56
60
 
57
61
  run Cuba
58
62
 
63
+ Here's an example showcasing how different matchers work:
64
+
65
+ require "cuba"
66
+
67
+ Cuba.use Rack::Session::Cookie
68
+
69
+ Cuba.define do
70
+
71
+ # /about
72
+ on path("about") do
73
+ res.write "About"
74
+ end
75
+
76
+ # /styles/basic.css
77
+ on path("styles"), extension("css") do |file|
78
+ res.write "Filename: #{file}" #=> "Filename: basic"
79
+ end
80
+
81
+ # /post/2011/02/16/hello
82
+ on path("post"), number, number, number, segment do |y, m, d, slug|
83
+ res.write "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
84
+ end
85
+
86
+ # /username/foobar
87
+ on path("username"), segment do |username|
88
+
89
+ user = User.find_by_username(username) # username == "foobar"
90
+
91
+ # /username/foobar/posts
92
+ on path("posts") do
93
+
94
+ # You can access `user` here, because the `on` blocks
95
+ # are closures.
96
+ res.write "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
97
+ end
98
+
99
+ # /username/foobar/following
100
+ on path("following") do
101
+ res.write user.following.size #=> "1301"
102
+ end
103
+ end
104
+
105
+ # /search?q=barbaz
106
+ on path("search"), param("q") do |query|
107
+ res.write "Searched for #{query}" #=> "Searched for barbaz"
108
+ end
109
+
110
+ on post
111
+ on path("login")
112
+
113
+ # POST /login, user: foo, pass: baz
114
+ on param("user"), param("pass") do |user, pass|
115
+ res.write "#{user}:#{pass}" #=> "foo:baz"
116
+ end
117
+
118
+ # If the params `user` and `pass` are not provided, this block will
119
+ # get executed.
120
+ on true do
121
+ res.write "You need to provide user and pass!"
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+
59
128
  That's it, you can now run `rackup` and enjoy what you have just created.
60
129
 
61
- For more information about what you can do, check [Rum's
62
- documentation](http://github.com/chneukirchen/rum). To see how you can test it,
63
- check the documentation for [Cutest](http://github.com/djanowski/cutest) and
64
- [Capybara](http://github.com/jnicklas/capybara).
130
+ To read more about testing, check the documentation for [Cutest][cutest] and
131
+ [Capybara][capybara].
65
132
 
66
133
  Installation
67
134
  ------------
68
135
 
69
136
  $ gem install cuba
70
-
71
- License
72
- -------
73
-
74
- Copyright (c) 2010 Michel Martens and Damian Janowski
75
-
76
- Permission is hereby granted, free of charge, to any person
77
- obtaining a copy of this software and associated documentation
78
- files (the "Software"), to deal in the Software without
79
- restriction, including without limitation the rights to use,
80
- copy, modify, merge, publish, distribute, sublicense, and/or sell
81
- copies of the Software, and to permit persons to whom the
82
- Software is furnished to do so, subject to the following
83
- conditions:
84
-
85
- The above copyright notice and this permission notice shall be
86
- included in all copies or substantial portions of the Software.
87
-
88
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
90
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
91
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
92
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
94
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
95
- OTHER DEALINGS IN THE SOFTWARE.
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "cuba"
3
- s.version = "0.3.0"
3
+ s.version = "1.0.0"
4
4
  s.summary = "Rum based microframework for web applications."
5
5
  s.description = "Cuba is a light wrapper for Rum, a microframework for Rack applications."
6
6
  s.authors = ["Michel Martens"]
7
7
  s.email = ["michel@soveran.com"]
8
8
  s.homepage = "http://github.com/soveran/cuba"
9
- s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/cuba/ron.rb", "lib/cuba/rum.rb", "lib/cuba/test.rb", "lib/cuba/version.rb", "lib/cuba.rb", "cuba.gemspec", "test/integration.rb", "test/webrat.log"]
9
+ s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/cuba/ron.rb", "lib/cuba/test.rb", "lib/cuba/version.rb", "lib/cuba.rb", "cuba.gemspec", "test/accept.rb", "test/captures.rb", "test/extension.rb", "test/helper.rb", "test/integration.rb", "test/number.rb", "test/on.rb", "test/path.rb", "test/run.rb", "test/segment.rb"]
10
10
  s.add_dependency "rack", "~> 1.2"
11
11
  s.add_dependency "tilt", "~> 1.1"
12
12
  s.add_development_dependency "cutest", "~> 0.1"
@@ -1,20 +1,309 @@
1
- require "cuba/rum"
1
+ require "rack"
2
2
  require "tilt"
3
3
 
4
+ class Rack::Response
5
+ # 301 Moved Permanently
6
+ # 302 Found
7
+ # 303 See Other
8
+ # 307 Temporary Redirect
9
+ def redirect(target, status = 302)
10
+ self.status = status
11
+ self["Location"] = target
12
+ end
13
+ end
14
+
15
+ # Based on Rum: http://github.com/chneukirchen/rum
16
+ #
17
+ # Summary of changes
18
+ #
19
+ # 1. Only relevant captures are yielded.
20
+ # 2. The #extension matcher is used more like #path.
21
+ # 3. Miscellaneous coding style changes.
22
+ #
4
23
  module Cuba
5
- class Ron < Rum
24
+ class Ron
25
+ attr :env
26
+ attr :req
27
+ attr :res
28
+ attr :captures
29
+
30
+ def initialize(&blk)
31
+ @blk = blk
32
+ @captures = []
33
+ end
34
+
35
+ def call(env)
36
+ dup._call(env)
37
+ end
38
+
39
+ def _call(env)
40
+ @env = env
41
+ @req = Rack::Request.new(env)
42
+ @res = Rack::Response.new
43
+ @matched = false
44
+
45
+ catch(:rum_run_next_app) do
46
+ instance_eval(&@blk)
47
+
48
+ @res.status = 404 unless @matched || !@res.empty?
49
+
50
+ return @res.finish
51
+ end.call(env)
52
+ end
53
+
54
+ # @private Used internally by #render to cache the
55
+ # Tilt templates.
6
56
  def _cache
7
57
  Thread.current[:_cache] ||= Tilt::Cache.new
8
58
  end
59
+ private :_cache
9
60
 
10
- def render(template, locals = {})
61
+ # Render any type of template file supported by Tilt.
62
+ #
63
+ # @example
64
+ #
65
+ # # Renders home, and is assumed to be HAML.
66
+ # render("home.haml")
67
+ #
68
+ # # Renders with some local variables
69
+ # render("home.haml", site_name: "My Site")
70
+ #
71
+ # # Renders with HAML options
72
+ # render("home.haml", {}, ugly: true, format: :html5)
73
+ #
74
+ def render(template, locals = {}, options = {})
11
75
  _cache.fetch(template, locals) {
12
- Tilt.new(template)
76
+ Tilt.new(template, 1, options)
13
77
  }.render(self, locals)
14
78
  end
15
79
 
80
+ # Basic wrapper for using Rack session.
16
81
  def session
17
82
  @session ||= env['rack.session']
18
83
  end
84
+
85
+ # The heart of the path / verb / any condition matching.
86
+ #
87
+ # @example
88
+ #
89
+ # on get do
90
+ # res.write "GET"
91
+ # end
92
+ #
93
+ # on get, path("signup") do
94
+ # res.write "Signup
95
+ # end
96
+ #
97
+ # on path("user"), segment do |uid|
98
+ # res.write "User: #{uid}"
99
+ # end
100
+ #
101
+ # on path("styles"), extension("css") do |file|
102
+ # res.write render("styles/#{file}.sass")
103
+ # end
104
+ #
105
+ def on(*args, &block)
106
+ # No use running any other matchers if we've already found a
107
+ # proper matcher.
108
+ return if @matched
109
+
110
+ try do
111
+ # For every block, we make sure to reset captures so that
112
+ # nesting matchers won't mess with each other's captures.
113
+ @captures = []
114
+
115
+ # We stop evaluation of this entire matcher unless
116
+ # each and every `arg` defined for this matcher evaluates
117
+ # to a non-false value.
118
+ #
119
+ # Short circuit examples:
120
+ # on true, false do
121
+ #
122
+ # # PATH_INFO=/user
123
+ # on true, path("signup")
124
+ args.each do |arg|
125
+ return unless arg == true || arg != false && arg.call
126
+ end
127
+
128
+ # The captures we yield here were generated and assembled
129
+ # by evaluating each of the `arg`s above. Most of these
130
+ # are carried out by #path.
131
+ yield *captures
132
+
133
+ # At this point, we've successfully matched with some corresponding
134
+ # matcher, so we can skip all other matchers defined.
135
+ @matched = true
136
+ end
137
+ end
138
+
139
+ # @private Used internally by #on to ensure that SCRIPT_NAME and
140
+ # PATH_INFO are reset to their proper values.
141
+ def try
142
+ script, path = env["SCRIPT_NAME"], env["PATH_INFO"]
143
+
144
+ yield
145
+
146
+ env["SCRIPT_NAME"], env["PATH_INFO"] = script, path
147
+
148
+ ensure
149
+ unless @matched
150
+ env["SCRIPT_NAME"], env["PATH_INFO"] = script, path
151
+ end
152
+ end
153
+ private :try
154
+
155
+ # Probably the most useful helper for writing matchers.
156
+ #
157
+ # @example
158
+ # # matches PATH_INFO=/signup
159
+ # on path("signup") do
160
+ #
161
+ # # matches PATH_INFO=/user123
162
+ # on path("user(\\d+)") do |uid|
163
+ #
164
+ # # matches PATH_INFO=/user/1
165
+ # on path("user"), path("(\\d+)") do |uid|
166
+ #
167
+ # In fact, the other matchers (#segment, #number, #extension)
168
+ # ride on this method.
169
+ def path(pattern)
170
+ lambda { consume(pattern) }
171
+ end
172
+
173
+ # @private Used by #path to adjust the `PATH_INFO` and `SCRIPT_NAME`.
174
+ # This is done so that nesting of matchers would work.
175
+ #
176
+ # @example
177
+ # # PATH_INFO=/doctors/account
178
+ # on path("doctors") do
179
+ # # PATH_INFO = /account
180
+ # on path("account") do
181
+ # res.write "Settings page"
182
+ # end
183
+ # end
184
+ def consume(pattern)
185
+ return unless match = env["PATH_INFO"].match(/\A\/(#{pattern})(?:\/|\z)/)
186
+
187
+ path, *vars = match.captures
188
+
189
+ env["SCRIPT_NAME"] += "/#{path}"
190
+ env["PATH_INFO"] = "/#{match.post_match}"
191
+
192
+ captures.push(*vars)
193
+ end
194
+ private :consume
195
+
196
+ # A matcher for numeric ids.
197
+ #
198
+ # @example
199
+ # on path("user"), number do |uid|
200
+ # res.write "User: #{uid}"
201
+ # end
202
+ def number
203
+ path("(\\d+)")
204
+ end
205
+
206
+ # A matcher for anything without slashes. Useful for mapping to slugs.
207
+ #
208
+ # @example
209
+ # on path("article"), segment do |slug|
210
+ # Article.find_by_slug(slug)
211
+ #
212
+ # end
213
+ def segment
214
+ path("([^\\/]+)")
215
+ end
216
+
217
+ # A matcher for files with a certain extension.
218
+ #
219
+ # @example
220
+ # # PATH_INFO=/style/app.css
221
+ # on path("style"), extension("css") do |file|
222
+ # res.write file # writes app
223
+ # end
224
+ def extension(ext = "\\w+")
225
+ path("([^\\/]+?)\.#{ext}\\z")
226
+ end
227
+
228
+ # Used to ensure that certain request parameters are present. Acts like a
229
+ # precondition / assertion for your route.
230
+ #
231
+ # @example
232
+ # # POST with data like user[fname]=John&user[lname]=Doe
233
+ # on path("signup"), param("user") do |atts|
234
+ # User.create(atts)
235
+ # end
236
+ def param(key, default = nil)
237
+ lambda { captures << (req[key] || default) }
238
+ end
239
+
240
+ def header(key, default = nil)
241
+ lambda { env[key.upcase.tr("-","_")] || default }
242
+ end
243
+
244
+ # Useful for matching against the request host (i.e. HTTP_HOST).
245
+ #
246
+ # @example
247
+ # on host("account1.example.com"), path("api") do
248
+ # res.write "You have reached the API of account1."
249
+ # end
250
+ def host(hostname)
251
+ req.host == hostname
252
+ end
253
+
254
+ # If you want to match against the HTTP_ACCEPT value.
255
+ #
256
+ # @example
257
+ # # HTTP_ACCEPT=application/xml
258
+ # on accept("application/xml") do
259
+ # # automatically set to application/xml.
260
+ # res.write res["Content-Type"]
261
+ # end
262
+ def accept(mimetype)
263
+ lambda do
264
+ env["HTTP_ACCEPT"].split(",").any? { |s| s.strip == mimetype } and
265
+ res["Content-Type"] = mimetype
266
+ end
267
+ end
268
+
269
+ # Syntactic sugar for providing catch-all matches.
270
+ #
271
+ # @example
272
+ # on default do
273
+ # res.write "404"
274
+ # end
275
+ def default
276
+ true
277
+ end
278
+
279
+ # Syntatic sugar for providing HTTP Verb matching.
280
+ #
281
+ # @example
282
+ # on get, path("signup") do
283
+ # end
284
+ #
285
+ # on post, path("signup") do
286
+ # end
287
+ def get ; req.get? end
288
+ def post ; req.post? end
289
+ def put ; req.put? end
290
+ def delete ; req.delete? end
291
+
292
+ # If you want to halt the processing of an existing handler
293
+ # and continue it via a different handler.
294
+ #
295
+ # @example
296
+ # def redirect(*args)
297
+ # run Cuba::Ron.new { on(default) { res.redirect(*args) }}
298
+ # end
299
+ #
300
+ # on path("account") do
301
+ # redirect "/login" unless session["uid"]
302
+ #
303
+ # res.write "Super secure account info."
304
+ # end
305
+ def run(app)
306
+ throw :rum_run_next_app, app
307
+ end
19
308
  end
20
- end
309
+ end
@@ -1,3 +1,3 @@
1
1
  module Cuba
2
- VERSION = "0.3.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,16 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "accept mimetypes" do
4
+ Cuba.define do
5
+ on accept("application/xml") do
6
+ res.write res["Content-Type"]
7
+ end
8
+ end
9
+
10
+ env = { "HTTP_ACCEPT" => "application/xml",
11
+ "SCRIPT_NAME" => "/", "PATH_INFO" => "/post" }
12
+
13
+ _, _, resp = Cuba.call(env)
14
+
15
+ assert_equal ["application/xml"], resp.body
16
+ end
@@ -0,0 +1,127 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+ require "stringio"
3
+
4
+ test "doesn't yield HOST" do
5
+ Cuba.define do
6
+ on host("example.com") do |*args|
7
+ res.write args.size
8
+ end
9
+ end
10
+
11
+ env = { "HTTP_HOST" => "example.com" }
12
+
13
+ _, _, resp = Cuba.call(env)
14
+
15
+ assert_equal ["0"], resp.body
16
+ end
17
+
18
+ test "doesn't yield the verb" do
19
+ Cuba.define do
20
+ on get do |*args|
21
+ res.write args.size
22
+ end
23
+ end
24
+
25
+ env = { "REQUEST_METHOD" => "GET" }
26
+
27
+ _, _, resp = Cuba.call(env)
28
+
29
+ assert_equal ["0"], resp.body
30
+ end
31
+
32
+ test "doesn't yield the path" do
33
+ Cuba.define do
34
+ on get, path("home") do |*args|
35
+ res.write args.size
36
+ end
37
+ end
38
+
39
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/home",
40
+ "SCRIPT_NAME" => "/" }
41
+
42
+ _, _, resp = Cuba.call(env)
43
+
44
+ assert_equal ["0"], resp.body
45
+ end
46
+
47
+ test "yields the segment" do
48
+ Cuba.define do
49
+ on get, path("user"), segment do |id|
50
+ res.write id
51
+ end
52
+ end
53
+
54
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/johndoe",
55
+ "SCRIPT_NAME" => "/" }
56
+
57
+ _, _, resp = Cuba.call(env)
58
+
59
+ assert_equal ["johndoe"], resp.body
60
+ end
61
+
62
+ test "yields a number" do
63
+ Cuba.define do
64
+ on get, path("user"), number do |id|
65
+ res.write id
66
+ end
67
+ end
68
+
69
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/101",
70
+ "SCRIPT_NAME" => "/" }
71
+
72
+ _, _, resp = Cuba.call(env)
73
+
74
+ assert_equal ["101"], resp.body
75
+ end
76
+
77
+ test "yields an extension" do
78
+ Cuba.define do
79
+ on get, path("css"), extension("css") do |file|
80
+ res.write file
81
+ end
82
+ end
83
+
84
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/css/app.css",
85
+ "SCRIPT_NAME" => "/" }
86
+
87
+ _, _, resp = Cuba.call(env)
88
+
89
+ assert_equal ["app"], resp.body
90
+ end
91
+
92
+ test "yields a param" do
93
+ Cuba.define do
94
+ on get, path("signup"), param("email") do |email|
95
+ res.write email
96
+ end
97
+ end
98
+
99
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/signup",
100
+ "SCRIPT_NAME" => "/", "rack.input" => StringIO.new,
101
+ "QUERY_STRING" => "email=john@doe.com" }
102
+
103
+ _, _, resp = Cuba.call(env)
104
+
105
+ assert_equal ["john@doe.com"], resp.body
106
+ end
107
+
108
+ test "yields a segment per nested block" do
109
+ Cuba.define do
110
+ on segment do |one|
111
+ on segment do |two|
112
+ on segment do |three|
113
+ res.write one
114
+ res.write two
115
+ res.write three
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/one/two/three",
122
+ "SCRIPT_NAME" => "/" }
123
+
124
+ _, _, resp = Cuba.call(env)
125
+
126
+ assert_equal ["one", "two", "three"], resp.body
127
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup do
4
+ Cuba.define do
5
+ on path("styles") do
6
+ on extension("css") do |file|
7
+ res.write file
8
+ end
9
+ end
10
+ end
11
+
12
+ { "SCRIPT_NAME" => "/", "PATH_INFO" => "/styles" }
13
+ end
14
+
15
+ test "/styles/reset.css" do |env|
16
+ env["PATH_INFO"] += "/reset.css"
17
+
18
+ _, _, resp = Cuba.call(env)
19
+
20
+ assert_equal ["reset"], resp.body
21
+ end
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
+ require "cuba"
3
+ require "cutest"
4
+
5
+ prepare { Cuba.reset! }
@@ -1,7 +1,4 @@
1
- $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
- require "cuba"
3
-
4
- prepare { Cuba.reset! }
1
+ require File.expand_path("helper", File.dirname(__FILE__))
5
2
 
6
3
  test "resetting" do
7
4
  old = Cuba.app
@@ -0,0 +1,36 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup do
4
+ { "SCRIPT_NAME" => "/", "PATH_INFO" => "/about/1/2" }
5
+ end
6
+
7
+ test "paths and numbers" do |env|
8
+ Cuba.define do
9
+ on path("about") do
10
+ on number, number do |one, two|
11
+ res.write one
12
+ res.write two
13
+ end
14
+ end
15
+ end
16
+
17
+ _, _, resp = Cuba.call(env)
18
+
19
+ assert_equal ["1", "2"], resp.body
20
+ end
21
+
22
+ test "paths and decimals" do |env|
23
+ Cuba.define do
24
+ on path("about") do
25
+ on number do |one|
26
+ res.write one
27
+ end
28
+ end
29
+ end
30
+
31
+ env["PATH_INFO"] = "/about/1.2"
32
+
33
+ _, _, resp = Cuba.call(env)
34
+
35
+ assert_equal [], resp.body
36
+ end
@@ -0,0 +1,91 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "executes on true" do
4
+ Cuba.define do
5
+ on true do
6
+ res.write "+1"
7
+ end
8
+ end
9
+
10
+ _, _, resp = Cuba.call({})
11
+
12
+ assert_equal ["+1"], resp.body
13
+ end
14
+
15
+ test "restores SCRIPT_NAME and PATH_INFO" do
16
+ Cuba.define do
17
+ on true do
18
+ env["SCRIPT_NAME"] = "foo"
19
+ env["PATH_INFO"] = "/hello"
20
+
21
+ raise "Something went wrong"
22
+ end
23
+ end
24
+
25
+ env = { "SCRIPT_NAME" => "/", "PATH_INFO" => "/hello" }
26
+
27
+ begin
28
+ _, _, resp = Cuba.call(env)
29
+ rescue
30
+ end
31
+
32
+ assert_equal "/", env["SCRIPT_NAME"]
33
+ assert_equal "/hello", env["PATH_INFO"]
34
+ end
35
+
36
+ test "ensures SCRIPT_NAME and PATH_INFO are reverted" do
37
+ Cuba.define do
38
+ on lambda { env["SCRIPT_NAME"] = "/hello"; false } do
39
+ res.write "Unreachable"
40
+ end
41
+ end
42
+
43
+ env = { "SCRIPT_NAME" => "/", "PATH_INFO" => "/hello" }
44
+
45
+ _, _, resp = Cuba.call(env)
46
+
47
+ assert_equal "/", env["SCRIPT_NAME"]
48
+ assert_equal "/hello", env["PATH_INFO"]
49
+ assert_equal [], resp.body
50
+ end
51
+
52
+ test "skips consecutive matches" do
53
+ Cuba.define do
54
+ on true do
55
+ env["foo"] = "foo"
56
+
57
+ res.write "foo"
58
+ end
59
+
60
+ on true do
61
+ env["bar"] = "bar"
62
+
63
+ res.write "bar"
64
+ end
65
+ end
66
+
67
+ env = {}
68
+
69
+ _, _, resp = Cuba.call(env)
70
+
71
+ assert_equal "foo", env["foo"]
72
+ assert_equal ["foo"], resp.body
73
+
74
+ assert ! env["bar"]
75
+ end
76
+
77
+ test "finds first match available" do
78
+ Cuba.define do
79
+ on false do
80
+ res.write "foo"
81
+ end
82
+
83
+ on true do
84
+ res.write "bar"
85
+ end
86
+ end
87
+
88
+ _, _, resp = Cuba.call({})
89
+
90
+ assert_equal ["bar"], resp.body
91
+ end
@@ -0,0 +1,73 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup do
4
+ { "SCRIPT_NAME" => "/", "PATH_INFO" => "/about" }
5
+ end
6
+
7
+ test "one level path" do |env|
8
+ Cuba.define do
9
+ on path("about") do
10
+ res.write "About"
11
+ end
12
+ end
13
+
14
+ _, _, resp = Cuba.call(env)
15
+
16
+ assert_equal ["About"], resp.body
17
+ assert_equal({ "SCRIPT_NAME" => "/", "PATH_INFO" => "/about" }, env)
18
+ end
19
+
20
+ test "two level nested paths" do |env|
21
+ Cuba.define do
22
+ on path("about") do
23
+ on path("1") do
24
+ res.write "+1"
25
+ end
26
+
27
+ on path("2") do
28
+ res.write "+2"
29
+ end
30
+ end
31
+ end
32
+
33
+ env["PATH_INFO"] = "/about/1"
34
+
35
+ _, _, resp = Cuba.call(env)
36
+
37
+ assert_equal ["+1"], resp.body
38
+
39
+ env["PATH_INFO"] = "/about/2"
40
+
41
+ _, _, resp = Cuba.call(env)
42
+
43
+ assert_equal ["+2"], resp.body
44
+ end
45
+
46
+ test "two level inlined paths" do |env|
47
+ Cuba.define do
48
+ on path("a"), path("b") do
49
+ res.write "a"
50
+ res.write "b"
51
+ end
52
+ end
53
+
54
+ env["PATH_INFO"] = "/a/b"
55
+
56
+ _, _, resp = Cuba.call(env)
57
+
58
+ assert_equal ["a", "b"], resp.body
59
+ end
60
+
61
+ test "a path with some regex captures" do |env|
62
+ Cuba.define do
63
+ on path("user(\\d+)") do |uid|
64
+ res.write uid
65
+ end
66
+ end
67
+
68
+ env["PATH_INFO"] = "/user123"
69
+
70
+ _, _, resp = Cuba.call(env)
71
+
72
+ assert_equal ["123"], resp.body
73
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "redirect canonical example" do
4
+ Cuba.define do
5
+ def redirect(*args)
6
+ run Cuba::Ron.new { on(true) { res.redirect(*args) }}
7
+ end
8
+
9
+ on path("account") do
10
+ redirect "/login", 307
11
+
12
+ res.write "Super secure content"
13
+ end
14
+ end
15
+
16
+ env = { "SCRIPT_NAME" => "/", "PATH_INFO" => "/account" }
17
+
18
+ _, _, resp = Cuba.call(env)
19
+
20
+ assert_equal "/login", resp["Location"]
21
+ assert_equal 307, resp.status
22
+ assert_equal [], resp.body
23
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup do
4
+ Cuba.define do
5
+ on path("post") do
6
+ on segment do |id|
7
+ res.write id
8
+ end
9
+ end
10
+ end
11
+
12
+ { "SCRIPT_NAME" => "/", "PATH_INFO" => "/post" }
13
+ end
14
+
15
+ test "matches numeric ids" do |env|
16
+ env["PATH_INFO"] += "/1"
17
+
18
+ _, _, resp = Cuba.call(env)
19
+
20
+ assert_equal ["1"], resp.body
21
+ end
22
+
23
+ test "matches decimal numbers" do |env|
24
+ env["PATH_INFO"] += "/1.1"
25
+
26
+ _, _, resp = Cuba.call(env)
27
+
28
+ assert_equal ["1.1"], resp.body
29
+ end
30
+
31
+ test "matches slugs" do |env|
32
+ env["PATH_INFO"] += "/my-blog-post-about-cuba"
33
+
34
+ _, _, resp = Cuba.call(env)
35
+
36
+ assert_equal ["my-blog-post-about-cuba"], resp.body
37
+ end
38
+
39
+ test "matches only the first segment available" do |env|
40
+ env["PATH_INFO"] += "/one/two/three"
41
+
42
+ _, _, resp = Cuba.call(env)
43
+
44
+ assert_equal ["one"], resp.body
45
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuba
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 3
8
- - 0
9
- version: 0.3.0
4
+ prerelease:
5
+ version: 1.0.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - Michel Martens
@@ -14,7 +10,7 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2010-10-09 00:00:00 -03:00
13
+ date: 2011-02-16 00:00:00 -03:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
@@ -25,9 +21,6 @@ dependencies:
25
21
  requirements:
26
22
  - - ~>
27
23
  - !ruby/object:Gem::Version
28
- segments:
29
- - 1
30
- - 2
31
24
  version: "1.2"
32
25
  type: :runtime
33
26
  version_requirements: *id001
@@ -39,9 +32,6 @@ dependencies:
39
32
  requirements:
40
33
  - - ~>
41
34
  - !ruby/object:Gem::Version
42
- segments:
43
- - 1
44
- - 1
45
35
  version: "1.1"
46
36
  type: :runtime
47
37
  version_requirements: *id002
@@ -53,9 +43,6 @@ dependencies:
53
43
  requirements:
54
44
  - - ~>
55
45
  - !ruby/object:Gem::Version
56
- segments:
57
- - 0
58
- - 1
59
46
  version: "0.1"
60
47
  type: :development
61
48
  version_requirements: *id003
@@ -67,9 +54,6 @@ dependencies:
67
54
  requirements:
68
55
  - - ~>
69
56
  - !ruby/object:Gem::Version
70
- segments:
71
- - 0
72
- - 1
73
57
  version: "0.1"
74
58
  type: :development
75
59
  version_requirements: *id004
@@ -87,13 +71,20 @@ files:
87
71
  - README.markdown
88
72
  - Rakefile
89
73
  - lib/cuba/ron.rb
90
- - lib/cuba/rum.rb
91
74
  - lib/cuba/test.rb
92
75
  - lib/cuba/version.rb
93
76
  - lib/cuba.rb
94
77
  - cuba.gemspec
78
+ - test/accept.rb
79
+ - test/captures.rb
80
+ - test/extension.rb
81
+ - test/helper.rb
95
82
  - test/integration.rb
96
- - test/webrat.log
83
+ - test/number.rb
84
+ - test/on.rb
85
+ - test/path.rb
86
+ - test/run.rb
87
+ - test/segment.rb
97
88
  has_rdoc: true
98
89
  homepage: http://github.com/soveran/cuba
99
90
  licenses: []
@@ -108,21 +99,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
99
  requirements:
109
100
  - - ">="
110
101
  - !ruby/object:Gem::Version
111
- segments:
112
- - 0
113
102
  version: "0"
114
103
  required_rubygems_version: !ruby/object:Gem::Requirement
115
104
  none: false
116
105
  requirements:
117
106
  - - ">="
118
107
  - !ruby/object:Gem::Version
119
- segments:
120
- - 0
121
108
  version: "0"
122
109
  requirements: []
123
110
 
124
111
  rubyforge_project:
125
- rubygems_version: 1.3.7
112
+ rubygems_version: 1.5.2
126
113
  signing_key:
127
114
  specification_version: 3
128
115
  summary: Rum based microframework for web applications.
@@ -1,157 +0,0 @@
1
- # Rum, the gRand Unified Mapper
2
- #
3
- # Rum is a powerful mapper for your Rack applications that can be used
4
- # as a microframework.
5
- #
6
- # More information at http://github.com/chneukirchen/rum
7
- #
8
- # == Copyright
9
- #
10
- # Copyright (C) 2008, 2009 Christian Neukirchen <http://purl.org/net/chneukirchen>
11
- #
12
- # Permission is hereby granted, free of charge, to any person obtaining a copy
13
- # of this software and associated documentation files (the "Software"), to
14
- # deal in the Software without restriction, including without limitation the
15
- # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
16
- # sell copies of the Software, and to permit persons to whom the Software is
17
- # furnished to do so, subject to the following conditions:
18
- #
19
- # The above copyright notice and this permission notice shall be included in
20
- # all copies or substantial portions of the Software.
21
- #
22
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25
- # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
26
- # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
- #
29
- require 'rack'
30
-
31
- class Rack::Response
32
- # 301 Moved Permanently
33
- # 302 Found
34
- # 303 See Other
35
- # 307 Temporary Redirect
36
- def redirect(target, status=302)
37
- self.status = status
38
- self["Location"] = target
39
- end
40
- end
41
-
42
- class Rum
43
- attr_reader :env, :req, :res
44
-
45
- def initialize(&blk)
46
- @blk = blk
47
- end
48
-
49
- def call(env)
50
- dup._call(env)
51
- end
52
-
53
- def _call(env)
54
- @env = env
55
- @req = Rack::Request.new(env)
56
- @res = Rack::Response.new
57
- @matched = false
58
- catch(:rum_run_next_app) {
59
- instance_eval(&@blk)
60
- @res.status = 404 unless @matched || !@res.empty?
61
- return @res.finish
62
- }.call(env)
63
- end
64
-
65
- def on(*arg, &block)
66
- return if @matched
67
- s, p = env["SCRIPT_NAME"], env["PATH_INFO"]
68
- yield *arg.map { |a| a == true || (a != false && a.call) || return }
69
- env["SCRIPT_NAME"], env["PATH_INFO"] = s, p
70
- @matched = true
71
- ensure
72
- unless @matched
73
- env["SCRIPT_NAME"], env["PATH_INFO"] = s, p
74
- end
75
- end
76
-
77
- def any(*args)
78
- args.any? { |a| a == true || (a != false && a.call) }
79
- end
80
-
81
- def also
82
- @matched = false
83
- end
84
-
85
- def path(p)
86
- lambda {
87
- if env["PATH_INFO"] =~ /\A\/(#{p})(\/|\z)/ #/
88
- env["SCRIPT_NAME"] += "/#{$1}"
89
- env["PATH_INFO"] = $2 + $'
90
- $1
91
- end
92
- }
93
- end
94
-
95
- def number
96
- path("\\d+")
97
- end
98
-
99
- def segment
100
- path("[^\\/]+")
101
- end
102
-
103
- def extension(e="\\w+")
104
- lambda { env["PATH_INFO"] =~ /\.(#{e})\z/ && $1 }
105
- end
106
-
107
- def param(p, default=nil)
108
- lambda { req[p] || default }
109
- end
110
-
111
- def header(p, default=nil)
112
- lambda { env[p.upcase.tr('-','_')] || default }
113
- end
114
-
115
- def default
116
- true
117
- end
118
-
119
- def host(h)
120
- req.host == h
121
- end
122
-
123
- def method(m)
124
- req.request_method = m
125
- end
126
-
127
- def get; req.get?; end
128
- def post; req.post?; end
129
- def put; req.put?; end
130
- def delete; req.delete?; end
131
-
132
- def accept(mimetype)
133
- lambda {
134
- env['HTTP_ACCEPT'].split(',').any? { |s| s.strip == mimetype } and
135
- res['Content-Type'] = mimetype
136
- }
137
- end
138
-
139
- def check(&block)
140
- block
141
- end
142
-
143
- def run(app)
144
- throw :rum_run_next_app, app
145
- end
146
-
147
- def puts(*args)
148
- args.each { |s|
149
- res.write s
150
- res.write "\n"
151
- }
152
- end
153
-
154
- def print(*args)
155
- args.each { |s| res.write s }
156
- end
157
- end
@@ -1,5 +0,0 @@
1
- # Logfile created on 2010-06-11 01:04:00 -0300 by logger.rb/20321
2
- D, [2010-06-11T01:04:00.071479 #37917] DEBUG -- : REQUESTING PAGE: GET / with {} and HTTP headers {}
3
- D, [2010-06-11T01:04:00.082487 #37917] DEBUG -- : REQUESTING PAGE: GET /login with {} and HTTP headers {"HTTP_REFERER"=>"/"}
4
- D, [2010-06-11T01:04:00.084940 #37917] DEBUG -- : REQUESTING PAGE: POST /login with {"user"=>"Michel"} and HTTP headers {"HTTP_REFERER"=>"/login"}
5
- D, [2010-06-11T01:04:00.087758 #37917] DEBUG -- : REQUESTING PAGE: GET / with {} and HTTP headers {"HTTP_REFERER"=>"/login"}