roda 3.45.0 → 3.49.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dbdf55425a681a0cb32fa0b4a44cd50bf5be6f85c78c1ea1adbdd19e28c2d61
4
- data.tar.gz: e91ee695e5f8648f4269a2c229b794ef2ee921f4291102519d4ca2cadd9bf13a
3
+ metadata.gz: 2d3b9688bae8b0013acedfc6a5bcb45c374e0a73fd30e56985f0ebeee942ef8c
4
+ data.tar.gz: acb8215250d1615f007a2ec01d2b6546376414d2fea23a4f2341af9efd951110
5
5
  SHA512:
6
- metadata.gz: aa46429e09091c3cf0112c251a9863845c69d4245674dcaddaec030244213d8b9ac1aae96cfa6093680c33f87c732ee4464e4cbe2572d192eb0a9f7e09acd43b
7
- data.tar.gz: 57cf84f7db6e6334918f0c01f7308c4c48db7e0ba9f2a14fe87420c3b2bd17471a229ec02a2ad9242ec6fd4aa67848f6dff04b319fea361a985dd264b52252ab
6
+ metadata.gz: 3a5434bdcb6926d6c5e656c25b46e16b035ad54d74ed16bdc1cf46005b8ef605b3371a628cae52d65803064aa3d2eac8a56c2e8e1e55b6f3a82c04a74c636855
7
+ data.tar.gz: 9dc3773fae04325373adb8a7a57d52166bb65c238b959999adf6f6f17a3be29fcdedcea677eeeaaa84f6a01b5accb4aff2594817165b8a77a45106a3d4a9dea3
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ = 3.49.0 (2021-10-13)
2
+
3
+ * Switch block_given? to defined?(yield) (jeremyevans)
4
+
5
+ * Automatically optimize remaining r.is/r.get/r.post calls with a single argument (jeremyevans)
6
+
7
+ = 3.48.0 (2021-09-13)
8
+
9
+ * Extract named_routes plugin from multi_route plugin (jeremyevans)
10
+
11
+ = 3.47.0 (2021-08-13)
12
+
13
+ * Automatically optimize remaining r.on calls with a single argument (jeremyevans)
14
+
15
+ = 3.46.0 (2021-07-12)
16
+
17
+ * Automatically optimize r.on/r.is/r.get/r.post methods with a single string, String, Integer, or regexp argument (jeremyevans)
18
+
1
19
  = 3.45.0 (2021-06-14)
2
20
 
3
21
  * Make typecast_params plugin check for null bytes in strings by default, with :allow_null_bytes option for previous behavior (jeremyevans)
data/README.rdoc CHANGED
@@ -13,7 +13,6 @@ Website :: http://roda.jeremyevans.net
13
13
  Source :: http://github.com/jeremyevans/roda
14
14
  Bugs :: http://github.com/jeremyevans/roda/issues
15
15
  Google Group :: http://groups.google.com/group/ruby-roda
16
- IRC :: irc://chat.freenode.net/#roda
17
16
 
18
17
  == Goals
19
18
 
@@ -1110,7 +1109,8 @@ Roda's plugin system is based on the plugin system used by
1110
1109
  Roda fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
1111
1110
  support unsupported versions of Ruby or JRuby, but such support may be dropped in any
1112
1111
  minor version if keeping it becomes a support issue. The minimum Ruby version
1113
- required to run the current version of Roda is 1.9.2.
1112
+ required to run the current version of Roda is 1.9.2, and the minimum JRuby version is
1113
+ 9.0.0.0.
1114
1114
 
1115
1115
  == License
1116
1116
 
