cuba 2.0.1 → 2.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +21 -0
- data/lib/cuba.rb +280 -4
- data/lib/cuba/version.rb +2 -2
- data/test/composition.rb +1 -1
- data/test/middleware.rb +41 -0
- data/test/redefinition.rb +17 -0
- data/test/run.rb +1 -1
- metadata +15 -14
- data/lib/cuba/ron.rb +0 -260
data/README.markdown
CHANGED
@@ -193,6 +193,27 @@ The fourth case, again, reverts to the basic matcher: it generates the string
|
|
193
193
|
The fifth case is different: it checks if the the parameter supplied is present
|
194
194
|
in the request (via POST or QUERY_STRING) and it pushes the value as a capture.
|
195
195
|
|
196
|
+
Composition
|
197
|
+
-----------
|
198
|
+
|
199
|
+
You can mount a Cuba app, along with middlewares, inside another Cuba app:
|
200
|
+
|
201
|
+
API = Cuba.build
|
202
|
+
|
203
|
+
API.use SomeMiddleware
|
204
|
+
|
205
|
+
API.define do
|
206
|
+
on param("url") do |url|
|
207
|
+
...
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
Cuba.define do
|
212
|
+
on "api" do
|
213
|
+
run API
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
196
217
|
Testing
|
197
218
|
-------
|
198
219
|
|
data/lib/cuba.rb
CHANGED
@@ -1,7 +1,28 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "tilt"
|
1
3
|
require "cuba/version"
|
2
|
-
require "cuba/ron"
|
3
4
|
|
4
|
-
|
5
|
+
class Rack::Response
|
6
|
+
# 301 Moved Permanently
|
7
|
+
# 302 Found
|
8
|
+
# 303 See Other
|
9
|
+
# 307 Temporary Redirect
|
10
|
+
def redirect(target, status = 302)
|
11
|
+
self.status = status
|
12
|
+
self["Location"] = target
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Cuba
|
17
|
+
class RedefinitionError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
@@methods = []
|
21
|
+
|
22
|
+
def self.method_added(meth)
|
23
|
+
@@methods << meth
|
24
|
+
end
|
25
|
+
|
5
26
|
def self.reset!
|
6
27
|
@app = nil
|
7
28
|
@prototype = nil
|
@@ -16,7 +37,11 @@ module Cuba
|
|
16
37
|
end
|
17
38
|
|
18
39
|
def self.define(&block)
|
19
|
-
app.run Cuba
|
40
|
+
app.run Cuba.new(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.build
|
44
|
+
Class.new(self)
|
20
45
|
end
|
21
46
|
|
22
47
|
def self.prototype
|
@@ -26,4 +51,255 @@ module Cuba
|
|
26
51
|
def self.call(env)
|
27
52
|
prototype.call(env)
|
28
53
|
end
|
29
|
-
|
54
|
+
|
55
|
+
attr :env
|
56
|
+
attr :req
|
57
|
+
attr :res
|
58
|
+
attr :captures
|
59
|
+
|
60
|
+
def initialize(&blk)
|
61
|
+
@blk = blk
|
62
|
+
@captures = []
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(env)
|
66
|
+
dup._call(env)
|
67
|
+
end
|
68
|
+
|
69
|
+
def _call(env)
|
70
|
+
@env = env
|
71
|
+
@req = Rack::Request.new(env)
|
72
|
+
@res = Rack::Response.new
|
73
|
+
@matched = false
|
74
|
+
|
75
|
+
catch(:ron_run_next_app) do
|
76
|
+
instance_eval(&@blk)
|
77
|
+
|
78
|
+
@res.status = 404 unless @matched || !@res.empty?
|
79
|
+
|
80
|
+
return @res.finish
|
81
|
+
end.call(env)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private Used internally by #render to cache the
|
85
|
+
# Tilt templates.
|
86
|
+
def _cache
|
87
|
+
Thread.current[:_cache] ||= Tilt::Cache.new
|
88
|
+
end
|
89
|
+
private :_cache
|
90
|
+
|
91
|
+
# Render any type of template file supported by Tilt.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
#
|
95
|
+
# # Renders home, and is assumed to be HAML.
|
96
|
+
# render("home.haml")
|
97
|
+
#
|
98
|
+
# # Renders with some local variables
|
99
|
+
# render("home.haml", site_name: "My Site")
|
100
|
+
#
|
101
|
+
# # Renders with HAML options
|
102
|
+
# render("home.haml", {}, ugly: true, format: :html5)
|
103
|
+
#
|
104
|
+
# # Renders in layout
|
105
|
+
# render("layout.haml") { render("home.haml") }
|
106
|
+
#
|
107
|
+
def render(template, locals = {}, options = {}, &block)
|
108
|
+
_cache.fetch(template, locals) {
|
109
|
+
Tilt.new(template, 1, options)
|
110
|
+
}.render(self, locals, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# The heart of the path / verb / any condition matching.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
#
|
117
|
+
# on get do
|
118
|
+
# res.write "GET"
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# on get, "signup" do
|
122
|
+
# res.write "Signup
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# on "user/:id" do |uid|
|
126
|
+
# res.write "User: #{uid}"
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# on "styles", extension("css") do |file|
|
130
|
+
# res.write render("styles/#{file}.sass")
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
def on(*args, &block)
|
134
|
+
# No use running any other matchers if we've already found a
|
135
|
+
# proper matcher.
|
136
|
+
return if @matched
|
137
|
+
|
138
|
+
try do
|
139
|
+
# For every block, we make sure to reset captures so that
|
140
|
+
# nesting matchers won't mess with each other's captures.
|
141
|
+
@captures = []
|
142
|
+
|
143
|
+
# We stop evaluation of this entire matcher unless
|
144
|
+
# each and every `arg` defined for this matcher evaluates
|
145
|
+
# to a non-false value.
|
146
|
+
#
|
147
|
+
# Short circuit examples:
|
148
|
+
# on true, false do
|
149
|
+
#
|
150
|
+
# # PATH_INFO=/user
|
151
|
+
# on true, "signup"
|
152
|
+
return unless args.all? { |arg| match(arg) }
|
153
|
+
|
154
|
+
begin
|
155
|
+
# The captures we yield here were generated and assembled
|
156
|
+
# by evaluating each of the `arg`s above. Most of these
|
157
|
+
# are carried out by #consume.
|
158
|
+
yield *captures
|
159
|
+
|
160
|
+
ensure
|
161
|
+
# Regardless of what happens in the `yield`, we should ensure that
|
162
|
+
# we successfully set `@matched` to true.
|
163
|
+
|
164
|
+
# At this point, we've successfully matched with some corresponding
|
165
|
+
# matcher, so we can skip all other matchers defined.
|
166
|
+
@matched = true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# @private Used internally by #on to ensure that SCRIPT_NAME and
|
172
|
+
# PATH_INFO are reset to their proper values.
|
173
|
+
def try
|
174
|
+
script, path = env["SCRIPT_NAME"], env["PATH_INFO"]
|
175
|
+
|
176
|
+
yield
|
177
|
+
|
178
|
+
ensure
|
179
|
+
env["SCRIPT_NAME"], env["PATH_INFO"] = script, path unless @matched
|
180
|
+
end
|
181
|
+
private :try
|
182
|
+
|
183
|
+
def consume(pattern)
|
184
|
+
return unless match = env["PATH_INFO"].match(/\A\/(#{pattern})((?:\/|\z))/)
|
185
|
+
|
186
|
+
path, *vars = match.captures
|
187
|
+
|
188
|
+
env["SCRIPT_NAME"] += "/#{path}"
|
189
|
+
env["PATH_INFO"] = "#{vars.pop}#{match.post_match}"
|
190
|
+
|
191
|
+
captures.push(*vars)
|
192
|
+
end
|
193
|
+
private :consume
|
194
|
+
|
195
|
+
def match(matcher, segment = "([^\\/]+)")
|
196
|
+
case matcher
|
197
|
+
when String then consume(matcher.gsub(/:\w+/, segment))
|
198
|
+
when Regexp then consume(matcher)
|
199
|
+
when Symbol then consume(segment)
|
200
|
+
when Proc then matcher.call
|
201
|
+
else
|
202
|
+
matcher
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# A matcher for files with a certain extension.
|
207
|
+
#
|
208
|
+
# @example
|
209
|
+
# # PATH_INFO=/style/app.css
|
210
|
+
# on "style", extension("css") do |file|
|
211
|
+
# res.write file # writes app
|
212
|
+
# end
|
213
|
+
def extension(ext = "\\w+")
|
214
|
+
lambda { consume("([^\\/]+?)\.#{ext}\\z") }
|
215
|
+
end
|
216
|
+
|
217
|
+
# Used to ensure that certain request parameters are present. Acts like a
|
218
|
+
# precondition / assertion for your route.
|
219
|
+
#
|
220
|
+
# @example
|
221
|
+
# # POST with data like user[fname]=John&user[lname]=Doe
|
222
|
+
# on "signup", param("user") do |atts|
|
223
|
+
# User.create(atts)
|
224
|
+
# end
|
225
|
+
def param(key)
|
226
|
+
lambda { captures << req[key] unless req[key].to_s.empty? }
|
227
|
+
end
|
228
|
+
|
229
|
+
def header(key)
|
230
|
+
lambda { env[key.upcase.tr("-","_")] }
|
231
|
+
end
|
232
|
+
|
233
|
+
# Useful for matching against the request host (i.e. HTTP_HOST).
|
234
|
+
#
|
235
|
+
# @example
|
236
|
+
# on host("account1.example.com"), "api" do
|
237
|
+
# res.write "You have reached the API of account1."
|
238
|
+
# end
|
239
|
+
def host(hostname)
|
240
|
+
hostname === req.host
|
241
|
+
end
|
242
|
+
|
243
|
+
# If you want to match against the HTTP_ACCEPT value.
|
244
|
+
#
|
245
|
+
# @example
|
246
|
+
# # HTTP_ACCEPT=application/xml
|
247
|
+
# on accept("application/xml") do
|
248
|
+
# # automatically set to application/xml.
|
249
|
+
# res.write res["Content-Type"]
|
250
|
+
# end
|
251
|
+
def accept(mimetype)
|
252
|
+
lambda do
|
253
|
+
String(env["HTTP_ACCEPT"]).split(",").any? { |s| s.strip == mimetype } and
|
254
|
+
res["Content-Type"] = mimetype
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Syntactic sugar for providing catch-all matches.
|
259
|
+
#
|
260
|
+
# @example
|
261
|
+
# on default do
|
262
|
+
# res.write "404"
|
263
|
+
# end
|
264
|
+
def default
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
# Syntatic sugar for providing HTTP Verb matching.
|
269
|
+
#
|
270
|
+
# @example
|
271
|
+
# on get, "signup" do
|
272
|
+
# end
|
273
|
+
#
|
274
|
+
# on post, "signup" do
|
275
|
+
# end
|
276
|
+
def get ; req.get? end
|
277
|
+
def post ; req.post? end
|
278
|
+
def put ; req.put? end
|
279
|
+
def delete ; req.delete? end
|
280
|
+
|
281
|
+
# If you want to halt the processing of an existing handler
|
282
|
+
# and continue it via a different handler.
|
283
|
+
#
|
284
|
+
# @example
|
285
|
+
# def redirect(*args)
|
286
|
+
# run Cuba.new { on(default) { res.redirect(*args) }}
|
287
|
+
# end
|
288
|
+
#
|
289
|
+
# on "account" do
|
290
|
+
# redirect "/login" unless session["uid"]
|
291
|
+
#
|
292
|
+
# res.write "Super secure account info."
|
293
|
+
# end
|
294
|
+
def run(app)
|
295
|
+
throw :ron_run_next_app, app
|
296
|
+
end
|
297
|
+
|
298
|
+
# In order to prevent people from overriding the standard Cuba
|
299
|
+
# methods like `get`, `put`, etc, we add this as a safety measure.
|
300
|
+
def self.method_added(meth)
|
301
|
+
if @@methods.include?(meth)
|
302
|
+
raise RedefinitionError, meth
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
data/lib/cuba/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "2.0.
|
1
|
+
class Cuba
|
2
|
+
VERSION = "2.1.0.rc1"
|
3
3
|
end
|
data/test/composition.rb
CHANGED
data/test/middleware.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class Shrimp
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
status, headers, resp = @app.call(env)
|
10
|
+
|
11
|
+
[status, headers, resp.body.reverse]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
test do
|
16
|
+
API = Cuba.build
|
17
|
+
API.use Shrimp
|
18
|
+
API.define do
|
19
|
+
on "v1/test" do
|
20
|
+
res.write "OK"
|
21
|
+
res.write "1"
|
22
|
+
res.write "2"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Cuba.define do
|
27
|
+
on "api" do
|
28
|
+
run API
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
_, _, body = Cuba.call({ "PATH_INFO" => "/api/v1/test", "SCRIPT_NAME" => "/" })
|
33
|
+
|
34
|
+
arr = []
|
35
|
+
|
36
|
+
body.each do |line|
|
37
|
+
arr << line
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_equal ["2", "1", "OK"], arr
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "adding your new custom helpers is ok" do
|
4
|
+
class Cuba
|
5
|
+
def foobar
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
test "redefining standard Cuba methods fails" do
|
11
|
+
assert_raise Cuba::RedefinitionError do
|
12
|
+
class Cuba
|
13
|
+
def get
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/test/run.rb
CHANGED
@@ -3,7 +3,7 @@ require File.expand_path("helper", File.dirname(__FILE__))
|
|
3
3
|
test "redirect canonical example" do
|
4
4
|
Cuba.define do
|
5
5
|
def redirect(*args)
|
6
|
-
run Cuba
|
6
|
+
run Cuba.new { on(true) { res.redirect(*args) }}
|
7
7
|
end
|
8
8
|
|
9
9
|
on "account" do
|
metadata
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
5
|
-
prerelease:
|
4
|
+
version: 2.1.0.rc1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Michel Martens
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-08-23 00:00:00.000000000 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
17
|
-
requirement: &
|
17
|
+
requirement: &2153945360 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *2153945360
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: tilt
|
28
|
-
requirement: &
|
28
|
+
requirement: &2153944940 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *2153944940
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: cutest
|
39
|
-
requirement: &
|
39
|
+
requirement: &2153944520 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *2153944520
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: capybara
|
50
|
-
requirement: &
|
50
|
+
requirement: &2153944100 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *2153944100
|
59
59
|
description: Cuba is a microframework for web applications.
|
60
60
|
email:
|
61
61
|
- michel@soveran.com
|
@@ -66,7 +66,6 @@ files:
|
|
66
66
|
- LICENSE
|
67
67
|
- README.markdown
|
68
68
|
- Rakefile
|
69
|
-
- lib/cuba/ron.rb
|
70
69
|
- lib/cuba/test.rb
|
71
70
|
- lib/cuba/version.rb
|
72
71
|
- lib/cuba.rb
|
@@ -80,10 +79,12 @@ files:
|
|
80
79
|
- test/integration.rb
|
81
80
|
- test/layout.rb
|
82
81
|
- test/match.rb
|
82
|
+
- test/middleware.rb
|
83
83
|
- test/number.rb
|
84
84
|
- test/on.rb
|
85
85
|
- test/param.rb
|
86
86
|
- test/path.rb
|
87
|
+
- test/redefinition.rb
|
87
88
|
- test/root.rb
|
88
89
|
- test/run.rb
|
89
90
|
- test/segment.rb
|
@@ -103,9 +104,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
105
|
none: false
|
105
106
|
requirements:
|
106
|
-
- - ! '
|
107
|
+
- - ! '>'
|
107
108
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
109
|
+
version: 1.3.1
|
109
110
|
requirements: []
|
110
111
|
rubyforge_project:
|
111
112
|
rubygems_version: 1.6.2
|
data/lib/cuba/ron.rb
DELETED
@@ -1,260 +0,0 @@
|
|
1
|
-
require "rack"
|
2
|
-
require "tilt"
|
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
|
-
module Cuba
|
16
|
-
class Ron
|
17
|
-
attr :env
|
18
|
-
attr :req
|
19
|
-
attr :res
|
20
|
-
attr :captures
|
21
|
-
|
22
|
-
def initialize(&blk)
|
23
|
-
@blk = blk
|
24
|
-
@captures = []
|
25
|
-
end
|
26
|
-
|
27
|
-
def call(env)
|
28
|
-
dup._call(env)
|
29
|
-
end
|
30
|
-
|
31
|
-
def _call(env)
|
32
|
-
@env = env
|
33
|
-
@req = Rack::Request.new(env)
|
34
|
-
@res = Rack::Response.new
|
35
|
-
@matched = false
|
36
|
-
|
37
|
-
catch(:ron_run_next_app) do
|
38
|
-
instance_eval(&@blk)
|
39
|
-
|
40
|
-
@res.status = 404 unless @matched || !@res.empty?
|
41
|
-
|
42
|
-
return @res.finish
|
43
|
-
end.call(env)
|
44
|
-
end
|
45
|
-
|
46
|
-
# @private Used internally by #render to cache the
|
47
|
-
# Tilt templates.
|
48
|
-
def _cache
|
49
|
-
Thread.current[:_cache] ||= Tilt::Cache.new
|
50
|
-
end
|
51
|
-
private :_cache
|
52
|
-
|
53
|
-
# Render any type of template file supported by Tilt.
|
54
|
-
#
|
55
|
-
# @example
|
56
|
-
#
|
57
|
-
# # Renders home, and is assumed to be HAML.
|
58
|
-
# render("home.haml")
|
59
|
-
#
|
60
|
-
# # Renders with some local variables
|
61
|
-
# render("home.haml", site_name: "My Site")
|
62
|
-
#
|
63
|
-
# # Renders with HAML options
|
64
|
-
# render("home.haml", {}, ugly: true, format: :html5)
|
65
|
-
#
|
66
|
-
# # Renders in layout
|
67
|
-
# render("layout.haml") { render("home.haml") }
|
68
|
-
#
|
69
|
-
def render(template, locals = {}, options = {}, &block)
|
70
|
-
_cache.fetch(template, locals) {
|
71
|
-
Tilt.new(template, 1, options)
|
72
|
-
}.render(self, locals, &block)
|
73
|
-
end
|
74
|
-
|
75
|
-
# The heart of the path / verb / any condition matching.
|
76
|
-
#
|
77
|
-
# @example
|
78
|
-
#
|
79
|
-
# on get do
|
80
|
-
# res.write "GET"
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# on get, "signup" do
|
84
|
-
# res.write "Signup
|
85
|
-
# end
|
86
|
-
#
|
87
|
-
# on "user/:id" do |uid|
|
88
|
-
# res.write "User: #{uid}"
|
89
|
-
# end
|
90
|
-
#
|
91
|
-
# on "styles", extension("css") do |file|
|
92
|
-
# res.write render("styles/#{file}.sass")
|
93
|
-
# end
|
94
|
-
#
|
95
|
-
def on(*args, &block)
|
96
|
-
# No use running any other matchers if we've already found a
|
97
|
-
# proper matcher.
|
98
|
-
return if @matched
|
99
|
-
|
100
|
-
try do
|
101
|
-
# For every block, we make sure to reset captures so that
|
102
|
-
# nesting matchers won't mess with each other's captures.
|
103
|
-
@captures = []
|
104
|
-
|
105
|
-
# We stop evaluation of this entire matcher unless
|
106
|
-
# each and every `arg` defined for this matcher evaluates
|
107
|
-
# to a non-false value.
|
108
|
-
#
|
109
|
-
# Short circuit examples:
|
110
|
-
# on true, false do
|
111
|
-
#
|
112
|
-
# # PATH_INFO=/user
|
113
|
-
# on true, "signup"
|
114
|
-
return unless args.all? { |arg| match(arg) }
|
115
|
-
|
116
|
-
begin
|
117
|
-
# The captures we yield here were generated and assembled
|
118
|
-
# by evaluating each of the `arg`s above. Most of these
|
119
|
-
# are carried out by #consume.
|
120
|
-
yield *captures
|
121
|
-
|
122
|
-
ensure
|
123
|
-
# Regardless of what happens in the `yield`, we should ensure that
|
124
|
-
# we successfully set `@matched` to true.
|
125
|
-
|
126
|
-
# At this point, we've successfully matched with some corresponding
|
127
|
-
# matcher, so we can skip all other matchers defined.
|
128
|
-
@matched = true
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# @private Used internally by #on to ensure that SCRIPT_NAME and
|
134
|
-
# PATH_INFO are reset to their proper values.
|
135
|
-
def try
|
136
|
-
script, path = env["SCRIPT_NAME"], env["PATH_INFO"]
|
137
|
-
|
138
|
-
yield
|
139
|
-
|
140
|
-
ensure
|
141
|
-
env["SCRIPT_NAME"], env["PATH_INFO"] = script, path unless @matched
|
142
|
-
end
|
143
|
-
private :try
|
144
|
-
|
145
|
-
def consume(pattern)
|
146
|
-
return unless match = env["PATH_INFO"].match(/\A\/(#{pattern})((?:\/|\z))/)
|
147
|
-
|
148
|
-
path, *vars = match.captures
|
149
|
-
|
150
|
-
env["SCRIPT_NAME"] += "/#{path}"
|
151
|
-
env["PATH_INFO"] = "#{vars.pop}#{match.post_match}"
|
152
|
-
|
153
|
-
captures.push(*vars)
|
154
|
-
end
|
155
|
-
private :consume
|
156
|
-
|
157
|
-
def match(matcher, segment = "([^\\/]+)")
|
158
|
-
case matcher
|
159
|
-
when String then consume(matcher.gsub(/:\w+/, segment))
|
160
|
-
when Regexp then consume(matcher)
|
161
|
-
when Symbol then consume(segment)
|
162
|
-
when Proc then matcher.call
|
163
|
-
else
|
164
|
-
matcher
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# A matcher for files with a certain extension.
|
169
|
-
#
|
170
|
-
# @example
|
171
|
-
# # PATH_INFO=/style/app.css
|
172
|
-
# on "style", extension("css") do |file|
|
173
|
-
# res.write file # writes app
|
174
|
-
# end
|
175
|
-
def extension(ext = "\\w+")
|
176
|
-
lambda { consume("([^\\/]+?)\.#{ext}\\z") }
|
177
|
-
end
|
178
|
-
|
179
|
-
# Used to ensure that certain request parameters are present. Acts like a
|
180
|
-
# precondition / assertion for your route.
|
181
|
-
#
|
182
|
-
# @example
|
183
|
-
# # POST with data like user[fname]=John&user[lname]=Doe
|
184
|
-
# on "signup", param("user") do |atts|
|
185
|
-
# User.create(atts)
|
186
|
-
# end
|
187
|
-
def param(key)
|
188
|
-
lambda { captures << req[key] unless req[key].to_s.empty? }
|
189
|
-
end
|
190
|
-
|
191
|
-
def header(key)
|
192
|
-
lambda { env[key.upcase.tr("-","_")] }
|
193
|
-
end
|
194
|
-
|
195
|
-
# Useful for matching against the request host (i.e. HTTP_HOST).
|
196
|
-
#
|
197
|
-
# @example
|
198
|
-
# on host("account1.example.com"), "api" do
|
199
|
-
# res.write "You have reached the API of account1."
|
200
|
-
# end
|
201
|
-
def host(hostname)
|
202
|
-
hostname === req.host
|
203
|
-
end
|
204
|
-
|
205
|
-
# If you want to match against the HTTP_ACCEPT value.
|
206
|
-
#
|
207
|
-
# @example
|
208
|
-
# # HTTP_ACCEPT=application/xml
|
209
|
-
# on accept("application/xml") do
|
210
|
-
# # automatically set to application/xml.
|
211
|
-
# res.write res["Content-Type"]
|
212
|
-
# end
|
213
|
-
def accept(mimetype)
|
214
|
-
lambda do
|
215
|
-
String(env["HTTP_ACCEPT"]).split(",").any? { |s| s.strip == mimetype } and
|
216
|
-
res["Content-Type"] = mimetype
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# Syntactic sugar for providing catch-all matches.
|
221
|
-
#
|
222
|
-
# @example
|
223
|
-
# on default do
|
224
|
-
# res.write "404"
|
225
|
-
# end
|
226
|
-
def default
|
227
|
-
true
|
228
|
-
end
|
229
|
-
|
230
|
-
# Syntatic sugar for providing HTTP Verb matching.
|
231
|
-
#
|
232
|
-
# @example
|
233
|
-
# on get, "signup" do
|
234
|
-
# end
|
235
|
-
#
|
236
|
-
# on post, "signup" do
|
237
|
-
# end
|
238
|
-
def get ; req.get? end
|
239
|
-
def post ; req.post? end
|
240
|
-
def put ; req.put? end
|
241
|
-
def delete ; req.delete? end
|
242
|
-
|
243
|
-
# If you want to halt the processing of an existing handler
|
244
|
-
# and continue it via a different handler.
|
245
|
-
#
|
246
|
-
# @example
|
247
|
-
# def redirect(*args)
|
248
|
-
# run Cuba::Ron.new { on(default) { res.redirect(*args) }}
|
249
|
-
# end
|
250
|
-
#
|
251
|
-
# on "account" do
|
252
|
-
# redirect "/login" unless session["uid"]
|
253
|
-
#
|
254
|
-
# res.write "Super secure account info."
|
255
|
-
# end
|
256
|
-
def run(app)
|
257
|
-
throw :ron_run_next_app, app
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|