actionpack 3.2.22.5 → 5.2.4

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.
Files changed (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/gtg/transition_table"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module GTG # :nodoc:
8
+ class Builder # :nodoc:
9
+ DUMMY = Nodes::Dummy.new
10
+
11
+ attr_reader :root, :ast, :endpoints
12
+
13
+ def initialize(root)
14
+ @root = root
15
+ @ast = Nodes::Cat.new root, DUMMY
16
+ @followpos = nil
17
+ end
18
+
19
+ def transition_table
20
+ dtrans = TransitionTable.new
21
+ marked = {}
22
+ state_id = Hash.new { |h, k| h[k] = h.length }
23
+
24
+ start = firstpos(root)
25
+ dstates = [start]
26
+ until dstates.empty?
27
+ s = dstates.shift
28
+ next if marked[s]
29
+ marked[s] = true # mark s
30
+
31
+ s.group_by { |state| symbol(state) }.each do |sym, ps|
32
+ u = ps.flat_map { |l| followpos(l) }
33
+ next if u.empty?
34
+
35
+ if u.uniq == [DUMMY]
36
+ from = state_id[s]
37
+ to = state_id[Object.new]
38
+ dtrans[from, to] = sym
39
+
40
+ dtrans.add_accepting(to)
41
+ ps.each { |state| dtrans.add_memo(to, state.memo) }
42
+ else
43
+ dtrans[state_id[s], state_id[u]] = sym
44
+
45
+ if u.include?(DUMMY)
46
+ to = state_id[u]
47
+
48
+ accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
49
+
50
+ accepting.each { |accepting_state|
51
+ dtrans.add_memo(to, accepting_state.memo)
52
+ }
53
+
54
+ dtrans.add_accepting(state_id[u])
55
+ end
56
+ end
57
+
58
+ dstates << u
59
+ end
60
+ end
61
+
62
+ dtrans
63
+ end
64
+
65
+ def nullable?(node)
66
+ case node
67
+ when Nodes::Group
68
+ true
69
+ when Nodes::Star
70
+ true
71
+ when Nodes::Or
72
+ node.children.any? { |c| nullable?(c) }
73
+ when Nodes::Cat
74
+ nullable?(node.left) && nullable?(node.right)
75
+ when Nodes::Terminal
76
+ !node.left
77
+ when Nodes::Unary
78
+ nullable?(node.left)
79
+ else
80
+ raise ArgumentError, "unknown nullable: %s" % node.class.name
81
+ end
82
+ end
83
+
84
+ def firstpos(node)
85
+ case node
86
+ when Nodes::Star
87
+ firstpos(node.left)
88
+ when Nodes::Cat
89
+ if nullable?(node.left)
90
+ firstpos(node.left) | firstpos(node.right)
91
+ else
92
+ firstpos(node.left)
93
+ end
94
+ when Nodes::Or
95
+ node.children.flat_map { |c| firstpos(c) }.uniq
96
+ when Nodes::Unary
97
+ firstpos(node.left)
98
+ when Nodes::Terminal
99
+ nullable?(node) ? [] : [node]
100
+ else
101
+ raise ArgumentError, "unknown firstpos: %s" % node.class.name
102
+ end
103
+ end
104
+
105
+ def lastpos(node)
106
+ case node
107
+ when Nodes::Star
108
+ firstpos(node.left)
109
+ when Nodes::Or
110
+ node.children.flat_map { |c| lastpos(c) }.uniq
111
+ when Nodes::Cat
112
+ if nullable?(node.right)
113
+ lastpos(node.left) | lastpos(node.right)
114
+ else
115
+ lastpos(node.right)
116
+ end
117
+ when Nodes::Terminal
118
+ nullable?(node) ? [] : [node]
119
+ when Nodes::Unary
120
+ lastpos(node.left)
121
+ else
122
+ raise ArgumentError, "unknown lastpos: %s" % node.class.name
123
+ end
124
+ end
125
+
126
+ def followpos(node)
127
+ followpos_table[node]
128
+ end
129
+
130
+ private
131
+
132
+ def followpos_table
133
+ @followpos ||= build_followpos
134
+ end
135
+
136
+ def build_followpos
137
+ table = Hash.new { |h, k| h[k] = [] }
138
+ @ast.each do |n|
139
+ case n
140
+ when Nodes::Cat
141
+ lastpos(n.left).each do |i|
142
+ table[i] += firstpos(n.right)
143
+ end
144
+ when Nodes::Star
145
+ lastpos(n).each do |i|
146
+ table[i] += firstpos(n)
147
+ end
148
+ end
149
+ end
150
+ table
151
+ end
152
+
153
+ def symbol(edge)
154
+ case edge
155
+ when Journey::Nodes::Symbol
156
+ edge.regexp
157
+ else
158
+ edge.left
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module GTG # :nodoc:
8
+ class MatchData # :nodoc:
9
+ attr_reader :memos
10
+
11
+ def initialize(memos)
12
+ @memos = memos
13
+ end
14
+ end
15
+
16
+ class Simulator # :nodoc:
17
+ attr_reader :tt
18
+
19
+ def initialize(transition_table)
20
+ @tt = transition_table
21
+ end
22
+
23
+ def memos(string)
24
+ input = StringScanner.new(string)
25
+ state = [0]
26
+ while sym = input.scan(%r([/.?]|[^/.?]+))
27
+ state = tt.move(state, sym)
28
+ end
29
+
30
+ acceptance_states = state.find_all { |s|
31
+ tt.accepting? s
32
+ }
33
+
34
+ return yield if acceptance_states.empty?
35
+
36
+ acceptance_states.flat_map { |x| tt.memo(x) }.compact
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/nfa/dot"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module GTG # :nodoc:
8
+ class TransitionTable # :nodoc:
9
+ include Journey::NFA::Dot
10
+
11
+ attr_reader :memos
12
+
13
+ def initialize
14
+ @regexp_states = {}
15
+ @string_states = {}
16
+ @accepting = {}
17
+ @memos = Hash.new { |h, k| h[k] = [] }
18
+ end
19
+
20
+ def add_accepting(state)
21
+ @accepting[state] = true
22
+ end
23
+
24
+ def accepting_states
25
+ @accepting.keys
26
+ end
27
+
28
+ def accepting?(state)
29
+ @accepting[state]
30
+ end
31
+
32
+ def add_memo(idx, memo)
33
+ @memos[idx] << memo
34
+ end
35
+
36
+ def memo(idx)
37
+ @memos[idx]
38
+ end
39
+
40
+ def eclosure(t)
41
+ Array(t)
42
+ end
43
+
44
+ def move(t, a)
45
+ return [] if t.empty?
46
+
47
+ regexps = []
48
+
49
+ t.map { |s|
50
+ if states = @regexp_states[s]
51
+ regexps.concat states.map { |re, v| re === a ? v : nil }
52
+ end
53
+
54
+ if states = @string_states[s]
55
+ states[a]
56
+ end
57
+ }.compact.concat regexps
58
+ end
59
+
60
+ def as_json(options = nil)
61
+ simple_regexp = Hash.new { |h, k| h[k] = {} }
62
+
63
+ @regexp_states.each do |from, hash|
64
+ hash.each do |re, to|
65
+ simple_regexp[from][re.source] = to
66
+ end
67
+ end
68
+
69
+ {
70
+ regexp_states: simple_regexp,
71
+ string_states: @string_states,
72
+ accepting: @accepting
73
+ }
74
+ end
75
+
76
+ def to_svg
77
+ svg = IO.popen("dot -Tsvg", "w+") { |f|
78
+ f.write(to_dot)
79
+ f.close_write
80
+ f.readlines
81
+ }
82
+ 3.times { svg.shift }
83
+ svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "")
84
+ end
85
+
86
+ def visualizer(paths, title = "FSM")
87
+ viz_dir = File.join __dir__, "..", "visualizer"
88
+ fsm_js = File.read File.join(viz_dir, "fsm.js")
89
+ fsm_css = File.read File.join(viz_dir, "fsm.css")
90
+ erb = File.read File.join(viz_dir, "index.html.erb")
91
+ states = "function tt() { return #{to_json}; }"
92
+
93
+ fun_routes = paths.sample(3).map do |ast|
94
+ ast.map { |n|
95
+ case n
96
+ when Nodes::Symbol
97
+ case n.left
98
+ when ":id" then rand(100).to_s
99
+ when ":format" then %w{ xml json }.sample
100
+ else
101
+ "omg"
102
+ end
103
+ when Nodes::Terminal then n.symbol
104
+ else
105
+ nil
106
+ end
107
+ }.compact.join
108
+ end
109
+
110
+ stylesheets = [fsm_css]
111
+ svg = to_svg
112
+ javascripts = [states, fsm_js]
113
+
114
+ fun_routes = fun_routes
115
+ stylesheets = stylesheets
116
+ svg = svg
117
+ javascripts = javascripts
118
+
119
+ require "erb"
120
+ template = ERB.new erb
121
+ template.result(binding)
122
+ end
123
+
124
+ def []=(from, to, sym)
125
+ to_mappings = states_hash_for(sym)[from] ||= {}
126
+ to_mappings[sym] = to
127
+ end
128
+
129
+ def states
130
+ ss = @string_states.keys + @string_states.values.flat_map(&:values)
131
+ rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
132
+ (ss + rs).uniq
133
+ end
134
+
135
+ def transitions
136
+ @string_states.flat_map { |from, hash|
137
+ hash.map { |s, to| [from, s, to] }
138
+ } + @regexp_states.flat_map { |from, hash|
139
+ hash.map { |s, to| [from, s, to] }
140
+ }
141
+ end
142
+
143
+ private
144
+
145
+ def states_hash_for(sym)
146
+ case sym
147
+ when String
148
+ @string_states
149
+ when Regexp
150
+ @regexp_states
151
+ else
152
+ raise ArgumentError, "unknown symbol: %s" % sym.class
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/nfa/transition_table"
4
+ require "action_dispatch/journey/gtg/transition_table"
5
+
6
+ module ActionDispatch
7
+ module Journey # :nodoc:
8
+ module NFA # :nodoc:
9
+ class Visitor < Visitors::Visitor # :nodoc:
10
+ def initialize(tt)
11
+ @tt = tt
12
+ @i = -1
13
+ end
14
+
15
+ def visit_CAT(node)
16
+ left = visit(node.left)
17
+ right = visit(node.right)
18
+
19
+ @tt.merge(left.last, right.first)
20
+
21
+ [left.first, right.last]
22
+ end
23
+
24
+ def visit_GROUP(node)
25
+ from = @i += 1
26
+ left = visit(node.left)
27
+ to = @i += 1
28
+
29
+ @tt.accepting = to
30
+
31
+ @tt[from, left.first] = nil
32
+ @tt[left.last, to] = nil
33
+ @tt[from, to] = nil
34
+
35
+ [from, to]
36
+ end
37
+
38
+ def visit_OR(node)
39
+ from = @i += 1
40
+ children = node.children.map { |c| visit(c) }
41
+ to = @i += 1
42
+
43
+ children.each do |child|
44
+ @tt[from, child.first] = nil
45
+ @tt[child.last, to] = nil
46
+ end
47
+
48
+ @tt.accepting = to
49
+
50
+ [from, to]
51
+ end
52
+
53
+ def terminal(node)
54
+ from_i = @i += 1 # new state
55
+ to_i = @i += 1 # new state
56
+
57
+ @tt[from_i, to_i] = node
58
+ @tt.accepting = to_i
59
+ @tt.add_memo(to_i, node.memo)
60
+
61
+ [from_i, to_i]
62
+ end
63
+ end
64
+
65
+ class Builder # :nodoc:
66
+ def initialize(ast)
67
+ @ast = ast
68
+ end
69
+
70
+ def transition_table
71
+ tt = TransitionTable.new
72
+ Visitor.new(tt).accept(@ast)
73
+ tt
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Journey # :nodoc:
5
+ module NFA # :nodoc:
6
+ module Dot # :nodoc:
7
+ def to_dot
8
+ edges = transitions.map { |from, sym, to|
9
+ " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
10
+ }
11
+
12
+ # memo_nodes = memos.values.flatten.map { |n|
13
+ # label = n
14
+ # if Journey::Route === n
15
+ # label = "#{n.verb.source} #{n.path.spec}"
16
+ # end
17
+ # " #{n.object_id} [label=\"#{label}\", shape=box];"
18
+ # }
19
+ # memo_edges = memos.flat_map { |k, memos|
20
+ # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
21
+ # }.uniq
22
+
23
+ <<-eodot
24
+ digraph nfa {
25
+ rankdir=LR;
26
+ node [shape = doublecircle];
27
+ #{accepting_states.join ' '};
28
+ node [shape = circle];
29
+ #{edges.join "\n"}
30
+ }
31
+ eodot
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module NFA # :nodoc:
8
+ class MatchData # :nodoc:
9
+ attr_reader :memos
10
+
11
+ def initialize(memos)
12
+ @memos = memos
13
+ end
14
+ end
15
+
16
+ class Simulator # :nodoc:
17
+ attr_reader :tt
18
+
19
+ def initialize(transition_table)
20
+ @tt = transition_table
21
+ end
22
+
23
+ def simulate(string)
24
+ input = StringScanner.new(string)
25
+ state = tt.eclosure(0)
26
+ until input.eos?
27
+ sym = input.scan(%r([/.?]|[^/.?]+))
28
+
29
+ # FIXME: tt.eclosure is not needed for the GTG
30
+ state = tt.eclosure(tt.move(state, sym))
31
+ end
32
+
33
+ acceptance_states = state.find_all { |s|
34
+ tt.accepting?(tt.eclosure(s).sort.last)
35
+ }
36
+
37
+ return if acceptance_states.empty?
38
+
39
+ memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact
40
+
41
+ MatchData.new(memos)
42
+ end
43
+
44
+ alias :=~ :simulate
45
+ alias :match :simulate
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/nfa/dot"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module NFA # :nodoc:
8
+ class TransitionTable # :nodoc:
9
+ include Journey::NFA::Dot
10
+
11
+ attr_accessor :accepting
12
+ attr_reader :memos
13
+
14
+ def initialize
15
+ @table = Hash.new { |h, f| h[f] = {} }
16
+ @memos = {}
17
+ @accepting = nil
18
+ @inverted = nil
19
+ end
20
+
21
+ def accepting?(state)
22
+ accepting == state
23
+ end
24
+
25
+ def accepting_states
26
+ [accepting]
27
+ end
28
+
29
+ def add_memo(idx, memo)
30
+ @memos[idx] = memo
31
+ end
32
+
33
+ def memo(idx)
34
+ @memos[idx]
35
+ end
36
+
37
+ def []=(i, f, s)
38
+ @table[f][i] = s
39
+ end
40
+
41
+ def merge(left, right)
42
+ @memos[right] = @memos.delete(left)
43
+ @table[right] = @table.delete(left)
44
+ end
45
+
46
+ def states
47
+ (@table.keys + @table.values.flat_map(&:keys)).uniq
48
+ end
49
+
50
+ # Returns set of NFA states to which there is a transition on ast symbol
51
+ # +a+ from some state +s+ in +t+.
52
+ def following_states(t, a)
53
+ Array(t).flat_map { |s| inverted[s][a] }.uniq
54
+ end
55
+
56
+ # Returns set of NFA states to which there is a transition on ast symbol
57
+ # +a+ from some state +s+ in +t+.
58
+ def move(t, a)
59
+ Array(t).map { |s|
60
+ inverted[s].keys.compact.find_all { |sym|
61
+ sym === a
62
+ }.map { |sym| inverted[s][sym] }
63
+ }.flatten.uniq
64
+ end
65
+
66
+ def alphabet
67
+ inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s)
68
+ end
69
+
70
+ # Returns a set of NFA states reachable from some NFA state +s+ in set
71
+ # +t+ on nil-transitions alone.
72
+ def eclosure(t)
73
+ stack = Array(t)
74
+ seen = {}
75
+ children = []
76
+
77
+ until stack.empty?
78
+ s = stack.pop
79
+ next if seen[s]
80
+
81
+ seen[s] = true
82
+ children << s
83
+
84
+ stack.concat(inverted[s][nil])
85
+ end
86
+
87
+ children.uniq
88
+ end
89
+
90
+ def transitions
91
+ @table.flat_map { |to, hash|
92
+ hash.map { |from, sym| [from, sym, to] }
93
+ }
94
+ end
95
+
96
+ private
97
+
98
+ def inverted
99
+ return @inverted if @inverted
100
+
101
+ @inverted = Hash.new { |h, from|
102
+ h[from] = Hash.new { |j, s| j[s] = [] }
103
+ }
104
+
105
+ @table.each { |to, hash|
106
+ hash.each { |from, sym|
107
+ if sym
108
+ sym = Nodes::Symbol === sym ? sym.regexp : sym.left
109
+ end
110
+
111
+ @inverted[from][sym] << to
112
+ }
113
+ }
114
+
115
+ @inverted
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end