roda 3.46.0 → 3.50.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: 5fd9ea4a9be8072ffc96e5bab91a845e99cc9a9d6641571167badd4146c93318
4
- data.tar.gz: ed062be1d4b922ba39456f3868e599e3c6134fecfedaa3bfc780667bfec5c5f2
3
+ metadata.gz: b9da24f38f427b9256f05e92f40d67fe98bbdefb86419e5b8c13243f88418f8a
4
+ data.tar.gz: 3acabff7d8879126d6fdf2199e0935c96d0ea1c975feef874122d67d2afe7315
5
5
  SHA512:
6
- metadata.gz: 59e5677db771776107744f2438505b467363b410d6b2c8d19b1b11e802bfd15b22974997b20185e9ba3a1c9a7b8721bb5e3dbc1481a575f0812300c9c3241cac
7
- data.tar.gz: 6233ec5251cfbeeaabdb44a7cba75d4831910215ae8239ec19873f58a62f14722bb6b57059f0814ae07e7ccc50977e3a2fbf74bb3b423a253b7dace5773c6887
6
+ metadata.gz: 7535b67bed981d52d337643c24f13b206163392594b3f969e611b79020a8578994b934a34abc186cf07850725c1ce8a99fbcb859b58cce289b8138b329ca4c68
7
+ data.tar.gz: 3fdbb4aa63d1cd82a7f6cbdfe01a9804388ab8b4461f9dda825332f030a1179737a21be4ea59c9e52fa856f6952d77578e972263397c62de4c42680340472313
data/CHANGELOG CHANGED
@@ -1,3 +1,25 @@
1
+ = 3.50.0 (2021-11-12)
2
+
3
+ * Add capture_erb plugin for capturing ERB template blocks, instead of injecting them into the template output (jeremyevans)
4
+
5
+ * Add inject_erb plugin for injecting content directly into ERB template output (jeremyevans)
6
+
7
+ * Allow hash_branch and hash_path in hash_routes plugin to be called without a block to remove existing handler (jeremyevans)
8
+
9
+ = 3.49.0 (2021-10-13)
10
+
11
+ * Switch block_given? to defined?(yield) (jeremyevans)
12
+
13
+ * Automatically optimize remaining r.is/r.get/r.post calls with a single argument (jeremyevans)
14
+
15
+ = 3.48.0 (2021-09-13)
16
+
17
+ * Extract named_routes plugin from multi_route plugin (jeremyevans)
18
+
19
+ = 3.47.0 (2021-08-13)
20
+
21
+ * Automatically optimize remaining r.on calls with a single argument (jeremyevans)
22
+
1
23
  = 3.46.0 (2021-07-12)
2
24
 
3
25
  * Automatically optimize r.on/r.is/r.get/r.post methods with a single string, String, Integer, or regexp argument (jeremyevans)
data/README.rdoc CHANGED
@@ -1109,7 +1109,8 @@ Roda's plugin system is based on the plugin system used by
1109
1109
  Roda fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
1110
1110
  support unsupported versions of Ruby or JRuby, but such support may be dropped in any
1111
1111
  minor version if keeping it becomes a support issue. The minimum Ruby version
1112
- 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.
1113
1114
 
1114
1115
  == License
1115
1116
 
@@ -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,21 @@
1
+ = New Features
2
+
3
+ * An inject_erb plugin has been added, adding an inject_erb method
4
+ that allows for injecting content directly into the template output
5
+ for the template currently being rendered. This allows you to more
6
+ easily wrap blocks in templates, by calling methods that accept
7
+ template blocks and injecting content before and after the block.
8
+
9
+ * A capture_erb plugin has been added, adding a capture_erb method
10
+ for capturing a template block in an erb template and returning
11
+ the content appended during the block as a string, instead of
12
+ having the content of the template block be included directly into
13
+ the template output. This can be combined with the inject_erb
14
+ plugin to inject modified versions of captured blocks into template
15
+ output.
16
+
17
+ * The hash_routes plugin now allows calling hash_branch and hash_path
18
+ without a block in order to remove the existing route handler. This
19
+ is designed to be used with code reloading libraries, so that if a
20
+ route file is deleted, the related hash branches/paths are also
21
+ removed, without having to reload all route files.
@@ -52,20 +52,60 @@ class Roda
52
52
  end