@@ -0,0 +1,19 @@
1
+ = Improvements
2
+
3
+ * The r.on, r.is, r.get and r.post methods (and other verb methods
4
+ if using the all_verbs plugin) have now been optimized when using
5
+ a single string or regexp matcher, or the String or Integer class
6
+ matcher. Since those four matchers are the most common types of
7
+ matchers passed to the methods, this can significantly improve
8
+ routing performance (about 50% in the r10k benchmark).
9
+
10
+ This optimization is automatically applied when freezing
11
+ applications, if the related methods have not been modified by
12
+ plugins.
13
+
14
+ This optimization does come at the expense of a small decrease
15
+ in routing performance (3-4%) for unoptimized cases, but the
16
+ majority of applications will see a overall performance benefit
17
+ from this change.
18
+
19
+ * Other minor performance improvements have been made.
@@ -0,0 +1,13 @@
1
+ = Improvements
2
+
3
+ * The r.on optimization added in 3.46.0 has been extended to optimize
4
+ all single argument calls. This results in the following speedups
5
+ based on argument type:
6
+
7
+ * Hash matching: 10%
8
+ * Array/Symbol/Class matching: 15%
9
+ * Proc matching: 25%
10
+ * true matching: 45%
11
+ * false/nil matching: 65%
12
+
13
+ * Other minor performance improvements have been made.
@@ -0,0 +1,10 @@
1
+ = New Features
2
+
3
+ * A named_routes plugin has been added, for defining named route
4
+ blocks that you can dispatch to with r.route. This feature was
5
+ previously available as part of the multi_route plugin, but there
6
+ are cases where the r.route method and support for named routes is
7
+ helpful even when the multi_route plugin is not used (such as when
8
+ the hash_routes plugin is used instead of the multi_route plugin).
9
+ The multi_route plugin now depends on the named_routes plugin, so
10
+ this change should not cause any backwards compatibility issues.
@@ -0,0 +1,18 @@
1
+ = Improvements
2
+
3
+ * The r.is optimization added in 3.46.0 has been extended to optimize
4
+ all single argument calls. This results in the following speedups
5
+ based on argument type:
6
+
7
+ * Hash/Class matching: 20%
8
+ * Symbol matching: 25%
9
+ * Array matching: 35%
10
+ * Proc matching: 50%
11
+ * false/nil matching: 65%
12
+
13
+ * Roda now uses defined?(yield) instead of block_given? internally
14
+ for better performance on CRuby. defined?(yield) is faster as it is
15
+ built into the VM, while block_given? is a regular method and has
16
+ the overhead of calling a regular method. Note that defined?(yield)
17
+ is not implemented correctly on JRuby before 9.0.0.0, so this
18
+ release of Roda drops support for JRuby versions before 9.0.0.0.
@@ -0,0 +1,215 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The _optimized_matching plugin is automatically used internally to speed
7
+ # up matching when a single argument String instance, String class, Integer
8
+ # class, or Regexp matcher is passed to +r.on+, +r.is_+, or a verb method
9
+ # such as +r.get+ or +r.post+.
10
+ #
11
+ # The optimization works by avoiding the +if_match+ method if possible.
12
+ # Instead of clearing the captures array on every call, and having the
13
+ # matching append to the captures, it checks directly for the match,
14
+ # and on succesful match, it yields directly to the block without using
15
+ # the captures array.
16
+ module OptimizedMatching
17
+ TERM = Base::RequestMethods::TERM
18
+
19
+ module RequestMethods
20
+ # Optimize the r.is method handling of a single string, String, Integer,
21
+ # regexp, or true, argument.
22
+ def is(*args, &block)
23
+ case args.length
24
+ when 1
25
+ _is1(args, &block)
26
+ when 0
27
+ always(&block) if @remaining_path.empty?
28
+ else
29
+ if_match(args << TERM, &block)
30
+ end
31
+ end
32
+
33
+ # Optimize the r.on method handling of a single string, String, Integer,
34
+ # or regexp argument. Inline the related matching code to avoid the
35
+ # need to modify @captures.
36
+ def on(*args, &block)
37
+ case args.length
38
+ when 1
39
+ case matcher = args[0]
40
+ when String
41
+ always{yield} if _match_string(matcher)
42
+ when Class
43
+ if matcher == String
44
+ rp = @remaining_path
45
+ if rp.getbyte(0) == 47
46
+ if last = rp.index('/', 1)
47
+ @remaining_path = rp[last, rp.length]
48
+ always{yield rp[1, last-1]}
49
+ elsif (len = rp.length) > 1
50
+ @remaining_path = ""
51
+ always{yield rp[1, len]}
52
+ end
53
+ end
54
+ elsif matcher == Integer
55
+ if matchdata = /\A\/(\d+)(?=\/|\z)/.match(@remaining_path)
56
+ @remaining_path = matchdata.post_match
57
+ always{yield(matchdata[1].to_i)}
58
+ end
59
+ else
60
+ path = @remaining_path
61
+ captures = @captures.clear
62
+ meth = :"_match_class_#{matcher}"
63
+ if respond_to?(meth, true)
64
+ # Allow calling private methods, as match methods are generally private
65
+ if send(meth, &block)
66
+ block_result(yield(*captures))
67
+ throw :halt, response.finish
68
+ else
69
+ @remaining_path = path
70
+ false
71
+ end
72
+ else
73
+ unsupported_matcher(matcher)
74
+ end
75
+ end
76
+ when Regexp
77
+ if matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)
78
+ @remaining_path = matchdata.post_match
79
+ always{yield(*matchdata.captures)}
80
+ end
81
+ when true
82
+ always(&block)
83
+ when false, nil
84
+ # nothing
85
+ else
86
+ path = @remaining_path
87
+ captures = @captures.clear
88
+
89
+ matched = case matcher
90
+ when Array
91
+ _match_array(matcher)
92
+ when Hash
93
+ _match_hash(matcher)
94
+ when Symbol
95
+ _match_symbol(matcher)
96
+ when Proc
97
+ matcher.call
98
+ else
99
+ unsupported_matcher(matcher)
100
+ end
101
+
102
+ if matched
103
+ block_result(yield(*captures))
104
+ throw :halt, response.finish
105
+ else
106
+ @remaining_path = path
107
+ false
108
+ end
109
+ end
110
+ when 0
111
+ always(&block)
112
+ else
113
+ if_match(args, &block)
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ # Optimize the r.get/r.post method handling of a single string, String, Integer,
120
+ # regexp, or true, argument.
121
+ def _verb(args, &block)
122
+ case args.length
123
+ when 0
124
+ always(&block)
125
+ when 1
126
+ _is1(args, &block)
127
+ else
128
+ if_match(args << TERM, &block)
129
+ end
130
+ end
131
+
132
+ # Internals of r.is/r.get/r.post optimization. Inline the related matching
133
+ # code to avoid the need to modify @captures.
134
+ def _is1(args, &block)
135
+ case matcher = args[0]
136
+ when String
137
+ rp = @remaining_path
138
+ if _match_string(matcher)
139
+ if @remaining_path.empty?
140
+ always{yield}
141
+ else
142
+ @remaining_path = rp
143
+ nil
144
+ end
145
+ end
146
+ when Class
147
+ if matcher == String
148
+ rp = @remaining_path
149
+ if rp.getbyte(0) == 47 && !rp.index('/', 1) && (len = rp.length) > 1
150
+ @remaining_path = ''
151
+ always{yield rp[1, len]}
152
+ end
153
+ elsif matcher == Integer
154
+ if matchdata = /\A\/(\d+)\z/.match(@remaining_path)
155
+ @remaining_path = ''
156
+ always{yield(matchdata[1].to_i)}
157
+ end
158
+ else
159
+ path = @remaining_path
160
+ captures = @captures.clear
161
+ meth = :"_match_class_#{matcher}"
162
+ if respond_to?(meth, true)
163
+ # Allow calling private methods, as match methods are generally private
164
+ if send(meth, &block) && @remaining_path.empty?
165
+ block_result(yield(*captures))
166
+ throw :halt, response.finish
167
+ else
168
+ @remaining_path = path
169
+ false
170
+ end
171
+ else
172
+ unsupported_matcher(matcher)
173
+ end
174
+ end
175
+ when Regexp
176
+ if (matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)) && matchdata.post_match.empty?
177
+ @remaining_path = ''
178
+ always{yield(*matchdata.captures)}
179
+ end
180
+ when true
181
+ always(&block) if @remaining_path.empty?
182
+ when false, nil
183
+ # nothing
184
+ else
185
+ path = @remaining_path
186
+ captures = @captures.clear
187
+
188
+ matched = case matcher
189
+ when Array
190
+ _match_array(matcher)
191
+ when Hash
192
+ _match_hash(matcher)
193
+ when Symbol
194
+ _match_symbol(matcher)
195
+ when Proc
196
+ matcher.call
197
+ else
198
+ unsupported_matcher(matcher)
199
+ end
200
+
201
+ if matched && @remaining_path.empty?
202
+ block_result(yield(*captures))
203
+ throw :halt, response.finish
204
+ else
205
+ @remaining_path = path
206
+ false
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ register_plugin(:_optimized_matching, OptimizedMatching)
214
+ end
215
+ end
@@ -62,8 +62,7 @@ class Roda
62
62
  end
