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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- 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
|