roda 3.104.0 → 3.105.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ea3b3c7c8ffcfffa22412e123d09e718e876c13bdfcddbfae461daa813d81bd
4
- data.tar.gz: 275d90d97fa2893654e368316c1233092ae5fcd60ac7e76ac026a45d2200582b
3
+ metadata.gz: 4f6f16557b167cdca71f0005ed7b4e40c4465b6400874e05a8ece95e7dad7b68
4
+ data.tar.gz: decd44332e836e46bc4bb2a6f93e7d40730fbf9f016c83b8a6ae99169c74efd3
5
5
  SHA512:
6
- metadata.gz: 546ee3b7cb9d6c8eac9eb6bbc40272a1a5760f522bbe94b38bb78afa3f552a61dd23496d2dfcf4e65fe490b1cad67b229fed4c31ac22b9d25409e501f6bb5c82
7
- data.tar.gz: f9e94f7612706155c60db6f9a14bb9b130386875f1c86e6a65bc29bf9fdb789719c08f5398f58708ac862ab8b3ec41fb34f11b21d7454878be4364fceb50ce91
6
+ metadata.gz: 768134a23ee25d9f9d41641b1bf3dd5d499624b05adf9261f90300407a5dcd55d1535f080547d573b11e19d161c4563eb8538d998ee627d11329e6b59223db88
7
+ data.tar.gz: 00aa1dfbbc81b5754a824fa668324886338f4a4827d27a2de6cfff5b6318c8e4aba1c8c93afb8e96f80b214b4ba6b96917b3df77108f1eaa60153fa5fc5add5e
@@ -52,9 +52,20 @@ class Roda
52
52
  end
53
53
  end
54
54
  elsif matcher == Integer
55
- if (matchdata = /\A\/(\d{1,100})(?=\/|\z)/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
56
- @remaining_path = matchdata.post_match
57
- always{yield(value)}
55
+ rp = @remaining_path
56
+ if /\A\/(\d{1,100})(?=\/|\z)/.match?(rp)
57
+ if last = rp.index('/', 1)
58
+ value = rp[1, last-1]
59
+ rp = rp[last, rp.length]
60
+ else
61
+ value = rp[1, rp.length]
62
+ rp = ""
63
+ end
64
+
65
+ if value = scope.send(:_convert_class_Integer, value)
66
+ @remaining_path = rp
67
+ always{yield(value)}
68
+ end
58
69
  end
59
70
  else
60
71
  path = @remaining_path
@@ -151,10 +162,11 @@ class Roda
151
162
  always{yield rp[1, len]}
152
163
  end
153
164
  elsif matcher == Integer
154
- if (matchdata = /\A\/(\d{1,100})\z/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
155
- @remaining_path = ''
156
- always{yield(value)}
157
- end
165
+ rp = @remaining_path
166
+ if /\A\/(\d{1,100})\z/.match?(rp) && (value = scope.send(:_convert_class_Integer, rp[1, 101]))
167
+ @remaining_path = ''
168
+ always{yield(value)}
169
+ end
158
170
  else
159
171
  path = @remaining_path
160
172
  captures = @captures.clear
@@ -8,7 +8,7 @@ class Roda
8
8
  private
9
9
 
10
10
  # Backend of symbol_matcher and class_matcher.
11
- def _symbol_class_matcher(expected_class, obj, matcher, block, &request_class_block)
11
+ def _symbol_class_matcher(expected_class, obj, matcher, block, options=OPTS, &request_class_block)
12
12
  unless obj.is_a?(expected_class)
13
13
  raise RodaError, "Invalid type passed to class_matcher or symbol_matcher: #{matcher.inspect}"
14
14
  end
@@ -30,23 +30,23 @@ class Roda
30
30
  raise RodaError, "cannot provide Symbol matcher to class_matcher unless using symbol_matchers plugin: #{matcher.inspect}"
31
31
  end
32
32
 
33
- regexp, consume_regexp, matcher_block = opts[:symbol_matchers][matcher]
33
+ regexp, consume_regexp, convert_meth, consume_meth = opts[:symbol_matchers][matcher]
34
34
 
35
35
  unless regexp
36
36
  raise RodaError, "unregistered symbol matcher given to #{type}_matcher: #{matcher.inspect}"
37
37
  end
38
38
 
39
- block = _merge_matcher_blocks(type, obj, block, matcher_block)
39
+ block = _merge_matcher_blocks(type, obj, block, convert_meth)
40
40
  when Class
41
41
  unless opts[:class_matchers]
42
42
  raise RodaError, "cannot provide Class matcher to symbol_matcher unless using class_matchers plugin: #{matcher.inspect}"
43
43
  end
44
44
 
45
- regexp, consume_regexp, matcher_block = opts[:class_matchers][matcher]
45
+ regexp, consume_regexp, convert_meth, consume_meth = opts[:class_matchers][matcher]
46
46
  unless regexp
47
47
  raise RodaError, "unregistered class matcher given to #{type}_matcher: #{matcher.inspect}"
48
48
  end
49
- block = _merge_matcher_blocks(type, obj, block, matcher_block)
49
+ block = _merge_matcher_blocks(type, obj, block, convert_meth)
50
50
  else
51
51
  raise RodaError, "unsupported matcher given to #{type}_matcher: #{matcher.inspect}"
52
52
  end
@@ -59,7 +59,8 @@ class Roda
59
59
  private convert_meth
60
60
  end
61
61
 
62
- array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth].freeze
62
+ consume_meth ||= options[:segment] ? :_consume_single_segment : :consume
63
+ array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth, consume_meth].freeze
63
64
 
