actionpack 7.2.2.1 → 8.1.2
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 +4 -4
- data/CHANGELOG.md +408 -95
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +12 -17
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/callbacks.rb +6 -0
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +3 -2
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +4 -4
- data/lib/action_controller/log_subscriber.rb +22 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/conditional_get.rb +30 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -5
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +66 -26
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +39 -9
- data/lib/action_controller/metal/redirecting.rb +109 -16
- data/lib/action_controller/metal/renderers.rb +29 -9
- data/lib/action_controller/metal/rendering.rb +8 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -92
- data/lib/action_controller/railtie.rb +33 -15
- data/lib/action_controller/renderer.rb +0 -1
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/test_case.rb +12 -2
- data/lib/action_dispatch/constants.rb +6 -0
- data/lib/action_dispatch/http/cache.rb +138 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +6 -0
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/request.rb +73 -23
- data/lib/action_dispatch/http/response.rb +65 -17
- data/lib/action_dispatch/http/url.rb +112 -16
- data/lib/action_dispatch/journey/formatter.rb +8 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/scanner.rb +44 -42
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +8 -4
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
- data/lib/action_dispatch/middleware/debug_view.rb +11 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
- data/lib/action_dispatch/middleware/executor.rb +17 -4
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +21 -0
- data/lib/action_dispatch/request/session.rb +1 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/inspector.rb +80 -57
- data/lib/action_dispatch/routing/mapper.rb +409 -228
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +21 -12
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +26 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
- data/lib/action_dispatch/testing/integration.rb +16 -7
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +1 -2
- data/lib/action_dispatch.rb +14 -4
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +19 -38
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -11,8 +11,105 @@ module ActionDispatch
|
|
|
11
11
|
HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
|
|
12
12
|
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
|
|
13
13
|
|
|
14
|
+
# DomainExtractor provides utility methods for extracting domain and subdomain
|
|
15
|
+
# information from host strings. This module is used internally by Action Dispatch
|
|
16
|
+
# to parse host names and separate the domain from subdomains based on the
|
|
17
|
+
# top-level domain (TLD) length.
|
|
18
|
+
#
|
|
19
|
+
# The module assumes a standard domain structure where domains consist of:
|
|
20
|
+
# - Subdomains (optional, can be multiple levels)
|
|
21
|
+
# - Domain name
|
|
22
|
+
# - Top-level domain (TLD, can be multiple levels like .co.uk)
|
|
23
|
+
#
|
|
24
|
+
# For example, in "api.staging.example.co.uk":
|
|
25
|
+
# - Subdomains: ["api", "staging"]
|
|
26
|
+
# - Domain: "example.co.uk" (with tld_length=2)
|
|
27
|
+
# - TLD: "co.uk"
|
|
28
|
+
module DomainExtractor
|
|
29
|
+
extend self
|
|
30
|
+
|
|
31
|
+
# Extracts the domain part from a host string, including the specified
|
|
32
|
+
# number of top-level domain components.
|
|
33
|
+
#
|
|
34
|
+
# The domain includes the main domain name plus the TLD components.
|
|
35
|
+
# The +tld_length+ parameter specifies how many components from the right
|
|
36
|
+
# should be considered part of the TLD.
|
|
37
|
+
#
|
|
38
|
+
# ==== Parameters
|
|
39
|
+
#
|
|
40
|
+
# [+host+]
|
|
41
|
+
# The host string to extract the domain from.
|
|
42
|
+
#
|
|
43
|
+
# [+tld_length+]
|
|
44
|
+
# The number of domain components that make up the TLD. For example,
|
|
45
|
+
# use 1 for ".com" or 2 for ".co.uk".
|
|
46
|
+
#
|
|
47
|
+
# ==== Examples
|
|
48
|
+
#
|
|
49
|
+
# # Standard TLD (tld_length = 1)
|
|
50
|
+
# DomainExtractor.domain_from("www.example.com", 1)
|
|
51
|
+
# # => "example.com"
|
|
52
|
+
#
|
|
53
|
+
# # Country-code TLD (tld_length = 2)
|
|
54
|
+
# DomainExtractor.domain_from("www.example.co.uk", 2)
|
|
55
|
+
# # => "example.co.uk"
|
|
56
|
+
#
|
|
57
|
+
# # Multiple subdomains
|
|
58
|
+
# DomainExtractor.domain_from("api.staging.myapp.herokuapp.com", 1)
|
|
59
|
+
# # => "herokuapp.com"
|
|
60
|
+
#
|
|
61
|
+
# # Single component (returns the host itself)
|
|
62
|
+
# DomainExtractor.domain_from("localhost", 1)
|
|
63
|
+
# # => "localhost"
|
|
64
|
+
def domain_from(host, tld_length)
|
|
65
|
+
host.split(".").last(1 + tld_length).join(".")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Extracts the subdomain components from a host string as an Array.
|
|
69
|
+
#
|
|
70
|
+
# Returns all the components that come before the domain and TLD parts.
|
|
71
|
+
# The +tld_length+ parameter is used to determine where the domain begins
|
|
72
|
+
# so that everything before it is considered a subdomain.
|
|
73
|
+
#
|
|
74
|
+
# ==== Parameters
|
|
75
|
+
#
|
|
76
|
+
# [+host+]
|
|
77
|
+
# The host string to extract subdomains from.
|
|
78
|
+
#
|
|
79
|
+
# [+tld_length+]
|
|
80
|
+
# The number of domain components that make up the TLD. This affects
|
|
81
|
+
# where the domain boundary is calculated.
|
|
82
|
+
#
|
|
83
|
+
# ==== Examples
|
|
84
|
+
#
|
|
85
|
+
# # Standard TLD (tld_length = 1)
|
|
86
|
+
# DomainExtractor.subdomains_from("www.example.com", 1)
|
|
87
|
+
# # => ["www"]
|
|
88
|
+
#
|
|
89
|
+
# # Country-code TLD (tld_length = 2)
|
|
90
|
+
# DomainExtractor.subdomains_from("api.staging.example.co.uk", 2)
|
|
91
|
+
# # => ["api", "staging"]
|
|
92
|
+
#
|
|
93
|
+
# # No subdomains
|
|
94
|
+
# DomainExtractor.subdomains_from("example.com", 1)
|
|
95
|
+
# # => []
|
|
96
|
+
#
|
|
97
|
+
# # Single subdomain with complex TLD
|
|
98
|
+
# DomainExtractor.subdomains_from("www.mysite.co.uk", 2)
|
|
99
|
+
# # => ["www"]
|
|
100
|
+
#
|
|
101
|
+
# # Multiple levels of subdomains
|
|
102
|
+
# DomainExtractor.subdomains_from("dev.api.staging.example.com", 1)
|
|
103
|
+
# # => ["dev", "api", "staging"]
|
|
104
|
+
def subdomains_from(host, tld_length)
|
|
105
|
+
parts = host.split(".")
|
|
106
|
+
parts[0..-(tld_length + 2)]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
14
110
|
mattr_accessor :secure_protocol, default: false
|
|
15
111
|
mattr_accessor :tld_length, default: 1
|
|
112
|
+
mattr_accessor :domain_extractor, default: DomainExtractor
|
|
16
113
|
|
|
17
114
|
class << self
|
|
18
115
|
# Returns the domain part of a host given the domain level.
|
|
@@ -96,34 +193,33 @@ module ActionDispatch
|
|
|
96
193
|
end
|
|
97
194
|
|
|
98
195
|
def extract_domain_from(host, tld_length)
|
|
99
|
-
|
|
196
|
+
domain_extractor.domain_from(host, tld_length)
|
|
100
197
|
end
|
|
101
198
|
|
|
102
199
|
def extract_subdomains_from(host, tld_length)
|
|
103
|
-
|
|
104
|
-
parts[0..-(tld_length + 2)]
|
|
200
|
+
domain_extractor.subdomains_from(host, tld_length)
|
|
105
201
|
end
|
|
106
202
|
|
|
107
203
|
def build_host_url(host, port, protocol, options, path)
|
|
108
204
|
if match = host.match(HOST_REGEXP)
|
|
109
|
-
|
|
110
|
-
host
|
|
111
|
-
port
|
|
205
|
+
protocol_from_host = match[1] if protocol.nil?
|
|
206
|
+
host = match[2]
|
|
207
|
+
port = match[3] unless options.key? :port
|
|
112
208
|
end
|
|
113
209
|
|
|
114
|
-
protocol = normalize_protocol
|
|
210
|
+
protocol = protocol_from_host || normalize_protocol(protocol).dup
|
|
115
211
|
host = normalize_host(host, options)
|
|
212
|
+
port = normalize_port(port, protocol)
|
|
116
213
|
|
|
117
|
-
result = protocol
|
|
214
|
+
result = protocol
|
|
118
215
|
|
|
119
216
|
if options[:user] && options[:password]
|
|
120
217
|
result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
|
|
121
218
|
end
|
|
122
219
|
|
|
123
220
|
result << host
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
221
|
+
|
|
222
|
+
result << ":" << port.to_s if port
|
|
127
223
|
|
|
128
224
|
result.concat path
|
|
129
225
|
end
|
|
@@ -169,11 +265,11 @@ module ActionDispatch
|
|
|
169
265
|
return unless port
|
|
170
266
|
|
|
171
267
|
case protocol
|
|
172
|
-
when "//" then
|
|
268
|
+
when "//" then port
|
|
173
269
|
when "https://"
|
|
174
|
-
|
|
270
|
+
port unless port.to_i == 443
|
|
175
271
|
else
|
|
176
|
-
|
|
272
|
+
port unless port.to_i == 80
|
|
177
273
|
end
|
|
178
274
|
end
|
|
179
275
|
end
|
|
@@ -272,7 +368,7 @@ module ActionDispatch
|
|
|
272
368
|
end
|
|
273
369
|
end
|
|
274
370
|
|
|
275
|
-
# Returns whether this request is using the standard port
|
|
371
|
+
# Returns whether this request is using the standard port.
|
|
276
372
|
#
|
|
277
373
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
|
278
374
|
# req.standard_port? # => true
|
|
@@ -307,7 +403,7 @@ module ActionDispatch
|
|
|
307
403
|
standard_port? ? "" : ":#{port}"
|
|
308
404
|
end
|
|
309
405
|
|
|
310
|
-
# Returns the requested port, such as 8080, based on SERVER_PORT
|
|
406
|
+
# Returns the requested port, such as 8080, based on SERVER_PORT.
|
|
311
407
|
#
|
|
312
408
|
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
|
|
313
409
|
# req.server_port # => 80
|
|
@@ -60,8 +60,13 @@ module ActionDispatch
|
|
|
60
60
|
|
|
61
61
|
def generate(name, options, path_parameters)
|
|
62
62
|
original_options = options.dup
|
|
63
|
-
path_params = options.delete(:path_params)
|
|
64
|
-
|
|
63
|
+
path_params = options.delete(:path_params)
|
|
64
|
+
if path_params.is_a?(Hash)
|
|
65
|
+
options = path_params.merge(options)
|
|
66
|
+
else
|
|
67
|
+
path_params = nil
|
|
68
|
+
options = options.dup
|
|
69
|
+
end
|
|
65
70
|
constraints = path_parameters.merge(options)
|
|
66
71
|
missing_keys = nil
|
|
67
72
|
|
|
@@ -79,7 +84,7 @@ module ActionDispatch
|
|
|
79
84
|
# top-level params' normal behavior of generating query_params should be
|
|
80
85
|
# preserved even if the same key is also a bind_param
|
|
81
86
|
parameterized_parts.key?(key) || route.defaults.key?(key) ||
|
|
82
|
-
(path_params
|
|
87
|
+
(path_params&.key?(key) && !original_options.key?(key))
|
|
83
88
|
end
|
|
84
89
|
|
|
85
90
|
defaults = route.defaults
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
|
-
require "strscan"
|
|
6
|
-
|
|
7
5
|
module ActionDispatch
|
|
8
6
|
module Journey # :nodoc:
|
|
9
7
|
module GTG # :nodoc:
|
|
@@ -16,7 +14,13 @@ module ActionDispatch
|
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
class Simulator # :nodoc:
|
|
19
|
-
|
|
17
|
+
STATIC_TOKENS = Array.new(64)
|
|
18
|
+
STATIC_TOKENS[".".ord] = "."
|
|
19
|
+
STATIC_TOKENS["/".ord] = "/"
|
|
20
|
+
STATIC_TOKENS["?".ord] = "?"
|
|
21
|
+
STATIC_TOKENS.freeze
|
|
22
|
+
|
|
23
|
+
INITIAL_STATE = [0, nil].freeze
|
|
20
24
|
|
|
21
25
|
attr_reader :tt
|
|
22
26
|
|
|
@@ -25,21 +29,38 @@ module ActionDispatch
|
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def memos(string)
|
|
28
|
-
input = StringScanner.new(string)
|
|
29
32
|
state = INITIAL_STATE
|
|
30
|
-
start_index = 0
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
pos = 0
|
|
35
|
+
eos = string.bytesize
|
|
36
|
+
|
|
37
|
+
while pos < eos
|
|
38
|
+
start_index = pos
|
|
39
|
+
pos += 1
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
if (token = STATIC_TOKENS[string.getbyte(start_index)])
|
|
42
|
+
state = tt.move(state, string, token, start_index, false)
|
|
43
|
+
else
|
|
44
|
+
while pos < eos && STATIC_TOKENS[string.getbyte(pos)].nil?
|
|
45
|
+
pos += 1
|
|
46
|
+
end
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
token = string.byteslice(start_index, pos - start_index)
|
|
49
|
+
state = tt.move(state, string, token, start_index, true)
|
|
50
|
+
end
|
|
38
51
|
end
|
|
39
52
|
|
|
40
|
-
acceptance_states =
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
acceptance_states = []
|
|
54
|
+
states_count = state.size
|
|
55
|
+
i = 0
|
|
56
|
+
while i < states_count
|
|
57
|
+
if state[i + 1].nil?
|
|
58
|
+
s = state[i]
|
|
59
|
+
if tt.accepting?(s)
|
|
60
|
+
acceptance_states.concat(tt.memo(s))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
i += 2
|
|
43
64
|
end
|
|
44
65
|
|
|
45
66
|
acceptance_states.empty? ? yield : acceptance_states
|
|
@@ -13,7 +13,6 @@ module ActionDispatch
|
|
|
13
13
|
attr_reader :memos
|
|
14
14
|
|
|
15
15
|
DEFAULT_EXP = /[^.\/?]+/
|
|
16
|
-
DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
|
|
17
16
|
|
|
18
17
|
def initialize
|
|
19
18
|
@stdparam_states = {}
|
|
@@ -47,25 +46,27 @@ module ActionDispatch
|
|
|
47
46
|
Array(t)
|
|
48
47
|
end
|
|
49
48
|
|
|
50
|
-
def move(t, full_string, start_index,
|
|
49
|
+
def move(t, full_string, token, start_index, token_matches_default)
|
|
51
50
|
return [] if t.empty?
|
|
52
51
|
|
|
53
52
|
next_states = []
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
transitions_count = t.size
|
|
55
|
+
i = 0
|
|
56
|
+
while i < transitions_count
|
|
57
|
+
s = t[i]
|
|
58
|
+
previous_start = t[i + 1]
|
|
59
59
|
if previous_start.nil?
|
|
60
60
|
# In the simple case of a "default" param regex do this fast-path and add all
|
|
61
61
|
# next states.
|
|
62
|
-
if
|
|
63
|
-
|
|
62
|
+
if token_matches_default && std_state = @stdparam_states[s]
|
|
63
|
+
next_states << std_state << nil
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# When we have a literal string, we can just pull the next state
|
|
67
67
|
if states = @string_states[s]
|
|
68
|
-
|
|
68
|
+
state = states[token]
|
|
69
|
+
next_states << state << nil unless state.nil?
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
@@ -80,19 +81,21 @@ module ActionDispatch
|
|
|
80
81
|
previous_start
|
|
81
82
|
end
|
|
82
83
|
|
|
83
|
-
slice_length =
|
|
84
|
+
slice_length = start_index + token.length - slice_start
|
|
84
85
|
curr_slice = full_string.slice(slice_start, slice_length)
|
|
85
86
|
|
|
86
87
|
states.each { |re, v|
|
|
87
88
|
# if we match, we can try moving past this
|
|
88
|
-
next_states <<
|
|
89
|
+
next_states << v << nil if !v.nil? && re.match?(curr_slice)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
# and regardless, we must continue accepting tokens and retrying this regexp. we
|
|
92
93
|
# need to remember where we started as well so we can take bigger slices.
|
|
93
|
-
next_states <<
|
|
94
|
+
next_states << s << slice_start
|
|
94
95
|
end
|
|
95
|
-
|
|
96
|
+
|
|
97
|
+
i += 2
|
|
98
|
+
end
|
|
96
99
|
|
|
97
100
|
next_states
|
|
98
101
|
end
|
|
@@ -107,10 +110,10 @@ module ActionDispatch
|
|
|
107
110
|
end
|
|
108
111
|
|
|
109
112
|
{
|
|
110
|
-
regexp_states: simple_regexp,
|
|
111
|
-
string_states: @string_states,
|
|
112
|
-
stdparam_states: @stdparam_states,
|
|
113
|
-
accepting: @accepting
|
|
113
|
+
regexp_states: simple_regexp.stringify_keys,
|
|
114
|
+
string_states: @string_states.stringify_keys,
|
|
115
|
+
stdparam_states: @stdparam_states.stringify_keys,
|
|
116
|
+
accepting: @accepting.stringify_keys
|
|
114
117
|
}
|
|
115
118
|
end
|
|
116
119
|
|
|
@@ -163,54 +166,43 @@ module ActionDispatch
|
|
|
163
166
|
end
|
|
164
167
|
|
|
165
168
|
def []=(from, to, sym)
|
|
166
|
-
to_mappings = states_hash_for(sym)[from] ||= {}
|
|
167
169
|
case sym
|
|
170
|
+
when String, Symbol
|
|
171
|
+
to_mapping = @string_states[from] ||= {}
|
|
172
|
+
# account for symbols in the constraints the same as strings
|
|
173
|
+
to_mapping[sym.to_s] = to
|
|
168
174
|
when Regexp
|
|
169
|
-
# we must match the whole string to a token boundary
|
|
170
175
|
if sym == DEFAULT_EXP
|
|
171
|
-
|
|
176
|
+
@stdparam_states[from] = to
|
|
172
177
|
else
|
|
173
|
-
|
|
178
|
+
to_mapping = @regexp_states[from] ||= {}
|
|
179
|
+
# we must match the whole string to a token boundary
|
|
180
|
+
to_mapping[/\A#{sym}\Z/] = to
|
|
174
181
|
end
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
sym = sym.to_s
|
|
182
|
+
else
|
|
183
|
+
raise ArgumentError, "unknown symbol: %s" % sym.class
|
|
178
184
|
end
|
|
179
|
-
to_mappings[sym] = to
|
|
180
185
|
end
|
|
181
186
|
|
|
182
187
|
def states
|
|
183
188
|
ss = @string_states.keys + @string_states.values.flat_map(&:values)
|
|
184
|
-
ps = @stdparam_states.keys + @stdparam_states.values
|
|
189
|
+
ps = @stdparam_states.keys + @stdparam_states.values
|
|
185
190
|
rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
|
|
186
191
|
(ss + ps + rs).uniq
|
|
187
192
|
end
|
|
188
193
|
|
|
189
194
|
def transitions
|
|
195
|
+
# double escaped because dot evaluates escapes
|
|
196
|
+
default_exp_anchored = "\\\\A#{DEFAULT_EXP.source}\\\\Z"
|
|
197
|
+
|
|
190
198
|
@string_states.flat_map { |from, hash|
|
|
191
199
|
hash.map { |s, to| [from, s, to] }
|
|
192
|
-
} + @stdparam_states.
|
|
193
|
-
|
|
200
|
+
} + @stdparam_states.map { |from, to|
|
|
201
|
+
[from, default_exp_anchored, to]
|
|
194
202
|
} + @regexp_states.flat_map { |from, hash|
|
|
195
|
-
hash.map { |
|
|
203
|
+
hash.map { |r, to| [from, r.source.gsub("\\") { "\\\\" }, to] }
|
|
196
204
|
}
|
|
197
205
|
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
206
|
end
|
|
215
207
|
end
|
|
216
208
|
end
|
|
@@ -74,6 +74,7 @@ module ActionDispatch
|
|
|
74
74
|
def initialize(left)
|
|
75
75
|
@left = left
|
|
76
76
|
@memo = nil
|
|
77
|
+
@to_s = nil
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def each(&block)
|
|
@@ -81,7 +82,7 @@ module ActionDispatch
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def to_s
|
|
84
|
-
Visitors::String::INSTANCE.accept(self, "")
|
|
85
|
+
@to_s ||= Visitors::String::INSTANCE.accept(self, "".dup).freeze
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
def to_dot
|