actionpack 4.1.16 → 4.2.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -690
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +0 -3
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +1 -7
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller.rb +1 -0
  11. data/lib/action_controller/base.rb +2 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +7 -1
  14. data/lib/action_controller/log_subscriber.rb +26 -25
  15. data/lib/action_controller/metal.rb +11 -7
  16. data/lib/action_controller/metal/conditional_get.rb +31 -6
  17. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  18. data/lib/action_controller/metal/force_ssl.rb +1 -1
  19. data/lib/action_controller/metal/head.rb +2 -0
  20. data/lib/action_controller/metal/http_authentication.rb +3 -15
  21. data/lib/action_controller/metal/instrumentation.rb +4 -7
  22. data/lib/action_controller/metal/live.rb +57 -6
  23. data/lib/action_controller/metal/mime_responds.rb +17 -227
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +19 -3
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +75 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +111 -11
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/model_naming.rb +1 -1
  32. data/lib/action_controller/railtie.rb +4 -0
  33. data/lib/action_controller/test_case.rb +87 -75
  34. data/lib/action_dispatch/http/cache.rb +1 -1
  35. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  36. data/lib/action_dispatch/http/headers.rb +43 -9
  37. data/lib/action_dispatch/http/mime_negotiation.rb +10 -4
  38. data/lib/action_dispatch/http/mime_type.rb +2 -16
  39. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  40. data/lib/action_dispatch/http/parameters.rb +11 -26
  41. data/lib/action_dispatch/http/request.rb +30 -10
  42. data/lib/action_dispatch/http/response.rb +52 -17
  43. data/lib/action_dispatch/http/upload.rb +3 -8
  44. data/lib/action_dispatch/http/url.rb +87 -70
  45. data/lib/action_dispatch/journey/formatter.rb +18 -17
  46. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +18 -26
  49. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  50. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  51. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  52. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  53. data/lib/action_dispatch/journey/parser.rb +52 -60
  54. data/lib/action_dispatch/journey/parser.y +11 -10
  55. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  56. data/lib/action_dispatch/journey/route.rb +3 -18
  57. data/lib/action_dispatch/journey/router.rb +34 -65
  58. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  59. data/lib/action_dispatch/journey/routes.rb +0 -4
  60. data/lib/action_dispatch/journey/visitors.rb +81 -92
  61. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  62. data/lib/action_dispatch/middleware/cookies.rb +27 -31
  63. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -3
  64. data/lib/action_dispatch/middleware/exception_wrapper.rb +19 -17
  65. data/lib/action_dispatch/middleware/flash.rb +7 -4
  66. data/lib/action_dispatch/middleware/public_exceptions.rb +13 -8
  67. data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
  68. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  69. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  71. data/lib/action_dispatch/middleware/static.rb +22 -23
  72. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +22 -18
  73. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +36 -8
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  75. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  79. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  80. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +119 -63
  81. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  82. data/lib/action_dispatch/routing/inspector.rb +4 -11
  83. data/lib/action_dispatch/routing/mapper.rb +399 -278
  84. data/lib/action_dispatch/routing/polymorphic_routes.rb +190 -78
  85. data/lib/action_dispatch/routing/redirection.rb +10 -12
  86. data/lib/action_dispatch/routing/route_set.rb +224 -177
  87. data/lib/action_dispatch/routing/url_for.rb +9 -4
  88. data/lib/action_dispatch/testing/assertions.rb +11 -7
  89. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  90. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  91. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  92. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  93. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  94. data/lib/action_dispatch/testing/integration.rb +15 -18
  95. data/lib/action_dispatch/testing/test_request.rb +1 -1
  96. data/lib/action_dispatch/testing/test_response.rb +5 -1
  97. data/lib/action_pack/gem_version.rb +3 -3
  98. metadata +57 -15
  99. data/lib/action_controller/metal/responder.rb +0 -297
@@ -1,11 +1,11 @@
1
1
  class ActionDispatch::Journey::Parser
2
-
2
+ options no_result_var
3
3
  token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
4
4
 
5
5
  rule
6
6
  expressions
7
- : expressions expression { result = Cat.new(val.first, val.last) }
8
- | expression { result = val.first }
7
+ : expression expressions { Cat.new(val.first, val.last) }
8
+ | expression { val.first }
9
9
  | or