63
63
 
64
64
  # Configure whether to append or overwrite if content_for
65
- # is called multiple times to set data. Overwrite is default, use
66
- # the :append option to append.
65
+ # is called multiple times with the same key.
67
66
  def self.configure(app, opts = OPTS)
68
67
  app.opts[:append_content_for] = opts.fetch(:append, true)
69
68
  end
@@ -76,8 +75,8 @@ class Roda
76
75
  def content_for(key, value=nil)
77
76
  append = opts[:append_content_for]
78
77
 
79
- if block_given? || value
80
- if block_given?
78
+ if defined?(yield) || value
79
+ if defined?(yield)
81
80
  outvar = render_opts[:template_opts][:outvar]
82
81
  buf_was = instance_variable_get(outvar)
83
82
 
@@ -278,7 +278,7 @@ class Roda
278
278
  Policy.new
279
279
  end
280
280
 
281
- yield policy if block_given?
281
+ yield policy if defined?(yield)
282
282
  policy.freeze
283
283
  end
284
284
 
@@ -287,7 +287,7 @@ class Roda
287
287
  # current content security policy.
288
288
  def content_security_policy
289
289
  policy = @_response.content_security_policy
290
- yield policy if block_given?
290
+ yield policy if defined?(yield)
291
291
  policy
