actionpack 4.2.11.3 → 5.0.0.beta1

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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +379 -462
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller.rb +0 -2
  6. data/lib/abstract_controller/base.rb +17 -32
  7. data/lib/abstract_controller/callbacks.rb +52 -19
  8. data/lib/abstract_controller/collector.rb +4 -9
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  11. data/lib/abstract_controller/rendering.rb +27 -22
  12. data/lib/abstract_controller/translation.rb +8 -7
  13. data/lib/action_controller.rb +4 -3
  14. data/lib/action_controller/api.rb +146 -0
  15. data/lib/action_controller/base.rb +6 -10
  16. data/lib/action_controller/caching.rb +1 -3
  17. data/lib/action_controller/caching/fragments.rb +48 -3
  18. data/lib/action_controller/form_builder.rb +48 -0
  19. data/lib/action_controller/log_subscriber.rb +1 -10
  20. data/lib/action_controller/metal.rb +89 -62
  21. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  22. data/lib/action_controller/metal/conditional_get.rb +65 -24
  23. data/lib/action_controller/metal/cookies.rb +0 -2
  24. data/lib/action_controller/metal/data_streaming.rb +2 -22
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  26. data/lib/action_controller/metal/exceptions.rb +11 -6
  27. data/lib/action_controller/metal/force_ssl.rb +6 -6
  28. data/lib/action_controller/metal/head.rb +14 -7
  29. data/lib/action_controller/metal/helpers.rb +9 -5
  30. data/lib/action_controller/metal/http_authentication.rb +37 -38
  31. data/lib/action_controller/metal/implicit_render.rb +23 -6
  32. data/lib/action_controller/metal/instrumentation.rb +0 -1
  33. data/lib/action_controller/metal/live.rb +17 -55
  34. data/lib/action_controller/metal/mime_responds.rb +17 -37
  35. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  36. data/lib/action_controller/metal/redirecting.rb +32 -9
  37. data/lib/action_controller/metal/renderers.rb +10 -8
  38. data/lib/action_controller/metal/rendering.rb +38 -6
  39. data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
  40. data/lib/action_controller/metal/rescue.rb +2 -4
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +231 -78
  43. data/lib/action_controller/metal/testing.rb +1 -12
  44. data/lib/action_controller/metal/url_for.rb +12 -5
  45. data/lib/action_controller/renderer.rb +111 -0
  46. data/lib/action_controller/template_assertions.rb +9 -0
  47. data/lib/action_controller/test_case.rb +267 -363
  48. data/lib/action_dispatch.rb +2 -1
  49. data/lib/action_dispatch/http/cache.rb +23 -26
  50. data/lib/action_dispatch/http/filter_parameters.rb +6 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  52. data/lib/action_dispatch/http/headers.rb +28 -11
  53. data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
  54. data/lib/action_dispatch/http/mime_type.rb +92 -61
  55. data/lib/action_dispatch/http/mime_types.rb +1 -4
  56. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  57. data/lib/action_dispatch/http/parameters.rb +45 -41
  58. data/lib/action_dispatch/http/request.rb +146 -82
  59. data/lib/action_dispatch/http/response.rb +180 -99
  60. data/lib/action_dispatch/http/url.rb +117 -8
  61. data/lib/action_dispatch/journey/formatter.rb +34 -28
  62. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  64. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  65. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  66. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  67. data/lib/action_dispatch/journey/path/pattern.rb +37 -41
  68. data/lib/action_dispatch/journey/route.rb +71 -17
  69. data/lib/action_dispatch/journey/router.rb +5 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  71. data/lib/action_dispatch/journey/routes.rb +14 -15
  72. data/lib/action_dispatch/journey/visitors.rb +86 -43
  73. data/lib/action_dispatch/middleware/cookies.rb +184 -135
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
  75. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
  76. data/lib/action_dispatch/middleware/flash.rb +61 -45
  77. data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
  78. data/lib/action_dispatch/middleware/params_parser.rb +30 -46
  79. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  80. data/lib/action_dispatch/middleware/reloader.rb +2 -4
  81. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  82. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  83. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  84. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  85. data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
  86. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  87. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  88. data/lib/action_dispatch/middleware/ssl.rb +93 -36
  89. data/lib/action_dispatch/middleware/stack.rb +43 -48
  90. data/lib/action_dispatch/middleware/static.rb +52 -40
  91. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  92. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  98. data/lib/action_dispatch/railtie.rb +0 -2
  99. data/lib/action_dispatch/request/session.rb +66 -34
  100. data/lib/action_dispatch/request/utils.rb +51 -19
  101. data/lib/action_dispatch/routing.rb +3 -8
  102. data/lib/action_dispatch/routing/inspector.rb +6 -30
  103. data/lib/action_dispatch/routing/mapper.rb +447 -322
  104. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  105. data/lib/action_dispatch/routing/redirection.rb +3 -3
  106. data/lib/action_dispatch/routing/route_set.rb +124 -227
  107. data/lib/action_dispatch/routing/url_for.rb +27 -10
  108. data/lib/action_dispatch/testing/assertions.rb +1 -1
  109. data/lib/action_dispatch/testing/assertions/response.rb +27 -9
  110. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  111. data/lib/action_dispatch/testing/integration.rb +237 -76
  112. data/lib/action_dispatch/testing/test_process.rb +5 -5
  113. data/lib/action_dispatch/testing/test_request.rb +12 -21
  114. data/lib/action_dispatch/testing/test_response.rb +1 -4
  115. data/lib/action_pack.rb +1 -1
  116. data/lib/action_pack/gem_version.rb +4 -4
  117. metadata +26 -25
  118. data/lib/action_controller/metal/hide_actions.rb +0 -40
  119. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  120. data/lib/action_controller/middleware.rb +0 -39
  121. data/lib/action_controller/model_naming.rb +0 -12
  122. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  123. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  124. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  125. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -6,6 +6,10 @@ module ActionDispatch