53
53
  end
54
54
  elsif matcher == Integer
55
- if matchdata = @remaining_path.match(/\A\/(\d+)(?=\/|\z)/)
55
+ if matchdata = /\A\/(\d+)(?=\/|\z)/.match(@remaining_path)
56
56
  @remaining_path = matchdata.post_match
57
57
  always{yield(matchdata[1].to_i)}
58
58
  end
59
59
  else
60
- if_match(args, &block)
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
61
75
  end
62
76
  when Regexp
63
- if matchdata = @remaining_path.match(self.class.cached_matcher(matcher){matcher})
77
+ if matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)
64
78
  @remaining_path = matchdata.post_match
65
79
  always{yield(*matchdata.captures)}
66
80
  end
81
+ when true
82
+ always(&block)
83
+ when false, nil
84
+ # nothing
67
85
  else
68
- if_match(args, &block)
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
69
109
  end
70
110
  when 0
71
111
  always(&block)
@@ -111,22 +151,60 @@ class Roda
111
151
  always{yield rp[1, len]}
112
152
  end
113
153
  elsif matcher == Integer
114
- if matchdata = @remaining_path.match(/\A\/(\d+)\z/)
154
+ if matchdata = /\A\/(\d+)\z/.match(@remaining_path)
115
155
  @remaining_path = ''
116
156
  always{yield(matchdata[1].to_i)}
117
157
  end
118
158
  else
119
- if_match(args << TERM, &block)
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
120
174
  end
121
175
  when Regexp
122
- if (matchdata = @remaining_path.match(self.class.cached_matcher(matcher){matcher})) && (post_match = matchdata.post_match).empty?
176
+ if (matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)) && matchdata.post_match.empty?
123
177
  @remaining_path = ''
124
178
  always{yield(*matchdata.captures)}
125
179
  end
126
180
  when true
127
181
  always(&block) if @remaining_path.empty?
182
+ when false, nil
183
+ # nothing
128
184
  else
129
- if_match(args << TERM, &block)
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
130
208
  end
131
209
  end
132
210
  end
@@ -0,0 +1,41 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The capture_erb plugin allows you to capture the content of a block
7
+ # in an ERB template, and return it as a value, instead of
8
+ # injecting the template block into the template output.
9
+ #
10
+ # <% value = capture_erb do %>
11
+ # Some content here.
12
+ # <% end %>
13
+ #
14
+ # +capture_erb+ can be used inside other methods that are called
15
+ # inside templates. It can be combined with the inject_erb plugin
16
+ # to wrap template blocks with arbitrary output and then inject the
17
+ # wrapped output into the template.
18
+ module CaptureERB
19
+ def self.load_dependencies(app)
20
+ app.plugin :render
21
+ end
22
+
23
+ module InstanceMethods
24
+ # Temporarily replace the ERB output buffer
25
+ # with an empty string, and then yield to the block.
26
+ # Return the value of the block, converted to a string.
27
+ # Restore the previous ERB output buffer before returning.
28
+ def capture_erb
29
+ outvar = render_opts[:template_opts][:outvar]
30
+ buf_was = instance_variable_get(outvar)
31
+ instance_variable_set(outvar, String.new)
32
+ yield.to_s
33
+ ensure
34
+ instance_variable_set(outvar, buf_was) if outvar && buf_was
35
+ end
36
+ end
37
+ end
38
+
39
+ register_plugin(:capture_erb, CaptureERB)
40
+ end
41
+ end
@@ -55,10 +55,10 @@ class Roda
55
55
  #
56
56
  # plugin :content_for, append: false
57
57
  module ContentFor
58
- # Depend on the render plugin, since this plugin only makes
59
- # sense when the render plugin is used.
58
+ # Depend on the capture_erb plugin, since it uses capture_erb
59
+ # to capture the content.
60
60
  def self.load_dependencies(app, _opts = OPTS)
61
- app.plugin :render
61
+ app.plugin :capture_erb
62
62
  end