10
10
  ;
11
11
  expression
@@ -14,13 +14,14 @@ rule
14
14
  | star
15
15
  ;
16
16
  group
17
- : LPAREN expressions RPAREN { result = Group.new(val[1]) }
17
+ : LPAREN expressions RPAREN { Group.new(val[1]) }
18
18
  ;
19
19
  or
20
- : expressions OR expression { result = Or.new([val.first, val.last]) }
20
+ : expression OR expression { Or.new([val.first, val.last]) }
21
+ | expression OR or { Or.new([val.first, val.last]) }
21
22
  ;
22
23
  star
23
- : STAR { result = Star.new(Symbol.new(val.last)) }
24
+ : STAR { Star.new(Symbol.new(val.last)) }
24
25
  ;
25
26
  terminal
26
27
  : symbol
@@ -29,16 +30,16 @@ rule
29
30
  | dot
30
31
  ;
31
32
  slash
32
- : SLASH { result = Slash.new('/') }
33
+ : SLASH { Slash.new('/') }
33
34
  ;
34
35
  symbol
35
- : SYMBOL { result = Symbol.new(val.first) }
36
+ : SYMBOL { Symbol.new(val.first) }
36
37
  ;
37
38
  literal
38
- : LITERAL { result = Literal.new(val.first) }
39
+ : LITERAL { Literal.new(val.first) }
39
40
  ;
40
41
  dot
41
- : DOT { result = Dot.new(val.first) }
42
+ : DOT { Dot.new(val.first) }
42
43
  ;
43
44
 
44
45
  end
@@ -1,27 +1,20 @@
1
+ require 'action_dispatch/journey/router/strexp'
2
+
1
3
  module ActionDispatch
2
4
  module Journey # :nodoc:
3
5
  module Path # :nodoc:
4
6
  class Pattern # :nodoc:
5
7
  attr_reader :spec, :requirements, :anchored
6
8
 
7
- def initialize(strexp)
8
- parser = Journey::Parser.new
9
-
10
- @anchored = true
9
+ def self.from_string string
10
+ new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
11
+ end
11
12
 
12
- case strexp
13
- when String
14
- @spec = parser.parse(strexp)
15
- @requirements = {}
16
- @separators = "/.?"
17
- when Router::Strexp
18
- @spec = parser.parse(strexp.path)
19
- @requirements = strexp.requirements
20
- @separators = strexp.separators.join
21
- @anchored = strexp.anchor
22
- else
23
- raise ArgumentError, "Bad expression: #{strexp}"
24
- end
13
+ def initialize(strexp)
14
+ @spec = strexp.ast
15
+ @requirements = strexp.requirements
16
+ @separators = strexp.separators.join
17
+ @anchored = strexp.anchor
25
18
 
26
19
  @names = nil
27
20
  @optional_names = nil
@@ -30,6 +23,10 @@ module ActionDispatch
30
23
  @offsets = nil
31
24
  end
32
25
 
26
+ def build_formatter
27
+ Visitors::FormatBuilder.new.accept(spec)
28
+ end
29
+
33
30
  def ast
34
31
  @spec.grep(Nodes::Symbol).each do |node|
35
32
  re = @requirements[node.to_sym]
@@ -53,9 +50,9 @@ module ActionDispatch
53
50
  end
54
51
 
55
52
  def optional_names
56
- @optional_names ||= spec.grep(Nodes::Group).map { |group|
53
+ @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
57
54
  group.grep(Nodes::Symbol)
58
- }.flatten.map { |n| n.name }.uniq
55
+ }.map { |n| n.name }.uniq
59
56
  end
60
57
 
61
58
  class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
@@ -16,14 +16,6 @@ module ActionDispatch
16
16
  @app = app
17
17
  @path = path
18
18
 
19
- # Unwrap any constraints so we can see what's inside for route generation.
20
- # This allows the formatter to skip over any mounted applications or redirects
21
- # that shouldn't be matched when using a url_for without a route name.
22
- while app.is_a?(Routing::Mapper::Constraints) do
23
- app = app.app
24
- end
25
- @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher)
26
-
27
19
  @constraints = constraints
28
20
  @defaults = defaults
29
21
  @required_defaults = nil
@@ -31,6 +23,7 @@ module ActionDispatch
31
23
  @parts = nil
32
24
  @decorated_ast = nil