64
65
  self::RodaRequest.class_eval do
65
66
  class_exec(meth, array, &request_class_block)
@@ -69,30 +70,30 @@ class Roda
69
70
  nil
70
71
  end
71
72
 
72
- # If both block and matche_meth are given,
73
+ # If both block and convert_meth are given,
73
74
  # define a method for block, and then return a
74
- # proc that calls matcher_meth first, and only calls
75
- # the newly defined method with the return values of matcher_meth
75
+ # proc that calls convert_meth first, and only calls
76
+ # the newly defined method with the return values of convert_meth
76
77
  # if matcher_method returns a truthy value.
77
- # Otherwise, return matcher_meth or block.
78
- def _merge_matcher_blocks(type, obj, block, matcher_meth)
79
- if matcher_meth
78
+ # Otherwise, return convert_meth or block.
79
+ def _merge_matcher_blocks(type, obj, block, convert_meth)
80
+ if convert_meth
80
81
  if block
81
- convert_meth = :"_convert_merge_#{type}_#{obj}"
82
- define_method(convert_meth, &block)
83
- private convert_meth
82
+ convert_merge_meth = :"_convert_merge_#{type}_#{obj}"
83
+ define_method(convert_merge_meth, &block)
84
+ private convert_merge_meth
84
85
 
85
86
  proc do |*a|
86
- if captures = send(matcher_meth, *a)
87
+ if captures = send(convert_meth, *a)
87
88
  if captures.is_a?(Array)
88
- send(convert_meth, *captures)
89
+ send(convert_merge_meth, *captures)
89
90
  else
90
- send(convert_meth, captures)
91
+ send(convert_merge_meth, captures)
91
92
  end
92
93
  end
93
94
  end
94
95
  else
95
- matcher_meth
96
+ convert_meth
96
97
  end
97
98
  else
98
99
  block
@@ -67,6 +67,17 @@ class Roda
67
67
  # Blocks passed to the class_matchers plugin are evaluated in route
68
68
  # block context.
69
69
  #
70
+ # When passing a regexp as a matcher, to class_matcher, you can provide a
71
+ # <tt>segment: true</tt> option to speed up the matching on Ruby 2.4+:
72
+ #
73
+ # symbol_matcher(:employee_id, /E-(\d{6})/, segment: true) do |employee_id|
74
+ # employee_id.to_i
75
+ # end
76
+ #
77
+ # Use of <tt>segment: true</tt> requires that the regexp not match more than
78
+ # one segment (i.e. it cannot match +/+). Additionally, the entire segment will
79
+ # be captured, so any capture groups in the regexp will be ignored.
80
+ #
70
81
  # This plugin does not work with the params_capturing plugin, as it does not
71
82
  # offer the ability to associate block arguments with named keys.
72
83
  module ClassMatchers
@@ -92,10 +103,14 @@ class Roda
92
103
  # captures if no block was registered with the class or symbol. In either case,
93
104
  # if a block is given, it should return an array with the captures to yield to
94
105
  # the match block.
95
- def class_matcher(klass, matcher, &block)
96
- _symbol_class_matcher(Class, klass, matcher, block) do |meth, (_, regexp, convert_meth)|
106
+ def class_matcher(klass, matcher, opts=OPTS, &block)
107
+ _symbol_class_matcher(Class, klass, matcher, block, opts) do |meth, (_, regexp, convert_meth, consume_meth)|
97
108
  if regexp
98
- define_method(meth){consume(regexp, convert_meth)}
109
+ if consume_meth == :_consume_single_segment
110
+ define_method(meth){_consume_single_segment(regexp, convert_meth)}
111
+ else
112
+ define_method(meth){consume(regexp, convert_meth)}
113
+ end
99
114
  else
100
115
  define_method(meth){_consume_segment(convert_meth)}
101
116
  end
@@ -2,16 +2,16 @@
2
2
 
3
3
  class Roda
4
4
  module RodaPlugins
5
- # The sec_fetch_site plugin allows for CSRF protection using the
5
+ # The sec_fetch_site_csrf plugin allows for CSRF protection using the
6
6
  # Sec-Fetch-Site header added in modern browsers. It allows for CSRF
7
7
  # protection without the use of CSRF tokens, which can simplify
8
8
  # form creation.
9
9
  #
10
- # The protection offered by the sec_fetch_site plugin is weaker than
10
+ # The protection offered by the sec_fetch_site_csrf plugin is weaker than
11
11
  # the protection offered by the route_csrf plugin with default settings,
12
12
  # since it doesn't support per-request tokens. Be aware you are trading