63
63
 
64
64
  # Configure whether to append or overwrite if content_for
@@ -72,18 +72,12 @@ class Roda
72
72
  # under the given key. If called without a block, retrieve
73
73
  # stored content with the given key, or return nil if there
74
74
  # is no content stored with that key.
75
- def content_for(key, value=nil)
75
+ def content_for(key, value=nil, &block)
76
76
  append = opts[:append_content_for]
77
77
 
78
- if block_given? || value
79
- if block_given?
80
- outvar = render_opts[:template_opts][:outvar]
81
- buf_was = instance_variable_get(outvar)
82
-
83
- # Use temporary output buffer for ERB-based rendering systems
84
- instance_variable_set(outvar, String.new)
85
- value = yield.to_s
86
- instance_variable_set(outvar, buf_was)
78
+ if block || value
79
+ if block
80
+ value = capture_erb(&block)
87
81
  end
88
82
 
89
83
  @_content_for ||= {}
@@ -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
@@ -394,20 +394,32 @@ class Roda
394
394
  dsl
395
395
  end
396
396
 
397
- # Add branch handler for the given namespace and segment.
397
+ # Add branch handler for the given namespace and segment. If called without
398
+ # a block, removes the existing branch handler if it exists.
398
399
  def hash_branch(namespace='', segment, &block)
399
400
  segment = "/#{segment}"
400
401
  routes = opts[:hash_branches][namespace] ||= {}
401
- routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
402
+ if block
403
+ routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
404
+ elsif meth = routes[segment]
405
+ routes.delete(segment)
406
+ remove_method(meth)
407
+ end
402
408
  end
403
409
 
404
410
  # Add path handler for the given namespace and path. When the
405
411
  # r.hash_paths method is called, checks the matching namespace
406
412
  # for the full remaining path, and dispatch to that block if
407
- # there is one.
413
+ # there is one. If called without a block, removes the existing
414
+ # path handler if it exists.
408
415
  def hash_path(namespace='', path, &block)
409
416
  routes = opts[:hash_paths][namespace] ||= {}
410
- routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
417
+ if block
418
+ routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
419
+ elsif meth = routes[path]
420
+ routes.delete(path)
421
+ remove_method(meth)
422
+ end
411
423
  end
412
424
  end
413
425
 
@@ -0,0 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The inject_erb plugin allows you to inject content directly
7
+ # into the template output:
8
+ #
9
+ # <% inject_erb("Some HTML Here") %>
10
+ #
11
+ # This will inject <tt>Some HTML Here</tt> into the template output,
12
+ # even though the tag being used is <tt><%</tt> and not <tt><%=</tt>.
13
+ #
14
+ # This method can be used inside methods, such as to wrap calls to
15
+ # methods that accept template blocks, to inject code before and after
16
+ # the template blocks.
17
+ module InjectERB
18
+ def self.load_dependencies(app)
19
+ app.plugin :render
20
+ end
21
+
22
+ module InstanceMethods
23
+ # Inject into the template output for the template currently being
24
+ # rendered.
25
+ def inject_erb(value)
26
+ instance_variable_get(render_opts[:template_opts][:outvar]) << value.to_s
27
+ end
28
+ end
29
+ end
30
+
31
+ register_plugin(:inject_erb, InjectERB)
32
+ end
33
+ 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
@@ -47,7 +47,7 @@ class Roda
47
47
  # The following plugin options are supported:
48
48
  #
49
49
  # :allowed_paths :: Set the template paths to allow. Attempts to render paths outside
50
- # of this directory will raise an error. Defaults to the +:views+ directory.
50
+ # of these paths will raise an error. Defaults to the +:views+ directory.
51
51
  # :cache :: nil/false to explicitly disable premanent template caching. By default, permanent
52
52
  # template caching is disabled by default if RACK_ENV is development. When permanent
53
53
  # template caching is disabled, for templates with paths in the file system, the
@@ -59,9 +59,9 @@ class Roda
59
59
  # templates, defaults to 'erb'.
60
60
  # :escape :: Use Erubi as the ERB template engine, and enable escaping by default,