6
6
  class Parser < Racc::Parser # :nodoc:
7
7
  include Journey::Nodes
8
8
 
9
+ def self.parse(string)
10
+ new.parse string
11
+ end
12
+
9
13
  def initialize
10
14
  @scanner = Scanner.new
11
15
  end
@@ -1,5 +1,3 @@
1
- require 'action_dispatch/journey/router/strexp'
2
-
3
1
  module ActionDispatch
4
2
  module Journey # :nodoc:
5
3
  module Path # :nodoc:
@@ -7,14 +5,20 @@ module ActionDispatch
7
5
  attr_reader :spec, :requirements, :anchored
8
6
 
9
7
  def self.from_string string
10
- new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
8
+ build(string, {}, "/.?", true)
9
+ end
10
+
11
+ def self.build(path, requirements, separators, anchored)
12
+ parser = Journey::Parser.new
13
+ ast = parser.parse path
14
+ new ast, requirements, separators, anchored
11
15
  end
12
16
 
13
- def initialize(strexp)
14
- @spec = strexp.ast
15
- @requirements = strexp.requirements
16
- @separators = strexp.separators.join
17
- @anchored = strexp.anchor
17
+ def initialize(ast, requirements, separators, anchored)
18
+ @spec = ast
19
+ @requirements = requirements
20
+ @separators = separators
21
+ @anchored = anchored
18
22
 
19
23
  @names = nil
20
24
  @optional_names = nil
@@ -28,12 +32,12 @@ module ActionDispatch
28
32
  end
29
33
 
30
34
  def ast
31
- @spec.grep(Nodes::Symbol).each do |node|
35
+ @spec.find_all(&:symbol?).each do |node|
32
36
  re = @requirements[node.to_sym]
33
37
  node.regexp = re if re
34
38
  end
35
39
 
36
- @spec.grep(Nodes::Star).each do |node|
40
+ @spec.find_all(&:star?).each do |node|
37
41
  node = node.left
38
42
  node.regexp = @requirements[node.to_sym] || /(.+)/
39
43
  end
@@ -42,7 +46,7 @@ module ActionDispatch
42
46
  end
43
47
 
44
48
  def names
45
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
49
+ @names ||= spec.find_all(&:symbol?).map(&:name)
46
50
  end
47
51
 
48
52
  def required_names
@@ -50,34 +54,9 @@ module ActionDispatch
50
54
  end
51
55
 
52
56
  def optional_names
53
- @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
54
- group.grep(Nodes::Symbol)
55
- }.map { |n| n.name }.uniq
56
- end
57
-
58
- class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
59
- attr_reader :offsets
60
-
61
- def initialize(matchers)
62
- @matchers = matchers
63
- @capture_count = [0]
64
- end
65
-
66
- def visit(node)
67
- super
68
- @capture_count
69
- end
70
-
71
- def visit_SYMBOL(node)
72
- node = node.to_sym
73
-
74
- if @matchers.key?(node)
75
- re = /#{@matchers[node]}|/
76
- @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
77
- else
78
- @capture_count << (@capture_count.last || 0)
79
- end
80
- end
57
+ @optional_names ||= spec.find_all(&:group?).flat_map { |group|
58
+ group.find_all(&:symbol?)
59
+ }.map(&:name).uniq
81
60
  end
