actionpack 4.2.10 → 7.2.0.rc1

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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,11 +1,15 @@
1
- require 'action_controller/metal/exceptions'
2
- require 'active_support/deprecation'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_controller/metal/exceptions"
3
6
 
4
7
  module ActionDispatch
8
+ # :stopdoc:
5
9
  module Journey
6
10
  # The Formatter class is used for formatting URLs. For example, parameters
7
- # passed to +url_for+ in Rails will eventually call Formatter#generate.
8
- class Formatter # :nodoc:
11
+ # passed to `url_for` in Rails will eventually call Formatter#generate.
12
+ class Formatter
9
13
  attr_reader :routes
10
14
 
11
15
  def initialize(routes)
@@ -13,63 +17,121 @@ module ActionDispatch
13
17
  @cache = nil
14
18
  end
15
19
 
16
- def generate(name, options, path_parameters, parameterize = nil)
20
+ class RouteWithParams
21
+ attr_reader :params
22
+
23
+ def initialize(route, parameterized_parts, params)
24
+ @route = route
25
+ @parameterized_parts = parameterized_parts
26
+ @params = params
27
+ end
28
+
29
+ def path(_)
30
+ @route.format(@parameterized_parts)
31
+ end
32
+ end
33
+
34
+ class MissingRoute
35
+ attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys
36
+
37
+ def initialize(constraints, missing_keys, unmatched_keys, routes, name)
38
+ @constraints = constraints
39
+ @missing_keys = missing_keys
40
+ @unmatched_keys = unmatched_keys
41
+ @routes = routes
42
+ @name = name
43
+ end
44
+
45
+ def path(method_name)
46
+ raise ActionController::UrlGenerationError.new(message, routes, name, method_name)
47
+ end
48
+
49
+ def params
50
+ path("unknown")
51
+ end
52
+
53
+ def message
54
+ message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
55
+ message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
56
+ message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
57
+ message
58
+ end
59
+ end
60
+
61
+ def generate(name, options, path_parameters)
62
+ original_options = options.dup
63
+ path_params = options.delete(:path_params) || {}
64
+ options = path_params.merge(options)
17
65
  constraints = path_parameters.merge(options)
18
- missing_keys = []
66
+ missing_keys = nil
19
67
 
20
68
  match_route(name, constraints) do |route|
21
- parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
69
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
22
70
 
23
- # Skip this route unless a name has been provided or it is a
24
- # standard Rails route since we can't determine whether an options
25
- # hash passed to url_for matches a Rack application or a redirect.
71
+ # Skip this route unless a name has been provided or it is a standard Rails
72
+ # route since we can't determine whether an options hash passed to url_for
73
+ # matches a Rack application or a redirect.
26
74
  next unless name || route.dispatcher?
27
75
 
28
76
  missing_keys = missing_keys(route, parameterized_parts)
29
- next unless missing_keys.empty?
30
- params = options.dup.delete_if do |key, _|
31
- parameterized_parts.key?(key) || route.defaults.key?(key)
77
+ next if missing_keys && !missing_keys.empty?
78
+ params = options.delete_if do |key, _|
79
+ # top-level params' normal behavior of generating query_params should be
80
+ # preserved even if the same key is also a bind_param
81
+ parameterized_parts.key?(key) || route.defaults.key?(key) ||
82
+ (path_params.key?(key) && !original_options.key?(key))
32
83
  end
33
84
 
34
85
  defaults = route.defaults
35
86
  required_parts = route.required_parts
36
- parameterized_parts.delete_if do |key, value|
37
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
87
+
88
+ route.parts.reverse_each do |key|
89
+ break if defaults[key].nil? && parameterized_parts[key].present?
90
+ next if parameterized_parts[key].to_s != defaults[key].to_s
91
+ break if required_parts.include?(key)
92
+
93
+ parameterized_parts.delete(key)
38
94
  end
39
95
 
40
- return [route.format(parameterized_parts), params]
96
+ return RouteWithParams.new(route, parameterized_parts, params)
41
97
  end
42
98
 
43
- message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
44
- message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
99
+ unmatched_keys = (missing_keys || []) & constraints.keys
100
+ missing_keys = (missing_keys || []) - unmatched_keys
45
101
 