61
61
  # which makes <tt><%= %></tt> escape output and <tt><%== %></tt> not escape output.
62
- # If given, sets the <tt>:escape=>true</tt> option for all template engines, which
62
+ # If given, sets the <tt>escape: true</tt> option for all template engines, which
63
63
  # can break some non-ERB template engines. You can use a string or array of strings
64
- # as the value for this option to only set the <tt>:escape=>true</tt> option for those
64
+ # as the value for this option to only set the <tt>escape: true</tt> option for those
65
65
  # specific template engines.
66
66
  # :layout :: The base name of the layout file, defaults to 'layout'. This can be provided as a hash
67
67
  # with the :template or :inline options.
@@ -130,6 +130,84 @@ class Roda
130
130
  # only argument, you can speed things up by specifying a +:cache_key+ option in
131
131
  # the hash, making sure the +:cache_key+ is unique to the template you are
132
132
  # rendering.
133
+ #
134
+ # = Accepting Template Blocks in Methods
135
+ #
136
+ # If you are used to Rails, you may be surprised that this type of template code
137
+ # doesn't work in Roda:
138
+ #
139
+ # <%= some_method do %>
140
+ # Some HTML
141
+ # <% end %>
142
+ #
143
+ # The reason this doesn't work is that this is not valid ERB syntax, it is Rails syntax,
144
+ # and requires attempting to parse the <tt>some_method do</tt> Ruby code with a regular
145
+ # expression. Since Roda uses ERB syntax, it does not support this.
146
+ #
147
+ # In general, these methods are used to wrap the content of the block and
148
+ # inject the content into the output. To get similar behavior with Roda, you have
149
+ # a few different options you can use.
150
+ #
151
+ # == Directly Inject Template Output
152
+ #
153
+ # You can switch from a <tt><%=</tt> tag to using a <tt><%</tt> tag:
154
+ #
155
+ # <% some_method do %>
156
+ # Some HTML
157
+ # <% end %>
158
+ #
159
+ # While this would output <tt>Some HTML</tt> into the template, it would not be able
160
+ # to inject content before or after the block. However, you can use the inject_erb_plugin
161
+ # to handle the injection:
162
+ #
163
+ # def some_method
164
+ # inject_erb "content before block"
165
+ # yield
166
+ # inject_erb "content after block"
167
+ # end
168
+ #
169
+ # If you need to modify the captured block before injecting it, you can use the
170
+ # capture_erb plugin to capture content from the template block, and modify that content,
171
+ # then use inject_erb to inject it into the template output:
172
+ #
173
+ # def some_method(&block)
174
+ # inject_erb "content before block"
175
+ # inject_erb capture_erb(&block).upcase
176
+ # inject_erb "content after block"
177
+ # end
178
+ #
179
+ # This is the recommended approach for handling this type of method, if you want to keep
180
+ # the template block in the same template.
181
+ #
182
+ # == Separate Block Output Into Separate Template
183
+ #
184
+ # By moving the <tt>Some HTML</tt> into a separate template, you can render that
185
+ # template inside the block:
186
+ #
187
+ # <%= some_method{render('template_name')} %>
188
+ #
189
+ # It's also possible to use an inline template:
190
+ #
191
+ # <%= some_method do render(:inline=><<-END)
192
+ # Some HTML
193
+ # END
194
+ # end %>
195
+ #
196
+ # This approach is useful if it makes sense to separate the template block into its
197
+ # own template. You lose the ability to use local variable from outside the
198
+ # template block inside the template block with this approach.
199
+ #
200
+ # == Separate Header and Footer
201
+ #
202
+ # You can define two separate methods, one that outputs the content before the block,
203
+ # and one that outputs the content after the block, and use those instead of a single
204
+ # call:
205
+ #
206
+ # <%= some_method_before %>
207
+ # Some HTML
208
+ # <%= some_method_after %>
209
+ #
210
+ # This is the simplest option to setup, but it is fairly tedious to use.
133
211
  module Render
134
212
  # Support for using compiled methods directly requires Ruby 2.3 for the
135
213
  # method binding to work, and Tilt 1.2 for Tilt::Template#compiled_method.