82
61
 
83
62
  class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
@@ -122,6 +101,11 @@ module ActionDispatch
122
101
  re = @matchers[node.left.to_sym] || '.+'
123
102
  "(#{re})"
124
103
  end
104
+
105
+ def visit_OR(node)
106
+ children = node.children.map { |n| visit n }
107
+ "(?:#{children.join(?|)})"
108
+ end
125
109
  end
126
110
 
127
111
  class UnanchoredRegexp < AnchoredRegexp # :nodoc:
@@ -184,8 +168,20 @@ module ActionDispatch
184
168
  def offsets
185
169
  return @offsets if @offsets
186
170
 
187
- viz = RegexpOffsets.new(@requirements)
188
- @offsets = viz.accept(spec)
171
+ @offsets = [0]
172
+
173
+ spec.find_all(&:symbol?).each do |node|
174
+ node = node.to_sym
175
+
176
+ if @requirements.key?(node)
177
+ re = /#{@requirements[node]}|/
178
+ @offsets.push((re.match('').length - 1) + @offsets.last)
179
+ else
180
+ @offsets << @offsets.last
181
+ end
182
+ end
183
+
184
+ @offsets
189
185
  end
190
186
  end
191
187
  end
@@ -1,35 +1,81 @@
1
1
  module ActionDispatch
2
2
  module Journey # :nodoc:
3
3
  class Route # :nodoc:
4
- attr_reader :app, :path, :defaults, :name
4
+ attr_reader :app, :path, :defaults, :name, :precedence
5
5
 
6
6
  attr_reader :constraints
7
7
  alias :conditions :constraints
8
8
 
9
- attr_accessor :precedence
9
+ module VerbMatchers
10
+ VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
11
+ VERBS.each do |v|
12
+ class_eval <<-eoc
13
+ class #{v}
14
+ def self.verb; name.split("::").last; end
15
+ def self.call(req); req.#{v.downcase}?; end
16
+ end
17
+ eoc
18
+ end
19
+
20
+ class Unknown
21
+ attr_reader :verb
22
+
23
+ def initialize(verb)
24
+ @verb = verb
25
+ end
26
+
27
+ def call(request); @verb === request.request_method; end
28
+ end
29
+
30
+ class All
31
+ def self.call(_); true; end
32
+ def self.verb; ''; end
33
+ end
34
+
35
+ VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
36
+ klass = const_get verb
37
+ hash[verb] = klass
38
+ hash[verb.downcase] = klass
39
+ hash[verb.downcase.to_sym] = klass
40
+ end
41
+
42
+ end
43
+
44
+ def self.verb_matcher(verb)
45
+ VerbMatchers::VERB_TO_CLASS.fetch(verb) do
46
+ VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
47
+ end
48
+ end
49
+
50
+ def self.build(name, app, path, constraints, required_defaults, defaults)
51
+ request_method_match = verb_matcher(constraints.delete(:request_method))
52
+ new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
53
+ end
10
54
 
11
55
  ##
12
56
  # +path+ is a path constraint.
13
57
  # +constraints+ is a hash of constraints to be applied to this route.
14
- def initialize(name, app, path, constraints, defaults = {})
58
+ def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence)
15
59
  @name = name
16
60
  @app = app
17
61
  @path = path
18
62
 
63
+ @request_method_match = request_method_match
19
64
  @constraints = constraints
20
65
  @defaults = defaults
21
66
  @required_defaults = nil
67
+ @_required_defaults = required_defaults
22
68
  @required_parts = nil
23
69
  @parts = nil
24
70
  @decorated_ast = nil
25
- @precedence = 0
71
+ @precedence = precedence
26
72
  @path_formatter = @path.build_formatter
27
73
  end
28
74
 
29
75
  def ast
30
76
  @decorated_ast ||= begin
31
77
  decorated_ast = path.ast
32
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
78
+ decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
33
79
  decorated_ast
34
80
  end
35
81
  end
@@ -60,7 +106,7 @@ module ActionDispatch
60
106
  end
61
107
 
62
108
  def parts
63
- @parts ||= segments.map { |n| n.to_sym }
109
+ @parts ||= segments.map(&:to_sym)
64
110
  end
65
111
  alias :segment_keys :parts
