actionpack 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (170) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +429 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +265 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +166 -0
  10. data/lib/abstract_controller/callbacks.rb +212 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +31 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +66 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +276 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +78 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +274 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +152 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  34. data/lib/action_controller/metal/exceptions.rb +53 -0
  35. data/lib/action_controller/metal/flash.rb +61 -0
  36. data/lib/action_controller/metal/force_ssl.rb +99 -0
  37. data/lib/action_controller/metal/head.rb +60 -0
  38. data/lib/action_controller/metal/helpers.rb +123 -0
  39. data/lib/action_controller/metal/http_authentication.rb +519 -0
  40. data/lib/action_controller/metal/implicit_render.rb +73 -0
  41. data/lib/action_controller/metal/instrumentation.rb +107 -0
  42. data/lib/action_controller/metal/live.rb +312 -0
  43. data/lib/action_controller/metal/mime_responds.rb +313 -0
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +293 -0
  46. data/lib/action_controller/metal/redirecting.rb +133 -0
  47. data/lib/action_controller/metal/renderers.rb +181 -0
  48. data/lib/action_controller/metal/rendering.rb +122 -0
  49. data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
  50. data/lib/action_controller/metal/rescue.rb +28 -0
  51. data/lib/action_controller/metal/streaming.rb +223 -0
  52. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  53. data/lib/action_controller/metal/testing.rb +16 -0
  54. data/lib/action_controller/metal/url_for.rb +58 -0
  55. data/lib/action_controller/railtie.rb +89 -0
  56. data/lib/action_controller/railties/helpers.rb +24 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +629 -0
  60. data/lib/action_dispatch.rb +112 -0
  61. data/lib/action_dispatch/http/cache.rb +222 -0
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +84 -0
  64. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  65. data/lib/action_dispatch/http/headers.rb +132 -0
  66. data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
  67. data/lib/action_dispatch/http/mime_type.rb +342 -0
  68. data/lib/action_dispatch/http/mime_types.rb +50 -0
  69. data/lib/action_dispatch/http/parameter_filter.rb +86 -0
  70. data/lib/action_dispatch/http/parameters.rb +126 -0
  71. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  72. data/lib/action_dispatch/http/request.rb +430 -0
  73. data/lib/action_dispatch/http/response.rb +519 -0
  74. data/lib/action_dispatch/http/upload.rb +84 -0
  75. data/lib/action_dispatch/http/url.rb +350 -0
  76. data/lib/action_dispatch/journey.rb +7 -0
  77. data/lib/action_dispatch/journey/formatter.rb +189 -0
  78. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  81. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  82. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  85. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  86. data/lib/action_dispatch/journey/parser.rb +199 -0
  87. data/lib/action_dispatch/journey/parser.y +50 -0
  88. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  89. data/lib/action_dispatch/journey/path/pattern.rb +198 -0
  90. data/lib/action_dispatch/journey/route.rb +203 -0
  91. data/lib/action_dispatch/journey/router.rb +156 -0
  92. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  93. data/lib/action_dispatch/journey/routes.rb +82 -0
  94. data/lib/action_dispatch/journey/scanner.rb +64 -0
  95. data/lib/action_dispatch/journey/visitors.rb +268 -0
  96. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  97. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  98. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  99. data/lib/action_dispatch/middleware/callbacks.rb +36 -0
  100. data/lib/action_dispatch/middleware/cookies.rb +685 -0
  101. data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
  102. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +300 -0
  106. data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
  107. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  108. data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
  109. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  110. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  111. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  112. data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
  113. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  114. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  115. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  116. data/lib/action_dispatch/middleware/stack.rb +116 -0
  117. data/lib/action_dispatch/middleware/static.rb +130 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  139. data/lib/action_dispatch/railtie.rb +55 -0
  140. data/lib/action_dispatch/request/session.rb +234 -0
  141. data/lib/action_dispatch/request/utils.rb +78 -0
  142. data/lib/action_dispatch/routing.rb +260 -0
  143. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  144. data/lib/action_dispatch/routing/inspector.rb +225 -0
  145. data/lib/action_dispatch/routing/mapper.rb +2267 -0
  146. data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
  147. data/lib/action_dispatch/routing/redirection.rb +201 -0
  148. data/lib/action_dispatch/routing/route_set.rb +890 -0
  149. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  150. data/lib/action_dispatch/routing/url_for.rb +236 -0
  151. data/lib/action_dispatch/system_test_case.rb +147 -0
  152. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  153. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  154. data/lib/action_dispatch/system_testing/server.rb +31 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  158. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  159. data/lib/action_dispatch/testing/assertions.rb +24 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +107 -0
  161. data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
  162. data/lib/action_dispatch/testing/integration.rb +652 -0
  163. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  164. data/lib/action_dispatch/testing/test_process.rb +50 -0
  165. data/lib/action_dispatch/testing/test_request.rb +71 -0
  166. data/lib/action_dispatch/testing/test_response.rb +53 -0
  167. data/lib/action_pack.rb +26 -0
  168. data/lib/action_pack/gem_version.rb +17 -0
  169. data/lib/action_pack/version.rb +10 -0
  170. metadata +318 -0
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/journey/router/utils"
4
+ require "action_dispatch/journey/routes"
5
+ require "action_dispatch/journey/formatter"
6
+
7
+ before = $-w
8
+ $-w = false
9
+ require "action_dispatch/journey/parser"
10
+ $-w = before
11
+
12
+ require "action_dispatch/journey/route"
13
+ require "action_dispatch/journey/path/pattern"
14
+
15
+ module ActionDispatch
16
+ module Journey # :nodoc:
17
+ class Router # :nodoc:
18
+ class RoutingError < ::StandardError # :nodoc:
19
+ end
20
+
21
+ attr_accessor :routes
22
+
23
+ def initialize(routes)
24
+ @routes = routes
25
+ end
26
+
27
+ def eager_load!
28
+ # Eagerly trigger the simulator's initialization so
29
+ # it doesn't happen during a request cycle.
30
+ simulator
31
+ nil
32
+ end
33
+
34
+ def serve(req)
35
+ find_routes(req).each do |match, parameters, route|
36
+ set_params = req.path_parameters
37
+ path_info = req.path_info
38
+ script_name = req.script_name
39
+
40
+ unless route.path.anchored
41
+ req.script_name = (script_name.to_s + match.to_s).chomp("/")
42
+ req.path_info = match.post_match
43
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
44
+ end
45
+
46
+ parameters = route.defaults.merge parameters.transform_values { |val|
47
+ val.dup.force_encoding(::Encoding::UTF_8)
48
+ }
49
+
50
+ req.path_parameters = set_params.merge parameters
51
+
52
+ status, headers, body = route.app.serve(req)
53
+
54
+ if "pass" == headers["X-Cascade"]
55
+ req.script_name = script_name
56
+ req.path_info = path_info
57
+ req.path_parameters = set_params
58
+ next
59
+ end
60
+
61
+ return [status, headers, body]
62
+ end
63
+
64
+ [404, { "X-Cascade" => "pass" }, ["Not Found"]]
65
+ end
66
+
67
+ def recognize(rails_req)
68
+ find_routes(rails_req).each do |match, parameters, route|
69
+ unless route.path.anchored
70
+ rails_req.script_name = match.to_s
71
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
72
+ end
73
+
74
+ parameters = route.defaults.merge parameters
75
+ yield(route, parameters)
76
+ end
77
+ end
78
+
79
+ def visualizer
80
+ tt = GTG::Builder.new(ast).transition_table
81
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
82
+ asts = groups.values.map(&:first)
83
+ tt.visualizer(asts)
84
+ end
85
+
86
+ private
87
+
88
+ def partitioned_routes
89
+ routes.partition { |r|
90
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
91
+ }
92
+ end
93
+
94
+ def ast
95
+ routes.ast
96
+ end
97
+
98
+ def simulator
99
+ routes.simulator
100
+ end
101
+
102
+ def custom_routes
103
+ routes.custom_routes
104
+ end
105
+
106
+ def filter_routes(path)
107
+ return [] unless ast
108
+ simulator.memos(path) { [] }
109
+ end
110
+
111
+ def find_routes(req)
112
+ routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
113
+ r.path.match(req.path_info)
114
+ }
115
+
116
+ routes =
117
+ if req.head?
118
+ match_head_routes(routes, req)
119
+ else
120
+ match_routes(routes, req)
121
+ end
122
+
123
+ routes.sort_by!(&:precedence)
124
+
125
+ routes.map! { |r|
126
+ match_data = r.path.match(req.path_info)
127
+ path_parameters = {}
128
+ match_data.names.zip(match_data.captures) { |name, val|
129
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
130
+ }
131
+ [match_data, path_parameters, r]
132
+ }
133
+ end
134
+
135
+ def match_head_routes(routes, req)
136
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
137
+ head_routes = match_routes(verb_specific_routes, req)
138
+
139
+ if head_routes.empty?
140
+ begin
141
+ req.request_method = "GET"
142
+ match_routes(routes, req)
143
+ ensure
144
+ req.request_method = "HEAD"
145
+ end
146
+ else
147
+ head_routes
148
+ end
149
+ end
150
+
151
+ def match_routes(routes, req)
152
+ routes.select { |r| r.matches?(req) }
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Journey # :nodoc:
5
+ class Router # :nodoc:
6
+ class Utils # :nodoc:
7
+ # Normalizes URI path.
8
+ #
9
+ # Strips off trailing slash and ensures there is a leading slash.
10
+ # Also converts downcase URL encoded string to uppercase.
11
+ #
12
+ # normalize_path("/foo") # => "/foo"
13
+ # normalize_path("/foo/") # => "/foo"
14
+ # normalize_path("foo") # => "/foo"
15
+ # normalize_path("") # => "/"
16
+ # normalize_path("/%ab") # => "/%AB"
17
+ def self.normalize_path(path)
18
+ path ||= ""
19
+ encoding = path.encoding
20
+ path = "/#{path}".dup
21
+ path.squeeze!("/".freeze)
22
+ path.sub!(%r{/+\Z}, "".freeze)
23
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
24
+ path = "/".dup if path == "".freeze
25
+ path.force_encoding(encoding)
26
+ path
27
+ end
28
+
29
+ # URI path and fragment escaping
30
+ # https://tools.ietf.org/html/rfc3986
31
+ class UriEncoder # :nodoc:
32
+ ENCODE = "%%%02X".freeze
33
+ US_ASCII = Encoding::US_ASCII
34
+ UTF_8 = Encoding::UTF_8
35
+ EMPTY = "".dup.force_encoding(US_ASCII).freeze
36
+ DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
37
+
38
+ ALPHA = "a-zA-Z".freeze
39
+ DIGIT = "0-9".freeze
40
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
41
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
42
+
43
+ ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
44
+
45
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
46
+ SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
47
+ PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
48
+
49
+ def escape_fragment(fragment)
50
+ escape(fragment, FRAGMENT)
51
+ end
52
+
53
+ def escape_path(path)
54
+ escape(path, PATH)
55
+ end
56
+
57
+ def escape_segment(segment)
58
+ escape(segment, SEGMENT)
59
+ end
60
+
61
+ def unescape_uri(uri)
62
+ encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
63
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
64
+ end
65
+
66
+ private
67
+ def escape(component, pattern)
68
+ component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
69
+ end
70
+
71
+ def percent_encode(unsafe)
72
+ safe = EMPTY.dup
73
+ unsafe.each_byte { |b| safe << DEC2HEX[b] }
74
+ safe
75
+ end
76
+ end
77
+
78
+ ENCODER = UriEncoder.new
79
+
80
+ def self.escape_path(path)
81
+ ENCODER.escape_path(path.to_s)
82
+ end
83
+
84
+ def self.escape_segment(segment)
85
+ ENCODER.escape_segment(segment.to_s)
86
+ end
87
+
88
+ def self.escape_fragment(fragment)
89
+ ENCODER.escape_fragment(fragment.to_s)
90
+ end
91
+
92
+ # Replaces any escaped sequences with their unescaped representations.
93
+ #
94
+ # uri = "/topics?title=Ruby%20on%20Rails"
95
+ # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
96
+ def self.unescape_uri(uri)
97
+ ENCODER.unescape_uri(uri)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Journey # :nodoc:
5
+ # The Routing table. Contains all routes for a system. Routes can be
6
+ # added to the table by calling Routes#add_route.
7
+ class Routes # :nodoc:
8
+ include Enumerable
9
+
10
+ attr_reader :routes, :custom_routes, :anchored_routes
11
+
12
+ def initialize
13
+ @routes = []
14
+ @ast = nil
15
+ @anchored_routes = []
16
+ @custom_routes = []
17
+ @simulator = nil
18
+ end
19
+
20
+ def empty?
21
+ routes.empty?
22
+ end
23
+
24
+ def length
25
+ routes.length
26
+ end
27
+ alias :size :length
28
+
29
+ def last
30
+ routes.last
31
+ end
32
+
33
+ def each(&block)
34
+ routes.each(&block)
35
+ end
36
+
37
+ def clear
38
+ routes.clear
39
+ anchored_routes.clear
40
+ custom_routes.clear
41
+ end
42
+
43
+ def partition_route(route)
44
+ if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
45
+ anchored_routes << route
46
+ else
47
+ custom_routes << route
48
+ end
49
+ end
50
+
51
+ def ast
52
+ @ast ||= begin
53
+ asts = anchored_routes.map(&:ast)
54
+ Nodes::Or.new(asts)
55
+ end
56
+ end
57
+
58
+ def simulator
59
+ return if ast.nil?
60
+ @simulator ||= begin
61
+ gtg = GTG::Builder.new(ast).transition_table
62
+ GTG::Simulator.new(gtg)
63
+ end
64
+ end
65
+
66
+ def add_route(name, mapping)
67
+ route = mapping.make_route name, routes.length
68
+ routes << route
69
+ partition_route(route)
70
+ clear_cache!
71
+ route
72
+ end
73
+
74
+ private
75
+
76
+ def clear_cache!
77
+ @ast = nil
78
+ @simulator = nil
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module ActionDispatch
6
+ module Journey # :nodoc:
7
+ class Scanner # :nodoc:
8
+ def initialize
9
+ @ss = nil
10
+ end
11
+
12
+ def scan_setup(str)
13
+ @ss = StringScanner.new(str)
14
+ end
15
+
16
+ def eos?
17
+ @ss.eos?
18
+ end
19
+
20
+ def pos
21
+ @ss.pos
22
+ end
23
+
24
+ def pre_match
25
+ @ss.pre_match
26
+ end
27
+
28
+ def next_token
29
+ return if @ss.eos?
30
+
31
+ until token = scan || @ss.eos?; end
32
+ token
33
+ end
34
+
35
+ private
36
+
37
+ def scan
38
+ case
39
+ # /
40
+ when @ss.skip(/\//)
41
+ [:SLASH, "/"]
42
+ when @ss.skip(/\(/)
43
+ [:LPAREN, "("]
44
+ when @ss.skip(/\)/)
45
+ [:RPAREN, ")"]
46
+ when @ss.skip(/\|/)
47
+ [:OR, "|"]
48
+ when @ss.skip(/\./)
49
+ [:DOT, "."]
50
+ when text = @ss.scan(/:\w+/)
51
+ [:SYMBOL, text]
52
+ when text = @ss.scan(/\*\w+/)
53
+ [:STAR, text]
54
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
55
+ text.tr! "\\", ""
56
+ [:LITERAL, text]
57
+ # any char
58
+ when text = @ss.scan(/./)
59
+ [:LITERAL, text]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # :stopdoc:
5
+ module Journey
6
+ class Format
7
+ ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
8
+ ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
9
+
10
+ Parameter = Struct.new(:name, :escaper) do
11
+ def escape(value); escaper.call value; end
12
+ end
13
+
14
+ def self.required_path(symbol)
15
+ Parameter.new symbol, ESCAPE_PATH
16
+ end
17
+
18
+ def self.required_segment(symbol)
19
+ Parameter.new symbol, ESCAPE_SEGMENT
20
+ end
21
+
22
+ def initialize(parts)
23
+ @parts = parts
24
+ @children = []
25
+ @parameters = []
26
+
27
+ parts.each_with_index do |object, i|
28
+ case object
29
+ when Journey::Format
30
+ @children << i
31
+ when Parameter
32
+ @parameters << i
33
+ end
34
+ end
35
+ end
36
+
37
+ def evaluate(hash)
38
+ parts = @parts.dup
39
+
40
+ @parameters.each do |index|
41
+ param = parts[index]
42
+ value = hash[param.name]
43
+ return "".freeze unless value
44
+ parts[index] = param.escape value
45
+ end
46
+
47
+ @children.each { |index| parts[index] = parts[index].evaluate(hash) }
48
+
49
+ parts.join
50
+ end
51
+ end
52
+
53
+ module Visitors # :nodoc:
54
+ class Visitor # :nodoc:
55
+ DISPATCH_CACHE = {}
56
+
57
+ def accept(node)
58
+ visit(node)
59
+ end
60
+
61
+ private
62
+
63
+ def visit(node)
64
+ send(DISPATCH_CACHE[node.type], node)
65
+ end
66
+
67
+ def binary(node)
68
+ visit(node.left)
69
+ visit(node.right)
70
+ end
71
+ def visit_CAT(n); binary(n); end
72
+
73
+ def nary(node)
74
+ node.children.each { |c| visit(c) }
75
+ end
76
+ def visit_OR(n); nary(n); end
77
+
78
+ def unary(node)
79
+ visit(node.left)
80
+ end
81
+ def visit_GROUP(n); unary(n); end
82
+ def visit_STAR(n); unary(n); end
83
+
84
+ def terminal(node); end
85
+ def visit_LITERAL(n); terminal(n); end
86
+ def visit_SYMBOL(n); terminal(n); end
87
+ def visit_SLASH(n); terminal(n); end
88
+ def visit_DOT(n); terminal(n); end
89
+
90
+ private_instance_methods(false).each do |pim|
91
+ next unless pim =~ /^visit_(.*)$/
92
+ DISPATCH_CACHE[$1.to_sym] = pim
93
+ end
94
+ end
95
+
96
+ class FunctionalVisitor # :nodoc:
97
+ DISPATCH_CACHE = {}
98
+
99
+ def accept(node, seed)
100
+ visit(node, seed)
101
+ end
102
+
103
+ def visit(node, seed)
104
+ send(DISPATCH_CACHE[node.type], node, seed)
105
+ end
106
+
107
+ def binary(node, seed)
108
+ visit(node.right, visit(node.left, seed))
109
+ end
110
+ def visit_CAT(n, seed); binary(n, seed); end
111
+
112
+ def nary(node, seed)
113
+ node.children.inject(seed) { |s, c| visit(c, s) }
114
+ end
115
+ def visit_OR(n, seed); nary(n, seed); end
116
+
117
+ def unary(node, seed)
118
+ visit(node.left, seed)
119
+ end
120
+ def visit_GROUP(n, seed); unary(n, seed); end
121
+ def visit_STAR(n, seed); unary(n, seed); end
122
+
123
+ def terminal(node, seed); seed; end
124
+ def visit_LITERAL(n, seed); terminal(n, seed); end
125
+ def visit_SYMBOL(n, seed); terminal(n, seed); end
126
+ def visit_SLASH(n, seed); terminal(n, seed); end
127
+ def visit_DOT(n, seed); terminal(n, seed); end
128
+
129
+ instance_methods(false).each do |pim|
130
+ next unless pim =~ /^visit_(.*)$/
131
+ DISPATCH_CACHE[$1.to_sym] = pim
132
+ end
133
+ end
134
+
135
+ class FormatBuilder < Visitor # :nodoc:
136
+ def accept(node); Journey::Format.new(super); end
137
+ def terminal(node); [node.left]; end
138
+
139
+ def binary(node)
140
+ visit(node.left) + visit(node.right)
141
+ end
142
+
143
+ def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
144
+
145
+ def visit_STAR(n)
146
+ [Journey::Format.required_path(n.left.to_sym)]
147
+ end
148
+
149
+ def visit_SYMBOL(n)
150
+ symbol = n.to_sym
151
+ if symbol == :controller
152
+ [Journey::Format.required_path(symbol)]
153
+ else
154
+ [Journey::Format.required_segment(symbol)]
155
+ end
156
+ end
157
+ end
158
+
159
+ # Loop through the requirements AST.
160
+ class Each < FunctionalVisitor # :nodoc:
161
+ def visit(node, block)
162
+ block.call(node)
163
+ super
164
+ end
165
+
166
+ INSTANCE = new
167
+ end
168
+
169
+ class String < FunctionalVisitor # :nodoc:
170
+ private
171
+
172
+ def binary(node, seed)
173
+ visit(node.right, visit(node.left, seed))
174
+ end
175
+
176
+ def nary(node, seed)
177
+ last_child = node.children.last
178
+ node.children.inject(seed) { |s, c|
179
+ string = visit(c, s)
180
+ string << "|" unless last_child == c
181
+ string
182
+ }
183
+ end
184
+
185
+ def terminal(node, seed)
186
+ seed + node.left
187
+ end
188
+
189
+ def visit_GROUP(node, seed)
190
+ visit(node.left, seed.dup << "(") << ")"
191
+ end
192
+
193
+ INSTANCE = new
194
+ end
195
+
196
+ class Dot < FunctionalVisitor # :nodoc:
197
+ def initialize
198
+ @nodes = []
199
+ @edges = []
200
+ end
201
+
202
+ def accept(node, seed = [[], []])
203
+ super
204
+ nodes, edges = seed
205
+ <<-eodot
206
+ digraph parse_tree {
207
+ size="8,5"
208
+ node [shape = none];
209
+ edge [dir = none];
210
+ #{nodes.join "\n"}
211
+ #{edges.join("\n")}
212
+ }
213
+ eodot
214
+ end
215
+
216
+ private
217
+
218
+ def binary(node, seed)
219
+ seed.last.concat node.children.map { |c|
220
+ "#{node.object_id} -> #{c.object_id};"
221
+ }
222
+ super
223
+ end
224
+
225
+ def nary(node, seed)
226
+ seed.last.concat node.children.map { |c|
227
+ "#{node.object_id} -> #{c.object_id};"
228
+ }
229
+ super
230
+ end
231
+
232
+ def unary(node, seed)
233
+ seed.last << "#{node.object_id} -> #{node.left.object_id};"
234
+ super
235
+ end
236
+
237
+ def visit_GROUP(node, seed)
238
+ seed.first << "#{node.object_id} [label=\"()\"];"
239
+ super
240
+ end
241
+
242
+ def visit_CAT(node, seed)
243
+ seed.first << "#{node.object_id} [label=\"○\"];"
244
+ super
245
+ end
246
+
247
+ def visit_STAR(node, seed)
248
+ seed.first << "#{node.object_id} [label=\"*\"];"
249
+ super
250
+ end
251
+
252
+ def visit_OR(node, seed)
253
+ seed.first << "#{node.object_id} [label=\"|\"];"
254
+ super
255
+ end
256
+
257
+ def terminal(node, seed)
258
+ value = node.left
259
+
260
+ seed.first << "#{node.object_id} [label=\"#{value}\"];"
261
+ seed
262
+ end
263
+ INSTANCE = new
264
+ end
265
+ end
266
+ end
267
+ # :startdoc:
268
+ end