@@ -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
@@ -6,8 +6,7 @@ class Roda
6
6
  # The status_303 plugin sets the default redirect status to be 303
7
7
  # rather than 302 when the request is not a GET and the
8
8
  # redirection occurs on an HTTP 1.1 connection as per RFC 7231.
9
- # The author knows of no cases where this actually matters in
10
- # practice.
9
+ # There are some frontend frameworks that require this behavior.
11
10
  #
12
11
  # Example:
13
12
  #
@@ -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
@@ -517,10 +517,10 @@ class Roda
517
517
  # SCRIPT_NAME to include the matched path, removes the matched
518
518
  # path from PATH_INFO, and updates captures with any regex captures.
519
519
  def consume(pattern)
520
- if matchdata = @remaining_path.match(pattern)
520
+ if matchdata = pattern.match(@remaining_path)
521
521
  @remaining_path = matchdata.post_match
522
522
  captures = matchdata.captures
523
- captures = yield(*captures) if block_given?
523
+ captures = yield(*captures) if defined?(yield)
524
524
  @captures.concat(captures)
525
525
  end
526
526
  end
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 = 46
7
+ RodaMinorVersion = 50
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
@@ -213,7 +213,7 @@ class Roda
213
213
  plugin :direct_call
214
214
  end
215
215
 
216
- if ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
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
217
  plugin :_optimized_matching
218
218
  end
219
219
  end
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.46.0
4
+ version: 3.50.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-07-12 00:00:00.000000000 Z
11
+ date: 2021-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -217,7 +217,11 @@ extra_rdoc_files:
217
217
  - doc/release_notes/3.44.0.txt
218
218
  - doc/release_notes/3.45.0.txt
219
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
220
223
  - doc/release_notes/3.5.0.txt
224
+ - doc/release_notes/3.50.0.txt
221
225
  - doc/release_notes/3.6.0.txt
222
226
  - doc/release_notes/3.7.0.txt
223
227
  - doc/release_notes/3.8.0.txt
@@ -270,7 +274,11 @@ files:
270
274
  - doc/release_notes/3.44.0.txt
271
275
  - doc/release_notes/3.45.0.txt
272
276
  - doc/release_notes/3.46.0.txt
277
+ - doc/release_notes/3.47.0.txt
278
+ - doc/release_notes/3.48.0.txt
279
+ - doc/release_notes/3.49.0.txt
273
280
  - doc/release_notes/3.5.0.txt
281
+ - doc/release_notes/3.50.0.txt
274
282
  - doc/release_notes/3.6.0.txt
275
283
  - doc/release_notes/3.7.0.txt
276
284
  - doc/release_notes/3.8.0.txt
@@ -288,6 +296,7 @@ files:
288
296
  - lib/roda/plugins/backtracking_array.rb
289
297
  - lib/roda/plugins/branch_locals.rb
290
298
  - lib/roda/plugins/caching.rb
299
+ - lib/roda/plugins/capture_erb.rb
291
300
  - lib/roda/plugins/chunked.rb
292
301
  - lib/roda/plugins/class_level_routing.rb
293
302
  - lib/roda/plugins/class_matchers.rb
@@ -323,6 +332,7 @@ files:
323
332
  - lib/roda/plugins/hooks.rb
324
333
  - lib/roda/plugins/host_authorization.rb
325
334
  - lib/roda/plugins/indifferent_params.rb
335
+ - lib/roda/plugins/inject_erb.rb
326
336
  - lib/roda/plugins/json.rb
327
337
  - lib/roda/plugins/json_parser.rb
328
338
  - lib/roda/plugins/mail_processor.rb
@@ -337,6 +347,7 @@ files:
337
347
  - lib/roda/plugins/multi_run.rb
338
348
  - lib/roda/plugins/multi_view.rb
339
349
  - lib/roda/plugins/multibyte_string_matcher.rb
350
+ - lib/roda/plugins/named_routes.rb
340
351
  - lib/roda/plugins/named_templates.rb
341
352
  - lib/roda/plugins/not_allowed.rb
342
353
  - lib/roda/plugins/not_found.rb