292
292
  end
293
293
  end
@@ -566,7 +566,7 @@ class Roda
566
566
  end
567
567
  false
568
568
  when Regexp
569
- if md = content.match(val)
569
+ if md = val.match(content)
570
570
  @captures.concat(md.captures)
571
571
  end
572
572
  else
@@ -72,7 +72,7 @@ class Roda
72
72
  end
73
73
 
74
74
  if mod
75
- raise RodaError, "can't provide both argument and block to response_module" if block_given?
75
+ raise RodaError, "can't provide both argument and block to response_module" if defined?(yield)
76
76
  klass.send(:include, mod)
77
77
  else
78
78
  if instance_variable_defined?(iv)
@@ -3,15 +3,10 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The multi_route plugin allows for multiple named routes, which the
7
- # main route block can dispatch to by name at any point by calling +route+.
8
- # If the named route doesn't handle the request, execution will continue,
9
- # and if the named route does handle the request, the response returned by
10
- # the named route will be returned.
11
- #
12
- # In addition, this plugin adds the +r.multi_route+ method, which will check
13
- # if the first segment in the path matches a named route, and dispatch
14
- # to that named route.
6
+ # The multi_route plugin builds on the named_routes plugin and allows for
7
+ # dispatching to multiple named routes # by calling the +r.multi_route+ method,
8
+ # which will check # if the first segment in the path matches a named route,
9
+ # and dispatch to that named route.
15
10
  #
16
11
  # The hash_routes plugin offers a +r.hash_routes+ method that is similar to
17
12
  # and performs better than the +r.multi_route+ method, and it is recommended
@@ -35,23 +30,14 @@ class Roda
35
30
  #
36
31
  # route do |r|
37
32
  # r.multi_route
38
- #
39
- # # or
40
- #
41
- # r.on "foo" do
42
- # r.route 'foo'
43
- # end
44
- #
45
- # r.on "bar" do
46
- # r.route 'bar'
47
- # end
48
33
  # end
49
34
  #
50
- # Note that in multi-threaded code, you should not attempt to add a
51
- # named route after accepting requests.
35
+ # Note that only named routes with string names will be dispatched to by the
36
+ # +r.multi_route+ method. Named routes with other names can be dispatched to
37
+ # using the named_routes plugin API, but will not be automatically dispatched
38
+ # to by +r.multi_route+.
52
39
  #
53
- # If you want to use the +r.multi_route+ method, use string names for the
54
- # named routes. Also, you can provide a block to +r.multi_route+ that is
40
+ # You can provide a block to +r.multi_route+ that is
55
41
  # called if the route matches but the named route did not handle the
56
42
  # request:
57
43
  #
@@ -62,20 +48,10 @@ class Roda
62
48
  # If a block is not provided to multi_route, the return value of the named
63
49
  # route block will be used.
64
50
  #
65
- # == Routing Files
66
- #
67
- # The convention when using the multi_route plugin is to have a single
68
- # named route per file, and these routing files should be stored in
69
- # a routes subdirectory in your application. So for the above example, you
70
- # would use the following files:
71
- #
72
- # routes/bar.rb
73
- # routes/foo.rb
74
- #
75
51
  # == Namespace Support
76
52
  #
77
53
  # The multi_route plugin also has support for namespaces, allowing you to
78
- # use r.multi_route at multiple levels in your routing tree. Example:
54
+ # use +r.multi_route+ at multiple levels in your routing tree. Example:
79
55
  #