66
112
 
@@ -68,16 +114,12 @@ module ActionDispatch
68
114
  @path_formatter.evaluate path_options
69
115
  end
70
116
 
71
- def optional_parts
72
- path.optional_names.map { |n| n.to_sym }
73
- end
74
-
75
117
  def required_parts
76
- @required_parts ||= path.required_names.map { |n| n.to_sym }
118
+ @required_parts ||= path.required_names.map(&:to_sym)
77
119
  end
78
120
 
79
121
  def required_default?(key)
80
- (constraints[:required_defaults] || []).include?(key)
122
+ @_required_defaults.include?(key)
81
123
  end
82
124
 
83
125
  def required_defaults
@@ -95,9 +137,8 @@ module ActionDispatch
95
137
  end
96
138
 
97
139
  def matches?(request)
98
- constraints.all? do |method, value|
99
- next true unless request.respond_to?(method)
100
-
140
+ match_verb(request) &&
141
+ constraints.all? { |method, value|
101
142
  case value
102
143
  when Regexp, String
103
144
  value === request.send(method).to_s
@@ -110,15 +151,28 @@ module ActionDispatch
110
151
  else
111
152
  value === request.send(method)
112
153
  end
113
- end
154
+ }
114
155
  end
115
156
 
116
157
  def ip
117
158
  constraints[:ip] || //
118
159
  end
119
160
 
161
+ def requires_matching_verb?
162
+ !@request_method_match.all? { |x| x == VerbMatchers::All }
163
+ end
164
+
120
165
  def verb
121
- constraints[:request_method] || //
166
+ verbs.join('|')
167
+ end
168
+
169
+ private
170
+ def verbs
171
+ @request_method_match.map(&:verb)
172
+ end
173
+
174
+ def match_verb(request)
175
+ @request_method_match.any? { |m| m.call request }
122
176
  end
123
177
  end
124
178
  end
@@ -1,5 +1,4 @@
1
1
  require 'action_dispatch/journey/router/utils'
2
- require 'action_dispatch/journey/router/strexp'
3
2
  require 'action_dispatch/journey/routes'
4
3
  require 'action_dispatch/journey/formatter'
5
4
 
@@ -68,8 +67,8 @@ module ActionDispatch
68
67
 
69
68
  def visualizer
70
69
  tt = GTG::Builder.new(ast).transition_table
71
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
72
- asts = groups.values.map { |v| v.first }
70
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
71
+ asts = groups.values.map(&:first)
73
72
  tt.visualizer(asts)
74
73
  end
75
74
 
@@ -88,7 +87,7 @@ module ActionDispatch
88
87
  end
89
88
 
90
89
  def custom_routes
91
- partitioned_routes.last
90
+ routes.custom_routes
92
91
  end
93
92
 
94
93
  def filter_routes(path)
@@ -102,7 +101,7 @@ module ActionDispatch
102
101
  }
103
102
 
104
103
  routes =
105
- if req.request_method == "HEAD"
104
+ if req.head?
106
105
  match_head_routes(routes, req)
107
106
  else
108
107
  match_routes(routes, req)
@@ -121,7 +120,7 @@ module ActionDispatch
121
120
  end
122
121
 
123
122
  def match_head_routes(routes, req)
124
- verb_specific_routes = routes.reject { |route| route.verb == // }
123
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
125
124
  head_routes = match_routes(verb_specific_routes, req)
126
125
 
127
126
  if head_routes.empty?
@@ -13,11 +13,11 @@ module ActionDispatch
13
13
  # normalize_path("") # => "/"
14
14
  # normalize_path("/%ab") # => "/%AB"
15
15
  def self.normalize_path(path)
16
- path = "/#{path}".force_encoding(Encoding::UTF_8)
17
- path.squeeze!('/')
18
- path.sub!(%r{/+\Z}, '')
16
+ path = "/#{path}"
17
+ path.squeeze!('/'.freeze)
18
+ path.sub!(%r{/+\Z}, ''.freeze)
19
19
  path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
20
- path = '/' if path == ''
20
+ path = '/' if path == ''.freeze
21
21
  path
22
22
  end
23
23
 
@@ -55,7 +55,7 @@ module ActionDispatch
55
55
 
56
56
  def unescape_uri(uri)
57
57
  encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
58
- uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
58
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding)
59
59
  end
60
60
 
61
61
  protected
@@ -5,13 +5,13 @@ module ActionDispatch
5
5
  class Routes # :nodoc:
