roda 3.54.0 → 3.57.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/CHANGELOG +32 -0
- data/doc/conventions.rdoc +14 -11
- data/doc/release_notes/3.55.0.txt +12 -0
- data/doc/release_notes/3.56.0.txt +33 -0
- data/doc/release_notes/3.57.0.txt +34 -0
- data/lib/roda/plugins/chunked.rb +2 -2
- data/lib/roda/plugins/common_logger.rb +12 -1
- data/lib/roda/plugins/cookies.rb +2 -0
- data/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/lib/roda/plugins/hash_branches.rb +145 -0
- data/lib/roda/plugins/hash_paths.rb +128 -0
- data/lib/roda/plugins/hash_routes.rb +13 -176
- data/lib/roda/plugins/json_parser.rb +6 -2
- data/lib/roda/plugins/middleware.rb +17 -2
- data/lib/roda/plugins/multi_public.rb +8 -0
- data/lib/roda/plugins/multi_route.rb +1 -1
- data/lib/roda/plugins/multi_view.rb +0 -4
- data/lib/roda/plugins/named_routes.rb +1 -2
- data/lib/roda/plugins/not_allowed.rb +13 -0
- data/lib/roda/plugins/public.rb +8 -0
- data/lib/roda/plugins/render.rb +5 -3
- data/lib/roda/plugins/route_csrf.rb +1 -0
- data/lib/roda/plugins/run_append_slash.rb +1 -1
- data/lib/roda/plugins/run_require_slash.rb +46 -0
- data/lib/roda/plugins/sessions.rb +1 -0
- data/lib/roda/plugins/sinatra_helpers.rb +10 -0
- data/lib/roda/plugins/static.rb +2 -0
- data/lib/roda/plugins/static_routing.rb +1 -1
- data/lib/roda/plugins/status_303.rb +6 -3
- data/lib/roda/plugins/status_handler.rb +35 -9
- data/lib/roda/plugins/symbol_status.rb +2 -0
- data/lib/roda/plugins/unescape_path.rb +2 -0
- data/lib/roda/request.rb +35 -1
- data/lib/roda/response.rb +5 -0
- data/lib/roda/version.rb +1 -1
- metadata +30 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 578868ccd08ba1fa91302273c92176def99072db735ec119d13cbf49e78a7249
|
|
4
|
+
data.tar.gz: b95b2a33a18a3b135f494ba7f0ffa1639a5fd876b0b199f414826cab7fa75a9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b7b769a1653315b1fd9bca1a993ff0e699f2a09fb4db135304d8c7473f2c834c61b91e188005ac029fd85feaca6cc6a106efdd83f0e53771cddd8f3a73204aa
|
|
7
|
+
data.tar.gz: 7bdc602a54f8f5b34a7edc0355b3e6a426b29dd4671c30ef9d7712c757e116c9e6679cd08884f28c7636a80560878284ff4de479f0c479d905948ebfce9b12b3
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
= 3.57.0 (2022-06-14)
|
|
2
|
+
|
|
3
|
+
* Make static_routing plugin depend on the hash_paths instead of the hash_routes plugin (jeremyevans)
|
|
4
|
+
|
|
5
|
+
* Split hash_branches and hash_paths plugins from hash_routes plugin (jeremyevans)
|
|
6
|
+
|
|
7
|
+
* Hex escape unprintable characters in common_logger plugin output (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Add hash_branch_view_subdir plugin for automatically appending a view subdirectory on a successful hash branch (jeremyevans)
|
|
10
|
+
|
|
11
|
+
= 3.56.0 (2022-05-13)
|
|
12
|
+
|
|
13
|
+
* Make status_303 plugin use 303 responses for HTTP/2 and higher versions (jeremyevans)
|
|
14
|
+
|
|
15
|
+
* Add RodaRequest#http_version for determining the HTTP version in use (jeremyevans)
|
|
16
|
+
|
|
17
|
+
* Do not set a body for 405 responses when using the verb methods in the not_allowed plugin (jeremyevans) (#267)
|
|
18
|
+
|
|
19
|
+
* Support status_handler method :keep_headers option in status_handler plugin (jeremyevans) (#267)
|
|
20
|
+
|
|
21
|
+
* Make not_allowed plugin have r.root return 405 responses for non-GET requests (jeremyevans) (#266)
|
|
22
|
+
|
|
23
|
+
* In Rack 3, only require the parts of rack used by Roda, instead of requiring rack itself and relying on autoload (jeremyevans)
|
|
24
|
+
|
|
25
|
+
* Add run_require_slash plugin, for skipping application dispatch for remaining paths that would violate Rack SPEC (jeremyevans)
|
|
26
|
+
|
|
27
|
+
= 3.55.0 (2022-04-12)
|
|
28
|
+
|
|
29
|
+
* Allow passing blocks to the view method in the render plugin (jeremyevans) (#262)
|
|
30
|
+
|
|
31
|
+
* Add :forward_response_headers middleware plugin option to use app headers as default for response (janko) (#259)
|
|
32
|
+
|
|
1
33
|
= 3.54.0 (2022-03-14)
|
|
2
34
|
|
|
3
35
|
* Make chunked plugin not use Transfer-Encoding: chunked by default (jeremyevans)
|
data/doc/conventions.rdoc
CHANGED
|
@@ -86,17 +86,17 @@ Large applications generally need more structure:
|
|
|
86
86
|
|
|
87
87
|
For larger apps, the +Rakefile+, +assets/+, +migrate+, +models.rb+, +models/+, +public/+, remain the same.
|
|
88
88
|
|
|
89
|
-
+app_name.rb+ should use the +
|
|
90
|
-
|
|
89
|
+
+app_name.rb+ should use the +hash_branch_view_subdir+ plugin (which builds on the +hash_branches+ and
|
|
90
|
+
+view_options+ plugin), or the +multi_run+ plugin.
|
|
91
|
+
The routes used by the +hash_branches+ or +multi_run+ should be stored in routing files in the +routes/+
|
|
91
92
|
directory, with one file per prefix.
|
|
92
93
|
|
|
93
94
|
For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
|
|
94
95
|
and one file per prefix in +spec/web/+. Substitute +spec+ with +test+ if that is what you are using as the
|
|
95
96
|
name of the directory.
|
|
96
97
|
|
|
97
|
-
You should have a separate view subdirectory per prefix.
|
|
98
|
-
|
|
99
|
-
specified on every call to view.
|
|
98
|
+
You should have a separate view subdirectory per prefix. With the +hash_branch_view_subdir+, the application
|
|
99
|
+
will automatically set a separate view subdirectory per routing tree branch.
|
|
100
100
|
|
|
101
101
|
+helpers/+ should be used to store helper methods for your application, that you call in your routing files
|
|
102
102
|
and views. In a small application, these methods should just be specified in +app_name.rb+
|
|
@@ -104,7 +104,7 @@ and views. In a small application, these methods should just be specified in +a
|
|
|
104
104
|
=== Really Large Applications
|
|
105
105
|
|
|
106
106
|
For very large applications, it's expected that there will be deviations from these conventions. However,
|
|
107
|
-
it is recommended to use the +
|
|
107
|
+
it is recommended to use the +hash_branch_view_subdir+ or +multi_run+ plugins to organize your application, and have
|
|
108
108
|
subdirectories in the +routes/+ directory, and nested subdirectories in the +views/+ directory.
|
|
109
109
|
|
|
110
110
|
== Roda Application File Layout
|
|
@@ -156,19 +156,22 @@ For larger applications, there are some slight changes to the Roda application f
|
|
|
156
156
|
|
|
157
157
|
plugin :render, escape: true, layout: './layout'
|
|
158
158
|
plugin :assets
|
|
159
|
-
plugin :
|
|
160
|
-
plugin :hash_routes
|
|
159
|
+
plugin :hash_branch_view_subdir
|
|
161
160
|
Dir['routes/*.rb'].each{|f| require_relative f}
|
|
162
161
|
|
|
163
162
|
route do |r|
|
|
164
|
-
r.
|
|
163
|
+
r.hash_branches('')
|
|
164
|
+
|
|
165
|
+
r.root do
|
|
166
|
+
# ...
|
|
167
|
+
end
|
|
165
168
|
end
|
|
166
169
|
|
|
167
170
|
Dir['helpers/*.rb'].each{|f| require_relative f}
|
|
168
171
|
end
|
|
169
172
|
|
|
170
|
-
After loading the +
|
|
173
|
+
After loading the +hash_branch_view_subdir+ plugin, you require all of your
|
|
171
174
|
routing files. Inside your route block, instead of defining your routes, you just call
|
|
172
|
-
+r.
|
|
175
|
+
+r.hash_branches+, which will dispatch to all of your routing files. After your route
|
|
173
176
|
block, you require all of your helper files containing the instance methods for your
|
|
174
177
|
route block or views, instead of defining the methods directly.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* A :forward_response_headers option has been added to the middleware
|
|
4
|
+
plugin, which uses the response headers added by the middleware
|
|
5
|
+
as default response headers even if the middleware does not handle
|
|
6
|
+
the response. Response headers set by the underlying application
|
|
7
|
+
take precedence over response headers set by the middleware.
|
|
8
|
+
|
|
9
|
+
* The render plugin view method now accepts a block and will pass the
|
|
10
|
+
block to the underlying render method call. This is useful for
|
|
11
|
+
rendering a template that yields inside of an existing layout.
|
|
12
|
+
Previously, you had to nest render calls to do that.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* RodaRequest#http_version has been added for determining the HTTP
|
|
4
|
+
version the request was submitted with. This will be a string
|
|
5
|
+
such as "HTTP/1.0", "HTTP/1.1", "HTTP/2", etc. This will use the
|
|
6
|
+
SERVER_PROTOCOL and HTTP_VERSION entries from the environment to
|
|
7
|
+
determine which HTTP version is in use.
|
|
8
|
+
|
|
9
|
+
* The status_handler method in the status_handler plugin now supports
|
|
10
|
+
a :keep_headers option. The value for this option should be an
|
|
11
|
+
array of header names to keep. All other headers are removed. The
|
|
12
|
+
default behavior without the option is still to remove all headers.
|
|
13
|
+
|
|
14
|
+
* A run_require_slash plugin has been added, which will skip
|
|
15
|
+
dispatching to another rack application if the remaining path is not
|
|
16
|
+
empty and does not start with a slash.
|
|
17
|
+
|
|
18
|
+
= Other Improvements
|
|
19
|
+
|
|
20
|
+
* The status_303 plugin will use 303 as the default redirect status
|
|
21
|
+
for non-GET requests for HTTP/2 and higher HTTP versions. Previously,
|
|
22
|
+
it only used 303 for HTTP/1.1.
|
|
23
|
+
|
|
24
|
+
* The not_allowed plugin now overrides the r.root method to return
|
|
25
|
+
405 responses to non-GET requests to the root.
|
|
26
|
+
|
|
27
|
+
* The not_allowed plugin no longer sets the body when returning 405
|
|
28
|
+
responses using methods such as r.get and r.post. Previously, the
|
|
29
|
+
body was unintentionally set to the same value as the Allow header.
|
|
30
|
+
|
|
31
|
+
* When using the Rack master branch (what will become Rack 3), Roda
|
|
32
|
+
only requires the parts of rack that it uses, instead of requiring
|
|
33
|
+
rack and relying on autoload to load the parts of rack in use.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* hash_branches and hash_paths plugins have been split off from the
|
|
4
|
+
hash_routes plugin, allowing you to use only those parts instead
|
|
5
|
+
of all of hash_routes.
|
|
6
|
+
|
|
7
|
+
The hash_branches plugin supports the hash_branch class method
|
|
8
|
+
and r.hash_branches routing method.
|
|
9
|
+
|
|
10
|
+
The hash_paths plugin supports the hash_path class method and
|
|
11
|
+
r.hash_paths routing method.
|
|
12
|
+
|
|
13
|
+
The hash_routes plugin functions as it did previously by
|
|
14
|
+
requiring the hash_branches and hash_paths plugins. It adds
|
|
15
|
+
the hash_routes DSL and r.hash_routes routing method.
|
|
16
|
+
|
|
17
|
+
* A hash_branch_view_subdir has been added. It builds on the
|
|
18
|
+
view_options plugin and new hash_branches plugin, automatically
|
|
19
|
+
appending a view subdirectory for each successful hash branch.
|
|
20
|
+
This can DRY up code that uses a separate view subdirectory for
|
|
21
|
+
each branch.
|
|
22
|
+
|
|
23
|
+
= Other Improvements
|
|
24
|
+
|
|
25
|
+
* Unprintable characters are now hex escaped in the output of the
|
|
26
|
+
common_logger plugin. This can protect users who use software
|
|
27
|
+
that respects shell escape sequences to view the logs.
|
|
28
|
+
|
|
29
|
+
= Backwards Compatibility
|
|
30
|
+
|
|
31
|
+
* The static_routing plugin now depends on the hash_paths plugin
|
|
32
|
+
instead of the hash_routes plugin, so you will need to update
|
|
33
|
+
your application to explicitly load the hash_routes plugin if
|
|
34
|
+
you were relying on static_routing to implicitly load it.
|
data/lib/roda/plugins/chunked.rb
CHANGED
|
@@ -215,7 +215,7 @@ class Roda
|
|
|
215
215
|
# If chunking by default, call chunked if it hasn't yet been
|
|
216
216
|
# called and chunking is not specifically disabled.
|
|
217
217
|
def view(*a)
|
|
218
|
-
if opts[:chunk_by_default] && !defined?(@_chunked)
|
|
218
|
+
if opts[:chunk_by_default] && !defined?(@_chunked) && !defined?(yield)
|
|
219
219
|
chunked(*a)
|
|
220
220
|
else
|
|
221
221
|
super
|
|
@@ -226,7 +226,7 @@ class Roda
|
|
|
226
226
|
# an overview. If a block is given, it is passed to #delay.
|
|
227
227
|
def chunked(template, opts=OPTS, &block)
|
|
228
228
|
unless defined?(@_chunked)
|
|
229
|
-
@_chunked = !self.opts[:force_chunked_encoding] ||
|
|
229
|
+
@_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
if block
|
|
@@ -20,6 +20,9 @@ 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
|
+
MUTATE_LINE = RUBY_VERSION < '2.3' || RUBY_VERSION >= '3'
|
|
24
|
+
private_constant :MUTATE_LINE
|
|
25
|
+
|
|
23
26
|
def self.configure(app, logger=nil, opts=OPTS)
|
|
24
27
|
app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr
|
|
25
28
|
app.opts[:common_logger_meth] = app.opts[:common_logger].method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
|
|
@@ -53,7 +56,15 @@ class Roda
|
|
|
53
56
|
|
|
54
57
|
env = @_request.env
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
line = "#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{result[0]} #{((length = result[1]['Content-Length']) && (length unless length == '0')) || '-'} #{elapsed_time}\n"
|
|
60
|
+
if MUTATE_LINE
|
|
61
|
+
line.gsub!(/[^[:print:]\n]/){|c| sprintf("\\x%x", c.ord)}
|
|
62
|
+
# :nocov:
|
|
63
|
+
else
|
|
64
|
+
line = line.gsub(/[^[:print:]\n]/){|c| sprintf("\\x%x", c.ord)}
|
|
65
|
+
# :nocov:
|
|
66
|
+
end
|
|
67
|
+
opts[:common_logger_meth].call(line)
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
# Create timer instance used for timing
|
data/lib/roda/plugins/cookies.rb
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The hash_branch_view_subdir plugin builds on the hash_branches and view_options
|
|
7
|
+
# plugins, automatically appending a view subdirectory for any matching hash branch
|
|
8
|
+
# taken. In cases where you are using a separate view subdirectory per hash branch,
|
|
9
|
+
# this can result in DRYer code. Example:
|
|
10
|
+
#
|
|
11
|
+
# plugin :hash_branch_view_subdir
|
|
12
|
+
#
|
|
13
|
+
# route do |r|
|
|
14
|
+
# r.hash_branches
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# hash_branch 'foo' do |r|
|
|
18
|
+
# # view subdirectory here is 'foo'
|
|
19
|
+
# r.hash_branches('foo')
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# hash_branch 'foo', 'bar' do |r|
|
|
23
|
+
# # view subdirectory here is 'foo/bar'
|
|
24
|
+
# end
|
|
25
|
+
module HashBranchViewSubdir
|
|
26
|
+
def self.load_dependencies(app)
|
|
27
|
+
app.plugin :hash_branches
|
|
28
|
+
app.plugin :view_options
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.configure(app)
|
|
32
|
+
app.opts[:hash_branch_view_subdir_methods] ||= {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
# Freeze the hash_branch_view_subdir metadata when freezing the app.
|
|
37
|
+
def freeze
|
|
38
|
+
opts[:hash_branch_view_subdir_methods].freeze.each_value(&:freeze)
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Duplicate hash_branch_view_subdir metadata in subclass.
|
|
43
|
+
def inherited(subclass)
|
|
44
|
+
super
|
|
45
|
+
|
|
46
|
+
h = subclass.opts[:hash_branch_view_subdir_methods]
|
|
47
|
+
opts[:hash_branch_view_subdir_methods].each do |namespace, routes|
|
|
48
|
+
h[namespace] = routes.dup
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Automatically append a view subdirectory for a successful hash_branch route,
|
|
53
|
+
# by modifying the generated method to append the view subdirectory before
|
|
54
|
+
# dispatching to the original block.
|
|
55
|
+
def hash_branch(namespace='', segment, &block)
|
|
56
|
+
meths = opts[:hash_branch_view_subdir_methods][namespace] ||= {}
|
|
57
|
+
|
|
58
|
+
if block
|
|
59
|
+
meth = meths[segment] = define_roda_method(meths[segment] || "_hash_branch_view_subdir_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
|
60
|
+
super do |*_|
|
|
61
|
+
append_view_subdir(segment)
|
|
62
|
+
send(meth, @_request)
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
if meth = meths.delete(segment)
|
|
66
|
+
remove_method(meth)
|
|
67
|
+
end
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
register_plugin(:hash_branch_view_subdir, HashBranchViewSubdir)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The hash_branches plugin allows for O(1) dispatch to multiple route tree branches,
|
|
7
|
+
# based on the next segment in the remaining path:
|
|
8
|
+
#
|
|
9
|
+
# class App < Roda
|
|
10
|
+
# plugin :hash_branches
|
|
11
|
+
#
|
|
12
|
+
# hash_branch("a") do |r|
|
|
13
|
+
# # /a branch
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# hash_branch("b") do |r|
|
|
17
|
+
# # /b branch
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# route do |r|
|
|
21
|
+
# r.hash_branches
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# With the above routing tree, the +r.hash_branches+ call in the main routing tree
|
|
26
|
+
# will dispatch requests for the +/a+ and +/b+ branches of the tree to the appropriate
|
|
27
|
+
# routing blocks.
|
|
28
|
+
#
|
|
29
|
+
# In this example, the hash branches for +/a+ and +/b+ are in the same file, but in larger
|
|
30
|
+
# applications, they are usually stored in separate files. This allows for easily splitting
|
|
31
|
+
# up the routing tree into a separate file per branch.
|
|
32
|
+
#
|
|
33
|
+
# The +hash_branch+ method supports namespaces, which allow for dispatching to sub-branches
|
|
34
|
+
# any level of the routing tree, fully supporting the needs of applications with large and
|
|
35
|
+
# deep routing branches:
|
|
36
|
+
#
|
|
37
|
+
# class App < Roda
|
|
38
|
+
# plugin :hash_branches
|
|
39
|
+
#
|
|
40
|
+
# # Only one argument used, so the namespace defaults to '', and the argument
|
|
41
|
+
# # specifies the route name
|
|
42
|
+
# hash_branch("a") do |r|
|
|
43
|
+
# # No argument given, so uses the already matched path as the namespace,
|
|
44
|
+
# # which is '/a' in this case.
|
|
45
|
+
# r.hash_branches
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# hash_branch("b") do |r|
|
|
49
|
+
# # uses :b as the namespace when looking up routes, as that was explicitly specified
|
|
50
|
+
# r.hash_branches(:b)
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# # Two arguments used, so first specifies the namespace and the second specifies
|
|
54
|
+
# # the branch name
|
|
55
|
+
# hash_branch("/a", "b") do |r|
|
|
56
|
+
# # /a/b path
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# hash_branch("/a", "c") do |r|
|
|
60
|
+
# # /a/c path
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# hash_branch(:b, "b") do |r|
|
|
64
|
+
# # /b/b path
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# hash_branch(:b, "c") do |r|
|
|
68
|
+
# # /b/c path
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# route do |r|
|
|
72
|
+
# # No argument given, so uses '' as the namespace, as no part of the path has
|
|
73
|
+
# # been matched yet
|
|
74
|
+
# r.hash_branches
|
|
75
|
+
# end
|
|
76
|
+
# end
|
|
77
|
+
#
|
|
78
|
+
# With the above routing tree, requests for the +/a+ and +/b+ branches will be
|
|
79
|
+
# dispatched to the appropriate +hash_branch+ block. Those blocks will the dispatch
|
|
80
|
+
# to the remaining +hash_branch+ blocks, with the +/a+ branch using the implicit namespace of
|
|
81
|
+
# +/a+, and the +/b+ branch using the explicit namespace of +:b+.
|
|
82
|
+
#
|
|
83
|
+
# It is best for performance to explicitly specify the namespace when calling
|
|
84
|
+
# +r.hash_branches+.
|
|
85
|
+
module HashBranches
|
|
86
|
+
def self.configure(app)
|
|
87
|
+
app.opts[:hash_branches] ||= {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
module ClassMethods
|
|
91
|
+
# Freeze the hash_branches metadata when freezing the app.
|
|
92
|
+
def freeze
|
|
93
|
+
opts[:hash_branches].freeze.each_value(&:freeze)
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Duplicate hash_branches metadata in subclass.
|
|
98
|
+
def inherited(subclass)
|
|
99
|
+
super
|
|
100
|
+
|
|
101
|
+
h = subclass.opts[:hash_branches]
|
|
102
|
+
opts[:hash_branches].each do |namespace, routes|
|
|
103
|
+
h[namespace] = routes.dup
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Add branch handler for the given namespace and segment. If called without
|
|
108
|
+
# a block, removes the existing branch handler if it exists.
|
|
109
|
+
def hash_branch(namespace='', segment, &block)
|
|
110
|
+
segment = "/#{segment}"
|
|
111
|
+
routes = opts[:hash_branches][namespace] ||= {}
|
|
112
|
+
if block
|
|
113
|
+
routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
|
114
|
+
elsif meth = routes.delete(segment)
|
|
115
|
+
remove_method(meth)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
module RequestMethods
|
|
121
|
+
# Checks the matching hash_branch namespace for a branch matching the next
|
|
122
|
+
# segment in the remaining path, and dispatch to that block if there is one.
|
|
123
|
+
def hash_branches(namespace=matched_path)
|
|
124
|
+
rp = @remaining_path
|
|
125
|
+
|
|
126
|
+
return unless rp.getbyte(0) == 47 # "/"
|
|
127
|
+
|
|
128
|
+
if routes = roda_class.opts[:hash_branches][namespace]
|
|
129
|
+
if segment_end = rp.index('/', 1)
|
|
130
|
+
if meth = routes[rp[0, segment_end]]
|
|
131
|
+
@remaining_path = rp[segment_end, 100000000]
|
|
132
|
+
always{scope.send(meth, self)}
|
|
133
|
+
end
|
|
134
|
+
elsif meth = routes[rp]
|
|
135
|
+
@remaining_path = ''
|
|
136
|
+
always{scope.send(meth, self)}
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
register_plugin(:hash_branches, HashBranches)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The hash_paths plugin allows for O(1) dispatch to multiple routes at any point
|
|
7
|
+
# in the routing tree. It is useful when you have a large number of specific routes
|
|
8
|
+
# to dispatch to at any point in the routing tree.
|
|
9
|
+
#
|
|
10
|
+
# You configure the hash paths to dispatch to using the +hash_path+ class method,
|
|
11
|
+
# specifying the remaining path, with a block to handle that path. Then you dispatch
|
|
12
|
+
# to the configured paths using +r.hash_paths+:
|
|
13
|
+
#
|
|
14
|
+
# class App < Roda
|
|
15
|
+
# plugin :hash_paths
|
|
16
|
+
#
|
|
17
|
+
# hash_path("/a") do |r|
|
|
18
|
+
# # /a path
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# hash_path("/a/b") do |r|
|
|
22
|
+
# # /a/b path
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# route do |r|
|
|
26
|
+
# r.hash_paths
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# With the above routing tree, the +r.hash_paths+ call will dispatch requests for the +/a+ and
|
|
31
|
+
# +/a/b+ request paths.
|
|
32
|
+
#
|
|
33
|
+
# The +hash_path+ class method supports namespaces, which allows +r.hash_paths+ to be used at
|
|
34
|
+
# any level of the routing tree. Here is an example that uses namespaces for sub-branches:
|
|
35
|
+
#
|
|
36
|
+
# class App < Roda
|
|
37
|
+
# plugin :hash_paths
|
|
38
|
+
#
|
|
39
|
+
# # Two arguments provided, so first argument is the namespace
|
|
40
|
+
# hash_path("/a", "/b") do |r|
|
|
41
|
+
# # /a/b path
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# hash_path("/a", "/c") do |r|
|
|
45
|
+
# # /a/c path
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# hash_path(:b, "/b") do |r|
|
|
49
|
+
# # /b/b path
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# hash_path(:b, "/c") do |r|
|
|
53
|
+
# # /b/c path
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# route do |r|
|
|
57
|
+
# r.on 'a' do
|
|
58
|
+
# # No argument given, so uses the already matched path as the namespace,
|
|
59
|
+
# # which is '/a' in this case.
|
|
60
|
+
# r.hash_paths
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# r.on 'b' do
|
|
64
|
+
# # uses :b as the namespace when looking up routes, as that was explicitly specified
|
|
65
|
+
# r.hash_paths(:b)
|
|
66
|
+
# end
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# With the above routing tree, requests for the +/a+ branch will be handled by the first
|
|
71
|
+
# +r.hash_paths+ call, and requests for the +/b+ branch will be handled by the second
|
|
72
|
+
# +r.hash_paths+ call. Those will dispatch to the configured hash paths for the +/a+ and
|
|
73
|
+
# +:b+ namespaces.
|
|
74
|
+
#
|
|
75
|
+
# It is best for performance to explicitly specify the namespace when calling
|
|
76
|
+
# +r.hash_paths+.
|
|
77
|
+
module HashPaths
|
|
78
|
+
def self.configure(app)
|
|
79
|
+
app.opts[:hash_paths] ||= {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
module ClassMethods
|
|
83
|
+
# Freeze the hash_paths metadata when freezing the app.
|
|
84
|
+
def freeze
|
|
85
|
+
opts[:hash_paths].freeze.each_value(&:freeze)
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Duplicate hash_paths metadata in subclass.
|
|
90
|
+
def inherited(subclass)
|
|
91
|
+
super
|
|
92
|
+
|
|
93
|
+
h = subclass.opts[:hash_paths]
|
|
94
|
+
opts[:hash_paths].each do |namespace, routes|
|
|
95
|
+
h[namespace] = routes.dup
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Add path handler for the given namespace and path. When the
|
|
100
|
+
# r.hash_paths method is called, checks the matching namespace
|
|
101
|
+
# for the full remaining path, and dispatch to that block if
|
|
102
|
+
# there is one. If called without a block, removes the existing
|
|
103
|
+
# path handler if it exists.
|
|
104
|
+
def hash_path(namespace='', path, &block)
|
|
105
|
+
routes = opts[:hash_paths][namespace] ||= {}
|
|
106
|
+
if block
|
|
107
|
+
routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
|
|
108
|
+
elsif meth = routes.delete(path)
|
|
109
|
+
remove_method(meth)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module RequestMethods
|
|
115
|
+
# Checks the matching hash_path namespace for a branch matching the
|
|
116
|
+
# remaining path, and dispatch to that block if there is one.
|
|
117
|
+
def hash_paths(namespace=matched_path)
|
|
118
|
+
if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
|
|
119
|
+
@remaining_path = ''
|
|
120
|
+
always{scope.send(meth, self)}
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
register_plugin(:hash_paths, HashPaths)
|
|
127
|
+
end
|
|
128
|
+
end
|