roda-cj 0.9.2 → 0.9.3
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.
- 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
|
|