cuba 2.0.1 → 2.1.0.rc1
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/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
|