46
- raise ActionController::UrlGenerationError, message
102
+ MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name)
47
103
  end
48
104
 
49
105
  def clear
50
106
  @cache = nil
51
107
  end
52
108
 
53
- private
109
+ def eager_load!
110
+ cache
111
+ nil
112
+ end
54
113
 
55
- def extract_parameterized_parts(route, options, recall, parameterize = nil)
114
+ private
115
+ def extract_parameterized_parts(route, options, recall)
56
116
  parameterized_parts = recall.merge(options)
57
117
 
58
- keys_to_keep = route.parts.reverse.drop_while { |part|
59
- !options.key?(part) || (options[part] || recall[part]).nil?
118
+ keys_to_keep = route.parts.reverse_each.drop_while { |part|
119
+ !(options.key?(part) || route.scope_options.key?(part)) || (options[part].nil? && recall[part].nil?)
60
120
  } | route.required_parts
61
121
 
62
- (parameterized_parts.keys - keys_to_keep).each do |bad_key|
63
- parameterized_parts.delete(bad_key)
122
+ parameterized_parts.delete_if do |bad_key, _|
123
+ !keys_to_keep.include?(bad_key)
64
124
  end
65
125
 
66
- if parameterize
67
- parameterized_parts.each do |k, v|
68
- parameterized_parts[k] = parameterize.call(k, v)
126
+ parameterized_parts.each do |k, v|
127
+ if k == :controller
128
+ parameterized_parts[k] = v
129
+ else
130
+ parameterized_parts[k] = v.to_param
69
131
  end
70
132
  end
71
133
 
72
- parameterized_parts.keep_if { |_, v| v }
134
+ parameterized_parts.compact!
73
135
  parameterized_parts
74
136
  end
75
137
 
@@ -81,28 +143,18 @@ module ActionDispatch
81
143
  if named_routes.key?(name)
82
144
  yield named_routes[name]
83
145
  else
84
- # Make sure we don't show the deprecation warning more than once
85
- warned = false
86
-
87
146
  routes = non_recursive(cache, options)
88
147
 
89
- hash = routes.group_by { |_, r| r.score(options) }
148
+ supplied_keys = options.each_with_object({}) do |(k, v), h|
149
+ h[k.to_s] = true if v
150
+ end
151
+
152
+ hash = routes.group_by { |_, r| r.score(supplied_keys) }
90
153
 
91
154
  hash.keys.sort.reverse_each do |score|
92
155
  break if score < 0
93
156
 
94
157
  hash[score].sort_by { |i, _| i }.each do |_, route|
95
- if name && !warned
96
- ActiveSupport::Deprecation.warn <<-MSG.squish
97
- You are trying to generate the URL for a named route called
98
- #{name.inspect} but no such route was found. In the future,
99
- this will result in an `ActionController::UrlGenerationError`
100
- exception.
101
- MSG
102
-
103
- warned = true
104
- end
105
-
106
158
  yield route
107
159
  end
108
160
  end
@@ -127,13 +179,20 @@ module ActionDispatch
127
179
 
128
180
  # Returns an array populated with missing keys if any are present.
129
181
  def missing_keys(route, parts)
130
- missing_keys = []
131
- tests = route.path.requirements
182
+ missing_keys = nil
183
+ tests = route.path.requirements_for_missing_keys_check
132
184
  route.required_parts.each { |key|
133
- if tests.key?(key)
134
- missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
185
+ case tests[key]
186
+ when nil
187
+ unless parts[key]
188
+ missing_keys ||= []
189
+ missing_keys << key
190
+ end
135
191
  else
136
- missing_keys << key unless parts[key]
192
+ unless tests[key].match?(parts[key])
193
+ missing_keys ||= []
194
+ missing_keys << key
195
+ end
137
196
  end
138
197
  }
139
198
  missing_keys
@@ -149,7 +208,7 @@ module ActionDispatch
149
208
 
150
209
  def build_cache
151
210
  root = { ___routes: [] }
152
- routes.each_with_index do |route, i|
211
+ routes.routes.each_with_index do |route, i|
153
212
  leaf = route.required_defaults.inject(root) do |h, tuple|
154
213
  h[tuple] ||= {}
155
214
  end
@@ -163,4 +222,5 @@ module ActionDispatch
163
222
  end
164
223
  end
165
224
  end
225
+ # :startdoc:
166
226
  end
@@ -1,55 +1,58 @@
1
- require 'action_dispatch/journey/gtg/transition_table'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/journey/gtg/transition_table"
2
6
 
3
7
  module ActionDispatch
4
8
  module Journey # :nodoc:
5
9
  module GTG # :nodoc:
6
10
  class Builder # :nodoc:
7
- DUMMY = Nodes::Dummy.new
11
+ DUMMY_END_NODE = Nodes::Dummy.new
8
12
 
9
13
  attr_reader :root, :ast, :endpoints
10
14
 
11
15
  def initialize(root)
12
16
  @root = root
13
- @ast = Nodes::Cat.new root, DUMMY
14
- @followpos = nil
17
+ @ast = Nodes::Cat.new root, DUMMY_END_NODE
18
+ @followpos = build_followpos
15
19
  end
16
20
 
17
21
  def transition_table
18
22
  dtrans = TransitionTable.new
19
- marked = {}
20
- state_id = Hash.new { |h,k| h[k] = h.length }
23
+ marked = {}.compare_by_identity
24
+ state_id = Hash.new { |h, k| h[k] = h.length }.compare_by_identity
25
+ dstates = [firstpos(root)]
21
26
 
22
- start = firstpos(root)
23
- dstates = [start]
24
27
  until dstates.empty?
25
28
  s = dstates.shift
26
29
  next if marked[s]
27
30
  marked[s] = true # mark s
28
31
 
29
32
  s.group_by { |state| symbol(state) }.each do |sym, ps|
30
- u = ps.flat_map { |l| followpos(l) }
33
+ u = ps.flat_map { |l| @followpos[l] }.uniq
31
34
  next if u.empty?
32
35
 
33
- if u.uniq == [DUMMY]
34
- from = state_id[s]
35
- to = state_id[Object.new]
36
- dtrans[from, to] = sym
36
+ from = state_id[s]
37
37
 
38
+ if u.all? { |pos| pos == DUMMY_END_NODE }
39
+ to = state_id[Object.new]
40
+ dtrans[from, to] = sym
38
41
  dtrans.add_accepting(to)
42
+
39
43
  ps.each { |state| dtrans.add_memo(to, state.memo) }
40
44
  else
41
- dtrans[state_id[s], state_id[u]] = sym
42
-
43
- if u.include?(DUMMY)
44
- to = state_id[u]
45
-
46
- accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
45
+ to = state_id[u]
46
+ dtrans[from, to] = sym
47
47
 
48
- accepting.each { |accepting_state|
49
- dtrans.add_memo(to, accepting_state.memo)
50
- }
48
+ if u.include?(DUMMY_END_NODE)
49
+ ps.each do |state|
50
+ if @followpos[state].include?(DUMMY_END_NODE)
51
+ dtrans.add_memo(to, state.memo)
52
+ end
53
+ end
51
54
 
52
- dtrans.add_accepting(state_id[u])
55
+ dtrans.add_accepting(to)
53
56
  end
54
57
  end
55
58
 
@@ -65,7 +68,9 @@ module ActionDispatch
65
68
  when Nodes::Group
66
69
  true
67
70
  when Nodes::Star
68
- true
71
+ # the default star regex is /(.+)/ which is NOT nullable but since different
72
+ # constraints can be provided we must actually check if this is the case or not.
73
+ node.regexp.match?("")
69
74
  when Nodes::Or
70
75
  node.children.any? { |c| nullable?(c) }
71
76
  when Nodes::Cat
@@ -75,7 +80,7 @@ module ActionDispatch
75
80
  when Nodes::Unary
76
81
  nullable?(node.left)
77
82
  else
78
- raise ArgumentError, 'unknown nullable: %s' % node.class.name
83
+ raise ArgumentError, "unknown nullable: %s" % node.class.name
79
84
  end
80
85
  end
81
86
 
@@ -90,22 +95,22 @@ module ActionDispatch
90
95
  firstpos(node.left)
91
96
  end
92
97
  when Nodes::Or
93
- node.children.flat_map { |c| firstpos(c) }.uniq
98
+ node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!)
94
99
  when Nodes::Unary