80
56
  # route('foo') do |r|
81
57
  # r.multi_route('foo')
@@ -103,81 +79,38 @@ class Roda
103
79
  #
104
80
  # route do |r|
105
81
  # r.multi_route
106
- #
107
- # # or
108
- #
109
- # r.on "foo" do
110
- # r.on("baz"){r.route("baz", "foo")}
111
- # r.on("quux"){r.route("quux", "foo")}
112
- # end
113
- #
114
- # r.on "bar" do
115
- # r.on("baz"){r.route("baz", "bar")}
116
- # r.on("quux"){r.route("quux", "bar")}
117
- # end
118
82
  # end
119
- #
120
- # === Routing Files
121
- #
122
- # The convention when using namespaces with the multi_route plugin is to
123
- # store the routing files in subdirectories per namespace. So for the
124
- # above example, you would have the following routing files:
125
- #
126
- # routes/bar.rb
127
- # routes/bar/baz.rb
128
- # routes/bar/quux.rb
129
- # routes/foo.rb
130
- # routes/foo/baz.rb
131
- # routes/foo/quux.rb
132
83
  module MultiRoute
84
+ def self.load_dependencies(app)
85
+ app.plugin :named_routes
86
+ end
87
+
133
88
  # Initialize storage for the named routes.
134
89
  def self.configure(app)
135
- app.opts[:namespaced_routes] ||= {}
136
90
  app::RodaRequest.instance_variable_set(:@namespaced_route_regexps, {})
137
91
  end
138
92
 
139
93
  module ClassMethods
140
- # Freeze the namespaced routes and setup the regexp matchers so that there can be no thread safety issues at runtime.
94
+ # Freeze the multi_route regexp matchers so that there can be no thread safety issues at runtime.
141
95
  def freeze
142
- opts[:namespaced_routes].freeze.each do |k,v|
143
- v.freeze
96
+ super
97
+ opts[:namespaced_routes].each_key do |k|
144
98
  self::RodaRequest.named_route_regexp(k)
145
99
  end
146
100
  self::RodaRequest.instance_variable_get(:@namespaced_route_regexps).freeze
147
- super
148
101
  end
149
102
 
150
103
  # Copy the named routes into the subclass when inheriting.
151
104
  def inherited(subclass)
152
105
  super
153
- nsr = subclass.opts[:namespaced_routes]
154
- opts[:namespaced_routes].each{|k, v| nsr[k] = v.dup}
155
106
  subclass::RodaRequest.instance_variable_set(:@namespaced_route_regexps, {})
156
107
  end
157
108
 
158
- # The names for the currently stored named routes
159
- def named_routes(namespace=nil)
160
- unless routes = opts[:namespaced_routes][namespace]
161
- raise RodaError, "unsupported multi_route namespace used: #{namespace.inspect}"
162
- end
163
- routes.keys
164
- end
165
-
166
- # Return the named route with the given name.
167
- def named_route(name, namespace=nil)
168
- opts[:namespaced_routes][namespace][name]
169
- end
170
-
171
- # If the given route has a name, treat it as a named route and
172
- # store the route block. Otherwise, this is the main route, so
173
- # call super.
109
+ # Clear the multi_route regexp matcher for the namespace.
174
110
  def route(name=nil, namespace=nil, &block)
111
+ super
175
112
  if name
176
- routes = opts[:namespaced_routes][namespace] ||= {}
177
- routes[name] = define_roda_method(routes[name] || "multi_route_#{namespace}_#{name}", 1, &convert_route_block(block))
178
113
  self::RodaRequest.clear_named_route_regexp!(namespace)
179
- else
180
- super(&block)
181
114
  end
182
115
  end
183
116
  end
@@ -206,19 +139,14 @@ class Roda
206
139
  # is given, yield to the block.
207
140
  def multi_route(namespace=nil)
208
141
  on self.class.named_route_regexp(namespace) do |section|
209
- r = route(section, namespace)
210
- if block_given?
142
+ res = route(section, namespace)
143
+ if defined?(yield)
211
144
  yield
212
145
  else
213
- r
146
+ res
214
147
  end
215
148
  end
216
149
  end
217
-
218
- # Dispatch to the named route with the given name.
219
- def route(name, namespace=nil)
220
- scope.send(roda_class.named_route(name, namespace), self)
221
- end
222
150
  end
223
151
  end
224
152
 
