roda-cj 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +26 -0
- data/README.rdoc +46 -12
- data/lib/roda.rb +183 -151
- data/lib/roda/plugins/all_verbs.rb +1 -1
- data/lib/roda/plugins/backtracking_array.rb +91 -0
- data/lib/roda/plugins/csrf.rb +60 -0
- data/lib/roda/plugins/halt.rb +2 -2
- data/lib/roda/plugins/json.rb +84 -0
- data/lib/roda/plugins/pass.rb +1 -1
- data/lib/roda/plugins/per_thread_caching.rb +70 -0
- data/lib/roda/plugins/render.rb +8 -35
- data/lib/roda/plugins/symbol_matchers.rb +75 -0
- data/lib/roda/plugins/symbol_views.rb +40 -0
- data/lib/roda/plugins/view_subdirs.rb +53 -0
- data/lib/roda/version.rb +1 -1
- data/spec/matchers_spec.rb +42 -0
- data/spec/plugin/backtracking_array_spec.rb +38 -0
- data/spec/plugin/csrf_spec.rb +49 -0
- data/spec/plugin/json_spec.rb +50 -0
- data/spec/plugin/pass_spec.rb +1 -1
- data/spec/plugin/per_thread_caching_spec.rb +28 -0
- data/spec/plugin/render_spec.rb +2 -1
- data/spec/plugin/symbol_matchers_spec.rb +62 -0
- data/spec/plugin/symbol_views_spec.rb +32 -0
- data/spec/plugin/view_subdirs_spec.rb +45 -0
- data/spec/plugin_spec.rb +11 -1
- data/spec/request_spec.rb +9 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5360a34504fec1081e28143f1f2ce25c69747061
|
4
|
+
data.tar.gz: a593de99e0868ce9478922e9fc1a775fd28afaf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bf78f056f2b0d1e034e7fab2be1a9dfaad92261c72e692da8c9bcdae895fefa10c348cb8a950445c769b7f7e944e110481e8d066d7a517043a175e647e952be
|
7
|
+
data.tar.gz: d963a3e7c9d02a9077d7f77678d7292b427b8d55ee21b614b7ebd1a401dccd83e9e4d037dd58d319b0490748cf3c33935206437e54ea908365ed8058d6892081
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
= HEAD
|
2
2
|
|
3
|
+
* Add backtracking_array plugin, allowing array matchers to backtrack if later matchers do not match (jeremyevans)
|
4
|
+
|
5
|
+
* Add :all hash matcher, allowing array matchers to include conditions where you want to match multiple conditions (jeremyevans)
|
6
|
+
|
7
|
+
* Add json plugin, allowing match blocks to return arrays/hashes, returning JSON (jeremyevans)
|
8
|
+
|
9
|
+
* Add view_subdirs plugin, for setting a subdirectory for views on a per-request basis (jeremyevans)
|
10
|
+
|
11
|
+
* Allow default halt method to take no arguments, and use the current response (jeremyevans)
|
12
|
+
|
13
|
+
* Add symbol_views plugin, allowing match blocks to return a template name symbol (jeremyevans)
|
14
|
+
|
15
|
+
* Add per_thread_caching plugin, for using separate caches per thread instead of shared thread-safe caches (jeremyevans)
|
16
|
+
|
17
|
+
* Add hash_matcher class method, for easily creating hash match methods (jeremyevans)
|
18
|
+
|
19
|
+
* Add symbol_matchers plugin, for using symbol-specific matching regexps (jeremyevans)
|
20
|
+
|
21
|
+
* Add csrf plugin for csrf protection using rack_csrf (jeremyevans)
|
22
|
+
|
23
|
+
* Optimize r.is, r.get, r.post and similar methods by reducing the number of Array objects created (jeremyevans)
|
24
|
+
|
25
|
+
* Support RequestClassMethods and ResponseClassMethods in plugins (jeremyevans)
|
26
|
+
|
27
|
+
* Add Roda::RodaCache for a thread safe cache, currently used for match patterns, templates, and plugins (jeremyevans)
|
28
|
+
|
3
29
|
* Optimize matching by caching consume regexp for strings, regexp, symbol, and :extension matchers (jeremyevans)
|
4
30
|
|
5
31
|
* Add r.root for matching root (path "/"), for easier to read version of r.is "" (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -268,20 +268,17 @@ This makes it easy to handle multiple strings without a Regexp:
|
|
268
268
|
|
269
269
|
=== Hash
|
270
270
|
|
271
|
-
Hashes
|
272
|
-
and match if that matcher returns true.
|
273
|
-
|
271
|
+
Hashes allow easily calling specialized match methods on the request.
|
274
272
|
The default registered matchers included with Roda are documented below.
|
275
|
-
You can add your own hash matchers
|
276
|
-
|
273
|
+
You can add your own hash matchers using the +hash_matcher+ class method,
|
274
|
+
which creates an appropriate request match method. The +hash_matcher+
|
275
|
+
block will be called with the value of the hash.
|
277
276
|
|
278
277
|
class App < Roda
|
279
|
-
|
280
|
-
|
281
|
-
...
|
282
|
-
end
|
278
|
+
hash_matcher(:foo) do |v|
|
279
|
+
...
|
283
280
|
end
|
284
|
-
|
281
|
+
|
285
282
|
route do |r|
|
286
283
|
r.on :foo=>'bar' do
|
287
284
|
...
|
@@ -289,6 +286,27 @@ method to the request class using the +request_module+ method:
|
|
289
286
|
end
|
290
287
|
end
|
291
288
|
|
289
|
+
==== :all
|
290
|
+
|
291
|
+
The :all matcher matches if all of the entries in the given array matches. So
|
292
|
+
|
293
|
+
r.on :all=>[:a, :b] do
|
294
|
+
...
|
295
|
+
end
|
296
|
+
|
297
|
+
is the same as:
|
298
|
+
|
299
|
+
r.on :a, :b do
|
300
|
+
...
|
301
|
+
end
|
302
|
+
|
303
|
+
The reason it also exists as a separate hash matcher is so you can use it inside
|
304
|
+
an array matcher. so:
|
305
|
+
|
306
|
+
r.on ['foo', {:all=>['foos', :id]}] do
|
307
|
+
end
|
308
|
+
|
309
|
+
Would match +/foo+ and +/foos/10+, but not +/foos+.
|
292
310
|
|
293
311
|
==== :extension
|
294
312
|
|
@@ -605,6 +623,7 @@ or modifying the +render_opts+ hash after loading the plugin:
|
|
605
623
|
render_opts[:layout] = "admin_layout" # Default layout template
|
606
624
|
render_opts[:layout_opts] = {:engine=>'haml'} # Default layout template options
|
607
625
|
render_opts[:opts] = {:default_encoding=>'UTF-8'} # Default template options
|
626
|
+
render_opts[:cache] = false # Disable template caching
|
608
627
|
end
|
609
628
|
|
610
629
|
== Plugins
|
@@ -617,6 +636,10 @@ override any Roda method and call +super+ to get the default behavior.
|
|
617
636
|
These plugins ship with roda:
|
618
637
|
|
619
638
|
all_verbs :: Adds routing methods to the request for all http verbs.
|
639
|
+
backtracking_array :: Allows array matchers to backtrack if later matchers
|
640
|
+
do not match.
|
641
|
+
csrf :: Adds CSRF protection and helper methods using
|
642
|
+
{rack_csrf}[https://github.com/baldowl/rack_csrf].
|
620
643
|
default_headers :: Override the default response headers used.
|
621
644
|
error_handler :: Adds a +error+ block that is called for all responses that
|
622
645
|
raise exceptions.
|
@@ -628,6 +651,8 @@ header_matchers :: Adds host, header, and accept hash matchers.
|
|
628
651
|
hooks :: Adds before and after methods to run code before and after requests.
|
629
652
|
indifferent_params :: Adds params method with indifferent access to params,
|
630
653
|
allowing use of symbol keys for accessing params.
|
654
|
+
json :: Allows match blocks to return arrays and hashes, using a json
|
655
|
+
representation as the response body.
|
631
656
|
middleware :: Allows the Roda app to be used as a rack middleware, calling the
|
632
657
|
next middleware if no route matches.
|
633
658
|
multi_route :: Adds the ability for multiple named route blocks, with the
|
@@ -636,8 +661,15 @@ not_found :: Adds a +not_found+ block that is called for all 404 responses
|
|
636
661
|
without bodies.
|
637
662
|
pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
|
638
663
|
it did not match.
|
664
|
+
per_thread_caching :: Switches the thread-safe cache from a shared cache to a
|
665
|
+
per-thread cache.
|
639
666
|
render :: Adds support for rendering templates via tilt, as described above.
|
640
667
|
streaming :: Adds support for streaming responses.
|
668
|
+
symbol_matchers :: Adds support for symbol-specific matching regexps.
|
669
|
+
symbol_views :: Allows match blocks to return template name symbols, uses the
|
670
|
+
template view as the response body.
|
671
|
+
view_subdirs :: Allows for setting a view subdirectory to use on a per-request
|
672
|
+
basis.
|
641
673
|
|
642
674
|
=== External Plugins
|
643
675
|
|
@@ -649,13 +681,15 @@ autoforme :: Adds support for easily creating a simple administrative front
|
|
649
681
|
|
650
682
|
=== How to create plugins
|
651
683
|
|
652
|
-
Authoring your own plugins is pretty straightforward. Plugins are just modules
|
653
|
-
|
684
|
+
Authoring your own plugins is pretty straightforward. Plugins are just modules,
|
685
|
+
which may contain any of the following modules:
|
654
686
|
|
655
687
|
InstanceMethods :: module included in the Roda class
|
656
688
|
ClassMethods :: module that extends the Roda class
|
657
689
|
RequestMethods :: module included in the class of the request
|
690
|
+
RequestClassMethods :: module extending the class of the request
|
658
691
|
ResponseMethods :: module included in the class of the response
|
692
|
+
ResponseClassMethods :: module extending the class of the response
|
659
693
|
|
660
694
|
If the plugin responds to +load_dependencies+, it will be called first, and should
|
661
695
|
be used if the plugin depends on another plugin.
|
data/lib/roda.rb
CHANGED
@@ -9,88 +9,46 @@ class Roda
|
|
9
9
|
# Error class raised by Roda
|
10
10
|
class RodaError < StandardError; end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@match_pattern_mutex = Mutex.new
|
22
|
-
|
23
|
-
def self.cached_matcher(obj)
|
24
|
-
unless pattern = @match_pattern_mutex.synchronize{@match_pattern_cache[obj]}
|
25
|
-
pattern = consume_pattern(yield)
|
26
|
-
@match_pattern_mutex.synchronize{@match_pattern_cache[obj] = pattern}
|
27
|
-
end
|
28
|
-
pattern
|
12
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
|
13
|
+
# A thread safe cache class, offering only #[] and #[]= methods,
|
14
|
+
# each protected by a mutex. Used on non-MRI where Hash is not
|
15
|
+
# thread safe.
|
16
|
+
class RodaCache
|
17
|
+
# Create a new thread safe cache.
|
18
|
+
def initialize
|
19
|
+
@mutex = Mutex.new
|
20
|
+
@hash = {}
|
29
21
|
end
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
subclass.instance_variable_set(:@match_pattern_mutex, Mutex.new)
|
35
|
-
end
|
36
|
-
# :nocov:
|
37
|
-
else
|
38
|
-
# Return the cached pattern for the given object. If the object is
|
39
|
-
# not already cached, yield to get the basic pattern, and convert the
|
40
|
-
# basic pattern to a pattern that does not partial segments.
|
41
|
-
def self.cached_matcher(obj)
|
42
|
-
unless pattern = @match_pattern_cache[obj]
|
43
|
-
pattern = @match_pattern_cache[obj] = consume_pattern(yield)
|
44
|
-
end
|
45
|
-
pattern
|
23
|
+
# Make getting value from underlying hash thread safe.
|
24
|
+
def [](key)
|
25
|
+
@mutex.synchronize{@hash[key]}
|
46
26
|
end
|
47
27
|
|
48
|
-
#
|
49
|
-
def
|
50
|
-
|
51
|
-
subclass.instance_variable_set(:@match_pattern_cache, {})
|
28
|
+
# Make setting value in underlying hash thread safe.
|
29
|
+
def []=(key, value)
|
30
|
+
@mutex.synchronize{@hash[key] = value}
|
52
31
|
end
|
53
32
|
end
|
33
|
+
else
|
34
|
+
# Hashes are already thread-safe in MRI, due to the GVL, so they
|
35
|
+
# can safely be used as a cache.
|
36
|
+
RodaCache = Hash
|
37
|
+
end
|
54
38
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# reflect the likely name for the class.
|
62
|
-
def inspect
|
63
|
-
"#{roda_class.inspect}::RodaRequest"
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
# The pattern to use for consuming, based on the given argument. The returned
|
69
|
-
# pattern requires the path starts with a string and does not match partial
|
70
|
-
# segments.
|
71
|
-
def consume_pattern(pattern)
|
72
|
-
/\A(\/(?:#{pattern}))(\/|\z)/
|
73
|
-
end
|
74
|
-
end
|
39
|
+
# Base class used for Roda requests. The instance methods for this
|
40
|
+
# class are added by Roda::RodaPlugins::Base::RequestMethods, the
|
41
|
+
# class methods are added by Roda::RodaPlugins::Base::RequestClassMethods.
|
42
|
+
class RodaRequest < ::Rack::Request;
|
43
|
+
@roda_class = ::Roda
|
44
|
+
@match_pattern_cache = ::Roda::RodaCache.new
|
75
45
|
end
|
76
46
|
|
77
47
|
# Base class used for Roda responses. The instance methods for this
|
78
|
-
# class are added by Roda::RodaPlugins::Base::ResponseMethods,
|
79
|
-
#
|
48
|
+
# class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
|
49
|
+
# methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
|
80
50
|
class RodaResponse < ::Rack::Response;
|
81
51
|
@roda_class = ::Roda
|
82
|
-
|
83
|
-
class << self
|
84
|
-
# Reference to the Roda class related to this response class.
|
85
|
-
attr_accessor :roda_class
|
86
|
-
|
87
|
-
# Since RodaResponse is anonymously subclassed when Roda is subclassed,
|
88
|
-
# and then assigned to a constant of the Roda subclass, make inspect
|
89
|
-
# reflect the likely name for the class.
|
90
|
-
def inspect
|
91
|
-
"#{roda_class.inspect}::RodaResponse"
|
92
|
-
end
|
93
|
-
end
|
94
52
|
end
|
95
53
|
|
96
54
|
@builder = ::Rack::Builder.new
|
@@ -100,11 +58,8 @@ class Roda
|
|
100
58
|
# Module in which all Roda plugins should be stored. Also contains logic for
|
101
59
|
# registering and loading plugins.
|
102
60
|
module RodaPlugins
|
103
|
-
# Mutex protecting the plugins hash
|
104
|
-
@mutex = ::Mutex.new
|
105
|
-
|
106
61
|
# Stores registered plugins
|
107
|
-
@plugins =
|
62
|
+
@plugins = RodaCache.new
|
108
63
|
|
109
64
|
# If the registered plugin already exists, use it. Otherwise,
|
110
65
|
# require it and return it. This raises a LoadError if such a
|
@@ -112,9 +67,9 @@ class Roda
|
|
112
67
|
# not register itself correctly.
|
113
68
|
def self.load_plugin(name)
|
114
69
|
h = @plugins
|
115
|
-
unless plugin =
|
70
|
+
unless plugin = h[name]
|
116
71
|
require "roda/plugins/#{name}"
|
117
|
-
raise RodaError, "Plugin #{name} did not register itself correctly in Roda::RodaPlugins" unless plugin =
|
72
|
+
raise RodaError, "Plugin #{name} did not register itself correctly in Roda::RodaPlugins" unless plugin = h[name]
|
118
73
|
end
|
119
74
|
plugin
|
120
75
|
end
|
@@ -122,7 +77,7 @@ class Roda
|
|
122
77
|
# Register the given plugin with Roda, so that it can be loaded using #plugin
|
123
78
|
# with a symbol. Should be used by plugin files.
|
124
79
|
def self.register_plugin(name, mod)
|
125
|
-
@
|
80
|
+
@plugins[name] = mod
|
126
81
|
end
|
127
82
|
|
128
83
|
# The base plugin for Roda, implementing all default functionality.
|
@@ -145,6 +100,14 @@ class Roda
|
|
145
100
|
app.call(env)
|
146
101
|
end
|
147
102
|
|
103
|
+
# Create a match_#{key} method in the request class using the given
|
104
|
+
# block, so that using a hash key in a request match method will
|
105
|
+
# call the block. The block should return nil or false to not
|
106
|
+
# match, and anything else to match.
|
107
|
+
def hash_matcher(key, &block)
|
108
|
+
request_module{define_method(:"match_#{key}", &block)}
|
109
|
+
end
|
110
|
+
|
148
111
|
# When inheriting Roda, setup a new rack app builder, copy the
|
149
112
|
# default middleware and opts into the subclass, and set the
|
150
113
|
# request and response classes in the subclasses to be subclasses
|
@@ -159,6 +122,7 @@ class Roda
|
|
159
122
|
|
160
123
|
request_class = Class.new(self::RodaRequest)
|
161
124
|
request_class.roda_class = subclass
|
125
|
+
request_class.match_pattern_cache = thread_safe_cache
|
162
126
|
subclass.const_set(:RodaRequest, request_class)
|
163
127
|
|
164
128
|
response_class = Class.new(self::RodaResponse)
|
@@ -187,9 +151,15 @@ class Roda
|
|
187
151
|
if defined?(mixin::RequestMethods)
|
188
152
|
self::RodaRequest.send(:include, mixin::RequestMethods)
|
189
153
|
end
|
154
|
+
if defined?(mixin::RequestClassMethods)
|
155
|
+
self::RodaRequest.extend mixin::RequestClassMethods
|
156
|
+
end
|
190
157
|
if defined?(mixin::ResponseMethods)
|
191
158
|
self::RodaResponse.send(:include, mixin::ResponseMethods)
|
192
159
|
end
|
160
|
+
if defined?(mixin::ResponseClassMethods)
|
161
|
+
self::RodaResponse.extend mixin::ResponseClassMethods
|
162
|
+
end
|
193
163
|
|
194
164
|
if mixin.respond_to?(:configure)
|
195
165
|
mixin.configure(self, *args, &block)
|
@@ -218,6 +188,12 @@ class Roda
|
|
218
188
|
@app = @builder.to_app
|
219
189
|
end
|
220
190
|
|
191
|
+
# A new thread safe cache instance. This is a method so it can be
|
192
|
+
# easily overridden for alternative implementations.
|
193
|
+
def thread_safe_cache
|
194
|
+
RodaCache.new
|
195
|
+
end
|
196
|
+
|
221
197
|
# Add a middleware to use for the rack application. Must be
|
222
198
|
# called before calling #route.
|
223
199
|
def use(*args, &block)
|
@@ -298,12 +274,50 @@ class Roda
|
|
298
274
|
# behavior after the request and response have been setup.
|
299
275
|
def _route(&block)
|
300
276
|
catch(:halt) do
|
301
|
-
request.
|
277
|
+
request.block_result(instance_exec(@_request, &block))
|
302
278
|
response.finish
|
303
279
|
end
|
304
280
|
end
|
305
281
|
end
|
306
282
|
|
283
|
+
# Class methods for RodaRequest
|
284
|
+
module RequestClassMethods
|
285
|
+
# Reference to the Roda class related to this request class.
|
286
|
+
attr_accessor :roda_class
|
287
|
+
|
288
|
+
# The cache to use for match patterns for this request class.
|
289
|
+
attr_accessor :match_pattern_cache
|
290
|
+
|
291
|
+
# Return the cached pattern for the given object. If the object is
|
292
|
+
# not already cached, yield to get the basic pattern, and convert the
|
293
|
+
# basic pattern to a pattern that does not partial segments.
|
294
|
+
def cached_matcher(obj)
|
295
|
+
cache = @match_pattern_cache
|
296
|
+
|
297
|
+
unless pattern = cache[obj]
|
298
|
+
pattern = cache[obj] = consume_pattern(yield)
|
299
|
+
end
|
300
|
+
|
301
|
+
pattern
|
302
|
+
end
|
303
|
+
|
304
|
+
# Since RodaRequest is anonymously subclassed when Roda is subclassed,
|
305
|
+
# and then assigned to a constant of the Roda subclass, make inspect
|
306
|
+
# reflect the likely name for the class.
|
307
|
+
def inspect
|
308
|
+
"#{roda_class.inspect}::RodaRequest"
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
|
313
|
+
# The pattern to use for consuming, based on the given argument. The returned
|
314
|
+
# pattern requires the path starts with a string and does not match partial
|
315
|
+
# segments.
|
316
|
+
def consume_pattern(pattern)
|
317
|
+
/\A(\/(?:#{pattern}))(\/|\z)/
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
307
321
|
# Instance methods for RodaRequest, mostly related to handling routing
|
308
322
|
# for the request.
|
309
323
|
module RequestMethods
|
@@ -312,8 +326,15 @@ class Roda
|
|
312
326
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
313
327
|
EMPTY_STRING = "".freeze
|
314
328
|
SLASH = "/".freeze
|
315
|
-
TERM = Object.new.freeze
|
316
329
|
SEGMENT = "([^\\/]+)".freeze
|
330
|
+
EMPTY_ARRAY = [].freeze
|
331
|
+
TERM_INSPECT = "TERM".freeze
|
332
|
+
|
333
|
+
TERM = Object.new
|
334
|
+
def TERM.inspect
|
335
|
+
TERM_INSPECT
|
336
|
+
end
|
337
|
+
TERM.freeze
|
317
338
|
|
318
339
|
# The current captures for the request. This gets modified as routing
|
319
340
|
# occurs.
|
@@ -336,24 +357,26 @@ class Roda
|
|
336
357
|
"#{env[SCRIPT_NAME]}#{env[PATH_INFO]}"
|
337
358
|
end
|
338
359
|
|
339
|
-
# If this is not a GET method, returns immediately. Otherwise,
|
340
|
-
#
|
360
|
+
# If this is not a GET method, returns immediately. Otherwise, if there
|
361
|
+
# are arguments, do a terminal match on the arguments, otherwise do a
|
362
|
+
# regular match.
|
341
363
|
def get(*args, &block)
|
342
|
-
|
364
|
+
_verb(args, &block) if get?
|
343
365
|
end
|
344
366
|
|
345
367
|
# Immediately stop execution of the route block and return the given
|
346
|
-
# rack response array of status, headers, and body.
|
347
|
-
|
348
|
-
|
368
|
+
# rack response array of status, headers, and body. If no argument
|
369
|
+
# is given, uses the current response.
|
370
|
+
def halt(res=response.finish)
|
371
|
+
throw :halt, res
|
349
372
|
end
|
350
373
|
|
351
374
|
# Handle #on block return values. By default, if a string is given
|
352
375
|
# and the response is empty, use the string as the response body.
|
353
|
-
def
|
376
|
+
def block_result(result)
|
354
377
|
res = response
|
355
|
-
if
|
356
|
-
res.write(
|
378
|
+
if res.empty? && (body = block_result_body(result))
|
379
|
+
res.write(body)
|
357
380
|
end
|
358
381
|
end
|
359
382
|
|
@@ -367,7 +390,7 @@ class Roda
|
|
367
390
|
# there is only a match if #on has fully matched the path.
|
368
391
|
def is(*args, &block)
|
369
392
|
args << TERM
|
370
|
-
|
393
|
+
_on(args, &block)
|
371
394
|
end
|
372
395
|
|
373
396
|
# Attempts to match on all of the arguments. If all of the
|
@@ -376,31 +399,14 @@ class Roda
|
|
376
399
|
# If any of the arguments fails, ensures the request state is
|
377
400
|
# returned to that before matches were attempted.
|
378
401
|
def on(*args, &block)
|
379
|
-
|
380
|
-
# We stop evaluation of this entire matcher unless
|
381
|
-
# each and every `arg` defined for this matcher evaluates
|
382
|
-
# to a non-false value.
|
383
|
-
#
|
384
|
-
# Short circuit examples:
|
385
|
-
# on true, false do
|
386
|
-
#
|
387
|
-
# # PATH_INFO=/user
|
388
|
-
# on true, "signup"
|
389
|
-
return unless args.all?{|arg| match(arg)}
|
390
|
-
|
391
|
-
# The captures we yield here were generated and assembled
|
392
|
-
# by evaluating each of the `arg`s above. Most of these
|
393
|
-
# are carried out by #consume.
|
394
|
-
handle_on_result(yield(*captures))
|
395
|
-
|
396
|
-
_halt response.finish
|
397
|
-
end
|
402
|
+
_on(args, &block)
|
398
403
|
end
|
399
404
|
|
400
|
-
# If this is not a
|
401
|
-
#
|
405
|
+
# If this is not a GET method, returns immediately. Otherwise, if there
|
406
|
+
# are arguments, do a terminal match on the arguments, otherwise do a
|
407
|
+
# regular match.
|
402
408
|
def post(*args, &block)
|
403
|
-
|
409
|
+
_verb(args, &block) if post?
|
404
410
|
end
|
405
411
|
|
406
412
|
# The response related to the current request.
|
@@ -411,31 +417,26 @@ class Roda
|
|
411
417
|
# Immediately redirect to the given path.
|
412
418
|
def redirect(path, status=302)
|
413
419
|
response.redirect(path, status)
|
414
|
-
|
420
|
+
throw :halt, response.finish
|
415
421
|
end
|
416
422
|
|
417
|
-
#
|
423
|
+
# If the current path is the root ("/"), match on the block. If a request
|
424
|
+
# method is given, return immediately if the request does not use the given
|
425
|
+
# method.
|
418
426
|
def root(request_method=nil, &block)
|
419
427
|
if env[PATH_INFO] == SLASH && (!request_method || send(:"#{request_method}?"))
|
420
|
-
|
428
|
+
_on(EMPTY_ARRAY, &block)
|
421
429
|
end
|
422
430
|
end
|
423
431
|
|
424
432
|
# Call the given rack app with the environment and immediately return
|
425
433
|
# the response as the response for this request.
|
426
434
|
def run(app)
|
427
|
-
|
435
|
+
throw :halt, app.call(env)
|
428
436
|
end
|
429
437
|
|
430
438
|
private
|
431
439
|
|
432
|
-
# Internal halt method, used so that halt can be overridden to handle
|
433
|
-
# non-rack response arrays, but internal code that always generates
|
434
|
-
# rack response arrays can use this for performance.
|
435
|
-
def _halt(response)
|
436
|
-
throw :halt, response
|
437
|
-
end
|
438
|
-
|
439
440
|
# Match any of the elements in the given array. Return at the
|
440
441
|
# first match without evaluating future matches. Returns false
|
441
442
|
# if no elements in the array match.
|
@@ -465,12 +466,54 @@ class Roda
|
|
465
466
|
# string so that regexp metacharacters are not matched, and recognizes
|
466
467
|
# colon tokens for placeholders.
|
467
468
|
def _match_string(str)
|
468
|
-
consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(
|
469
|
+
consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
|
469
470
|
end
|
470
471
|
|
471
472
|
# Match the given symbol if any segment matches.
|
472
473
|
def _match_symbol(sym)
|
473
|
-
consume(self.class.cached_matcher(sym){
|
474
|
+
consume(self.class.cached_matcher(sym){_match_symbol_regexp(sym)})
|
475
|
+
end
|
476
|
+
|
477
|
+
# The regular expression to use for matching symbols. By default, any non-empty
|
478
|
+
# segment matches.
|
479
|
+
def _match_symbol_regexp(s)
|
480
|
+
SEGMENT
|
481
|
+
end
|
482
|
+
|
483
|
+
# Internal match method taking array of matchers instead of multiple
|
484
|
+
# arguments.
|
485
|
+
def _on(args)
|
486
|
+
script = env[SCRIPT_NAME]
|
487
|
+
path = env[PATH_INFO]
|
488
|
+
|
489
|
+
# For every block, we make sure to reset captures so that
|
490
|
+
# nesting matchers won't mess with each other's captures.
|
491
|
+
captures.clear
|
492
|
+
|
493
|
+
return unless match_all(args)
|
494
|
+
block_result(yield(*captures))
|
495
|
+
throw :halt, response.finish
|
496
|
+
ensure
|
497
|
+
env[SCRIPT_NAME] = script
|
498
|
+
env[PATH_INFO] = path
|
499
|
+
end
|
500
|
+
|
501
|
+
# Backbone of the verb method support, using a terminal match if
|
502
|
+
# args is not empty, or a regular match if it is empty.
|
503
|
+
def _verb(args, &block)
|
504
|
+
unless args.empty?
|
505
|
+
args << TERM
|
506
|
+
end
|
507
|
+
_on(args, &block)
|
508
|
+
end
|
509
|
+
|
510
|
+
# The body to use for the response if the response does not return
|
511
|
+
# a body. By default, a String is returned directly, and nil is
|
512
|
+
# returned otherwise.
|
513
|
+
def block_result_body(result)
|
514
|
+
if result.is_a?(String)
|
515
|
+
result
|
516
|
+
end
|
474
517
|
end
|
475
518
|
|
476
519
|
# Attempts to match the pattern to the current path. If there is no
|
@@ -478,9 +521,7 @@ class Roda
|
|
478
521
|
# SCRIPT_NAME to include the matched path, removes the matched
|
479
522
|
# path from PATH_INFO, and updates captures with any regex captures.
|
480
523
|
def consume(pattern)
|
481
|
-
matchdata = env[PATH_INFO].match(pattern)
|
482
|
-
|
483
|
-
return false unless matchdata
|
524
|
+
return unless matchdata = env[PATH_INFO].match(pattern)
|
484
525
|
|
485
526
|
vars = matchdata.captures
|
486
527
|
|
@@ -491,16 +532,6 @@ class Roda
|
|
491
532
|
captures.concat(vars)
|
492
533
|
end
|
493
534
|
|
494
|
-
# Backbone of the verb method support, calling #is if there are any
|
495
|
-
# arguments, or #on if there are none.
|
496
|
-
def is_or_on(*args, &block)
|
497
|
-
if args.empty?
|
498
|
-
on(*args, &block)
|
499
|
-
else
|
500
|
-
is(*args, &block)
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
535
|
# Attempt to match the argument to the given request, handling
|
505
536
|
# common ruby types.
|
506
537
|
def match(matcher)
|
@@ -524,6 +555,11 @@ class Roda
|
|
524
555
|
end
|
525
556
|
end
|
526
557
|
|
558
|
+
# Match only if all of the arguments in the given array match.
|
559
|
+
def match_all(args)
|
560
|
+
args.all?{|arg| match(arg)}
|
561
|
+
end
|
562
|
+
|
527
563
|
# Match files with the given extension. Requires that the
|
528
564
|
# request path end with the extension.
|
529
565
|
def match_extension(ext)
|
@@ -555,22 +591,18 @@ class Roda
|
|
555
591
|
captures << v
|
556
592
|
end
|
557
593
|
end
|
594
|
+
end
|
558
595
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
path = env[PATH_INFO]
|
564
|
-
|
565
|
-
# For every block, we make sure to reset captures so that
|
566
|
-
# nesting matchers won't mess with each other's captures.
|
567
|
-
captures.clear
|
568
|
-
|
569
|
-
yield
|
596
|
+
# Class methods for RodaResponse
|
597
|
+
module ResponseClassMethods
|
598
|
+
# Reference to the Roda class related to this response class.
|
599
|
+
attr_accessor :roda_class
|
570
600
|
|
571
|
-
|
572
|
-
|
573
|
-
|
601
|
+
# Since RodaResponse is anonymously subclassed when Roda is subclassed,
|
602
|
+
# and then assigned to a constant of the Roda subclass, make inspect
|
603
|
+
# reflect the likely name for the class.
|
604
|
+
def inspect
|
605
|
+
"#{roda_class.inspect}::RodaResponse"
|
574
606
|
end
|
575
607
|
end
|
576
608
|
|