33
25
  @precedence = 0
26
+ @path_formatter = @path.build_formatter
34
27
  end
35
28
 
36
29
  def ast
@@ -72,15 +65,7 @@ module ActionDispatch
72
65
  alias :segment_keys :parts
73
66
 
74
67
  def format(path_options)
75
- path_options.delete_if do |key, value|
76
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
77
- end
78
-
79
- Visitors::Formatter.new(path_options).accept(path.spec)
80
- end
81
-
82
- def optimized_path
83
- Visitors::OptimizedPath.new.accept(path.spec)
68
+ @path_formatter.evaluate path_options
84
69
  end
85
70
 
86
71
  def optional_parts
@@ -106,7 +91,7 @@ module ActionDispatch
106
91
  end
107
92
 
108
93
  def dispatcher?
109
- @dispatcher
94
+ @app.dispatcher?
110
95
  end
111
96
 
112
97
  def matches?(request)
@@ -20,62 +20,32 @@ module ActionDispatch
20
20
  # :nodoc:
21
21
  VERSION = '2.0.0'
22
22
 
23
- class NullReq # :nodoc:
24
- attr_reader :env
25
- def initialize(env)
26
- @env = env
27
- end
28
-
29
- def request_method
30
- env['REQUEST_METHOD']
31
- end
32
-
33
- def path_info
34
- env['PATH_INFO']
35
- end
36
-
37
- def ip
38
- env['REMOTE_ADDR']
39
- end
40
-
41
- def [](k)
42
- env[k]
43
- end
44
- end
45
-
46
- attr_reader :request_class, :formatter
47
23
  attr_accessor :routes
48
24
 
49
- def initialize(routes, options)
50
- @options = options
51
- @params_key = options[:parameters_key]
52
- @request_class = options[:request_class] || NullReq
53
- @routes = routes
25
+ def initialize(routes)
26
+ @routes = routes
54
27
  end
55
28
 
56
- def call(env)
57
- env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
58
-
59
- find_routes(env).each do |match, parameters, route|
60
- script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
61
- 'PATH_INFO',
62
- @params_key)
29
+ def serve(req)
30
+ find_routes(req).each do |match, parameters, route|
31
+ set_params = req.path_parameters
32
+ path_info = req.path_info
33
+ script_name = req.script_name
63
34
 
64
35
  unless route.path.anchored
65
- env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
66
- matched_path = match.post_match
67
- env['PATH_INFO'] = matched_path
68
- env['PATH_INFO'] = "/" + matched_path unless matched_path.start_with? "/"
36
+ req.script_name = (script_name.to_s + match.to_s).chomp('/')
37
+ req.path_info = match.post_match
38
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
69
39
  end
70
40
 
71
- env[@params_key] = (set_params || {}).merge parameters
41
+ req.path_parameters = set_params.merge parameters
72
42
 
73
- status, headers, body = route.app.call(env)
43
+ status, headers, body = route.app.serve(req)
74
44
 
75
45
  if 'pass' == headers['X-Cascade']
76
- env['SCRIPT_NAME'] = script_name
77
- env['PATH_INFO'] = path_info
78
- env[@params_key] = set_params
46
+ req.script_name = script_name
47
+ req.path_info = path_info
48
+ req.path_parameters = set_params
79
49
  next
80
50
  end
81
51
 
@@ -85,14 +55,14 @@ module ActionDispatch
85
55
  return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
86
56
  end
87
57
 
88
- def recognize(req)
89
- find_routes(req.env).each do |match, parameters, route|
58
+ def recognize(rails_req)
59
+ find_routes(rails_req).each do |match, parameters, route|
90
60
  unless route.path.anchored
91
- req.env['SCRIPT_NAME'] = match.to_s
92
- req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
61
+ rails_req.script_name = match.to_s
62
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
93
63
  end
94
64
 
95
- yield(route, nil, parameters)
65
+ yield(route, parameters)
96
66
  end
97
67
  end
98
68
 
@@ -123,33 +93,34 @@ module ActionDispatch
123
93
 
124
94
  def filter_routes(path)
125
95
  return [] unless ast
126
- data = simulator.match(path)
127
- data ? data.memos : []
96
+ simulator.memos(path) { [] }
128
97
  end
129
98
 