@@ -91,7 +91,7 @@ class Roda
91
91
  # dispatch the request to the stored rack application.
92
92
  def multi_run
93
93
  on self.class.multi_run_regexp do |prefix|
94
- yield prefix if block_given?
94
+ yield prefix if defined?(yield)
95
95
  run scope.class.multi_run_apps[prefix]
96
96
  end
97
97
  end
@@ -0,0 +1,160 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The named_routes plugin allows for multiple named routes, which the
7
+ # main route block can dispatch to by name at any point by calling +r.route+.
8
+ # If the named route doesn't handle the request, execution will continue,
9
+ # and if the named route does handle the request, the response returned by
10
+ # the named route will be returned.
11
+ #
12
+ # Example:
13
+ #
14
+ # plugin :multi_route
15
+ #
16
+ # route('foo') do |r|
17
+ # r.is 'bar' do
18
+ # '/foo/bar'
19
+ # end
20
+ # end
21
+ #
22
+ # route('bar') do |r|
23
+ # r.is 'foo' do
24
+ # '/bar/foo'
25
+ # end
26
+ # end
27
+ #
28
+ # route do |r|
29
+ # r.on "foo" do
30
+ # r.route 'foo'
31
+ # end
32
+ #
33
+ # r.on "bar" do
34
+ # r.route 'bar'
35
+ # end
36
+ # end
37
+ #
38
+ # Note that in multi-threaded code, you should not attempt to add a
39
+ # named route after accepting requests.
40
+ #
41
+ # == Routing Files
42
+ #
43
+ # The convention when using the named_routes plugin is to have a single
44
+ # named route per file, and these routing files should be stored in
45
+ # a routes subdirectory in your application. So for the above example, you
46
+ # would use the following files:
47
+ #
48
+ # routes/bar.rb
49
+ # routes/foo.rb
50
+ #
51
+ # == Namespace Support
52
+ #
53
+ # The named_routes plugin also has support for namespaces, allowing you to
54
+ # use +r.route+ at multiple levels in your routing tree. Example:
55
+ #
56
+ # route('foo') do |r|
57
+ # r.on("baz"){r.route("baz", "foo")}
58
+ # r.on("quux"){r.route("quux", "foo")}
59
+ # end
60
+ #
61
+ # route('bar') do |r|
62
+ # r.on("baz"){r.route("baz", "bar")}
63
+ # r.on("quux"){r.route("quux", "bar")}
64
+ # end
65
+ #
66
+ # route('baz', 'foo') do |r|
67
+ # # handles /foo/baz prefix
68
+ # end
69
+ #
70
+ # route('quux', 'foo') do |r|
71
+ # # handles /foo/quux prefix
72
+ # end
73
+ #
74
+ # route('baz', 'bar') do |r|
75
+ # # handles /bar/baz prefix
76
+ # end
77
+ #
78
+ # route('quux', 'bar') do |r|
79
+ # # handles /bar/quux prefix
80
+ # end
81
+ #
82
+ # route do |r|
83
+ # r.on "foo" do
84
+ # r.route("foo")
85
+ # end
86
+ #
87
+ # r.on "bar" do
88
+ # r.route("bar")
89
+ # end
90
+ # end
91
+ #
92
+ # === Routing Files
93
+ #
94
+ # The convention when using namespaces with the multi_route plugin is to
95
+ # store the routing files in subdirectories per namespace. So for the
96
+ # above example, you would have the following routing files:
97
+ #
98
+ # routes/bar.rb
99
+ # routes/bar/baz.rb
100
+ # routes/bar/quux.rb
101
+ # routes/foo.rb
102
+ # routes/foo/baz.rb
103
+ # routes/foo/quux.rb
104
+ module NamedRoutes
105
+ # Initialize storage for the named routes.
106
+ def self.configure(app)
107
+ app.opts[:namespaced_routes] ||= {}
108
+ end
109
+
110
+ module ClassMethods
111
+ # Freeze the namespaced routes so that there can be no thread safety issues at runtime.
112
+ def freeze
113
+ opts[:namespaced_routes].freeze.each_value(&:freeze)
114
+ super
115
+ end
116
+
117
+ # Copy the named routes into the subclass when inheriting.
118
+ def inherited(subclass)
119
+ super
120
+ nsr = subclass.opts[:namespaced_routes]
121
+ opts[:namespaced_routes].each{|k, v| nsr[k] = v.dup}
122
+ end
123
+
124
+ # The names for the currently stored named routes
125
+ def named_routes(namespace=nil)
126
+ unless routes = opts[:namespaced_routes][namespace]
127
+ raise RodaError, "unsupported multi_route namespace used: #{namespace.inspect}"
128
+ end
129
+ routes.keys
130
+ end
131
+
132
+ # Return the named route with the given name.
133
+ def named_route(name, namespace=nil)
134
+ opts[:namespaced_routes][namespace][name]
135
+ end
136
+
137
+ # If the given route has a name, treat it as a named route and
138
+ # store the route block. Otherwise, this is the main route, so
139
+ # call super.
140
+ def route(name=nil, namespace=nil, &block)
141
+ if name
142
+ routes = opts[:namespaced_routes][namespace] ||= {}
143
+ routes[name] = define_roda_method(routes[name] || "multi_route_#{namespace}_#{name}", 1, &convert_route_block(block))
144
+ else
145
+ super(&block)
146
+ end
147
+ end
148
+ end
149
+
150
+ module RequestMethods
151
+ # Dispatch to the named route with the given name.
152
+ def route(name, namespace=nil)
153
+ scope.send(roda_class.named_route(name, namespace), self)
154
+ end
155
+ end
156
+ end
157
+
158
+ register_plugin(:named_routes, NamedRoutes)
159
+ end
160
+ end
@@ -34,7 +34,7 @@ class Roda
34
34
  # routing normally.
