roda 2.11.0 → 2.12.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 +10 -0
- data/README.rdoc +32 -0
- data/Rakefile +1 -1
- data/doc/release_notes/2.12.0.txt +40 -0
- data/lib/roda.rb +43 -9
- data/lib/roda/plugins/assets.rb +15 -8
- data/lib/roda/plugins/class_level_routing.rb +3 -0
- data/lib/roda/plugins/match_affix.rb +9 -0
- data/lib/roda/plugins/optimized_string_matchers.rb +51 -0
- data/lib/roda/plugins/pass.rb +5 -2
- data/lib/roda/plugins/symbol_matchers.rb +18 -2
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/error_handler_spec.rb +21 -0
- data/spec/plugin/optimized_string_matchers_spec.rb +40 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfcafebba5d38f3d4346b485721c5625aa0df988
|
4
|
+
data.tar.gz: c3e1918167a29a21c0a0936cf5296778dba0f052
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9c3d4d68c0f1e45f1c16edc923eced0070f4e1be5f9fa27465e0e8f398e1b49b0ee8e4dc5daade672f01530487846217b59b7e157f2e3c1f4c7a4fc4955072d
|
7
|
+
data.tar.gz: ee4d4ea602738536680bc27eec01502105dcb7e6bbccd7aa22cfc305eadf701b0980b2ee43bc37fe3a645434595091c408d99cb1e747675980fc5c5d83dc834f
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 2.12.0 (2016-03-15)
|
2
|
+
|
3
|
+
* Allow error handler access to the request's remaining_path (jeremyevans)
|
4
|
+
|
5
|
+
* Add optimized_string_matchers plugin, containing optimized matchers for single string arguments (jeremyevans)
|
6
|
+
|
7
|
+
* Optimize string matching code for strings without placeholders for up to a 60% performance increase (jeremyevans)
|
8
|
+
|
9
|
+
* Optimize symbol matching code for up to a 60% performance increase (jeremyevans)
|
10
|
+
|
1
11
|
= 2.11.0 (2016-02-16)
|
2
12
|
|
3
13
|
* Support :scope option in render plugin, for specifying object in which to evaluate the template (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -782,6 +782,38 @@ Example:
|
|
782
782
|
'X-XSS-Protection'=>'1; mode=block'
|
783
783
|
end
|
784
784
|
|
785
|
+
=== Rendering Templates Derived From User Input
|
786
|
+
|
787
|
+
Roda's rendering plugin assumes that template paths given to it are trusted. If you provide a path
|
788
|
+
to the +render+/+view+ methods that is derived from user input, you are opening yourself
|
789
|
+
for people rendering arbitrary files on the system that that have a file name ending in the
|
790
|
+
default template extension. For example, if you do:
|
791
|
+
|
792
|
+
class App < Roda
|
793
|
+
plugin :render
|
794
|
+
route do |r|
|
795
|
+
view(r['page'])
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
Then attackers can submit a <tt>page</tt> parameter such as <tt>'../../../../tmp/upload'</tt>
|
800
|
+
to render the <tt>/tmp/upload.erb</tt> file. If you have another part of your system that
|
801
|
+
allows users to create files with arbitrary extensions (even temporary files), then it may
|
802
|
+
be possible to combine these two issues into a remote code execution exploit.
|
803
|
+
|
804
|
+
If you do want to allow users to choose which template to use, you should use a whitelist:
|
805
|
+
|
806
|
+
class App < Roda
|
807
|
+
plugin :render
|
808
|
+
ALLOWED_PAGES = %w'page1 page2 page3'
|
809
|
+
route do |r|
|
810
|
+
page = r['page']
|
811
|
+
if ALLOWED_PAGES.include?(page)
|
812
|
+
view(page)
|
813
|
+
end
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
785
817
|
== Reloading
|
786
818
|
|
787
819
|
Most rack-based reloaders will work with Roda, including:
|
data/Rakefile
CHANGED
@@ -58,7 +58,7 @@ end
|
|
58
58
|
desc "Make local version of website, with rdoc"
|
59
59
|
task :website => [:website_base, :website_rdoc]
|
60
60
|
|
61
|
-
desc "
|
61
|
+
desc "Serve local version of website via rackup"
|
62
62
|
task :serve => :website do
|
63
63
|
sh %{#{FileUtils::RUBY} -C www -S rackup}
|
64
64
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An optimized_string_matchers plugin has been added, which contains
|
4
|
+
optimized matchers for single strings. r.on_branch is an optimized
|
5
|
+
version of r.on and r.is_exactly is optimized version of r.is:
|
6
|
+
|
7
|
+
plugin :optimized_string_matchers
|
8
|
+
|
9
|
+
route do |r|
|
10
|
+
r.on_branch "x" do
|
11
|
+
# matches /x and paths starting with /x/
|
12
|
+
r.is_exactly "y" do
|
13
|
+
# matches /x/y
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Both of these methods will work even if the strings have placeholders,
|
19
|
+
but no captures will be yielded to the blocks.
|
20
|
+
|
21
|
+
* The error_handler plugin now has access to the request's
|
22
|
+
remaining_path when handling an error. You can then compare the
|
23
|
+
remaining_path to path_info to see how much of request was already
|
24
|
+
routed, which can be useful when reporting errors.
|
25
|
+
|
26
|
+
= Other Improvements
|
27
|
+
|
28
|
+
* String matching for strings without placeholders is now 60% faster
|
29
|
+
as it uses optimized string operations instead of a regexp match.
|
30
|
+
|
31
|
+
* Symbol matching is now 60% faster as it uses optimized string
|
32
|
+
operations instead of a regexp match.
|
33
|
+
|
34
|
+
= Backwards Compatibility
|
35
|
+
|
36
|
+
* The match methods no longer automatically reset the remaining_path
|
37
|
+
via ensure. This means that using non-local jumps out of the code
|
38
|
+
such as begin/rescue and throw/catch will not reset remaining_path
|
39
|
+
automatically. Users that want to reset remaining path in
|
40
|
+
such cases should use their own ensure blocks.
|
data/lib/roda.rb
CHANGED
@@ -326,6 +326,7 @@ class Roda
|
|
326
326
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
327
327
|
EMPTY_STRING = "".freeze
|
328
328
|
SLASH = "/".freeze
|
329
|
+
COLON = ":".freeze
|
329
330
|
SEGMENT = "([^\\/]+)".freeze
|
330
331
|
TERM_INSPECT = "TERM".freeze
|
331
332
|
GET_REQUEST_METHOD = 'GET'.freeze
|
@@ -676,12 +677,43 @@ class Roda
|
|
676
677
|
# string so that regexp metacharacters are not matched, and recognizes
|
677
678
|
# colon tokens for placeholders.
|
678
679
|
def _match_string(str)
|
679
|
-
|
680
|
+
if str.index(COLON)
|
681
|
+
consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
|
682
|
+
else
|
683
|
+
rp = @remaining_path
|
684
|
+
if rp.start_with?("/#{str}")
|
685
|
+
last = str.length + 1
|
686
|
+
case rp[last]
|
687
|
+
when SLASH
|
688
|
+
@remaining_path = rp[last, rp.length]
|
689
|
+
when nil
|
690
|
+
@remaining_path = EMPTY_STRING
|
691
|
+
when Integer
|
692
|
+
# :nocov:
|
693
|
+
# Ruby 1.8 support
|
694
|
+
if rp[last].chr == SLASH
|
695
|
+
@remaining_path = rp[last, rp.length]
|
696
|
+
end
|
697
|
+
# :nocov:
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
680
701
|
end
|
681
702
|
|
682
703
|
# Match the given symbol if any segment matches.
|
683
704
|
def _match_symbol(sym)
|
684
|
-
|
705
|
+
rp = @remaining_path
|
706
|
+
if rp[0, 1] == SLASH
|
707
|
+
if last = rp.index('/', 1)
|
708
|
+
if last > 1
|
709
|
+
@captures << rp[1, last-1]
|
710
|
+
@remaining_path = rp[last, rp.length]
|
711
|
+
end
|
712
|
+
elsif rp.length > 1
|
713
|
+
@captures << rp[1,rp.length]
|
714
|
+
@remaining_path = EMPTY_STRING
|
715
|
+
end
|
716
|
+
end
|
685
717
|
end
|
686
718
|
|
687
719
|
# The regular expression to use for matching symbols. By default, any non-empty
|
@@ -760,11 +792,13 @@ class Roda
|
|
760
792
|
# nesting matchers won't mess with each other's captures.
|
761
793
|
@captures.clear
|
762
794
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
795
|
+
if match_all(args)
|
796
|
+
block_result(yield(*captures))
|
797
|
+
throw :halt, response.finish
|
798
|
+
else
|
799
|
+
@remaining_path = path
|
800
|
+
false
|
801
|
+
end
|
768
802
|
end
|
769
803
|
|
770
804
|
# Attempt to match the argument to the given request, handling
|
@@ -773,12 +807,12 @@ class Roda
|
|
773
807
|
case matcher
|
774
808
|
when String
|
775
809
|
_match_string(matcher)
|
776
|
-
when Regexp
|
777
|
-
_match_regexp(matcher)
|
778
810
|
when Symbol
|
779
811
|
_match_symbol(matcher)
|
780
812
|
when TERM
|
781
813
|
empty_path?
|
814
|
+
when Regexp
|
815
|
+
_match_regexp(matcher)
|
782
816
|
when Hash
|
783
817
|
_match_hash(matcher)
|
784
818
|
when Array
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -9,13 +9,16 @@ class Roda
|
|
9
9
|
#
|
10
10
|
# This uses the render plugin for rendering the assets, and the render
|
11
11
|
# plugin uses tilt internally, so you can use any template engine
|
12
|
-
# supported by tilt for
|
12
|
+
# supported by tilt for your assets. Tilt ships with support for
|
13
13
|
# the following asset template engines, assuming the necessary libaries
|
14
14
|
# are installed:
|
15
15
|
#
|
16
16
|
# css :: Less, Sass, Scss
|
17
17
|
# js :: CoffeeScript
|
18
18
|
#
|
19
|
+
# You can also use opal as a javascript template engine, assuming it is
|
20
|
+
# installed.
|
21
|
+
#
|
19
22
|
# == Usage
|
20
23
|
#
|
21
24
|
# When loading the plugin, use the :css and :js options
|
@@ -28,12 +31,13 @@ class Roda
|
|
28
31
|
# assets/css/some_file.scss
|
29
32
|
# assets/js/some_file.coffee
|
30
33
|
#
|
31
|
-
#
|
34
|
+
# The values for the :css and :js options can be arrays to load multiple
|
35
|
+
# files. If you want to change the paths where asset files are stored, see the
|
32
36
|
# Options section below.
|
33
37
|
#
|
34
38
|
# === Serving
|
35
39
|
#
|
36
|
-
# In your routes, call the r.assets method to add a route to your assets,
|
40
|
+
# In your routes, call the +r.assets+ method to add a route to your assets,
|
37
41
|
# which will make your app serve the rendered assets:
|
38
42
|
#
|
39
43
|
# route do |r|
|
@@ -126,9 +130,12 @@ class Roda
|
|
126
130
|
# === Asset Compression
|
127
131
|
#
|
128
132
|
# If you have the yuicompressor gem installed and working, it will be used
|
129
|
-
# automatically to compress your javascript and css assets.
|
130
|
-
#
|
131
|
-
#
|
133
|
+
# automatically to compress your javascript and css assets. For javascript
|
134
|
+
# assets, if yuicompressor is not available, the plugin will check for
|
135
|
+
# closure_compiler, uglifier, and minjs and use the first one that works.
|
136
|
+
# If no compressors are available, the assets will just be concatenated
|
137
|
+
# together and not compressed during compilation. You can use the
|
138
|
+
# :css_compressor and :js_compressor options to specify the compressor to use.
|
132
139
|
#
|
133
140
|
# === With Asset Groups
|
134
141
|
#
|
@@ -143,7 +150,7 @@ class Roda
|
|
143
150
|
#
|
144
151
|
# === Serving
|
145
152
|
#
|
146
|
-
#
|
153
|
+
# When compiling assets, +r.assets+ will serve the compiled asset
|
147
154
|
# files. However, it is recommended to have the main webserver (e.g. nginx)
|
148
155
|
# serve the compiled files, instead of relying on the application.
|
149
156
|
#
|
@@ -165,7 +172,7 @@ class Roda
|
|
165
172
|
# when loading the plugin. The value of this option should be the filename
|
166
173
|
# where the compiled asset metadata is stored.
|
167
174
|
#
|
168
|
-
# If the compiled
|
175
|
+
# If the compiled asset metadata file does not exist when the assets plugin
|
169
176
|
# is loaded, the plugin will run in non-compiled mode. However, when you call
|
170
177
|
# compile_assets, it will write the compiled asset metadata file after
|
171
178
|
# compiling the assets.
|
@@ -79,6 +79,8 @@ class Roda
|
|
79
79
|
# If the normal routing tree doesn't handle an action, try each class level route
|
80
80
|
# to see if it matches.
|
81
81
|
def call
|
82
|
+
req = @_request
|
83
|
+
rp = req.remaining_path
|
82
84
|
result = super
|
83
85
|
|
84
86
|
if result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
|
@@ -87,6 +89,7 @@ class Roda
|
|
87
89
|
@_response = self.class::RodaResponse.new
|
88
90
|
super do |r|
|
89
91
|
opts[:class_level_routes].each do |meth, args, blk|
|
92
|
+
req.instance_variable_set(:@remaining_path, rp)
|
90
93
|
r.send(meth, *args) do |*a|
|
91
94
|
instance_exec(*a, &blk)
|
92
95
|
end
|
@@ -44,6 +44,15 @@ class Roda
|
|
44
44
|
/\A#{roda_class.opts[:match_prefix] || PREFIX}(?:#{pattern})#{roda_class.opts[:match_suffix] || SUFFIX}/
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
module RequestMethods
|
49
|
+
private
|
50
|
+
|
51
|
+
# Use regexps for all string matches, so that the prefix and suffix matches work.
|
52
|
+
def _match_string(str)
|
53
|
+
consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
|
54
|
+
end
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
register_plugin(:match_affix, MatchAffix)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The optimized_string_matchers plugin adds two optimized matcher methods,
|
7
|
+
# +r.on_branch+ and +r.is_exactly+. +r.on_branch+ is an optimized version of
|
8
|
+
# +r.on+ that only accepts a single string, and +r.is_exactly+ is an
|
9
|
+
# optimized version of +r.is+ that only accepts a single string.
|
10
|
+
#
|
11
|
+
# plugin :optimized_string_matchers
|
12
|
+
#
|
13
|
+
# route do |r|
|
14
|
+
# r.on_branch "x" do
|
15
|
+
# # matches /x and paths starting with /x/
|
16
|
+
# r.is_exactly "y" do
|
17
|
+
# # matches /x/y
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Note that both of these methods only work with plain strings, not
|
23
|
+
# with strings with embedded colons for capturing. Matching will work
|
24
|
+
# correctly in such cases, but the captures will not be yielded to the
|
25
|
+
# match blocks.
|
26
|
+
module OptimizedStringMatchers
|
27
|
+
EMPTY_STRING = ''.freeze
|
28
|
+
|
29
|
+
module RequestMethods
|
30
|
+
# Optimized version of +on+ that only supports a single string.
|
31
|
+
def on_branch(s)
|
32
|
+
always{yield} if _match_string(s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Optimized version of +is+ that only supports a single string.
|
36
|
+
def is_exactly(s)
|
37
|
+
rp = @remaining_path
|
38
|
+
if _match_string(s)
|
39
|
+
if @remaining_path == EMPTY_STRING
|
40
|
+
always{yield}
|
41
|
+
else
|
42
|
+
@remaining_path = rp
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
register_plugin(:optimized_string_matchers, OptimizedStringMatchers)
|
50
|
+
end
|
51
|
+
end
|
data/lib/roda/plugins/pass.rb
CHANGED
@@ -10,7 +10,7 @@ class Roda
|
|
10
10
|
#
|
11
11
|
# route do |r|
|
12
12
|
# r.on "foo/:bar" do |bar|
|
13
|
-
# pass if bar == 'baz'
|
13
|
+
# r.pass if bar == 'baz'
|
14
14
|
# "/foo/#{bar} (not baz)"
|
15
15
|
# end
|
16
16
|
#
|
@@ -34,7 +34,10 @@ class Roda
|
|
34
34
|
|
35
35
|
# Handle passing inside the match block.
|
36
36
|
def if_match(_)
|
37
|
-
|
37
|
+
rp = @remaining_path
|
38
|
+
ret = catch(:pass){super}
|
39
|
+
@remaining_path = rp
|
40
|
+
ret
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
@@ -44,6 +44,9 @@ class Roda
|
|
44
44
|
# r.is "album:opt" do |id| end
|
45
45
|
# # matches /album (yielding nil) and /album/foo (yielding "foo")
|
46
46
|
# # does not match /album/ or /album/foo/bar
|
47
|
+
#
|
48
|
+
# If using this plugin with the params_capturing plugin, this plugin should
|
49
|
+
# be loaded first.
|
47
50
|
module SymbolMatchers
|
48
51
|
def self.configure(app)
|
49
52
|
app.symbol_matcher(:d, /(\d+)/)
|
@@ -64,8 +67,21 @@ class Roda
|
|
64
67
|
module RequestMethods
|
65
68
|
private
|
66
69
|
|
67
|
-
#
|
68
|
-
#
|
70
|
+
# Use regular expressions to the symbol-specific regular expression
|
71
|
+
# if the symbol is registered. Otherwise, call super for the default
|
72
|
+
# behavior.
|
73
|
+
def _match_symbol(s)
|
74
|
+
meth = :"match_symbol_#{s}"
|
75
|
+
if respond_to?(meth)
|
76
|
+
re = send(meth)
|
77
|
+
consume(self.class.cached_matcher(re){re})
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return the symbol-specific regular expression if one is registered.
|
84
|
+
# Otherwise, call super for the default behavior.
|
69
85
|
def _match_symbol_regexp(s)
|
70
86
|
meth = :"match_symbol_#{s}"
|
71
87
|
if respond_to?(meth)
|
data/lib/roda/version.rb
CHANGED
@@ -118,4 +118,25 @@ describe "error_handler plugin" do
|
|
118
118
|
|
119
119
|
proc{req}.must_raise(ArgumentError)
|
120
120
|
end
|
121
|
+
|
122
|
+
it "has access to current remaining_path" do
|
123
|
+
app(:bare) do
|
124
|
+
plugin :error_handler do |e|
|
125
|
+
request.remaining_path
|
126
|
+
end
|
127
|
+
|
128
|
+
route do |r|
|
129
|
+
r.on('a') do
|
130
|
+
raise ArgumentError, "bad idea"
|
131
|
+
end
|
132
|
+
|
133
|
+
raise ArgumentError, "bad idea"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
body.must_equal '/'
|
138
|
+
body('/b').must_equal '/b'
|
139
|
+
body('/a').must_equal ''
|
140
|
+
body('/a/c').must_equal '/c'
|
141
|
+
end
|
121
142
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "optimized_string_matchers plugin" do
|
4
|
+
it "should support on_branch and is_exactly match methods" do
|
5
|
+
app(:optimized_string_matchers) do |r|
|
6
|
+
r.on_branch "e" do
|
7
|
+
r.is_exactly "f" do
|
8
|
+
"ef"
|
9
|
+
end
|
10
|
+
|
11
|
+
"ee"
|
12
|
+
end
|
13
|
+
|
14
|
+
r.on_branch "a/:b" do |*b|
|
15
|
+
r.is_exactly ":c" do |*c|
|
16
|
+
"c-#{c.length}"
|
17
|
+
end
|
18
|
+
|
19
|
+
"a-#{b.length}"
|
20
|
+
end
|
21
|
+
|
22
|
+
"cc"
|
23
|
+
end
|
24
|
+
|
25
|
+
body.must_equal 'cc'
|
26
|
+
body('/a').must_equal 'cc'
|
27
|
+
body('/a/').must_equal 'cc'
|
28
|
+
body('/a/b/').must_equal 'a-0'
|
29
|
+
body('/a/b/').must_equal 'a-0'
|
30
|
+
body('/a/b/c').must_equal 'c-0'
|
31
|
+
body('/a/b/c/').must_equal 'a-0'
|
32
|
+
body('/a/b/c/d').must_equal 'a-0'
|
33
|
+
body('/e').must_equal 'ee'
|
34
|
+
body('/eb').must_equal 'cc'
|
35
|
+
body('/e/').must_equal 'ee'
|
36
|
+
body('/e/f').must_equal 'ef'
|
37
|
+
body('/e/f/').must_equal 'ee'
|
38
|
+
body('/e/fe').must_equal 'ee'
|
39
|
+
end
|
40
|
+
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: 2.
|
4
|
+
version: 2.12.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: 2016-
|
11
|
+
date: 2016-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -177,6 +177,7 @@ extra_rdoc_files:
|
|
177
177
|
- doc/release_notes/2.9.0.txt
|
178
178
|
- doc/release_notes/2.10.0.txt
|
179
179
|
- doc/release_notes/2.11.0.txt
|
180
|
+
- doc/release_notes/2.12.0.txt
|
180
181
|
files:
|
181
182
|
- CHANGELOG
|
182
183
|
- MIT-LICENSE
|
@@ -191,6 +192,7 @@ files:
|
|
191
192
|
- doc/release_notes/2.1.0.txt
|
192
193
|
- doc/release_notes/2.10.0.txt
|
193
194
|
- doc/release_notes/2.11.0.txt
|
195
|
+
- doc/release_notes/2.12.0.txt
|
194
196
|
- doc/release_notes/2.2.0.txt
|
195
197
|
- doc/release_notes/2.3.0.txt
|
196
198
|
- doc/release_notes/2.4.0.txt
|
@@ -242,6 +244,7 @@ files:
|
|
242
244
|
- lib/roda/plugins/named_templates.rb
|
243
245
|
- lib/roda/plugins/not_allowed.rb
|
244
246
|
- lib/roda/plugins/not_found.rb
|
247
|
+
- lib/roda/plugins/optimized_string_matchers.rb
|
245
248
|
- lib/roda/plugins/padrino_render.rb
|
246
249
|
- lib/roda/plugins/param_matchers.rb
|
247
250
|
- lib/roda/plugins/params_capturing.rb
|
@@ -320,6 +323,7 @@ files:
|
|
320
323
|
- spec/plugin/named_templates_spec.rb
|
321
324
|
- spec/plugin/not_allowed_spec.rb
|
322
325
|
- spec/plugin/not_found_spec.rb
|
326
|
+
- spec/plugin/optimized_string_matchers_spec.rb
|
323
327
|
- spec/plugin/padrino_render_spec.rb
|
324
328
|
- spec/plugin/param_matchers_spec.rb
|
325
329
|
- spec/plugin/params_capturing_spec.rb
|