6
6
  include Enumerable
7
7
 
8
- attr_reader :routes, :named_routes
8
+ attr_reader :routes, :custom_routes, :anchored_routes
9
9
 
10
10
  def initialize
11
11
  @routes = []
12
- @named_routes = {}
13
12
  @ast = nil
14
- @partitioned_routes = nil
13
+ @anchored_routes = []
14
+ @custom_routes = []
15
15
  @simulator = nil
16
16
  end
17
17
 
@@ -34,18 +34,21 @@ module ActionDispatch
34
34
 
35
35
  def clear
36
36
  routes.clear
37
- named_routes.clear
37
+ anchored_routes.clear
38
+ custom_routes.clear
38
39
  end
39
40
 
40
- def partitioned_routes
41
- @partitioned_routes ||= routes.partition do |r|
42
- r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
41
+ def partition_route(route)
42
+ if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
43
+ anchored_routes << route
44
+ else
45
+ custom_routes << route
43
46
  end
44
47
  end
45
48
 
46
49
  def ast
47
50
  @ast ||= begin
48
- asts = partitioned_routes.first.map(&:ast)
51
+ asts = anchored_routes.map(&:ast)
49
52
  Nodes::Or.new(asts) unless asts.empty?
50
53
  end
51
54
  end
@@ -57,13 +60,10 @@ module ActionDispatch
57
60
  end
58
61
  end
59
62
 
60
- # Add a route to the routing table.
61
- def add_route(app, path, conditions, defaults, name = nil)
62
- route = Route.new(name, app, path, conditions, defaults)
63
-
64
- route.precedence = routes.length
63
+ def add_route(name, mapping)
64
+ route = mapping.make_route name, routes.length
65
65
  routes << route
66
- named_routes[name] = route if name && !named_routes[name]
66
+ partition_route(route)
67
67
  clear_cache!
68
68
  route
69
69
  end
@@ -72,7 +72,6 @@ module ActionDispatch
72
72
 
73
73
  def clear_cache!
74
74
  @ast = nil
75
- @partitioned_routes = nil
76
75
  @simulator = nil
77
76
  end
78
77
  end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module ActionDispatch
4
2
  module Journey # :nodoc:
5
3
  class Format
@@ -92,6 +90,45 @@ module ActionDispatch
92
90
  end
93
91
  end
94
92
 
93
+ class FunctionalVisitor # :nodoc:
94
+ DISPATCH_CACHE = {}
95
+
96
+ def accept(node, seed)
97
+ visit(node, seed)
98
+ end
99
+
100
+ def visit node, seed
101
+ send(DISPATCH_CACHE[node.type], node, seed)
102
+ end
103
+
104
+ def binary(node, seed)
105
+ visit(node.right, visit(node.left, seed))
106
+ end
107
+ def visit_CAT(n, seed); binary(n, seed); end
108
+
109
+ def nary(node, seed)
110
+ node.children.inject(seed) { |s, c| visit(c, s) }
111
+ end
112
+ def visit_OR(n, seed); nary(n, seed); end
113
+
114
+ def unary(node, seed)
115
+ visit(node.left, seed)
116
+ end
117
+ def visit_GROUP(n, seed); unary(n, seed); end
118
+ def visit_STAR(n, seed); unary(n, seed); end
119
+
120
+ def terminal(node, seed); seed; end
121
+ def visit_LITERAL(n, seed); terminal(n, seed); end
122
+ def visit_SYMBOL(n, seed); terminal(n, seed); end
123
+ def visit_SLASH(n, seed); terminal(n, seed); end
124
+ def visit_DOT(n, seed); terminal(n, seed); end
125
+
126
+ instance_methods(false).each do |pim|
127
+ next unless pim =~ /^visit_(.*)$/
128
+ DISPATCH_CACHE[$1.to_sym] = pim
129
+ end
130
+ end
131
+
95
132
  class FormatBuilder < Visitor # :nodoc:
96
133
  def accept(node); Journey::Format.new(super); end
97
134
  def terminal(node); [node.left]; end
@@ -117,104 +154,110 @@ module ActionDispatch
117
154
  end
118
155
 
119
156
  # Loop through the requirements AST
120
- class Each < Visitor # :nodoc:
121
- attr_reader :block
122
-
123
- def initialize(block)
124
- @block = block
125
- end
126
-
127
- def visit(node)
157
+ class Each < FunctionalVisitor # :nodoc:
158
+ def visit(node, block)
128
159
  block.call(node)
129
160
  super