95
100
  firstpos(node.left)
96
101
  when Nodes::Terminal
97
102
  nullable?(node) ? [] : [node]
98
103
  else
99
- raise ArgumentError, 'unknown firstpos: %s' % node.class.name
104
+ raise ArgumentError, "unknown firstpos: %s" % node.class.name
100
105
  end
101
106
  end
102
107
 
103
108
  def lastpos(node)
104
109
  case node
105
110
  when Nodes::Star
106
- firstpos(node.left)
111
+ lastpos(node.left)
107
112
  when Nodes::Or
108
- node.children.flat_map { |c| lastpos(c) }.uniq
113
+ node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
109
114
  when Nodes::Cat
110
115
  if nullable?(node.right)
111
116
  lastpos(node.left) | lastpos(node.right)
@@ -117,44 +122,26 @@ module ActionDispatch
117
122
  when Nodes::Unary
118
123
  lastpos(node.left)
119
124
  else
120
- raise ArgumentError, 'unknown lastpos: %s' % node.class.name
125
+ raise ArgumentError, "unknown lastpos: %s" % node.class.name
121
126
  end
122
127
  end
123
128
 
124
- def followpos(node)
125
- followpos_table[node]
126
- end
127
-
128
129
  private
129
-
130
- def followpos_table
131
- @followpos ||= build_followpos
132
- end
133
-
134
130
  def build_followpos
