roda 3.104.0 → 3.106.0
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/lib/roda/plugins/_optimized_matching.rb +19 -7
- data/lib/roda/plugins/_symbol_class_matchers.rb +20 -19
- data/lib/roda/plugins/branch_locals.rb +2 -0
- data/lib/roda/plugins/chunked.rb +4 -2
- data/lib/roda/plugins/class_matchers.rb +18 -3
- data/lib/roda/plugins/common_logger.rb +2 -0
- data/lib/roda/plugins/content_for.rb +2 -0
- data/lib/roda/plugins/content_security_policy.rb +4 -0
- data/lib/roda/plugins/flash.rb +2 -0
- data/lib/roda/plugins/host_routing.rb +4 -0
- data/lib/roda/plugins/indifferent_params.rb +2 -0
- data/lib/roda/plugins/mailer.rb +2 -0
- data/lib/roda/plugins/not_allowed.rb +2 -0
- data/lib/roda/plugins/params_capturing.rb +2 -0
- data/lib/roda/plugins/permissions_policy.rb +4 -0
- data/lib/roda/plugins/relative_path.rb +2 -0
- data/lib/roda/plugins/request_headers.rb +2 -0
- data/lib/roda/plugins/response_request.rb +7 -0
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +4 -4
- data/lib/roda/plugins/shape_friendly.rb +232 -0
- data/lib/roda/plugins/sinatra_helpers.rb +1 -0
- data/lib/roda/plugins/symbol_matchers.rb +15 -6
- data/lib/roda/plugins/type_routing.rb +4 -2
- data/lib/roda/plugins/typecast_params.rb +2 -0
- data/lib/roda/plugins/view_options.rb +2 -0
- data/lib/roda/request.rb +34 -1
- data/lib/roda/session_middleware.rb +2 -1
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +8 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90404574f8af3e952feb444cabe5bb01430e2d7c3658daee9f22f0c22e25d5f6
|
|
4
|
+
data.tar.gz: 347ba47cf4c3b9e280848ba717a1a4a9f57f8ac0712d2aaa8e5942020c22cb29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00e03ccebd8d48c4c1576ae0da2929d85a30430114b9a292bd8b9029fba31b4a7fa10767d7fb59a643c4280fb659e9afa90dacde27c7a5bdbc98fc5ee1df40a6
|
|
7
|
+
data.tar.gz: 7867f8da5bad9db4102f09fc692158feee7605c8021b5d01bb07ebfe57d3a82ca0b18d7fc1341d6e5cdf9ff534736a6c9c3ce2420b2078abe96209ad423bce92
|
|
@@ -52,9 +52,20 @@ class Roda
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
elsif matcher == Integer
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
rp = @remaining_path
|
|
56
|
+
if /\A\/(\d{1,100})(?=\/|\z)/.match?(rp)
|
|
57
|
+
if last = rp.index('/', 1)
|
|
58
|
+
value = rp[1, last-1]
|
|
59
|
+
rp = rp[last, rp.length]
|
|
60
|
+
else
|
|
61
|
+
value = rp[1, rp.length]
|
|
62
|
+
rp = ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if value = scope.send(:_convert_class_Integer, value)
|
|
66
|
+
@remaining_path = rp
|
|
67
|
+
always{yield(value)}
|
|
68
|
+
end
|
|
58
69
|
end
|
|
59
70
|
else
|
|
60
71
|
path = @remaining_path
|
|
@@ -151,10 +162,11 @@ class Roda
|
|
|
151
162
|
always{yield rp[1, len]}
|
|
152
163
|
end
|
|
153
164
|
elsif matcher == Integer
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
rp = @remaining_path
|
|
166
|
+
if /\A\/(\d{1,100})\z/.match?(rp) && (value = scope.send(:_convert_class_Integer, rp[1, 101]))
|
|
167
|
+
@remaining_path = ''
|
|
168
|
+
always{yield(value)}
|
|
169
|
+
end
|
|
158
170
|
else
|
|
159
171
|
path = @remaining_path
|
|
160
172
|
captures = @captures.clear
|
|
@@ -8,7 +8,7 @@ class Roda
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
# Backend of symbol_matcher and class_matcher.
|
|
11
|
-
def _symbol_class_matcher(expected_class, obj, matcher, block, &request_class_block)
|
|
11
|
+
def _symbol_class_matcher(expected_class, obj, matcher, block, options=OPTS, &request_class_block)
|
|
12
12
|
unless obj.is_a?(expected_class)
|
|
13
13
|
raise RodaError, "Invalid type passed to class_matcher or symbol_matcher: #{matcher.inspect}"
|
|
14
14
|
end
|
|
@@ -30,23 +30,23 @@ class Roda
|
|
|
30
30
|
raise RodaError, "cannot provide Symbol matcher to class_matcher unless using symbol_matchers plugin: #{matcher.inspect}"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
regexp, consume_regexp,
|
|
33
|
+
regexp, consume_regexp, convert_meth, consume_meth = opts[:symbol_matchers][matcher]
|
|
34
34
|
|
|
35
35
|
unless regexp
|
|
36
36
|
raise RodaError, "unregistered symbol matcher given to #{type}_matcher: #{matcher.inspect}"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
block = _merge_matcher_blocks(type, obj, block,
|
|
39
|
+
block = _merge_matcher_blocks(type, obj, block, convert_meth)
|
|
40
40
|
when Class
|
|
41
41
|
unless opts[:class_matchers]
|
|
42
42
|
raise RodaError, "cannot provide Class matcher to symbol_matcher unless using class_matchers plugin: #{matcher.inspect}"
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
regexp, consume_regexp,
|
|
45
|
+
regexp, consume_regexp, convert_meth, consume_meth = opts[:class_matchers][matcher]
|
|
46
46
|
unless regexp
|
|
47
47
|
raise RodaError, "unregistered class matcher given to #{type}_matcher: #{matcher.inspect}"
|
|
48
48
|
end
|
|
49
|
-
block = _merge_matcher_blocks(type, obj, block,
|
|
49
|
+
block = _merge_matcher_blocks(type, obj, block, convert_meth)
|
|
50
50
|
else
|
|
51
51
|
raise RodaError, "unsupported matcher given to #{type}_matcher: #{matcher.inspect}"
|
|
52
52
|
end
|
|
@@ -59,7 +59,8 @@ class Roda
|
|
|
59
59
|
private convert_meth
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
consume_meth ||= options[:segment] ? :_consume_single_segment : :consume
|
|
63
|
+
array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth, consume_meth].freeze
|
|
63
64
|
|
|
64
65
|
self::RodaRequest.class_eval do
|
|
65
66
|
class_exec(meth, array, &request_class_block)
|
|
@@ -69,30 +70,30 @@ class Roda
|
|
|
69
70
|
nil
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
# If both block and
|
|
73
|
+
# If both block and convert_meth are given,
|
|
73
74
|
# define a method for block, and then return a
|
|
74
|
-
# proc that calls
|
|
75
|
-
# the newly defined method with the return values of
|
|
75
|
+
# proc that calls convert_meth first, and only calls
|
|
76
|
+
# the newly defined method with the return values of convert_meth
|
|
76
77
|
# if matcher_method returns a truthy value.
|
|
77
|
-
# Otherwise, return
|
|
78
|
-
def _merge_matcher_blocks(type, obj, block,
|
|
79
|
-
if
|
|
78
|
+
# Otherwise, return convert_meth or block.
|
|
79
|
+
def _merge_matcher_blocks(type, obj, block, convert_meth)
|
|
80
|
+
if convert_meth
|
|
80
81
|
if block
|
|
81
|
-
|
|
82
|
-
define_method(
|
|
83
|
-
private
|
|
82
|
+
convert_merge_meth = :"_convert_merge_#{type}_#{obj}"
|
|
83
|
+
define_method(convert_merge_meth, &block)
|
|
84
|
+
private convert_merge_meth
|
|
84
85
|
|
|
85
86
|
proc do |*a|
|
|
86
|
-
if captures = send(
|
|
87
|
+
if captures = send(convert_meth, *a)
|
|
87
88
|
if captures.is_a?(Array)
|
|
88
|
-
send(
|
|
89
|
+
send(convert_merge_meth, *captures)
|
|
89
90
|
else
|
|
90
|
-
send(
|
|
91
|
+
send(convert_merge_meth, captures)
|
|
91
92
|
end
|
|
92
93
|
end
|
|
93
94
|
end
|
|
94
95
|
else
|
|
95
|
-
|
|
96
|
+
convert_meth
|
|
96
97
|
end
|
|
97
98
|
else
|
|
98
99
|
block
|
|
@@ -21,6 +21,8 @@ class Roda
|
|
|
21
21
|
# have higher precedence than the render_locals plugin options, but lower precedence
|
|
22
22
|
# than options you directly pass to the view/render methods.
|
|
23
23
|
module BranchLocals
|
|
24
|
+
SCOPE_INSTANCE_VARIABLES = [:@_layout_locals, :@_view_locals].freeze
|
|
25
|
+
|
|
24
26
|
# Load the render_locals plugin before this plugin, since this plugin
|
|
25
27
|
# works by overriding methods in the render_locals plugin.
|
|
26
28
|
def self.load_dependencies(app)
|
data/lib/roda/plugins/chunked.rb
CHANGED
|
@@ -148,6 +148,8 @@ class Roda
|
|
|
148
148
|
# method is not called until template rendering, the flash may not be
|
|
149
149
|
# rotated.
|
|
150
150
|
module Chunked
|
|
151
|
+
SCOPE_INSTANCE_VARIABLES = [:@_chunked, :@_each_chunk_args, :@_delays, :@_flusher].freeze
|
|
152
|
+
|
|
151
153
|
# Depend on the render plugin
|
|
152
154
|
def self.load_dependencies(app, opts=OPTS)
|
|
153
155
|
app.plugin :render
|
|
@@ -215,7 +217,7 @@ class Roda
|
|
|
215
217
|
# If chunking by default, call chunked if it hasn't yet been
|
|
216
218
|
# called and chunking is not specifically disabled.
|
|
217
219
|
def view(*a)
|
|
218
|
-
if opts[:chunk_by_default] &&
|
|
220
|
+
if opts[:chunk_by_default] && @_chunked != false && !defined?(yield)
|
|
219
221
|
chunked(*a)
|
|
220
222
|
else
|
|
221
223
|
super
|
|
@@ -225,7 +227,7 @@ class Roda
|
|
|
225
227
|
# Render a response to the user in chunks. See Chunked for
|
|
226
228
|
# an overview. If a block is given, it is passed to #delay.
|
|
227
229
|
def chunked(template, opts=OPTS, &block)
|
|
228
|
-
|
|
230
|
+
if @_chunked.nil?
|
|
229
231
|
@_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
|
|
230
232
|
end
|
|
231
233
|
|
|
@@ -67,6 +67,17 @@ class Roda
|
|
|
67
67
|
# Blocks passed to the class_matchers plugin are evaluated in route
|
|
68
68
|
# block context.
|
|
69
69
|
#
|
|
70
|
+
# When passing a regexp as a matcher, to class_matcher, you can provide a
|
|
71
|
+
# <tt>segment: true</tt> option to speed up the matching on Ruby 2.4+:
|
|
72
|
+
#
|
|
73
|
+
# symbol_matcher(:employee_id, /E-(\d{6})/, segment: true) do |employee_id|
|
|
74
|
+
# employee_id.to_i
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# Use of <tt>segment: true</tt> requires that the regexp not match more than
|
|
78
|
+
# one segment (i.e. it cannot match +/+). Additionally, the entire segment will
|
|
79
|
+
# be captured, so any capture groups in the regexp will be ignored.
|
|
80
|
+
#
|
|
70
81
|
# This plugin does not work with the params_capturing plugin, as it does not
|
|
71
82
|
# offer the ability to associate block arguments with named keys.
|
|
72
83
|
module ClassMatchers
|
|
@@ -92,10 +103,14 @@ class Roda
|
|
|
92
103
|
# captures if no block was registered with the class or symbol. In either case,
|
|
93
104
|
# if a block is given, it should return an array with the captures to yield to
|
|
94
105
|
# the match block.
|
|
95
|
-
def class_matcher(klass, matcher, &block)
|
|
96
|
-
_symbol_class_matcher(Class, klass, matcher, block) do |meth, (_, regexp, convert_meth)|
|
|
106
|
+
def class_matcher(klass, matcher, opts=OPTS, &block)
|
|
107
|
+
_symbol_class_matcher(Class, klass, matcher, block, opts) do |meth, (_, regexp, convert_meth, consume_meth)|
|
|
97
108
|
if regexp
|
|
98
|
-
|
|
109
|
+
if consume_meth == :_consume_single_segment
|
|
110
|
+
define_method(meth){_consume_single_segment(regexp, convert_meth)}
|
|
111
|
+
else
|
|
112
|
+
define_method(meth){consume(regexp, convert_meth)}
|
|
113
|
+
end
|
|
99
114
|
else
|
|
100
115
|
define_method(meth){_consume_segment(convert_meth)}
|
|
101
116
|
end
|
|
@@ -20,6 +20,8 @@ class Roda
|
|
|
20
20
|
# plugin :common_logger, Logger.new('filename')
|
|
21
21
|
# plugin :common_logger, Logger.new('filename'), method: :debug
|
|
22
22
|
module CommonLogger
|
|
23
|
+
SCOPE_INSTANCE_VARIABLES = [:@_request_timer].freeze
|
|
24
|
+
|
|
23
25
|
MUTATE_LINE = RUBY_VERSION < '2.3' || RUBY_VERSION >= '3'
|
|
24
26
|
private_constant :MUTATE_LINE
|
|
25
27
|
|
|
@@ -55,6 +55,8 @@ class Roda
|
|
|
55
55
|
#
|
|
56
56
|
# plugin :content_for, append: false
|
|
57
57
|
module ContentFor
|
|
58
|
+
SCOPE_INSTANCE_VARIABLES = [:@_content_for].freeze
|
|
59
|
+
|
|
58
60
|
# Depend on the capture_erb plugin, since it uses capture_erb
|
|
59
61
|
# to capture the content.
|
|
60
62
|
def self.load_dependencies(app, _opts = OPTS)
|
|
@@ -111,6 +111,8 @@ class Roda
|
|
|
111
111
|
# is given. Also, there is a +report_only?+ method for returning whether report only
|
|
112
112
|
# mode is enabled.
|
|
113
113
|
module ContentSecurityPolicy
|
|
114
|
+
RESPONSE_INSTANCE_VARIABLES = [:@content_security_policy, :@skip_content_security_policy].freeze
|
|
115
|
+
|
|
114
116
|
# Represents a content security policy.
|
|
115
117
|
class Policy
|
|
116
118
|
'
|
|
@@ -192,6 +194,8 @@ class Roda
|
|
|
192
194
|
# Clear all settings, useful to remove any inherited settings.
|
|
193
195
|
def clear
|
|
194
196
|
@opts = {}
|
|
197
|
+
@report_only = nil
|
|
198
|
+
@header_value = nil
|
|
195
199
|
end
|
|
196
200
|
|
|
197
201
|
# Do not allow future modifications to any settings.
|
data/lib/roda/plugins/flash.rb
CHANGED
|
@@ -71,6 +71,8 @@ class Roda
|
|
|
71
71
|
# This plugin uses the host method on the request to get the hostname (this method
|
|
72
72
|
# is defined by Rack).
|
|
73
73
|
module HostRouting
|
|
74
|
+
REQUEST_INSTANCE_VARIABLES = [:@_host_routing_host].freeze
|
|
75
|
+
|
|
74
76
|
# Setup the host routing support. The block yields an object used to
|
|
75
77
|
# configure the plugin. Options:
|
|
76
78
|
#
|
|
@@ -110,6 +112,8 @@ class Roda
|
|
|
110
112
|
def initialize
|
|
111
113
|
@hosts = []
|
|
112
114
|
@host_hash = {}
|
|
115
|
+
@default_host = nil
|
|
116
|
+
@default_block = nil
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
# Run the DSL for the given block.
|
|
@@ -39,6 +39,8 @@ class Roda
|
|
|
39
39
|
# it affects all rack applications instead of just the Roda app that
|
|
40
40
|
# you load the plugin into.
|
|
41
41
|
module IndifferentParams
|
|
42
|
+
SCOPE_INSTANCE_VARIABLES = [:@_params].freeze
|
|
43
|
+
|
|
42
44
|
INDIFFERENT_PROC = lambda{|h,k| h[k.to_s] if k.is_a?(Symbol)}
|
|
43
45
|
|
|
44
46
|
if Rack.release > '2'
|
data/lib/roda/plugins/mailer.rb
CHANGED
|
@@ -127,6 +127,8 @@ class Roda
|
|
|
127
127
|
# Roda application if you want your helper methods to automatically be
|
|
128
128
|
# available in your email views.
|
|
129
129
|
module Mailer
|
|
130
|
+
RESPONSE_INSTANCE_VARIABLES = [:@mail, :@mail_attachments].freeze
|
|
131
|
+
|
|
130
132
|
# Error raised when the using the mail class method, but the routing
|
|
131
133
|
# tree doesn't return the mail object.
|
|
132
134
|
class Error < ::Roda::RodaError; end
|
|
@@ -68,6 +68,8 @@ class Roda
|
|
|
68
68
|
#
|
|
69
69
|
# This plugin depends on the all_verbs plugin.
|
|
70
70
|
module NotAllowed
|
|
71
|
+
REQUEST_INSTANCE_VARIABLES = [:@_is_verbs].freeze
|
|
72
|
+
|
|
71
73
|
# Depend on the all_verbs plugin, as this plugin overrides methods
|
|
72
74
|
# defined by it and calls super.
|
|
73
75
|
def self.load_dependencies(app)
|
|
@@ -55,6 +55,8 @@ class Roda
|
|
|
55
55
|
# the symbol_matchers plugin with custom symbol matching and are using
|
|
56
56
|
# symbols that capture multiple values or no values.
|
|
57
57
|
module ParamsCapturing
|
|
58
|
+
REQUEST_INSTANCE_VARIABLES = [:@_params_captures].freeze
|
|
59
|
+
|
|
58
60
|
module RequestMethods
|
|
59
61
|
# Lazily initialize captures entry when params is called.
|
|
60
62
|
def params
|
|
@@ -104,6 +104,8 @@ class Roda
|
|
|
104
104
|
# setting a policy. This is faster than calling +permissions_policy.clear+, since
|
|
105
105
|
# it does not duplicate the default policy.
|
|
106
106
|
module PermissionsPolicy
|
|
107
|
+
RESPONSE_INSTANCE_VARIABLES = [:@permissions_policy, :@skip_permissions_policy].freeze
|
|
108
|
+
|
|
107
109
|
SUPPORTED_SETTINGS = %w'
|
|
108
110
|
accelerometer
|
|
109
111
|
ambient-light-sensor
|
|
@@ -180,6 +182,8 @@ class Roda
|
|
|
180
182
|
# Clear all settings, useful to remove any inherited settings.
|
|
181
183
|
def clear
|
|
182
184
|
@opts = {}
|
|
185
|
+
@report_only = nil
|
|
186
|
+
@header_value = nil
|
|
183
187
|
end
|
|
184
188
|
|
|
185
189
|
# Do not allow future modifications to any settings.
|
|
@@ -31,6 +31,8 @@ class Roda
|
|
|
31
31
|
# This plugin is mostly designed for applications using Roda as a static
|
|
32
32
|
# site generator, where the generated site can be hosted at any subpath.
|
|
33
33
|
module RelativePath
|
|
34
|
+
SCOPE_INSTANCE_VARIABLES = [:@_relative_prefix].freeze
|
|
35
|
+
|
|
34
36
|
module InstanceMethods
|
|
35
37
|
# Return a relative path for the absolute path based on the current path
|
|
36
38
|
# of the request by adding the appropriate prefix.
|
|
@@ -10,6 +10,13 @@ class Roda
|
|
|
10
10
|
#
|
|
11
11
|
# plugin :response_request
|
|
12
12
|
module ResponseRequest
|
|
13
|
+
# This isn't set because it breaks usage with the error_handler/class_level_routing
|
|
14
|
+
# plugins and the shape_friendly plugin, due to those calling RodaResponse#initialize,
|
|
15
|
+
# which would reset @request to nil. It isn't strictly necessary to set this for
|
|
16
|
+
# shape friendliness, as the Roda#initialize sets it directly after creating the
|
|
17
|
+
# RodaRequest, so in normal use, the instance variable will already be set.
|
|
18
|
+
# RESPONSE_INSTANCE_VARIABLES = [:@request].freeze
|
|
19
|
+
|
|
13
20
|
module InstanceMethods
|
|
14
21
|
# Set the response's request to the current request.
|
|
15
22
|
def initialize(env)
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
class Roda
|
|
4
4
|
module RodaPlugins
|
|
5
|
-
# The
|
|
5
|
+
# The sec_fetch_site_csrf plugin allows for CSRF protection using the
|
|
6
6
|
# Sec-Fetch-Site header added in modern browsers. It allows for CSRF
|
|
7
7
|
# protection without the use of CSRF tokens, which can simplify
|
|
8
8
|
# form creation.
|
|
9
9
|
#
|
|
10
|
-
# The protection offered by the
|
|
10
|
+
# The protection offered by the sec_fetch_site_csrf plugin is weaker than
|
|
11
11
|
# the protection offered by the route_csrf plugin with default settings,
|
|
12
12
|
# since it doesn't support per-request tokens. Be aware you are trading
|
|
13
|
-
# security for simplicity when using the
|
|
14
|
-
# of the route_csrf plugin. Other caveats in using the
|
|
13
|
+
# security for simplicity when using the sec_fetch_site_csrf plugin instead
|
|
14
|
+
# of the route_csrf plugin. Other caveats in using the sec_fetch_site_csrf
|
|
15
15
|
# plugin:
|
|
16
16
|
#
|
|
17
17
|
# * Not all browsers set the Sec-Fetch-Site header. Some browsers
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The shape_friendly plugin makes the scope, request, and response objects
|
|
7
|
+
# used by Roda friendly to object shapes, which can result in better
|
|
8
|
+
# performance on modern versions of Ruby, especially when the JIT compiler
|
|
9
|
+
# is enabled. All plugins shipped with Roda integrate with this plugin.
|
|
10
|
+
#
|
|
11
|
+
# In order for this behavior to be beneficial for your application, you
|
|
12
|
+
# need to either avoid setting instance variables inside your routing
|
|
13
|
+
# tree, or you need to register each instance variable used, to ensure
|
|
14
|
+
# that all instances have the same shape.
|
|
15
|
+
#
|
|
16
|
+
# If you want to avoid using instance variables in your routing tree, you
|
|
17
|
+
# can switch to using local variables. If using the render plugin, you can
|
|
18
|
+
# pass data from your routing tree to your views using the +:locals+ option.
|
|
19
|
+
# When passing data from your routing tree to other routing trees, such as
|
|
20
|
+
# when using the hash_branches plugin, you can use the shared_vars plugin,
|
|
21
|
+
# which stores the data in the Rack environment.
|
|
22
|
+
#
|
|
23
|
+
# To register instance variables set in your applications routing tree,
|
|
24
|
+
# use a +:scope_instance_variables+ option when loading the plugin,
|
|
25
|
+
# listing the instance variable symbols you are setting in the route
|
|
26
|
+
# block scope:
|
|
27
|
+
#
|
|
28
|
+
# class MyApp < Roda
|
|
29
|
+
# plugin :shape_friendly, scope_instance_variables: [:@var1, @:var2]
|
|
30
|
+
#
|
|
31
|
+
# route do |r|
|
|
32
|
+
# r.root do
|
|
33
|
+
# @var1 = "a"
|
|
34
|
+
# @var1.inspect
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @var2 = "b"
|
|
38
|
+
# @var2.inspect
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# When developing external plugins, you can set one of three constants in
|
|
43
|
+
# the plugin module to integrate with this plugin to support shape friendly
|
|
44
|
+
# behavior:
|
|
45
|
+
#
|
|
46
|
+
# * +SCOPE_INSTANCE_VARIABLES+: For instance variables set in +InstanceMethods+.
|
|
47
|
+
# * +REQUEST_INSTANCE_VARIABLES+: For instance variables set in +RequestMethods+.
|
|
48
|
+
# * +RESPONSE_INSTANCE_VARIABLES+: For instance variables set in +ResponseMethods+.
|
|
49
|
+
#
|
|
50
|
+
# These constants are set in the plugin module, not in the plugin's
|
|
51
|
+
# +InstanceMethods+, +RequestMethods+, or +ResponseMethods+ modules, to
|
|
52
|
+
# avoid pollution of the related namespaces in the class loading the plugin.
|
|
53
|
+
module ShapeFriendly
|
|
54
|
+
# This is used by the base support and not by this plugin.
|
|
55
|
+
RESPONSE_INSTANCE_VARIABLES = [:@status].freeze
|
|
56
|
+
|
|
57
|
+
# Set the Roda application to create the private
|
|
58
|
+
# _initialize_nil_instance_variables for the plugin to use.
|
|
59
|
+
def self.configure(app, opts=OPTS)
|
|
60
|
+
if scope_ivs = opts[:scope_instance_variables]
|
|
61
|
+
app.opts[:shape_friendly_scope_instance_variables] = Array(scope_ivs).dup.freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
app.instance_exec do
|
|
65
|
+
def_initialize_nil_instance_variables(app, :SCOPE_INSTANCE_VARIABLES)
|
|
66
|
+
def_initialize_nil_instance_variables(app::RodaRequest, :REQUEST_INSTANCE_VARIABLES)
|
|
67
|
+
def_initialize_nil_instance_variables(app::RodaResponse, :RESPONSE_INSTANCE_VARIABLES)
|
|
68
|
+
|
|
69
|
+
# Avoid overhead of super call if possible
|
|
70
|
+
include(instance_method(:initialize).owner == Roda::RodaPlugins::Base::InstanceMethods ? OptimizedInstanceMethods : UnoptimizedInstanceMethods)
|
|
71
|
+
app::RodaRequest.send(:include, app::RodaRequest.instance_method(:initialize).owner == Roda::RodaPlugins::Base::RequestMethods ? OptimizedRequestMethods : UnoptimizedRequestMethods)
|
|
72
|
+
app::RodaResponse.send(:include, app::RodaResponse.instance_method(:initialize).owner == Roda::RodaPlugins::Base::ResponseMethods ? OptimizedResponseMethods : UnoptimizedResponseMethods)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
module ClassMethods
|
|
77
|
+
# Automatically refresh the instance variables used if the plugin
|
|
78
|
+
# sets instance variables.
|
|
79
|
+
def plugin(plugin, *args, &block)
|
|
80
|
+
super
|
|
81
|
+
plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
|
82
|
+
[
|
|
83
|
+
[self, :SCOPE_INSTANCE_VARIABLES],
|
|
84
|
+
[self::RodaRequest, :REQUEST_INSTANCE_VARIABLES],
|
|
85
|
+
[self::RodaResponse, :RESPONSE_INSTANCE_VARIABLES],
|
|
86
|
+
].each do |klass, ivs_const|
|
|
87
|
+
if plugin.const_defined?(ivs_const)
|
|
88
|
+
def_initialize_nil_instance_variables(klass, ivs_const)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
# :nocov:
|
|
94
|
+
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
|
95
|
+
# :nocov:
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# If there are any intance variables configured in one of the plugins
|
|
100
|
+
# (looking for the +const+ constant in the plugin), override the
|
|
101
|
+
# private _initialize_nil_instance_variables method for the class,
|
|
102
|
+
# and have it initialize each instance variable to nil.
|
|
103
|
+
def def_initialize_nil_instance_variables(klass, const)
|
|
104
|
+
ivs = []
|
|
105
|
+
|
|
106
|
+
plugins.each do |mod|
|
|
107
|
+
ivs.concat(mod.const_get(const)) if mod.const_defined?(const)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if const == :SCOPE_INSTANCE_VARIABLES && (scope_ivs = opts[:shape_friendly_scope_instance_variables])
|
|
111
|
+
ivs.concat(scope_ivs)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
ivs.each do |iv|
|
|
115
|
+
unless /\A@[a-z_][a-z0-9_]*\z/.match(iv)
|
|
116
|
+
raise RodaError, "invalid scope instance variable used"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
unless ivs.empty?
|
|
121
|
+
ivs.uniq!
|
|
122
|
+
|
|
123
|
+
klass.class_eval(<<-RUBY, __FILE__, __LINE__+1)
|
|
124
|
+
def _initialize_nil_instance_variables
|
|
125
|
+
#{ivs.reverse.join(" = ")} = nil
|
|
126
|
+
end
|
|
127
|
+
private :_initialize_nil_instance_variables
|
|
128
|
+
alias _initialize_nil_instance_variables _initialize_nil_instance_variables
|
|
129
|
+
RUBY
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# :nocov:
|
|
136
|
+
if RUBY_VERSION >= '4.0'
|
|
137
|
+
# :nocov:
|
|
138
|
+
module InstanceMethods
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def instance_variables_to_inspect
|
|
142
|
+
instance_variables.reject{|v| instance_variable_get(v).nil?}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
module OptimizedInstanceMethods
|
|
148
|
+
# Initialize configured instance variables to nil.
|
|
149
|
+
def initialize(env)
|
|
150
|
+
_initialize_nil_instance_variables
|
|
151
|
+
klass = self.class
|
|
152
|
+
@_request = klass::RodaRequest.new(self, env)
|
|
153
|
+
@_response = klass::RodaResponse.new
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def _initialize_nil_instance_variables
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
module UnoptimizedInstanceMethods
|
|
164
|
+
# Initialize configured instance variables to nil.
|
|
165
|
+
def initialize(env)
|
|
166
|
+
_initialize_nil_instance_variables
|
|
167
|
+
super
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def _initialize_nil_instance_variables
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
module OptimizedRequestMethods
|
|
178
|
+
# Initialize configured instance variables to nil.
|
|
179
|
+
def initialize(scope, env)
|
|
180
|
+
_initialize_nil_instance_variables
|
|
181
|
+
@scope = scope
|
|
182
|
+
@captures = []
|
|
183
|
+
@remaining_path = _remaining_path(env)
|
|
184
|
+
@env = env
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def _initialize_nil_instance_variables
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
module UnoptimizedRequestMethods
|
|
195
|
+
# Initialize configured instance variables to nil.
|
|
196
|
+
def initialize(scope, env)
|
|
197
|
+
_initialize_nil_instance_variables
|
|
198
|
+
super
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def _initialize_nil_instance_variables
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
module OptimizedResponseMethods
|
|
209
|
+
def initialize
|
|
210
|
+
_initialize_nil_instance_variables
|
|
211
|
+
@headers = _initialize_headers
|
|
212
|
+
@body = []
|
|
213
|
+
@length = 0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
module UnoptimizedResponseMethods
|
|
218
|
+
# Initialize configured instance variables to nil.
|
|
219
|
+
def initialize
|
|
220
|
+
_initialize_nil_instance_variables
|
|
221
|
+
super
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# For response methods, there is no default definition for
|
|
226
|
+
# _initialize_nil_instance_variables, as the plugin will always
|
|
227
|
+
# define a method in the class it is loaded into.
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
register_plugin(:shape_friendly, ShapeFriendly)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -19,6 +19,15 @@ class Roda
|
|
|
19
19
|
# Then the route will only if the path is +/foobar123+, but not if it is
|
|
20
20
|
# +/foo+, +/FooBar123+, or +/foobar_123+.
|
|
21
21
|
#
|
|
22
|
+
# You can provide a <tt>segment: true</tt> option to symbol_matcher to speed up
|
|
23
|
+
# the matching on Ruby 2.4+:
|
|
24
|
+
#
|
|
25
|
+
# symbol_matcher :username, /([a-z0-9]{6,20})/, segment: true
|
|
26
|
+
#
|
|
27
|
+
# Use of <tt>segment: true</tt> requires that the regexp not match more than
|
|
28
|
+
# one segment (i.e. it cannot match +/+). Additionally, the entire segment will
|
|
29
|
+
# be captured, so any capture groups in the regexp will be ignored.
|
|
30
|
+
#
|
|
22
31
|
# By default, this plugin sets up the following symbol matchers:
|
|
23
32
|
#
|
|
24
33
|
# :d :: <tt>/(\d+)/</tt>, a decimal segment
|
|
@@ -106,8 +115,8 @@ class Roda
|
|
|
106
115
|
|
|
107
116
|
def self.configure(app)
|
|
108
117
|
app.opts[:symbol_matchers] ||= {}
|
|
109
|
-
app.symbol_matcher(:d, /(\d+)
|
|
110
|
-
app.symbol_matcher(:w, /(\w+)
|
|
118
|
+
app.symbol_matcher(:d, /(\d+)/, segment: true)
|
|
119
|
+
app.symbol_matcher(:w, /(\w+)/, segment: true)
|
|
111
120
|
app.symbol_matcher(:rest, /(.*)/)
|
|
112
121
|
end
|
|
113
122
|
|
|
@@ -122,8 +131,8 @@ class Roda
|
|
|
122
131
|
# captures if no block was registered with the symbol or class. In either case,
|
|
123
132
|
# if a block is given, it should return an array with the captures to yield to
|
|
124
133
|
# the match block.
|
|
125
|
-
def symbol_matcher(s, matcher, &block)
|
|
126
|
-
_symbol_class_matcher(Symbol, s, matcher, block) do |meth, array|
|
|
134
|
+
def symbol_matcher(s, matcher, opts=OPTS, &block)
|
|
135
|
+
_symbol_class_matcher(Symbol, s, matcher, block, opts) do |meth, array|
|
|
127
136
|
define_method(meth){array}
|
|
128
137
|
end
|
|
129
138
|
|
|
@@ -147,9 +156,9 @@ class Roda
|
|
|
147
156
|
meth = :"match_symbol_#{s}"
|
|
148
157
|
if respond_to?(meth, true)
|
|
149
158
|
# Allow calling private match methods
|
|
150
|
-
_, re, convert_meth = send(meth)
|
|
159
|
+
_, re, convert_meth, consume_meth = send(meth)
|
|
151
160
|
if re
|
|
152
|
-
|
|
161
|
+
send(consume_meth, re, convert_meth)
|
|
153
162
|
else
|
|
154
163
|
# defined in class_matchers plugin
|
|
155
164
|
_consume_segment(convert_meth)
|
|
@@ -89,6 +89,8 @@ class Roda
|
|
|
89
89
|
# :use_header :: Whether to take the +Accept+ header into account.
|
|
90
90
|
# Default is +true+.
|
|
91
91
|
module TypeRouting
|
|
92
|
+
REQUEST_INSTANCE_VARIABLES = [:@requested_type, :@type_routing_extension].freeze
|
|
93
|
+
|
|
92
94
|
CONFIGURATION = {
|
|
93
95
|
:mimes => {
|
|
94
96
|
'text/json' => :json,
|
|
@@ -158,7 +160,7 @@ class Roda
|
|
|
158
160
|
|
|
159
161
|
# Returns the data type the client requests.
|
|
160
162
|
def requested_type
|
|
161
|
-
return @requested_type if
|
|
163
|
+
return @requested_type if @requested_type
|
|
162
164
|
|
|
163
165
|
opts = @scope.opts[:type_routing]
|
|
164
166
|
@requested_type = accept_response_type if opts[:use_header]
|
|
@@ -168,7 +170,7 @@ class Roda
|
|
|
168
170
|
# Append the type routing extension back to the path if it was
|
|
169
171
|
# removed before routing.
|
|
170
172
|
def real_remaining_path
|
|
171
|
-
if
|
|
173
|
+
if @type_routing_extension
|
|
172
174
|
"#{super}.#{@type_routing_extension}"
|
|
173
175
|
else
|
|
174
176
|
super
|
|
@@ -307,6 +307,8 @@ class Roda
|
|
|
307
307
|
# By design, typecast_params only deals with string keys, it is not possible to use
|
|
308
308
|
# symbol keys as arguments to the conversion methods and have them converted.
|
|
309
309
|
module TypecastParams
|
|
310
|
+
SCOPE_INSTANCE_VARIABLES = [:@_typecast_params, :@_typecast_query_params, :@_typecast_body_params].freeze
|
|
311
|
+
|
|
310
312
|
# Sentinal value for whether to raise exception during #process
|
|
311
313
|
CHECK_NIL = Object.new.freeze
|
|
312
314
|
|
|
@@ -80,6 +80,8 @@ class Roda
|
|
|
80
80
|
# end
|
|
81
81
|
# end
|
|
82
82
|
module ViewOptions
|
|
83
|
+
SCOPE_INSTANCE_VARIABLES = [:@_view_subdir, :@_view_options, :@_layout_options].freeze
|
|
84
|
+
|
|
83
85
|
# Load the render plugin before this plugin, since this plugin
|
|
84
86
|
# works by overriding methods in the render plugin.
|
|
85
87
|
def self.load_dependencies(app)
|
data/lib/roda/request.rb
CHANGED
|
@@ -444,7 +444,7 @@ class Roda
|
|
|
444
444
|
# Match integer segment of up to 100 decimal characters, and yield resulting value as an
|
|
445
445
|
# integer.
|
|
446
446
|
def _match_class_Integer
|
|
447
|
-
|
|
447
|
+
_consume_single_segment(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
|
|
448
448
|
end
|
|
449
449
|
|
|
450
450
|
# Match only if all of the arguments in the given array match.
|
|
@@ -585,6 +585,39 @@ class Roda
|
|
|
585
585
|
end
|
|
586
586
|
end
|
|
587
587
|
|
|
588
|
+
if RUBY_VERSION >= "2.4"
|
|
589
|
+
# A faster version of consume that can be used if you are sure the regexp
|
|
590
|
+
# will match a single segment if it matches, capturing the entire segment.
|
|
591
|
+
def _consume_single_segment(regexp, meth=nil)
|
|
592
|
+
rp = @remaining_path
|
|
593
|
+
if regexp.match?(rp)
|
|
594
|
+
if last = rp.index('/', 1)
|
|
595
|
+
val = rp[1, last-1]
|
|
596
|
+
@remaining_path = rp[last, rp.length]
|
|
597
|
+
else
|
|
598
|
+
val = rp[1, rp.length]
|
|
599
|
+
@remaining_path = ""
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
if meth
|
|
603
|
+
if captures = scope.send(meth, val)
|
|
604
|
+
if captures.is_a?(Array)
|
|
605
|
+
@captures.concat(captures)
|
|
606
|
+
else
|
|
607
|
+
@captures << captures
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
else
|
|
611
|
+
@captures << val
|
|
612
|
+
end
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
# :nocov:
|
|
616
|
+
else
|
|
617
|
+
alias _consume_single_segment consume
|
|
618
|
+
# :nocov:
|
|
619
|
+
end
|
|
620
|
+
|
|
588
621
|
# The default path to use for redirects when a path is not given.
|
|
589
622
|
# For non-GET requests, redirects to the current path, which will
|
|
590
623
|
# trigger a GET request. This is to make the common case where
|
|
@@ -29,6 +29,7 @@ class RodaSessionMiddleware
|
|
|
29
29
|
|
|
30
30
|
def initialize(req)
|
|
31
31
|
@req = req
|
|
32
|
+
@data = nil
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
# The Roda sessions plugin options used by the middleware for this
|
|
@@ -124,7 +125,7 @@ class RodaSessionMiddleware
|
|
|
124
125
|
|
|
125
126
|
# Whether the session has already been loaded from the cookie yet.
|
|
126
127
|
def loaded?
|
|
127
|
-
|
|
128
|
+
!!@data
|
|
128
129
|
end
|
|
129
130
|
|
|
130
131
|
def empty?
|
data/lib/roda/version.rb
CHANGED
data/lib/roda.rb
CHANGED
|
@@ -17,6 +17,7 @@ class Roda
|
|
|
17
17
|
@app = nil
|
|
18
18
|
@inherit_middleware = true
|
|
19
19
|
@middleware = []
|
|
20
|
+
@plugins = []
|
|
20
21
|
@opts = {}
|
|
21
22
|
@raw_route_block = nil
|
|
22
23
|
@route_block = nil
|
|
@@ -42,6 +43,9 @@ class Roda
|
|
|
42
43
|
# The settings/options hash for the current class.
|
|
43
44
|
attr_reader :opts
|
|
44
45
|
|
|
46
|
+
# The plugins loaded into the current class.
|
|
47
|
+
attr_reader :plugins
|
|
48
|
+
|
|
45
49
|
# The route block that this class uses.
|
|
46
50
|
attr_reader :route_block
|
|
47
51
|
|
|
@@ -217,13 +221,14 @@ class Roda
|
|
|
217
221
|
plugin :direct_call
|
|
218
222
|
end
|
|
219
223
|
|
|
220
|
-
if ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
|
|
224
|
+
if RUBY_VERSION > "2.4" && ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
|
|
221
225
|
plugin :_optimized_matching
|
|
222
226
|
end
|
|
223
227
|
end
|
|
224
228
|
|
|
225
229
|
build_rack_app
|
|
226
230
|
@opts.freeze
|
|
231
|
+
@plugins.freeze
|
|
227
232
|
@middleware.freeze
|
|
228
233
|
|
|
229
234
|
super
|
|
@@ -250,6 +255,7 @@ class Roda
|
|
|
250
255
|
super
|
|
251
256
|
subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
|
|
252
257
|
subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
|
|
258
|
+
subclass.instance_variable_set(:@plugins, @plugins.dup)
|
|
253
259
|
subclass.instance_variable_set(:@opts, opts.dup)
|
|
254
260
|
subclass.opts.delete(:subclassed)
|
|
255
261
|
subclass.opts.to_a.each do |k,v|
|
|
@@ -291,6 +297,7 @@ class Roda
|
|
|
291
297
|
end
|
|
292
298
|
|
|
293
299
|
plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
|
|
300
|
+
@plugins << plugin unless @plugins.include?(plugin)
|
|
294
301
|
include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
|
|
295
302
|
extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
|
|
296
303
|
self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.106.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -292,6 +292,7 @@ files:
|
|
|
292
292
|
- lib/roda/plugins/sec_fetch_site_csrf.rb
|
|
293
293
|
- lib/roda/plugins/send_file.rb
|
|
294
294
|
- lib/roda/plugins/sessions.rb
|
|
295
|
+
- lib/roda/plugins/shape_friendly.rb
|
|
295
296
|
- lib/roda/plugins/shared_vars.rb
|
|
296
297
|
- lib/roda/plugins/sinatra_helpers.rb
|
|
297
298
|
- lib/roda/plugins/slash_path_empty.rb
|