cuba 0.3.0 → 1.0.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/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"}