135
- table = Hash.new { |h, k| h[k] = [] }
131
+ table = Hash.new { |h, k| h[k] = [] }.compare_by_identity
136
132
  @ast.each do |n|
137
133
  case n
138
134
  when Nodes::Cat
139
135
  lastpos(n.left).each do |i|
140
136
  table[i] += firstpos(n.right)
141
137
  end
142
- when Nodes::Star
143
- lastpos(n).each do |i|
144
- table[i] += firstpos(n)
145
- end
146
138
  end
147
139
  end
148
140
  table
149
141
  end
150
142
 
151
143
  def symbol(edge)
152
- case edge
153
- when Journey::Nodes::Symbol
154
- edge.regexp
155
- else
156
- edge.left
157
- end
144
+ edge.symbol? ? edge.regexp : edge.left
158
145
  end
159
146
  end
160
147
  end
@@ -1,4 +1,8 @@
1
- require 'strscan'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "strscan"
2
6
 
3
7
  module ActionDispatch
4
8
  module Journey # :nodoc:
@@ -12,34 +16,33 @@ module ActionDispatch
12
16
  end
13
17
 
14
18
  class Simulator # :nodoc:
19
+ INITIAL_STATE = [ [0, nil] ].freeze
20
+
15
21
  attr_reader :tt
16
22
 
17
23
  def initialize(transition_table)
18
24
  @tt = transition_table
19
25
  end
20
26
 
21
- def simulate(string)
22
- ms = memos(string) { return }
23
- MatchData.new(ms)
24
- end
25
-
26
- alias :=~ :simulate
27
- alias :match :simulate
28
-
29
27
  def memos(string)
30
28
  input = StringScanner.new(string)
31
- state = [0]
29
+ state = INITIAL_STATE
30
+ start_index = 0
31
+
32
32
  while sym = input.scan(%r([/.?]|[^/.?]+))
33
- state = tt.move(state, sym)
34
- end
33
+ end_index = start_index + sym.length
35
34
 
36
- acceptance_states = state.find_all { |s|
37
- tt.accepting? s
38
- }
35
+ state = tt.move(state, string, start_index, end_index)
39
36
 
40
- return yield if acceptance_states.empty?
37
+ start_index = end_index
38
+ end
39
+
40
+ acceptance_states = state.each_with_object([]) do |s_d, memos|
41
+ s, idx = s_d
42
+ memos.concat(tt.memo(s)) if idx.nil? && tt.accepting?(s)
43
+ end
41
44
 
42
- acceptance_states.flat_map { |x| tt.memo(x) }.compact
45
+ acceptance_states.empty? ? yield : acceptance_states
43
46
  end
44
47
  end
45
48
  end