roda 3.32.0 → 3.37.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 +17 -8
- data/doc/release_notes/3.33.0.txt +8 -0
- data/doc/release_notes/3.34.0.txt +17 -0
- data/doc/release_notes/3.35.0.txt +12 -0
- data/doc/release_notes/3.36.0.txt +17 -0
- data/doc/release_notes/3.37.0.txt +42 -0
- data/lib/roda.rb +6 -0
- data/lib/roda/plugins/all_verbs.rb +2 -0
- data/lib/roda/plugins/backtracking_array.rb +1 -3
- data/lib/roda/plugins/class_level_routing.rb +0 -1
- data/lib/roda/plugins/content_security_policy.rb +3 -3
- data/lib/roda/plugins/custom_matchers.rb +87 -0
- data/lib/roda/plugins/disallow_file_uploads.rb +2 -0
- data/lib/roda/plugins/exception_page.rb +15 -5
- data/lib/roda/plugins/match_affix.rb +1 -1
- data/lib/roda/plugins/multi_public.rb +87 -0
- data/lib/roda/plugins/not_allowed.rb +2 -0
- data/lib/roda/plugins/path.rb +21 -11
- data/lib/roda/plugins/public.rb +38 -30
- data/lib/roda/plugins/r.rb +35 -0
- data/lib/roda/plugins/render.rb +4 -0
- data/lib/roda/plugins/render_locals.rb +2 -0
- data/lib/roda/plugins/sinatra_helpers.rb +4 -4
- data/lib/roda/plugins/type_routing.rb +6 -1
- data/lib/roda/plugins/typecast_params.rb +6 -9
- data/lib/roda/plugins/view_options.rb +2 -0
- data/lib/roda/request.rb +2 -4
- data/lib/roda/version.rb +1 -1
- metadata +16 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de8c769572875e477058b48557b22861b9de731fa3b00b20a3022a67c929609d
|
|
4
|
+
data.tar.gz: 5cacc0d7b988bd3dc5bafbc30fcac145f6f67ddcc0e7809e1fc9cd60d8a16273
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1bf7df28d82f31820cd19c29b5a3f0f3afbbae2c41cf05974b16698c7ffb1725e924491874ddbb3f355a88990d4aab6d5b5b38f2d9c7612a548bfe0e3701ac2e
|
|
7
|
+
data.tar.gz: 32fca636d6324d29958cbcdf5c949c413eaeda7b74a701032d5766a60ec6b3f2db7ec262268dac697c3b3f5f9b183e127ddf265c7ba5015578969bc7f8be02a2
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
= 3.37.0 (2020-10-16)
|
|
2
|
+
|
|
3
|
+
* Add custom_matchers plugin, for supporting arbitrary objects as matchers (jeremyevans)
|
|
4
|
+
|
|
5
|
+
= 3.36.0 (2020-09-14)
|
|
6
|
+
|
|
7
|
+
* Add multi_public plugin, for serving files from multiple public directories (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Support report-to directive in the content_security_policy plugin (jeremyevans)
|
|
10
|
+
|
|
11
|
+
* Add Vary response header when using type_routing plugin with Accept request header to prevent caching issues (jeremyevans)
|
|
12
|
+
|
|
13
|
+
= 3.35.0 (2020-08-14)
|
|
14
|
+
|
|
15
|
+
* Add r plugin for r method for accessing request, useful when r local variable is not in scope (jeremyevans)
|
|
16
|
+
|
|
17
|
+
* Warn when loading a plugin with arguments or a block if the plugin does not accept arguments or block (jeremyevans)
|
|
18
|
+
|
|
19
|
+
= 3.34.0 (2020-07-14)
|
|
20
|
+
|
|
21
|
+
* Remove unnecessary conditionals (jeremyevans)
|
|
22
|
+
|
|
23
|
+
* Allow loading the match_affix plugin with a single argument (jeremyevans)
|
|
24
|
+
|
|
25
|
+
* Do not include pre/post context sections if empty in the exception_page plugin (jeremyevans)
|
|
26
|
+
|
|
27
|
+
= 3.33.0 (2020-06-16)
|
|
28
|
+
|
|
29
|
+
* Add :brotli option to public plugin to supplement it to serve brotli-compressed files like :gzip does for gzipped files (hmdne) (#194)
|
|
30
|
+
|
|
31
|
+
* Add url method to path plugin, similar to path but returning the entire URL (jeremyevans)
|
|
32
|
+
|
|
1
33
|
= 3.32.0 (2020-05-15)
|
|
2
34
|
|
|
3
35
|
* Make :dependencies option in assets plugin work correctly with render plugin template caching (jeremyevans) (#191)
|
data/doc/conventions.rdoc
CHANGED
|
@@ -15,6 +15,8 @@ For a small application, the following directory layout is recommended:
|
|
|
15
15
|
Rakefile
|
|
16
16
|
app_name.rb
|
|
17
17
|
assets/
|
|
18
|
+
config.ru
|
|
19
|
+
db.rb
|
|
18
20
|
migrate/
|
|
19
21
|
models.rb
|
|
20
22
|
models/
|
|
@@ -25,6 +27,8 @@ For a small application, the following directory layout is recommended:
|
|
|
25
27
|
+app_name.rb+ should contain the Roda application, and should reflect the name of your application.
|
|
26
28
|
So, if your application is named +FooBar+, you should use +foo_bar.rb+.
|
|
27
29
|
|
|
30
|
+
+config.ru+ should contain the code the webserver uses to determine which application to run.
|
|
31
|
+
|
|
28
32
|
+views/+ should contain your template files. This assumes you are using the +render+ plugin
|
|
29
33
|
and server-side rendering. If you are creating a single page application and just serving
|
|
30
34
|
JSON, then you won't need a +views+ directory. For small applications, all view files should be
|
|
@@ -36,7 +40,11 @@ Again, for pure JSON applications, you won't need a +public+ directory.
|
|
|
36
40
|
+assets/+ should contain the source files for your CSS and javascript assets. If you are
|
|
37
41
|
not using the +assets+ plugin, you won't need an +assets+ directory.
|
|
38
42
|
|
|
39
|
-
+
|
|
43
|
+
+db.rb+ should contain the minimum code to setup a database connection, without loading any of
|
|
44
|
+
the applications models. This can be required in cases where you don't want the models loaded,
|
|
45
|
+
such as when running migrations. This file should be required by +models.rb+.
|
|
46
|
+
|
|
47
|
+
+models.rb+ should contain all code related to your ORM. This file should be required
|
|
40
48
|
by +app_name.rb+. This keeps your model code separate from your web code, making it easier
|
|
41
49
|
to use outside of your web code. It allows you to get an IRB shell for accessing your models
|
|
42
50
|
via <tt>irb -r ./models</tt>, without loading the Roda application.
|
|
@@ -46,7 +54,7 @@ via <tt>irb -r ./models</tt>, without loading the Roda application.
|
|
|
46
54
|
+migrate/+ should create your database migration files, if you are using an ORM that uses
|
|
47
55
|
migrations.
|
|
48
56
|
|
|
49
|
-
+spec/+ should contain your specifications/tests. For a small application, it's recommended
|
|
57
|
+
+spec/+ (or +test/+ should contain your specifications/tests. For a small application, it's recommended
|
|
50
58
|
to a have a single file for your model tests, and a single file for your web/integration tests.
|
|
51
59
|
|
|
52
60
|
+Rakefile+ should contain the rake tasks for the application. The convention is that the
|
|
@@ -83,7 +91,8 @@ The routes used by the +hash_routes+ or +multi_run+ should be stored in routing
|
|
|
83
91
|
directory, with one file per prefix.
|
|
84
92
|
|
|
85
93
|
For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
|
|
86
|
-
and one file per prefix in +spec/web/+.
|
|
94
|
+
and one file per prefix in +spec/web/+. Substitute +spec+ with +test+ if that is what you are using as the
|
|
95
|
+
name of the directory.
|
|
87
96
|
|
|
88
97
|
You should have a separate view subdirectory per prefix. If you are using +hash_routes+ and +view_options+ plugins,
|
|
89
98
|
use +set_view_subdir+ in your routing files to specify the subdirectory to use, so it doesn't need to be
|
|
@@ -105,7 +114,7 @@ subdirectories in the +routes/+ directory, and nested subdirectories in the +vie
|
|
|
105
114
|
For a small application, the convention in Roda is to layout your Roda application file (+app_name.rb+) like this:
|
|
106
115
|
|
|
107
116
|
require 'roda'
|
|
108
|
-
|
|
117
|
+
require_relative 'models'
|
|
109
118
|
|
|
110
119
|
class AppName < Roda
|
|
111
120
|
SOME_CONSTANT = 1
|
|
@@ -138,24 +147,24 @@ used in your route block or views.
|
|
|
138
147
|
For larger applications, there are some slight changes to the Roda application file layout:
|
|
139
148
|
|
|
140
149
|
require 'roda'
|
|
141
|
-
|
|
150
|
+
require_relative 'models'
|
|
142
151
|
|
|
143
152
|
class AppName < Roda
|
|
144
153
|
SOME_CONSTANT = 1
|
|
145
154
|
|
|
146
155
|
use SomeMiddleware
|
|
147
156
|
|
|
148
|
-
plugin :render, escape: true
|
|
157
|
+
plugin :render, escape: true, layout: './layout'
|
|
149
158
|
plugin :assets
|
|
150
159
|
plugin :view_options
|
|
151
160
|
plugin :hash_routes
|
|
152
|
-
Dir['
|
|
161
|
+
Dir['routes/*.rb'].each{|f| require_relative f}
|
|
153
162
|
|
|
154
163
|
route do |r|
|
|
155
164
|
r.hash_routes
|
|
156
165
|
end
|
|
157
166
|
|
|
158
|
-
Dir['
|
|
167
|
+
Dir['helpers/*.rb'].each{|f| require_relative f}
|
|
159
168
|
end
|
|
160
169
|
|
|
161
170
|
After loading the +view_options+ and +hash_routes+ plugin, you require all of your
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* The path plugin now supports a url method, allowing for returning
|
|
4
|
+
the entire URL instead of just the path for class-based paths.
|
|
5
|
+
|
|
6
|
+
* The public plugin now supports a :brotli option that will directly
|
|
7
|
+
serve brotli-compressed files (with .br extension) similar to how the
|
|
8
|
+
:gzip option directly serves gzipped files (with the .gz extension).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
= Improvements
|
|
2
|
+
|
|
3
|
+
* Multiple unneeded conditionals have been removed.
|
|
4
|
+
|
|
5
|
+
* pre_content and post_context sections in backtraces are no longer
|
|
6
|
+
included in the exception_page plugin output if they would be
|
|
7
|
+
empty.
|
|
8
|
+
|
|
9
|
+
* The match_affix plugin can be loaded again with a single argument.
|
|
10
|
+
It was originally designed to accept a single argument, but a bug
|
|
11
|
+
introduced in 2.29.0 made it require two arguments.
|
|
12
|
+
|
|
13
|
+
* Core Roda and all plugins that ship with Roda now have 100% branch
|
|
14
|
+
coverage.
|
|
15
|
+
|
|
16
|
+
* The sinatra_helpers plugin no longer emits statement not reached
|
|
17
|
+
warnings in verbose mode.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* An r plugin has been added. This plugin adds an r method for the
|
|
4
|
+
request, useful for allowing the use of r.halt and r.redirect even
|
|
5
|
+
in methods where the r local variable is not in scope.
|
|
6
|
+
|
|
7
|
+
= Other Improvements
|
|
8
|
+
|
|
9
|
+
* Attempting to load a plugin with an argument or block when the plugin
|
|
10
|
+
does not accept arguments or a block now warns. This is because a
|
|
11
|
+
future update to support a block or an optional argument could break
|
|
12
|
+
the call.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* A multi_public plugin has been added, which allows serving static
|
|
4
|
+
files from multiple separate directories. This is especially
|
|
5
|
+
useful when there are different access control requirements per
|
|
6
|
+
directory.
|
|
7
|
+
|
|
8
|
+
* The content_security_policy now supports a
|
|
9
|
+
content_security_policy.report_to method to set the
|
|
10
|
+
report-to directive.
|
|
11
|
+
|
|
12
|
+
= Other Improvements
|
|
13
|
+
|
|
14
|
+
* When using the type_routing plugin and performing type routing
|
|
15
|
+
using the Accept request header, the Vary response header will be
|
|
16
|
+
added or updated so that http caches do not cache a response for one
|
|
17
|
+
type and serve it for a different type.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* A custom_matchers plugin has been added, which allows using
|
|
4
|
+
arbitrary objects as matchers, as long as the matcher has been
|
|
5
|
+
registered. You can register matchers using the custom_matcher
|
|
6
|
+
class method, which takes the class of the matcher, and a block
|
|
7
|
+
which is yielded the matcher object. The block should return
|
|
8
|
+
nil or false if the matcher doesn't match, and any other value
|
|
9
|
+
if the matcher does match. Example:
|
|
10
|
+
|
|
11
|
+
plugin :custom_matchers
|
|
12
|
+
method_segment = Struct.new(:request_method, :next_segment)
|
|
13
|
+
custom_matcher(method_segment) do |matcher|
|
|
14
|
+
# self is the request instance ("r" yielded in the route block below)
|
|
15
|
+
if matcher.request_method == self.request_method
|
|
16
|
+
match(matcher.next_segment)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
get_foo = method_segment.new('GET', 'foo')
|
|
21
|
+
post_any = method_segment.new('POST', String)
|
|
22
|
+
route do |r|
|
|
23
|
+
r.on('baz') do
|
|
24
|
+
r.on(get_foo) do
|
|
25
|
+
# GET method, /baz/foo prefix
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
r.is(post_any) do |seg|
|
|
29
|
+
# for POST /baz/bar, seg is "bar"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
r.on('quux') do
|
|
34
|
+
r.is(get_foo) do
|
|
35
|
+
# GET method, /quux/foo route
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
r.on(post_any) do |seg|
|
|
39
|
+
# for POST /quux/xyz, seg is "xyz"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/roda.rb
CHANGED
|
@@ -272,6 +272,12 @@ class Roda
|
|
|
272
272
|
raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen?
|
|
273
273
|
plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
|
274
274
|
raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module)
|
|
275
|
+
|
|
276
|
+
if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block)
|
|
277
|
+
# RODA4: switch from warning to error
|
|
278
|
+
RodaPlugins.warn("Plugin #{plugin} does not accept arguments or a block, but arguments or a block was passed when loading this. This will raise an error in Roda 4.")
|
|
279
|
+
end
|
|
280
|
+
|
|
275
281
|
plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
|
|
276
282
|
include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
|
|
277
283
|
extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
|
|
@@ -34,7 +34,9 @@ class Roda
|
|
|
34
34
|
module AllVerbs
|
|
35
35
|
module RequestMethods
|
|
36
36
|
%w'delete head options link patch put trace unlink'.each do |verb|
|
|
37
|
+
# :nocov:
|
|
37
38
|
if ::Rack::Request.method_defined?("#{verb}?")
|
|
39
|
+
# :nocov:
|
|
38
40
|
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
39
41
|
def #{verb}(*args, &block)
|
|
40
42
|
_verb(args, &block) if #{verb}?
|
|
@@ -33,9 +33,7 @@ class Roda
|
|
|
33
33
|
# elements. If the remaining elements could not be
|
|
34
34
|
# matched, reset the state and continue to the next
|
|
35
35
|
# entry in the array.
|
|
36
|
-
def _match_array(arg, rest
|
|
37
|
-
return super unless rest
|
|
38
|
-
|
|
36
|
+
def _match_array(arg, rest)
|
|
39
37
|
path = @remaining_path
|
|
40
38
|
captures = @captures
|
|
41
39
|
caps = captures.dup
|
|
@@ -56,6 +56,7 @@ class Roda
|
|
|
56
56
|
# * media_src
|
|
57
57
|
# * object_src
|
|
58
58
|
# * plugin_types
|
|
59
|
+
# * report_to
|
|
59
60
|
# * report_uri
|
|
60
61
|
# * require_sri_for
|
|
61
62
|
# * sandbox
|
|
@@ -123,6 +124,7 @@ class Roda
|
|
|
123
124
|
media-src
|
|
124
125
|
object-src
|
|
125
126
|
plugin-types
|
|
127
|
+
report-to
|
|
126
128
|
report-uri
|
|
127
129
|
require-sri-for
|
|
128
130
|
sandbox
|
|
@@ -145,9 +147,7 @@ class Roda
|
|
|
145
147
|
# add_* method name adds to the setting value, or clears setting if no values
|
|
146
148
|
# are given.
|
|
147
149
|
define_method("add_#{meth}") do |*args|
|
|
148
|
-
|
|
149
|
-
@opts[setting]
|
|
150
|
-
else
|
|
150
|
+
unless args.empty?
|
|
151
151
|
@opts[setting] ||= EMPTY_ARRAY
|
|
152
152
|
@opts[setting] += args
|
|
153
153
|
@opts[setting].freeze
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The custom_matchers plugin supports using arbitrary objects
|
|
7
|
+
# as matchers, as long as the application has been configured
|
|
8
|
+
# to accept such objects.
|
|
9
|
+
#
|
|
10
|
+
# After loading the plugin, support for custom matchers can be
|
|
11
|
+
# configured using the +custom_matcher+ class method. This
|
|
12
|
+
# method is generally passed the class of the object you want
|
|
13
|
+
# to use as a custom matcher, as well as a block. The block
|
|
14
|
+
# will be called in the context of the request instance
|
|
15
|
+
# with the specific matcher used in the match method.
|
|
16
|
+
#
|
|
17
|
+
# Blocks can append to the captures in order to yield the appropriate
|
|
18
|
+
# values to match blocks, or call request methods that append to the
|
|
19
|
+
# captures.
|
|
20
|
+
#
|
|
21
|
+
# Example:
|
|
22
|
+
#
|
|
23
|
+
# plugin :custom_matchers
|
|
24
|
+
# method_segment = Struct.new(:request_method, :next_segment)
|
|
25
|
+
# custom_matcher(method_segment) do |matcher|
|
|
26
|
+
# # self is the request instance ("r" yielded in the route block below)
|
|
27
|
+
# if matcher.request_method == self.request_method
|
|
28
|
+
# match(matcher.next_segment)
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# get_foo = method_segment.new('GET', 'foo')
|
|
33
|
+
# post_any = method_segment.new('POST', String)
|
|
34
|
+
# route do |r|
|
|
35
|
+
# r.on('baz') do
|
|
36
|
+
# r.on(get_foo) do
|
|
37
|
+
# # GET method, /baz/foo prefix
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# r.is(post_any) do |seg|
|
|
41
|
+
# # for POST /baz/bar, seg is "bar"
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# r.on('quux') do
|
|
46
|
+
# r.is(get_foo) do
|
|
47
|
+
# # GET method, /quux/foo route
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# r.on(post_any) do |seg|
|
|
51
|
+
# # for POST /quux/xyz, seg is "xyz"
|
|
52
|
+
# end
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
module CustomMatchers
|
|
56
|
+
def self.configure(app)
|
|
57
|
+
app.opts[:custom_matchers] ||= OPTS
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module ClassMethods
|
|
61
|
+
def custom_matcher(match_class, &block)
|
|
62
|
+
custom_matchers = Hash[opts[:custom_matchers]]
|
|
63
|
+
meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
|
|
64
|
+
opts[:custom_matchers] = custom_matchers.freeze
|
|
65
|
+
self::RodaRequest.send(:define_method, meth, &block)
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module RequestMethods
|
|
71
|
+
# Try custom matchers before calling super
|
|
72
|
+
def unsupported_matcher(matcher)
|
|
73
|
+
roda_class.opts[:custom_matchers].each do |match_class, meth|
|
|
74
|
+
if match_class === matcher
|
|
75
|
+
return send(meth, matcher)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
register_plugin(:custom_matchers, CustomMatchers)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
@@ -252,11 +252,21 @@ END
|
|
|
252
252
|
begin
|
|
253
253
|
lineno -= 1
|
|
254
254
|
lines = ::File.readlines(filename)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
if line = lines[lineno]
|
|
256
|
+
pre_lineno = [lineno-context, 0].max
|
|
257
|
+
if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
|
|
258
|
+
frame[:pre_context_lineno] = pre_lineno
|
|
259
|
+
frame[:pre_context] = pre_context
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
post_lineno = [lineno+context, lines.size].min
|
|
263
|
+
if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
|
|
264
|
+
frame[:post_context_lineno] = post_lineno
|
|
265
|
+
frame[:post_context] = post_context
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
frame[:context_line] = line.chomp
|
|
269
|
+
end
|
|
260
270
|
rescue
|
|
261
271
|
end
|
|
262
272
|
|
|
@@ -28,7 +28,7 @@ class Roda
|
|
|
28
28
|
#
|
|
29
29
|
# This plugin automatically loads the placeholder_string_matchers plugin.
|
|
30
30
|
module MatchAffix
|
|
31
|
-
def self.load_dependencies(app, _prefix, _suffix)
|
|
31
|
+
def self.load_dependencies(app, _prefix, _suffix=nil)
|
|
32
32
|
app.plugin :placeholder_string_matchers
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The multi_public plugin adds an +r.multi_public+ method that accepts an argument specifying
|
|
7
|
+
# a directory from which to serve static files. It is similar to the public plugin, but
|
|
8
|
+
# allows for multiple separate directories.
|
|
9
|
+
#
|
|
10
|
+
# Here's an example of using the multi_public plugin to serve 3 different types of files
|
|
11
|
+
# from 3 different directories:
|
|
12
|
+
#
|
|
13
|
+
# plugin :multi_public,
|
|
14
|
+
# img: 'static/images',
|
|
15
|
+
# font: 'assets/fonts',
|
|
16
|
+
# form: 'static/forms/pdfs'
|
|
17
|
+
#
|
|
18
|
+
# r.route do
|
|
19
|
+
# r.on "images" do
|
|
20
|
+
# r.multi_public(:img)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# r.on "fonts" do
|
|
24
|
+
# r.multi_public(:font)
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# r.on "forms" do
|
|
28
|
+
# r.multi_public(:form)
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# It is possible to simplify the routing tree for this using string keys and an array
|
|
33
|
+
# matcher:
|
|
34
|
+
#
|
|
35
|
+
# plugin :multi_public,
|
|
36
|
+
# 'images' => 'static/images',
|
|
37
|
+
# 'fonts' => 'assets/fonts',
|
|
38
|
+
# 'forms' => 'static/forms/pdfs'
|
|
39
|
+
#
|
|
40
|
+
# r.route do
|
|
41
|
+
# r.on %w"images fonts forms" do |dir|
|
|
42
|
+
# r.multi_public(dir)
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# You can provide custom headers and default mime type for each directory using an array
|
|
47
|
+
# of three elements as the value, with the first element being the path, the second
|
|
48
|
+
# being the custom headers, and the third being the default mime type:
|
|
49
|
+
#
|
|
50
|
+
# plugin :multi_public,
|
|
51
|
+
# 'images' => ['static/images', {'Cache-Control'=>'max-age=86400'}, nil],
|
|
52
|
+
# 'fonts' => ['assets/fonts', {'Cache-Control'=>'max-age=31536000'}, 'font/ttf'],
|
|
53
|
+
# 'forms' => ['static/forms/pdfs', nil, 'application/pdf']
|
|
54
|
+
#
|
|
55
|
+
# r.route do
|
|
56
|
+
# r.on %w"images fonts forms" do |dir|
|
|
57
|
+
# r.multi_public(dir)
|
|
58
|
+
# end
|
|
59
|
+
# end
|
|
60
|
+
module MultiPublic
|
|
61
|
+
def self.load_dependencies(app, _, opts=OPTS)
|
|
62
|
+
app.plugin(:public, opts)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Use the given directories to setup servers. Any opts are passed to the public plugin.
|
|
66
|
+
def self.configure(app, directories, _=OPTS)
|
|
67
|
+
roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
|
|
68
|
+
directories.each do |key, path|
|
|
69
|
+
path, headers, mime = path
|
|
70
|
+
roots[key] = ::Rack::File.new(app.expand_path(path), headers||{}, mime||'text/plain')
|
|
71
|
+
end
|
|
72
|
+
roots.freeze
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module RequestMethods
|
|
76
|
+
# Serve files from the directory corresponding to the given key if the file exists and
|
|
77
|
+
# this is a GET request.
|
|
78
|
+
def multi_public(key)
|
|
79
|
+
public_serve_with(roda_class.opts[:multi_public_servers].fetch(key))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
register_plugin(:multi_public, MultiPublic)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
@@ -104,7 +104,9 @@ class Roda
|
|
|
104
104
|
# arguments, record the verb used. If given an argument, add an is
|
|
105
105
|
# check with the arguments.
|
|
106
106
|
%w'get post delete head options link patch put trace unlink'.each do |verb|
|
|
107
|
+
# :nocov:
|
|
107
108
|
if ::Rack::Request.method_defined?("#{verb}?")
|
|
109
|
+
# :nocov:
|
|
108
110
|
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
109
111
|
def #{verb}(*args, &block)
|
|
110
112
|
if (empty = args.empty?) && @_is_verbs
|
data/lib/roda/plugins/path.rb
CHANGED
|
@@ -10,7 +10,8 @@ class Roda
|
|
|
10
10
|
#
|
|
11
11
|
# Additionally, you can call the +path+ class method with a class and a block, and it will register
|
|
12
12
|
# the class. You can then call the +path+ instance method with an instance of that class, and it will
|
|
13
|
-
# execute the block in the context of the route block scope with the arguments provided to path.
|
|
13
|
+
# execute the block in the context of the route block scope with the arguments provided to path. You
|
|
14
|
+
# can call the +url+ instance method with the same arguments as the +path+ method to get the full URL.
|
|
14
15
|
#
|
|
15
16
|
# Example:
|
|
16
17
|
#
|
|
@@ -46,11 +47,11 @@ class Roda
|
|
|
46
47
|
#
|
|
47
48
|
# r.post 'quux' do
|
|
48
49
|
# bar = Quux[1]
|
|
49
|
-
# r.redirect
|
|
50
|
+
# r.redirect url(quux, '/bar') # http://example.com/quux/1/bar
|
|
50
51
|
# end
|
|
51
52
|
# end
|
|
52
53
|
#
|
|
53
|
-
# The path method accepts the following options when not called with a class:
|
|
54
|
+
# The path class method accepts the following options when not called with a class:
|
|
54
55
|
#
|
|
55
56
|
# :add_script_name :: Prefix the path generated with SCRIPT_NAME. This defaults to the app's
|
|
56
57
|
# :add_script_name option.
|
|
@@ -62,7 +63,7 @@ class Roda
|
|
|
62
63
|
# method. If a Symbol or String, uses the value as the url method name.
|
|
63
64
|
# :url_only :: Do not create a path method, just a url method.
|
|
64
65
|
#
|
|
65
|
-
# Note that if :add_script_name, :url, or :url_only is used, the path method will also create a
|
|
66
|
+
# Note that if :add_script_name, :relative, :url, or :url_only is used, the path method will also create a
|
|
66
67
|
# <tt>_*_path</tt> private method.
|
|
67
68
|
module Path
|
|
68
69
|
DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze
|
|
@@ -165,14 +166,8 @@ class Roda
|
|
|
165
166
|
end
|
|
166
167
|
|
|
167
168
|
url_block = lambda do |*a, &blk|
|
|
168
|
-
r = request
|
|
169
|
-
scheme = r.scheme
|
|
170
|
-
port = r.port
|
|
171
|
-
uri = ["#{scheme}://#{r.host}#{":#{port}" unless DEFAULT_PORTS[scheme] == port}"]
|
|
172
|
-
uri << request.script_name.to_s if add_script_name
|
|
173
169
|
# Allow calling private _method to get path
|
|
174
|
-
|
|
175
|
-
File.join(uri)
|
|
170
|
+
"#{_base_url}#{request.script_name if add_script_name}#{send(_meth, *a, &blk)}"
|
|
176
171
|
end
|
|
177
172
|
|
|
178
173
|
define_method(url_meth, &url_block)
|
|
@@ -207,6 +202,21 @@ class Roda
|
|
|
207
202
|
path = request.script_name.to_s + path if opts[:add_script_name]
|
|
208
203
|
path
|
|
209
204
|
end
|
|
205
|
+
|
|
206
|
+
# Similar to #path, but returns a complete URL.
|
|
207
|
+
def url(*args, &block)
|
|
208
|
+
"#{_base_url}#{path(*args, &block)}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
# The string to prepend to the path to make the path a URL.
|
|
214
|
+
def _base_url
|
|
215
|
+
r = @_request
|
|
216
|
+
scheme = r.scheme
|
|
217
|
+
port = r.port
|
|
218
|
+
"#{scheme}://#{r.host}#{":#{port}" unless DEFAULT_PORTS[scheme] == port}"
|
|
219
|
+
end
|
|
210
220
|
end
|
|
211
221
|
end
|
|
212
222
|
|
data/lib/roda/plugins/public.rb
CHANGED
|
@@ -42,6 +42,8 @@ class Roda
|
|
|
42
42
|
# :default_mime :: The default mime type to use if the mime type is not recognized.
|
|
43
43
|
# :gzip :: Whether to serve already gzipped files with a .gz extension for clients
|
|
44
44
|
# supporting gzipped transfer encoding.
|
|
45
|
+
# :brotli :: Whether to serve already brotli-compressed files with a .br extension
|
|
46
|
+
# for clients supporting brotli transfer encoding.
|
|
45
47
|
# :headers :: A hash of headers to use for statically served files
|
|
46
48
|
# :root :: Use this option for the root of the public directory (default: "public")
|
|
47
49
|
def self.configure(app, opts={})
|
|
@@ -52,41 +54,13 @@ class Roda
|
|
|
52
54
|
end
|
|
53
55
|
app.opts[:public_server] = ::Rack::File.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
|
54
56
|
app.opts[:public_gzip] = opts[:gzip]
|
|
57
|
+
app.opts[:public_brotli] = opts[:brotli]
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
module RequestMethods
|
|
58
61
|
# Serve files from the public directory if the file exists and this is a GET request.
|
|
59
62
|
def public
|
|
60
|
-
|
|
61
|
-
path = PARSER.unescape(real_remaining_path)
|
|
62
|
-
return if path.include?("\0")
|
|
63
|
-
|
|
64
|
-
roda_opts = roda_class.opts
|
|
65
|
-
server = roda_opts[:public_server]
|
|
66
|
-
path = ::File.join(server.root, *public_path_segments(path))
|
|
67
|
-
|
|
68
|
-
if roda_opts[:public_gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
|
|
69
|
-
gzip_path = path + '.gz'
|
|
70
|
-
|
|
71
|
-
if public_file_readable?(gzip_path)
|
|
72
|
-
res = public_serve(server, gzip_path)
|
|
73
|
-
headers = res[1]
|
|
74
|
-
|
|
75
|
-
unless res[0] == 304
|
|
76
|
-
if mime_type = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
|
77
|
-
headers['Content-Type'] = mime_type
|
|
78
|
-
end
|
|
79
|
-
headers['Content-Encoding'] = 'gzip'
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
halt res
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if public_file_readable?(path)
|
|
87
|
-
halt public_serve(server, path)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
63
|
+
public_serve_with(roda_class.opts[:public_server])
|
|
90
64
|
end
|
|
91
65
|
|
|
92
66
|
private
|
|
@@ -113,6 +87,40 @@ class Roda
|
|
|
113
87
|
# :nocov:
|
|
114
88
|
end
|
|
115
89
|
|
|
90
|
+
def public_serve_with(server)
|
|
91
|
+
return unless is_get?
|
|
92
|
+
path = PARSER.unescape(real_remaining_path)
|
|
93
|
+
return if path.include?("\0")
|
|
94
|
+
|
|
95
|
+
roda_opts = roda_class.opts
|
|
96
|
+
path = ::File.join(server.root, *public_path_segments(path))
|
|
97
|
+
|
|
98
|
+
public_serve_compressed(server, path, '.br', 'br') if roda_opts[:public_brotli]
|
|
99
|
+
public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
|
|
100
|
+
|
|
101
|
+
if public_file_readable?(path)
|
|
102
|
+
halt public_serve(server, path)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def public_serve_compressed(server, path, suffix, encoding)
|
|
107
|
+
if env['HTTP_ACCEPT_ENCODING'] =~ /\b#{encoding}\b/
|
|
108
|
+
compressed_path = path + suffix
|
|
109
|
+
|
|
110
|
+
if public_file_readable?(compressed_path)
|
|
111
|
+
res = public_serve(server, compressed_path)
|
|
112
|
+
headers = res[1]
|
|
113
|
+
|
|
114
|
+
unless res[0] == 304
|
|
115
|
+
headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
|
116
|
+
headers['Content-Encoding'] = encoding
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
halt res
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
116
124
|
if ::Rack.release > '2'
|
|
117
125
|
# Serve the given path using the given Rack::File server.
|
|
118
126
|
def public_serve(server, path)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The r plugin adds an +r+ instance method that will return the request.
|
|
7
|
+
# This allows you to use common Roda idioms such as +r.halt+ and
|
|
8
|
+
# +r.redirect+ even when +r+ isn't a local variable in scope. Example:
|
|
9
|
+
#
|
|
10
|
+
# plugin :r
|
|
11
|
+
#
|
|
12
|
+
# def foo
|
|
13
|
+
# r.redirect "/bar"
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# route do |r|
|
|
17
|
+
# r.get "foo" do
|
|
18
|
+
# foo
|
|
19
|
+
# end
|
|
20
|
+
# r.get "bar" do
|
|
21
|
+
# "bar"
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
module R
|
|
25
|
+
module InstanceMethods
|
|
26
|
+
# The request object.
|
|
27
|
+
def r
|
|
28
|
+
@_request
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
register_plugin(:r, R)
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/roda/plugins/render.rb
CHANGED
|
@@ -287,7 +287,9 @@ class Roda
|
|
|
287
287
|
false
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
+
# :nocov:
|
|
290
291
|
if COMPILED_METHOD_SUPPORT
|
|
292
|
+
# :nocov:
|
|
291
293
|
# Compile a method in the given module with the given name that will
|
|
292
294
|
# call the compiled template method, updating the compiled template method
|
|
293
295
|
def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY)
|
|
@@ -337,7 +339,9 @@ class Roda
|
|
|
337
339
|
def inherited(subclass)
|
|
338
340
|
super
|
|
339
341
|
opts = subclass.opts[:render] = subclass.opts[:render].dup
|
|
342
|
+
# :nocov:
|
|
340
343
|
if COMPILED_METHOD_SUPPORT
|
|
344
|
+
# :nocov:
|
|
341
345
|
opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new
|
|
342
346
|
end
|
|
343
347
|
opts[:cache] = opts[:cache].dup
|
|
@@ -43,7 +43,9 @@ class Roda
|
|
|
43
43
|
module InstanceMethods
|
|
44
44
|
private
|
|
45
45
|
|
|
46
|
+
# :nocov:
|
|
46
47
|
if Render::COMPILED_METHOD_SUPPORT
|
|
48
|
+
# :nocov:
|
|
47
49
|
# Disable use of cached templates, since it assumes a render/view call with no
|
|
48
50
|
# options will have no locals.
|
|
49
51
|
def _cached_template_method(template)
|
|
@@ -374,7 +374,7 @@ class Roda
|
|
|
374
374
|
|
|
375
375
|
module ResponseMethods
|
|
376
376
|
# Set or retrieve the response status code.
|
|
377
|
-
def status(value = (return @status
|
|
377
|
+
def status(value = nil || (return @status))
|
|
378
378
|
@status = value
|
|
379
379
|
end
|
|
380
380
|
|
|
@@ -401,7 +401,7 @@ class Roda
|
|
|
401
401
|
|
|
402
402
|
# Set multiple response headers with Hash, or return the headers if no
|
|
403
403
|
# argument is given.
|
|
404
|
-
def headers(hash = (return @headers
|
|
404
|
+
def headers(hash = nil || (return @headers))
|
|
405
405
|
@headers.merge!(hash)
|
|
406
406
|
end
|
|
407
407
|
|
|
@@ -412,7 +412,7 @@ class Roda
|
|
|
412
412
|
|
|
413
413
|
# Set the Content-Type of the response body given a media type or file
|
|
414
414
|
# extension. See plugin documentation for options.
|
|
415
|
-
def content_type(type = (return @headers["Content-Type"]
|
|
415
|
+
def content_type(type = nil || (return @headers["Content-Type"]), opts = OPTS)
|
|
416
416
|
unless (mime_type = mime_type(type) || opts[:default])
|
|
417
417
|
raise RodaError, "Unknown media type: #{type}"
|
|
418
418
|
end
|
|
@@ -478,7 +478,7 @@ class Roda
|
|
|
478
478
|
# If a type and value are given, set the value in Rack's MIME registry.
|
|
479
479
|
# If only a type is given, lookup the type in Rack's MIME registry and
|
|
480
480
|
# return it.
|
|
481
|
-
def mime_type(type=(return
|
|
481
|
+
def mime_type(type=nil || (return), value = nil)
|
|
482
482
|
return type.to_s if type.to_s.include?('/')
|
|
483
483
|
type = ".#{type}" unless type.to_s[0] == ?.
|
|
484
484
|
if value
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
class Roda
|
|
5
5
|
module RodaPlugins
|
|
6
6
|
# This plugin makes it easier to to respond to specific request data types. User agents can request
|
|
7
|
-
# specific data types by either supplying an appropriate +Accept+ header
|
|
7
|
+
# specific data types by either supplying an appropriate +Accept+ request header
|
|
8
8
|
# or by appending it as file extension to the path.
|
|
9
9
|
#
|
|
10
10
|
# Example:
|
|
@@ -50,6 +50,10 @@ class Roda
|
|
|
50
50
|
# Content-Type header will be set to Roda's default (which you can override via
|
|
51
51
|
# the default_headers plugin).
|
|
52
52
|
#
|
|
53
|
+
# If the type routing is based on the +Accept+ request header and not the file extension,
|
|
54
|
+
# then an appropriate +Vary+ header will be set or appended to, so that HTTP caches do
|
|
55
|
+
# not serve the same result for requests with different +Accept+ headers.
|
|
56
|
+
#
|
|
53
57
|
# To match custom extensions, use the :types option:
|
|
54
58
|
#
|
|
55
59
|
# plugin :type_routing, types: {
|
|
@@ -195,6 +199,7 @@ class Roda
|
|
|
195
199
|
@env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
|
|
196
200
|
mime, _= part.split(/\s*;\s*/, 2)
|
|
197
201
|
if sym = mimes[mime]
|
|
202
|
+
response['Vary'] = (vary = response['Vary']) ? "#{vary}, Accept" : 'Accept'
|
|
198
203
|
return sym
|
|
199
204
|
end
|
|
200
205
|
end
|
|
@@ -609,10 +609,9 @@ class Roda
|
|
|
609
609
|
when nil
|
|
610
610
|
keys = (0...@obj.length)
|
|
611
611
|
|
|
612
|
-
valid =
|
|
613
|
-
when Array
|
|
612
|
+
valid = if @obj.is_a?(Array)
|
|
614
613
|
true
|
|
615
|
-
|
|
614
|
+
else
|
|
616
615
|
keys = keys.map(&:to_s)
|
|
617
616
|
keys.all?{|k| @obj.has_key?(k)}
|
|
618
617
|
end
|
|
@@ -742,9 +741,7 @@ class Roda
|
|
|
742
741
|
return
|
|
743
742
|
end
|
|
744
743
|
|
|
745
|
-
|
|
746
|
-
v.subkey(keys, do_raise)
|
|
747
|
-
end
|
|
744
|
+
self[key].subkey(keys, do_raise)
|
|
748
745
|
rescue => e
|
|
749
746
|
handle_error(key, reason, e)
|
|
750
747
|
end
|
|
@@ -808,10 +805,10 @@ class Roda
|
|
|
808
805
|
@nested_params = nil
|
|
809
806
|
|
|
810
807
|
if capturing_started
|
|
811
|
-
# Unset capturing if capturing was
|
|
808
|
+
# Unset capturing if capturing was already started.
|
|
812
809
|
@capture = nil
|
|
813
810
|
else
|
|
814
|
-
# If capturing was already started, update cached nested params
|
|
811
|
+
# If capturing was not already started, update cached nested params
|
|
815
812
|
# before resetting symbolize setting.
|
|
816
813
|
@nested_params = nested_params
|
|
817
814
|
end
|
|
@@ -878,7 +875,7 @@ class Roda
|
|
|
878
875
|
def handle_error(key, reason, e, do_raise=false)
|
|
879
876
|
case e
|
|
880
877
|
when String
|
|
881
|
-
handle_error(key, reason, Error.new(e), do_raise
|
|
878
|
+
handle_error(key, reason, Error.new(e), do_raise)
|
|
882
879
|
when Error, ArgumentError
|
|
883
880
|
if @capture && (le = @capture.last) && le == e
|
|
884
881
|
raise e if do_raise
|
|
@@ -126,7 +126,9 @@ class Roda
|
|
|
126
126
|
|
|
127
127
|
private
|
|
128
128
|
|
|
129
|
+
# :nocov:
|
|
129
130
|
if Render::COMPILED_METHOD_SUPPORT
|
|
131
|
+
# :nocov:
|
|
130
132
|
# Return nil if using custom view or layout options.
|
|
131
133
|
# If using a view subdir, prefix the template key with the subdir.
|
|
132
134
|
def _cached_template_method_key(template)
|
data/lib/roda/request.rb
CHANGED
|
@@ -466,10 +466,8 @@ class Roda
|
|
|
466
466
|
rp = @remaining_path
|
|
467
467
|
if rp.getbyte(0) == 47
|
|
468
468
|
if last = rp.index('/', 1)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
@remaining_path = rp[last, rp.length]
|
|
472
|
-
end
|
|
469
|
+
@captures << rp[1, last-1]
|
|
470
|
+
@remaining_path = rp[last, rp.length]
|
|
473
471
|
elsif rp.length > 1
|
|
474
472
|
@captures << rp[1,rp.length]
|
|
475
473
|
@remaining_path = ""
|
data/lib/roda/version.rb
CHANGED
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.
|
|
4
|
+
version: 3.37.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: 2020-
|
|
11
|
+
date: 2020-10-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -208,6 +208,11 @@ extra_rdoc_files:
|
|
|
208
208
|
- doc/release_notes/3.30.0.txt
|
|
209
209
|
- doc/release_notes/3.31.0.txt
|
|
210
210
|
- doc/release_notes/3.32.0.txt
|
|
211
|
+
- doc/release_notes/3.33.0.txt
|
|
212
|
+
- doc/release_notes/3.34.0.txt
|
|
213
|
+
- doc/release_notes/3.35.0.txt
|
|
214
|
+
- doc/release_notes/3.36.0.txt
|
|
215
|
+
- doc/release_notes/3.37.0.txt
|
|
211
216
|
files:
|
|
212
217
|
- CHANGELOG
|
|
213
218
|
- MIT-LICENSE
|
|
@@ -241,6 +246,11 @@ files:
|
|
|
241
246
|
- doc/release_notes/3.30.0.txt
|
|
242
247
|
- doc/release_notes/3.31.0.txt
|
|
243
248
|
- doc/release_notes/3.32.0.txt
|
|
249
|
+
- doc/release_notes/3.33.0.txt
|
|
250
|
+
- doc/release_notes/3.34.0.txt
|
|
251
|
+
- doc/release_notes/3.35.0.txt
|
|
252
|
+
- doc/release_notes/3.36.0.txt
|
|
253
|
+
- doc/release_notes/3.37.0.txt
|
|
244
254
|
- doc/release_notes/3.4.0.txt
|
|
245
255
|
- doc/release_notes/3.5.0.txt
|
|
246
256
|
- doc/release_notes/3.6.0.txt
|
|
@@ -267,6 +277,7 @@ files:
|
|
|
267
277
|
- lib/roda/plugins/content_security_policy.rb
|
|
268
278
|
- lib/roda/plugins/cookies.rb
|
|
269
279
|
- lib/roda/plugins/csrf.rb
|
|
280
|
+
- lib/roda/plugins/custom_matchers.rb
|
|
270
281
|
- lib/roda/plugins/default_headers.rb
|
|
271
282
|
- lib/roda/plugins/default_status.rb
|
|
272
283
|
- lib/roda/plugins/delay_build.rb
|
|
@@ -301,6 +312,7 @@ files:
|
|
|
301
312
|
- lib/roda/plugins/middleware.rb
|
|
302
313
|
- lib/roda/plugins/middleware_stack.rb
|
|
303
314
|
- lib/roda/plugins/module_include.rb
|
|
315
|
+
- lib/roda/plugins/multi_public.rb
|
|
304
316
|
- lib/roda/plugins/multi_route.rb
|
|
305
317
|
- lib/roda/plugins/multi_run.rb
|
|
306
318
|
- lib/roda/plugins/multi_view.rb
|
|
@@ -320,6 +332,7 @@ files:
|
|
|
320
332
|
- lib/roda/plugins/placeholder_string_matchers.rb
|
|
321
333
|
- lib/roda/plugins/precompile_templates.rb
|
|
322
334
|
- lib/roda/plugins/public.rb
|
|
335
|
+
- lib/roda/plugins/r.rb
|
|
323
336
|
- lib/roda/plugins/relative_path.rb
|
|
324
337
|
- lib/roda/plugins/render.rb
|
|
325
338
|
- lib/roda/plugins/render_each.rb
|
|
@@ -377,7 +390,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
377
390
|
- !ruby/object:Gem::Version
|
|
378
391
|
version: '0'
|
|
379
392
|
requirements: []
|
|
380
|
-
rubygems_version: 3.1.
|
|
393
|
+
rubygems_version: 3.1.4
|
|
381
394
|
signing_key:
|
|
382
395
|
specification_version: 4
|
|
383
396
|
summary: Routing tree web toolkit
|