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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f2740123aa0c6fafe4c1665f9c83e408cef9105
4
- data.tar.gz: 74ec8c03a23405c2ac18eb23ceea5ccb3f4813c1
3
+ metadata.gz: 5360a34504fec1081e28143f1f2ce25c69747061
4
+ data.tar.gz: a593de99e0868ce9478922e9fc1a775fd28afaf7
5
5
  SHA512:
6
- metadata.gz: 876514335f729d2beeea7b218bc14d7cac1772f5b568a4a985457b01321c16ef33116bb3dd605e218d3512185d34dffaca426b9e803d0e860c56619eb2838359
7
- data.tar.gz: d046733fe6737ccabc612523c7812b1be778cea3675223a1a5bc3c202a91fad2da26e2fac842789542368fcb95e0b8d201cff9235de3410c3e1e5e414314f1f0
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 call a <tt>match_*</tt> method with the given key using the hash value,
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 by adding the appropriate <tt>match_*</tt>
276
- method to the request class using the +request_module+ method:
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
- request_module do
280
- def match_foo(v)
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
- that contain one of the following modules:
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
- # Base class used for Roda requests. The instance methods for this
13
- # class are added by Roda::RodaPlugins::Base::RequestMethods, so this
14
- # only contains the class methods.
15
- class RodaRequest < ::Rack::Request;
16
- @roda_class = ::Roda
17
- @match_pattern_cache = {}
18
-
19
- if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
20
- # :nocov:
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
- def self.inherited(subclass)
32
- super
33
- subclass.instance_variable_set(:@match_pattern_cache, {})
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
- # Initialize the match_pattern cache in the subclass.
49
- def self.inherited(subclass)
50
- super
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
- class << self
56
- # Reference to the Roda class related to this request class.
57
- attr_accessor :roda_class
58
-
59
- # Since RodaRequest is anonymously subclassed when Roda is subclassed,
60
- # and then assigned to a constant of the Roda subclass, make inspect
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, so this
79
- # only contains the class methods.
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 = @mutex.synchronize{h[name]}
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 = @mutex.synchronize{h[name]}
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
- @mutex.synchronize{@plugins[name] = mod}
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.handle_on_result(instance_exec(@_request, &block))
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, calls
340
- # #is if there are any arguments, or #on if there are no arguments.
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
- is_or_on(*args, &block) if get?
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
- def halt(response)
348
- _halt(response)
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 handle_on_result(result)
376
+ def block_result(result)
354
377
  res = response
355
- if result.is_a?(String) && res.empty?
356
- res.write(result)
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
- on(*args, &block)
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
- try do
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 POST method, returns immediately. Otherwise, calls
401
- # #is if there are any arguments, or #on if there are no arguments.
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
- is_or_on(*args, &block) if post?
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
- _halt response.finish
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
- on(&block)
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
- _halt app.call(env)
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(/:\w+/, SEGMENT)})
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){SEGMENT})
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
- # Yield to the given block, clearing any captures before
560
- # yielding and restoring the SCRIPT_NAME and PATH_INFO on exit.
561
- def try
562
- script = env[SCRIPT_NAME]
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
- ensure
572
- env[SCRIPT_NAME] = script
573
- env[PATH_INFO] = path
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