13
- # security for simplicity when using the sec_fetch_site plugin instead
14
- # of the route_csrf plugin. Other caveats in using the sec_fetch_site
13
+ # security for simplicity when using the sec_fetch_site_csrf plugin instead
14
+ # of the route_csrf plugin. Other caveats in using the sec_fetch_site_csrf
15
15
  # plugin:
16
16
  #
17
17
  # * Not all browsers set the Sec-Fetch-Site header. Some browsers
@@ -19,6 +19,15 @@ class Roda
19
19
  # Then the route will only if the path is +/foobar123+, but not if it is
20
20
  # +/foo+, +/FooBar123+, or +/foobar_123+.
21
21
  #
22
+ # You can provide a <tt>segment: true</tt> option to symbol_matcher to speed up
23
+ # the matching on Ruby 2.4+:
24
+ #
25
+ # symbol_matcher :username, /([a-z0-9]{6,20})/, segment: true
26
+ #
27
+ # Use of <tt>segment: true</tt> requires that the regexp not match more than
28
+ # one segment (i.e. it cannot match +/+). Additionally, the entire segment will
29
+ # be captured, so any capture groups in the regexp will be ignored.
30
+ #
22
31
  # By default, this plugin sets up the following symbol matchers:
23
32
  #
24
33
  # :d :: <tt>/(\d+)/</tt>, a decimal segment
@@ -106,8 +115,8 @@ class Roda
106
115
 
107
116
  def self.configure(app)
108
117
  app.opts[:symbol_matchers] ||= {}
109
- app.symbol_matcher(:d, /(\d+)/)
110
- app.symbol_matcher(:w, /(\w+)/)
118
+ app.symbol_matcher(:d, /(\d+)/, segment: true)
119
+ app.symbol_matcher(:w, /(\w+)/, segment: true)
111
120
  app.symbol_matcher(:rest, /(.*)/)
112
121
  end
113
122
 
@@ -122,8 +131,8 @@ class Roda
122
131
  # captures if no block was registered with the symbol or class. In either case,
123
132
  # if a block is given, it should return an array with the captures to yield to
124
133
  # the match block.
125
- def symbol_matcher(s, matcher, &block)
126
- _symbol_class_matcher(Symbol, s, matcher, block) do |meth, array|
134
+ def symbol_matcher(s, matcher, opts=OPTS, &block)
135
+ _symbol_class_matcher(Symbol, s, matcher, block, opts) do |meth, array|
127
136
  define_method(meth){array}
128
137
  end
129
138
 
@@ -147,9 +156,9 @@ class Roda
147
156
  meth = :"match_symbol_#{s}"
148
157
  if respond_to?(meth, true)
149
158
  # Allow calling private match methods
150
- _, re, convert_meth = send(meth)
159
+ _, re, convert_meth, consume_meth = send(meth)
151
160
  if re
152
- consume(re, convert_meth)
161
+ send(consume_meth, re, convert_meth)
153
162
  else
154
163
  # defined in class_matchers plugin
155
164
  _consume_segment(convert_meth)
data/lib/roda/request.rb CHANGED
@@ -444,7 +444,7 @@ class Roda
444
444
  # Match integer segment of up to 100 decimal characters, and yield resulting value as an
445
445
  # integer.
446
446
  def _match_class_Integer
447
- consume(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
447
+ _consume_single_segment(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
448
448
  end
449
449
 
450
450
  # Match only if all of the arguments in the given array match.
@@ -585,6 +585,39 @@ class Roda
585
585
  end
586
586
  end
587
587
 
588
+ if RUBY_VERSION >= "2.4"
589
+ # A faster version of consume that can be used if you are sure the regexp
590
+ # will match a single segment if it matches, capturing the entire segment.
591
+ def _consume_single_segment(regexp, meth=nil)
592
+ rp = @remaining_path
593
+ if regexp.match?(rp)
594
+ if last = rp.index('/', 1)
595
+ val = rp[1, last-1]
596
+ @remaining_path = rp[last, rp.length]
597
+ else
598
+ val = rp[1, rp.length]
599
+ @remaining_path = ""
600
+ end
601
+
602
+ if meth
603
+ if captures = scope.send(meth, val)
604
+ if captures.is_a?(Array)
605
+ @captures.concat(captures)
606
+ else
607
+ @captures << captures
608
+ end
609
+ end
610
+ else
611
+ @captures << val
612
+ end
613
+ end
614
+ end
615
+ # :nocov:
616
+ else
617
+ alias _consume_single_segment consume
618
+ # :nocov:
619
+ end
620
+
588
621
  # The default path to use for redirects when a path is not given.
589
622
  # For non-GET requests, redirects to the current path, which will
590
623
  # trigger a GET request. This is to make the common case where
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 104
7
+ RodaMinorVersion = 105
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
data/lib/roda.rb CHANGED
@@ -217,7 +217,7 @@ class Roda
217
217
  plugin :direct_call
218
218
  end
219
219
 
220
- if ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
220
+ if RUBY_VERSION > "2.4" && ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
221
221
  plugin :_optimized_matching
222
222
  end
223
223
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.104.0
4
+ version: 3.105.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans