omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  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 +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  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 +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  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 +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  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 +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  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 +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  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 +691 -0
  65. data/lib/action_controller.rb +80 -0
  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 +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  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 +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  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 +284 -0
  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 +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/journey/gtg/transition_table"
6
+
7
+ module ActionDispatch
8
+ module Journey # :nodoc:
9
+ module GTG # :nodoc:
10
+ class Builder # :nodoc:
11
+ DUMMY_END_NODE = Nodes::Dummy.new
12
+
13
+ attr_reader :root, :ast, :endpoints
14
+
15
+ def initialize(root)
16
+ @root = root
17
+ @ast = Nodes::Cat.new root, DUMMY_END_NODE
18
+ @followpos = build_followpos
19
+ end
20
+
21
+ def transition_table
22
+ dtrans = TransitionTable.new
23
+ marked = {}.compare_by_identity
24
+ state_id = Hash.new { |h, k| h[k] = h.length }.compare_by_identity
25
+ dstates = [firstpos(root)]
26
+
27
+ until dstates.empty?
28
+ s = dstates.shift
29
+ next if marked[s]
30
+ marked[s] = true # mark s
31
+
32
+ s.group_by { |state| symbol(state) }.each do |sym, ps|
33
+ u = ps.flat_map { |l| @followpos[l] }.uniq
34
+ next if u.empty?
35
+
36
+ from = state_id[s]
37
+
38
+ if u.all? { |pos| pos == DUMMY_END_NODE }
39
+ to = state_id[Object.new]
40
+ dtrans[from, to] = sym
41
+ dtrans.add_accepting(to)
42
+
43
+ ps.each { |state| dtrans.add_memo(to, state.memo) }
44
+ else
45
+ to = state_id[u]
46
+ dtrans[from, to] = sym
47
+
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
54
+
55
+ dtrans.add_accepting(to)
56
+ end
57
+ end
58
+
59
+ dstates << u
60
+ end
61
+ end
62
+
63
+ dtrans
64
+ end
65
+
66
+ def nullable?(node)
67
+ case node
68
+ when Nodes::Group
69
+ true
70
+ when Nodes::Star
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?("")
74
+ when Nodes::Or
75
+ node.children.any? { |c| nullable?(c) }
76
+ when Nodes::Cat
77
+ nullable?(node.left) && nullable?(node.right)
78
+ when Nodes::Terminal
79
+ !node.left
80
+ when Nodes::Unary
81
+ nullable?(node.left)
82
+ else
83
+ raise ArgumentError, "unknown nullable: %s" % node.class.name
84
+ end
85
+ end
86
+
87
+ def firstpos(node)
88
+ case node
89
+ when Nodes::Star
90
+ firstpos(node.left)
91
+ when Nodes::Cat
92
+ if nullable?(node.left)
93
+ firstpos(node.left) | firstpos(node.right)
94
+ else
95
+ firstpos(node.left)
96
+ end
97
+ when Nodes::Or
98
+ node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!)
99
+ when Nodes::Unary
100
+ firstpos(node.left)
101
+ when Nodes::Terminal
102
+ nullable?(node) ? [] : [node]
103
+ else
104
+ raise ArgumentError, "unknown firstpos: %s" % node.class.name
105
+ end
106
+ end
107
+
108
+ def lastpos(node)
109
+ case node
110
+ when Nodes::Star
111
+ lastpos(node.left)
112
+ when Nodes::Or
113
+ node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
114
+ when Nodes::Cat
115
+ if nullable?(node.right)
116
+ lastpos(node.left) | lastpos(node.right)
117
+ else
118
+ lastpos(node.right)
119
+ end
120
+ when Nodes::Terminal
121
+ nullable?(node) ? [] : [node]
122
+ when Nodes::Unary
123
+ lastpos(node.left)
124
+ else
125
+ raise ArgumentError, "unknown lastpos: %s" % node.class.name
126
+ end
127
+ end
128
+
129
+ private
130
+ def build_followpos
131
+ table = Hash.new { |h, k| h[k] = [] }.compare_by_identity
132
+ @ast.each do |n|
133
+ case n
134
+ when Nodes::Cat
135
+ lastpos(n.left).each do |i|
136
+ table[i] += firstpos(n.right)
137
+ end
138
+ end
139
+ end
140
+ table
141
+ end
142
+
143
+ def symbol(edge)
144
+ edge.symbol? ? edge.regexp : edge.left
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "strscan"
6
+
7
+ module ActionDispatch
8
+ module Journey # :nodoc:
9
+ module GTG # :nodoc:
10
+ class MatchData # :nodoc:
11
+ attr_reader :memos
12
+
13
+ def initialize(memos)
14
+ @memos = memos
15
+ end
16
+ end
17
+
18
+ class Simulator # :nodoc:
19
+ INITIAL_STATE = [ [0, nil] ].freeze
20
+
21
+ attr_reader :tt
22
+
23
+ def initialize(transition_table)
24
+ @tt = transition_table
25
+ end
26
+
27
+ def memos(string)
28
+ input = StringScanner.new(string)
29
+ state = INITIAL_STATE
30
+ start_index = 0
31
+
32
+ while sym = input.scan(%r([/.?]|[^/.?]+))
33
+ end_index = start_index + sym.length
34
+
35
+ state = tt.move(state, string, start_index, end_index)
36
+
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
44
+
45
+ acceptance_states.empty? ? yield : acceptance_states
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/journey/nfa/dot"
6
+
7
+ module ActionDispatch
8
+ module Journey # :nodoc:
9
+ module GTG # :nodoc:
10
+ class TransitionTable # :nodoc:
11
+ include Journey::NFA::Dot
12
+
13
+ attr_reader :memos
14
+
15
+ DEFAULT_EXP = /[^.\/?]+/
16
+ DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
17
+
18
+ def initialize
19
+ @stdparam_states = {}
20
+ @regexp_states = {}
21
+ @string_states = {}
22
+ @accepting = {}
23
+ @memos = Hash.new { |h, k| h[k] = [] }
24
+ end
25
+
26
+ def add_accepting(state)
27
+ @accepting[state] = true
28
+ end
29
+
30
+ def accepting_states
31
+ @accepting.keys
32
+ end
33
+
34
+ def accepting?(state)
35
+ @accepting[state]
36
+ end
37
+
38
+ def add_memo(idx, memo)
39
+ @memos[idx] << memo
40
+ end
41
+
42
+ def memo(idx)
43
+ @memos[idx]
44
+ end
45
+
46
+ def eclosure(t)
47
+ Array(t)
48
+ end
49
+
50
+ def move(t, full_string, start_index, end_index)
51
+ return [] if t.empty?
52
+
53
+ next_states = []
54
+
55
+ tok = full_string.slice(start_index, end_index - start_index)
56
+ token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
57
+
58
+ t.each { |s, previous_start|
59
+ if previous_start.nil?
60
+ # In the simple case of a "default" param regex do this fast-path and add all
61
+ # next states.
62
+ if token_matches_default_component && states = @stdparam_states[s]
63
+ states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
64
+ end
65
+
66
+ # When we have a literal string, we can just pull the next state
67
+ if states = @string_states[s]
68
+ next_states << [states[tok], nil].freeze unless states[tok].nil?
69
+ end
70
+ end
71
+
72
+ # For regexes that aren't the "default" style, they may potentially not be
73
+ # terminated by the first "token" [./?], so we need to continue to attempt to
74
+ # match this regexp as well as any successful paths that continue out of it.
75
+ # both paths could be valid.
76
+ if states = @regexp_states[s]
77
+ slice_start = if previous_start.nil?
78
+ start_index
79
+ else
80
+ previous_start
81
+ end
82
+
83
+ slice_length = end_index - slice_start
84
+ curr_slice = full_string.slice(slice_start, slice_length)
85
+
86
+ states.each { |re, v|
87
+ # if we match, we can try moving past this
88
+ next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
89
+ }
90
+
91
+ # and regardless, we must continue accepting tokens and retrying this regexp. we
92
+ # need to remember where we started as well so we can take bigger slices.
93
+ next_states << [s, slice_start].freeze
94
+ end
95
+ }
96
+
97
+ next_states
98
+ end
99
+
100
+ def as_json(options = nil)
101
+ simple_regexp = Hash.new { |h, k| h[k] = {} }
102
+
103
+ @regexp_states.each do |from, hash|
104
+ hash.each do |re, to|
105
+ simple_regexp[from][re.source] = to
106
+ end
107
+ end
108
+
109
+ {
110
+ regexp_states: simple_regexp,
111
+ string_states: @string_states,
112
+ stdparam_states: @stdparam_states,
113
+ accepting: @accepting
114
+ }
115
+ end
116
+
117
+ def to_svg
118
+ svg = IO.popen("dot -Tsvg", "w+") { |f|
119
+ f.write(to_dot)
120
+ f.close_write
121
+ f.readlines
122
+ }
123
+ 3.times { svg.shift }
124
+ svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "")
125
+ end
126
+
127
+ def visualizer(paths, title = "FSM")
128
+ viz_dir = File.join __dir__, "..", "visualizer"
129
+ fsm_js = File.read File.join(viz_dir, "fsm.js")
130
+ fsm_css = File.read File.join(viz_dir, "fsm.css")
131
+ erb = File.read File.join(viz_dir, "index.html.erb")
132
+ states = "function tt() { return #{to_json}; }"
133
+
134
+ fun_routes = paths.sample(3).map do |ast|
135
+ ast.filter_map { |n|
136
+ case n
137
+ when Nodes::Symbol
138
+ case n.left
139
+ when ":id" then rand(100).to_s
140
+ when ":format" then %w{ xml json }.sample
141
+ else
142
+ "omg"
143
+ end
144
+ when Nodes::Terminal then n.symbol
145
+ else
146
+ nil
147
+ end
148
+ }.join
149
+ end
150
+
151
+ stylesheets = [fsm_css]
152
+ svg = to_svg
153
+ javascripts = [states, fsm_js]
154
+
155
+ fun_routes = fun_routes
156
+ stylesheets = stylesheets
157
+ svg = svg
158
+ javascripts = javascripts
159
+
160
+ require "erb"
161
+ template = ERB.new erb
162
+ template.result(binding)
163
+ end
164
+
165
+ def []=(from, to, sym)
166
+ to_mappings = states_hash_for(sym)[from] ||= {}
167
+ case sym
168
+ when Regexp
169
+ # we must match the whole string to a token boundary
170
+ if sym == DEFAULT_EXP
171
+ sym = DEFAULT_EXP_ANCHORED
172
+ else
173
+ sym = /\A#{sym}\Z/
174
+ end
175
+ when Symbol
176
+ # account for symbols in the constraints the same as strings
177
+ sym = sym.to_s
178
+ end
179
+ to_mappings[sym] = to
180
+ end
181
+
182
+ def states
183
+ ss = @string_states.keys + @string_states.values.flat_map(&:values)
184
+ ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
185
+ rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
186
+ (ss + ps + rs).uniq
187
+ end
188
+
189
+ def transitions
190
+ @string_states.flat_map { |from, hash|
191
+ hash.map { |s, to| [from, s, to] }
192
+ } + @stdparam_states.flat_map { |from, hash|
193
+ hash.map { |s, to| [from, s, to] }
194
+ } + @regexp_states.flat_map { |from, hash|
195
+ hash.map { |s, to| [from, s, to] }
196
+ }
197
+ end
198
+
199
+ private
200
+ def states_hash_for(sym)
201
+ case sym
202
+ when String, Symbol
203
+ @string_states
204
+ when Regexp
205
+ if sym == DEFAULT_EXP
206
+ @stdparam_states
207
+ else
208
+ @regexp_states
209
+ end
210
+ else
211
+ raise ArgumentError, "unknown symbol: %s" % sym.class
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ module NFA # :nodoc:
8
+ module Dot # :nodoc:
9
+ def to_dot
10
+ edges = transitions.map { |from, sym, to|
11
+ " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
12
+ }
13
+
14
+ <<-eodot
15
+ digraph nfa {
16
+ rankdir=LR;
17
+ node [shape = doublecircle];
18
+ #{accepting_states.join ' '};
19
+ node [shape = circle];
20
+ #{edges.join "\n"}
21
+ }
22
+ eodot
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/journey/visitors"
6
+
7
+ module ActionDispatch
8
+ module Journey # :nodoc:
9
+ class Ast # :nodoc:
10
+ attr_reader :names, :path_params, :tree, :wildcard_options, :terminals
11
+ alias :root :tree
12
+
13
+ def initialize(tree, formatted)
14
+ @tree = tree
15
+ @path_params = []
16
+ @names = []
17
+ @symbols = []
18
+ @stars = []
19
+ @terminals = []
20
+ @wildcard_options = {}
21
+
22
+ visit_tree(formatted)
23
+ end
24
+
25
+ def requirements=(requirements)
26
+ # inject any regexp requirements for `star` nodes so they can be determined
27
+ # nullable, which requires knowing if the regex accepts an empty string.
28
+ (symbols + stars).each do |node|
29
+ re = requirements[node.to_sym]
30
+ node.regexp = re if re
31
+ end
32
+ end
33
+
34
+ def route=(route)
35
+ terminals.each { |n| n.memo = route }
36
+ end
37
+
38
+ def glob?
39
+ stars.any?
40
+ end
41
+
42
+ private
43
+ attr_reader :symbols, :stars
44
+
45
+ def visit_tree(formatted)
46
+ tree.each do |node|
47
+ if node.symbol?
48
+ path_params << node.to_sym
49
+ names << node.name
50
+ symbols << node
51
+ elsif node.star?
52
+ stars << node
53
+
54
+ if formatted != false
55
+ # Add a constraint for wildcard route to make it non-greedy and match the
56
+ # optional format part of the route by default.
57
+ wildcard_options[node.name.to_sym] ||= /.+?/m
58
+ end
59
+ end
60
+
61
+ if node.terminal?
62
+ terminals << node
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ module Nodes # :nodoc:
69
+ class Node # :nodoc:
70
+ include Enumerable
71
+
72
+ attr_accessor :left, :memo
73
+
74
+ def initialize(left)
75
+ @left = left
76
+ @memo = nil
77
+ end
78
+
79
+ def each(&block)
80
+ Visitors::Each::INSTANCE.accept(self, block)
81
+ end
82
+
83
+ def to_s
84
+ Visitors::String::INSTANCE.accept(self, "")
85
+ end
86
+
87
+ def to_dot
88
+ Visitors::Dot::INSTANCE.accept(self)
89
+ end
90
+
91
+ def to_sym
92
+ name.to_sym
93
+ end
94
+
95
+ def name
96
+ -left.tr("*:", "")
97
+ end
98
+
99
+ def type
100
+ raise NotImplementedError
101
+ end
102
+
103
+ def symbol?; false; end
104
+ def literal?; false; end
105
+ def terminal?; false; end
106
+ def star?; false; end
107
+ def cat?; false; end
108
+ def group?; false; end
109
+ end
110
+
111
+ class Terminal < Node # :nodoc:
112
+ alias :symbol :left
113
+ def terminal?; true; end
114
+ end
115
+
116
+ class Literal < Terminal # :nodoc:
117
+ def literal?; true; end
118
+ def type; :LITERAL; end
119
+ end
120
+
121
+ class Dummy < Literal # :nodoc:
122
+ def initialize(x = Object.new)
123
+ super
124
+ end
125
+
126
+ def literal?; false; end
127
+ end
128
+
129
+ class Slash < Terminal # :nodoc:
130
+ def type; :SLASH; end
131
+ end
132
+
133
+ class Dot < Terminal # :nodoc:
134
+ def type; :DOT; end
135
+ end
136
+
137
+ class Symbol < Terminal # :nodoc:
138
+ attr_accessor :regexp
139
+ alias :symbol :regexp
140
+ attr_reader :name
141
+
142
+ DEFAULT_EXP = /[^.\/?]+/
143
+ GREEDY_EXP = /(.+)/
144
+ def initialize(left, regexp = DEFAULT_EXP)
145
+ super(left)
146
+ @regexp = regexp
147
+ @name = -left.tr("*:", "")
148
+ end
149
+
150
+ def type; :SYMBOL; end
151
+ def symbol?; true; end
152
+ end
153
+
154
+ class Unary < Node # :nodoc:
155
+ def children; [left] end
156
+ end
157
+
158
+ class Group < Unary # :nodoc:
159
+ def type; :GROUP; end
160
+ def group?; true; end
161
+ end
162
+
163
+ class Star < Unary # :nodoc:
164
+ attr_accessor :regexp
165
+
166
+ def initialize(left)
167
+ super(left)
168
+
169
+ # By default wildcard routes are non-greedy and must match something.
170
+ @regexp = /.+?/m
171
+ end
172
+
173
+ def star?; true; end
174
+ def type; :STAR; end
175
+
176
+ def name
177
+ left.name.tr "*:", ""
178
+ end
179
+ end
180
+
181
+ class Binary < Node # :nodoc:
182
+ attr_accessor :right
183
+
184
+ def initialize(left, right)
185
+ super(left)
186
+ @right = right
187
+ end
188
+
189
+ def children; [left, right] end
190
+ end
191
+
192
+ class Cat < Binary # :nodoc:
193
+ def cat?; true; end
194
+ def type; :CAT; end
195
+ end
196
+
197
+ class Or < Node # :nodoc:
198
+ attr_reader :children
199
+
200
+ def initialize(children)
201
+ @children = children
202
+ end
203
+
204
+ def type; :OR; end
205
+ end
206
+ end
207
+ end
208
+ end