130
161
  end
162
+
163
+ INSTANCE = new
131
164
  end
132
165
 
133
- class String < Visitor # :nodoc:
166
+ class String < FunctionalVisitor # :nodoc:
134
167
  private
135
168
 
136
- def binary(node)
137
- [visit(node.left), visit(node.right)].join
169
+ def binary(node, seed)
170
+ visit(node.right, visit(node.left, seed))
138
171
  end
139
172
 
140
- def nary(node)
141
- node.children.map { |c| visit(c) }.join '|'
173
+ def nary(node, seed)
174
+ last_child = node.children.last
175
+ node.children.inject(seed) { |s, c|
176
+ string = visit(c, s)
177
+ string << "|".freeze unless last_child == c
178
+ string
179
+ }
142
180
  end
143
181
 
144
- def terminal(node)
145
- node.left
182
+ def terminal(node, seed)
183
+ seed + node.left
146
184
  end
147
185
 
148
- def visit_GROUP(node)
149
- "(#{visit(node.left)})"
186
+ def visit_GROUP(node, seed)
187
+ visit(node.left, seed << "(".freeze) << ")".freeze
150
188
  end
189
+
190
+ INSTANCE = new
151
191
  end
152
192
 
153
- class Dot < Visitor # :nodoc:
193
+ class Dot < FunctionalVisitor # :nodoc:
154
194
  def initialize
155
195
  @nodes = []
156
196
  @edges = []
157
197
  end
158
198
 
159
- def accept(node)
199
+ def accept(node, seed = [[], []])
160
200
  super
201
+ nodes, edges = seed
161
202
  <<-eodot
162
203
  digraph parse_tree {
163
204
  size="8,5"
164
205
  node [shape = none];
165
206
  edge [dir = none];
166
- #{@nodes.join "\n"}
167
- #{@edges.join("\n")}
207
+ #{nodes.join "\n"}
208
+ #{edges.join("\n")}
168
209
  }
169
210
  eodot
170
211
  end
171
212
 
172
213
  private
173
214
 
174
- def binary(node)
175
- node.children.each do |c|
176
- @edges << "#{node.object_id} -> #{c.object_id};"
177
- end
215
+ def binary(node, seed)
216
+ seed.last.concat node.children.map { |c|
217
+ "#{node.object_id} -> #{c.object_id};"
218
+ }
178
219
  super
179
220
  end
180
221
 
181
- def nary(node)
182
- node.children.each do |c|
183
- @edges << "#{node.object_id} -> #{c.object_id};"
184
- end
222
+ def nary(node, seed)
223
+ seed.last.concat node.children.map { |c|
224
+ "#{node.object_id} -> #{c.object_id};"
225
+ }
185
226
  super
186
227
  end
187
228
 
188
- def unary(node)
189
- @edges << "#{node.object_id} -> #{node.left.object_id};"
229
+ def unary(node, seed)
230
+ seed.last << "#{node.object_id} -> #{node.left.object_id};"
190
231
  super
191
232
  end
192
233
 
193
- def visit_GROUP(node)
194
- @nodes << "#{node.object_id} [label=\"()\"];"
234
+ def visit_GROUP(node, seed)
235
+ seed.first << "#{node.object_id} [label=\"()\"];"
195
236
  super
196
237
  end
197
238
 
198
- def visit_CAT(node)
199
- @nodes << "#{node.object_id} [label=\"○\"];"
239
+ def visit_CAT(node, seed)
240
+ seed.first << "#{node.object_id} [label=\"○\"];"
200
241
  super
201
242
  end
202
243
 
203
- def visit_STAR(node)
204
- @nodes << "#{node.object_id} [label=\"*\"];"
244
+ def visit_STAR(node, seed)
245
+ seed.first << "#{node.object_id} [label=\"*\"];"
205
246
  super
206
247
  end
207
248
 
208
- def visit_OR(node)
209
- @nodes << "#{node.object_id} [label=\"|\"];"
249
+ def visit_OR(node, seed)
250
+ seed.first << "#{node.object_id} [label=\"|\"];"
210
251
  super
211
252
  end
212
253
 
213
- def terminal(node)
254
+ def terminal(node, seed)
214
255
  value = node.left
215
256
 
216
- @nodes << "#{node.object_id} [label=\"#{value}\"];"
257
+ seed.first << "#{node.object_id} [label=\"#{value}\"];"
258
+ seed
217
259
  end
260
+ INSTANCE = new
218
261
  end
219
262
  end
220
263
  end