35
35
  def run(app, opts=OPTS)
36
36
  res = catch(:halt){super(app)}
37
- yield res if block_given?
37
+ yield res if defined?(yield)
38
38
  throw(:halt, res) unless opts[:not_found] == :pass && res[0] == 404
39
39
  end
40
40
  end
@@ -61,7 +61,7 @@ class Roda
61
61
  def shared(vars=nil)
62
62
  h = env['roda.shared'] ||= {}
63
63
 
64
- if block_given?
64
+ if defined?(yield)
65
65
  if vars
66
66
  begin
67
67
  env['roda.shared'] = h.merge(vars)
@@ -384,7 +384,7 @@ class Roda
384
384
 
385
385
  # Set or retrieve the response body. When a block is given,
386
386
  # evaluation is deferred until the body is needed.
387
- def body(value = (return @body unless block_given?; nil), &block)
387
+ def body(value = (return @body unless defined?(yield); nil), &block)
388
388
  if block
389
389
  @body = DelayedBody.new(&block)
390
390
  else
@@ -184,7 +184,7 @@ class Roda
184
184
  path = super
185
185
 
186
186
  if opts[:use_extension]
187
- if m = path.match(opts[:extension_regexp])
187
+ if m = opts[:extension_regexp].match(path)
188
188
  @type_routing_extension = @requested_type = m[2].to_sym
189
189
  path = m[1]
190
190
  end
@@ -564,7 +564,7 @@ class Roda
564
564
  end
565
565
 
566
566
  v = @obj[key]
567
- v = yield if v.nil? && block_given?
567
+ v = yield if v.nil? && defined?(yield)
568
568
 
569
569
  begin
570
570
  sub = self.class.nest(v, Array(@nesting) + [key])
@@ -580,7 +580,7 @@ class Roda
580
580
  # Return the nested value for key. If there is no nested_value for +key+,
581
581
  # calls the block to return the value, or returns nil if there is no block given.
582
582
  def fetch(key)
583
- send(:[], key){return(yield if block_given?)}
583
+ send(:[], key){return(yield if defined?(yield))}
584
584
  end
585
585
 
586
586
  # Captures conversions inside the given block, and returns a hash of all conversions,
data/lib/roda/request.rb CHANGED
@@ -24,7 +24,7 @@ class Roda
24
24
 
25
25
  # Return the cached pattern for the given object. If the object is
26
26
  # not already cached, yield to get the basic pattern, and convert the
27
- # basic pattern to a pattern that does not partial segments.
27
+ # basic pattern to a pattern that does not match partial segments.
28
28
  def cached_matcher(obj)
29
29
  cache = @match_pattern_cache
30
30
 
@@ -234,9 +234,7 @@ class Roda
234
234
 
235
235
  # An alias of remaining_path. If a plugin changes remaining_path then
236
236
  # it should override this method to return the untouched original.
237
- def real_remaining_path
238
- remaining_path
239
- end
237
+ alias real_remaining_path remaining_path
240
238
 
241
239
  # Match POST requests. If no arguments are provided, matches all POST
