roda 2.11.0 → 2.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|