130
- def find_routes env
131
- req = request_class.new(env)
132
-
99
+ def find_routes req
133
100
  routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
134
101
  r.path.match(req.path_info)
135
102
  }
136
- routes.concat get_routes_as_head(routes)
137
103
 
138
- routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
104
+ if req.env["REQUEST_METHOD"] === "HEAD"
105
+ routes.concat get_routes_as_head(routes)
106
+ end
107
+
108
+ routes.select! { |r| r.matches?(req) }
109
+ routes.sort_by!(&:precedence)
139
110
 
140
111
  routes.map! { |r|
141
112
  match_data = r.path.match(req.path_info)
142
- match_names = match_data.names.map { |n| n.to_sym }
143
- match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
144
- info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
145
-
146
- [match_data, r.defaults.merge(info), r]
113
+ path_parameters = r.defaults.dup
114
+ match_data.names.zip(match_data.captures) { |name,val|
115
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
116
+ }
117
+ [match_data, path_parameters, r]
147
118
  }
148
119
  end
149
120
 
150
121
  def get_routes_as_head(routes)
151
122
  precedence = (routes.map(&:precedence).max || 0) + 1
152
- routes = routes.select { |r|
123
+ routes.select { |r|
153
124
  r.verb === "GET" && !(r.verb === "HEAD")
154
125
  }.map! { |r|
155
126
  Route.new(r.name,
@@ -160,8 +131,6 @@ module ActionDispatch
160
131
  route.precedence = r.precedence + precedence
161
132
  end
162
133
  }
163
- routes.flatten!
164
- routes
165
134
  end
166
135
  end
167
136
  end
@@ -6,18 +6,21 @@ module ActionDispatch
6
6
  alias :compile :new
7
7
  end
8
8
 
9
- attr_reader :path, :requirements, :separators, :anchor
9
+ attr_reader :path, :requirements, :separators, :anchor, :ast
10
10
 
11
- def initialize(path, requirements, separators, anchor = true)
11
+ def self.build(path, requirements, separators, anchor = true)
12
+ parser = Journey::Parser.new
13
+ ast = parser.parse path
14
+ new ast, path, requirements, separators, anchor
15
+ end
16
+
17
+ def initialize(ast, path, requirements, separators, anchor = true)
18
+ @ast = ast
12
19
  @path = path
13
20
  @requirements = requirements
14
21
  @separators = separators
15
22
  @anchor = anchor
16
23
  end
17
-
18
- def names
19
- @path.scan(/:\w+/).map { |s| s.tr(':', '') }
20
- end
21
24
  end
22
25
  end
23
26
  end
@@ -15,10 +15,6 @@ module ActionDispatch
15
15
  @simulator = nil
16
16
  end
17
17
 
18
- def empty?
19
- routes.empty?
20
- end
21
-
22
18
  def length
23
19
  routes.length
24
20
  end
@@ -1,14 +1,57 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'thread_safe'
4
-
5
3
  module ActionDispatch
6
4
  module Journey # :nodoc:
5
+ class Format
6
+ ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
7
+ ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
8
+
9
+ class Parameter < Struct.new(:name, :escaper)
10
+ def escape(value); escaper.call value; end
11
+ end
12
+
13
+ def self.required_path(symbol)
14
+ Parameter.new symbol, ESCAPE_PATH
15
+ end
16
+
17
+ def self.required_segment(symbol)
18
+ Parameter.new symbol, ESCAPE_SEGMENT
19
+ end
20
+
21
+ def initialize(parts)
22
+ @parts = parts
23
+ @children = []
24
+ @parameters = []
25
+
26
+ parts.each_with_index do |object,i|
27
+ case object
28
+ when Journey::Format
29
+ @children << i
30
+ when Parameter
31
+ @parameters << i
32
+ end
33
+ end
34
+ end
35
+
36
+ def evaluate(hash)
37
+ parts = @parts.dup
38
+
39
+ @parameters.each do |index|
40
+ param = parts[index]
41
+ value = hash[param.name]
42
+ return ''.freeze unless value
43
+ parts[index] = param.escape value
44
+ end
45
+
46
+ @children.each { |index| parts[index] = parts[index].evaluate(hash) }
47
+
48
+ parts.join
49
+ end
50
+ end
51
+
7
52
  module Visitors # :nodoc:
8
53
  class Visitor # :nodoc:
9
- DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
10
- h[k] = :"visit_#{k}"
11
- }
54
+ DISPATCH_CACHE = {}
12
55
 
13
56
  def accept(node)
14
57
  visit(node)
@@ -38,11 +81,41 @@ module ActionDispatch
38
81
  def visit_STAR(n); unary(n); end
39
82
 
40
83
  def terminal(node); end
41
- %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
42
- class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
84
+ def visit_LITERAL(n); terminal(n); end
85
+ def visit_SYMBOL(n); terminal(n); end
86
+ def visit_SLASH(n); terminal(n); end
87
+ def visit_DOT(n); terminal(n); end
88
+
89
+ private_instance_methods(false).each do |pim|
90
+ next unless pim =~ /^visit_(.*)$/
91
+ DISPATCH_CACHE[$1.to_sym] = pim
43
92
  end
44
93
  end
45
94
 
95
+ class FormatBuilder < Visitor # :nodoc:
96
+ def accept(node); Journey::Format.new(super); end
97
+ def terminal(node); [node.left]; end
98
+
99
+ def binary(node)
100
+ visit(node.left) + visit(node.right)
101
+ end
102
+
103
+ def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
104
+
105
+ def visit_STAR(n)
106
+ [Journey::Format.required_path(n.left.to_sym)]
107
+ end
108
+
109
+ def visit_SYMBOL(n)
110
+ symbol = n.to_sym
111
+ if symbol == :controller
112
+ [Journey::Format.required_path(symbol)]
113
+ else
114
+ [Journey::Format.required_segment(symbol)]
115
+ end
116
+ end
117
+ end
118
+
46
119
  # Loop through the requirements AST
47
120
  class Each < Visitor # :nodoc:
48
121
  attr_reader :block
@@ -52,8 +125,8 @@ module ActionDispatch
52
125
  end
53
126
 
54
127
  def visit(node)
55
- super
56
128
  block.call(node)
129
+ super
57
130
  end
58
131
  end
59
132
 
@@ -77,90 +150,6 @@ module ActionDispatch
77
150
  end
78
151
  end
79
152
 
80
- class OptimizedPath < Visitor # :nodoc:
81
- def accept(node)
82
- Array(visit(node))
83
- end
84
-
85
- private
86
-
87
- def visit_CAT(node)
88
- [visit(node.left), visit(node.right)].flatten
89
- end
90
-
91
- def visit_SYMBOL(node)
92
- node.left[1..-1].to_sym
93
- end
94
-
95
- def visit_STAR(node)
96
- visit(node.left)
97
- end
98
-
99
- def visit_GROUP(node)
100
- []
101
- end
102
-
103
- %w{ LITERAL SLASH DOT }.each do |t|
104
- class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
105
- end
106
- end
107
-
108
- # Used for formatting urls (url_for)
109
- class Formatter < Visitor # :nodoc:
110
- attr_reader :options
111
-
112
- def initialize(options)
113
- @options = options
114
- end
115
-
116
- private
117
- def escape_path(value)
118
- Router::Utils.escape_path(value)
119
- end
120
-
121
- def escape_segment(value)
122
- Router::Utils.escape_segment(value)
123
- end
124
-
125
- def visit(node, optional = false)
126
- case node.type
127
- when :LITERAL, :SLASH, :DOT
128
- node.left
129
- when :STAR
130
- visit_STAR(node.left)
131
- when :GROUP
132
- visit(node.left, true)
133
- when :CAT
134
- visit_CAT(node, optional)
135
- when :SYMBOL
136
- visit_SYMBOL(node, node.to_sym)
137
- end
138
- end
139
-
140
- def visit_CAT(node, optional)
141
- left = visit(node.left, optional)
142
- right = visit(node.right, optional)
143
-
144
- if optional && !(right && left)
145
- ""
146
- else
147
- [left, right].join
148
- end
149
- end
150
-
151
- def visit_STAR(node)
152
- if value = options[node.to_sym]
153
- escape_path(value)
154
- end
155
- end
156
-
157
- def visit_SYMBOL(node, name)
158
- if value = options[name]
159
- name == :controller ? escape_path(value) : escape_segment(value)
160
- end
161
- end
162
- end
163
-
164
153
  class Dot < Visitor # :nodoc:
165
154
  def initialize
166
155
  @nodes = []