242
240
  # requests, otherwise, matches only POST requests where the arguments
@@ -336,7 +334,7 @@ class Roda
336
334
  # Use <tt>r.get true</tt> to handle +GET+ requests where the current
337
335
  # path is empty.
338
336
  def root(&block)
339
- if remaining_path == "/" && is_get?
337
+ if @remaining_path == "/" && is_get?
340
338
  always(&block)
341
339
  end
342
340
  end
@@ -519,10 +517,10 @@ class Roda
519
517
  # SCRIPT_NAME to include the matched path, removes the matched
520
518
  # path from PATH_INFO, and updates captures with any regex captures.
521
519
  def consume(pattern)
522
- if matchdata = remaining_path.match(pattern)
520
+ if matchdata = pattern.match(@remaining_path)
523
521
  @remaining_path = matchdata.post_match
524
522
  captures = matchdata.captures
525
- captures = yield(*captures) if block_given?
523
+ captures = yield(*captures) if defined?(yield)
526
524
  @captures.concat(captures)
527
525
  end
528
526
  end
@@ -548,7 +546,7 @@ class Roda
548
546
 
549
547
  # Whether the current path is considered empty.
550
548
  def empty_path?
551
- remaining_path.empty?
549
+ @remaining_path.empty?
552
550
  end
553
551
 
554
552
  # If all of the arguments match, yields to the match block and
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 45
7
+ RodaMinorVersion = 49
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
data/lib/roda.rb CHANGED
@@ -212,6 +212,10 @@ class Roda
212
212
  if @middleware.empty? && use_new_dispatch_api?
213
213
  plugin :direct_call
214
214
  end
215
+
216
+ 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}
217
+ plugin :_optimized_matching
218
+ end
215
219
  end
216
220
 
217
221
  build_rack_app
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.45.0
4
+ version: 3.49.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-14 00:00:00.000000000 Z
11
+ date: 2021-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -216,6 +216,10 @@ extra_rdoc_files:
216
216
  - doc/release_notes/3.43.0.txt
217
217
  - doc/release_notes/3.44.0.txt
218
218
  - doc/release_notes/3.45.0.txt
219
+ - doc/release_notes/3.46.0.txt
220
+ - doc/release_notes/3.47.0.txt
221
+ - doc/release_notes/3.48.0.txt
222
+ - doc/release_notes/3.49.0.txt
219
223
  - doc/release_notes/3.5.0.txt
220
224
  - doc/release_notes/3.6.0.txt
221
225
  - doc/release_notes/3.7.0.txt
@@ -268,6 +272,10 @@ files:
268
272
  - doc/release_notes/3.43.0.txt
269
273
  - doc/release_notes/3.44.0.txt
270
274
  - doc/release_notes/3.45.0.txt
275
+ - doc/release_notes/3.46.0.txt
276
+ - doc/release_notes/3.47.0.txt
277
+ - doc/release_notes/3.48.0.txt
278
+ - doc/release_notes/3.49.0.txt
271
279
  - doc/release_notes/3.5.0.txt
272
280
  - doc/release_notes/3.6.0.txt
273
281
  - doc/release_notes/3.7.0.txt
@@ -278,6 +286,7 @@ files:
278
286
  - lib/roda/plugins.rb
279
287
  - lib/roda/plugins/_after_hook.rb
280
288
  - lib/roda/plugins/_before_hook.rb
289
+ - lib/roda/plugins/_optimized_matching.rb
281
290
  - lib/roda/plugins/_symbol_regexp_matchers.rb
282
291
  - lib/roda/plugins/all_verbs.rb
283
292
  - lib/roda/plugins/assets.rb
@@ -334,6 +343,7 @@ files:
334
343
  - lib/roda/plugins/multi_run.rb
335
344
  - lib/roda/plugins/multi_view.rb
336
345
  - lib/roda/plugins/multibyte_string_matcher.rb
346
+ - lib/roda/plugins/named_routes.rb
337
347
  - lib/roda/plugins/named_templates.rb
338
348
  - lib/roda/plugins/not_allowed.rb
339
349
  - lib/roda/plugins/not_found.rb
@@ -409,7 +419,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
409
419
  - !ruby/object:Gem::Version
410
420
  version: '0'
411
421
  requirements: []
412
- rubygems_version: 3.2.15
422
+ rubygems_version: 3.2.22
413
423
  signing_key:
414
424
  specification_version: 4
415
425
  summary: Routing tree web toolkit