actionview 5.0.7.2 → 5.1.7

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +169 -345
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view/base.rb +19 -19
  6. data/lib/action_view/buffers.rb +1 -1
  7. data/lib/action_view/context.rb +1 -1
  8. data/lib/action_view/dependency_tracker.rb +4 -5
  9. data/lib/action_view/digestor.rb +22 -13
  10. data/lib/action_view/flows.rb +5 -6
  11. data/lib/action_view/gem_version.rb +2 -2
  12. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  13. data/lib/action_view/helpers/asset_tag_helper.rb +62 -36
  14. data/lib/action_view/helpers/asset_url_helper.rb +111 -49
  15. data/lib/action_view/helpers/atom_feed_helper.rb +12 -13
  16. data/lib/action_view/helpers/cache_helper.rb +32 -20
  17. data/lib/action_view/helpers/capture_helper.rb +2 -2
  18. data/lib/action_view/helpers/controller_helper.rb +2 -2
  19. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  20. data/lib/action_view/helpers/date_helper.rb +119 -109
  21. data/lib/action_view/helpers/debug_helper.rb +2 -3
  22. data/lib/action_view/helpers/form_helper.rb +440 -31
  23. data/lib/action_view/helpers/form_options_helper.rb +12 -12
  24. data/lib/action_view/helpers/form_tag_helper.rb +20 -19
  25. data/lib/action_view/helpers/javascript_helper.rb +6 -6
  26. data/lib/action_view/helpers/number_helper.rb +48 -46
  27. data/lib/action_view/helpers/output_safety_helper.rb +8 -8
  28. data/lib/action_view/helpers/record_tag_helper.rb +2 -2
  29. data/lib/action_view/helpers/rendering_helper.rb +2 -3
  30. data/lib/action_view/helpers/sanitize_helper.rb +16 -12
  31. data/lib/action_view/helpers/tag_helper.rb +194 -77
  32. data/lib/action_view/helpers/tags/base.rb +121 -102
  33. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  34. data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -8
  35. data/lib/action_view/helpers/tags/collection_helpers.rb +60 -60
  36. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -2
  37. data/lib/action_view/helpers/tags/collection_select.rb +2 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +36 -36
  39. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  40. data/lib/action_view/helpers/tags/label.rb +4 -0
  41. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  42. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  43. data/lib/action_view/helpers/tags/select.rb +9 -9
  44. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  45. data/lib/action_view/helpers/tags/text_field.rb +5 -5
  46. data/lib/action_view/helpers/tags/translator.rb +14 -12
  47. data/lib/action_view/helpers/text_helper.rb +20 -19
  48. data/lib/action_view/helpers/translation_helper.rb +6 -6
  49. data/lib/action_view/helpers/url_helper.rb +48 -46
  50. data/lib/action_view/helpers.rb +1 -1
  51. data/lib/action_view/layouts.rb +51 -47
  52. data/lib/action_view/log_subscriber.rb +25 -9
  53. data/lib/action_view/lookup_context.rb +19 -25
  54. data/lib/action_view/path_set.rb +19 -19
  55. data/lib/action_view/railtie.rb +13 -4
  56. data/lib/action_view/record_identifier.rb +6 -6
  57. data/lib/action_view/renderer/abstract_renderer.rb +17 -17
  58. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +7 -1
  59. data/lib/action_view/renderer/partial_renderer.rb +188 -187
  60. data/lib/action_view/renderer/renderer.rb +4 -0
  61. data/lib/action_view/renderer/streaming_template_renderer.rb +45 -47
  62. data/lib/action_view/renderer/template_renderer.rb +64 -66
  63. data/lib/action_view/rendering.rb +4 -5
  64. data/lib/action_view/routing_url_for.rb +9 -13
  65. data/lib/action_view/tasks/cache_digests.rake +7 -7
  66. data/lib/action_view/template/error.rb +5 -15
  67. data/lib/action_view/template/handlers/builder.rb +7 -7
  68. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  69. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  70. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  71. data/lib/action_view/template/handlers/erb.rb +9 -76
  72. data/lib/action_view/template/handlers.rb +4 -4
  73. data/lib/action_view/template/html.rb +2 -4
  74. data/lib/action_view/template/resolver.rb +107 -90
  75. data/lib/action_view/template/text.rb +5 -8
  76. data/lib/action_view/template/types.rb +1 -1
  77. data/lib/action_view/template.rb +26 -27
  78. data/lib/action_view/test_case.rb +20 -21
  79. data/lib/action_view/testing/resolvers.rb +29 -30
  80. data/lib/action_view/version.rb +1 -1
  81. data/lib/action_view/view_paths.rb +20 -8
  82. data/lib/action_view.rb +5 -5
  83. data/lib/assets/compiled/rails-ujs.js +683 -0
  84. metadata +18 -12
@@ -1,10 +1,9 @@
1
- require 'fiber'
1
+ require "fiber"
2
2
 
3
3
  module ActionView
4
4
  # == TODO
5
5
  #
6
6
  # * Support streaming from child templates, partials and so on.
7
- # * Integrate exceptions with exceptron
8
7
  # * Rack::Cache needs to support streaming bodies
9
8
  class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
10
9
  # A valid Rack::Body (i.e. it responds to each).
@@ -27,17 +26,16 @@ module ActionView
27
26
 
28
27
  private
29
28
 
30
- # This is the same logging logic as in ShowExceptions middleware.
31
- # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
32
- def log_error(exception) #:nodoc:
33
- logger = ActionView::Base.logger
34
- return unless logger
29
+ # This is the same logging logic as in ShowExceptions middleware.
30
+ def log_error(exception)
31
+ logger = ActionView::Base.logger
32
+ return unless logger
35
33
 
36
- message = "\n#{exception.class} (#{exception.message}):\n"
37
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
38
- message << " " << exception.backtrace.join("\n ")
39
- logger.fatal("#{message}\n\n")
40
- end
34
+ message = "\n#{exception.class} (#{exception.message}):\n"
35
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
36
+ message << " " << exception.backtrace.join("\n ")
37
+ logger.fatal("#{message}\n\n")
38
+ end
41
39
  end
42
40
 
43
41
  # For streaming, instead of rendering a given a template, we return a Body
@@ -56,48 +54,48 @@ module ActionView
56
54
 
57
55
  private
58
56
 
59
- def delayed_render(buffer, template, layout, view, locals)
60
- # Wrap the given buffer in the StreamingBuffer and pass it to the
61
- # underlying template handler. Now, every time something is concatenated
62
- # to the buffer, it is not appended to an array, but streamed straight
63
- # to the client.
64
- output = ActionView::StreamingBuffer.new(buffer)
65
- yielder = lambda { |*name| view._layout_for(*name) }
66
-
67
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
68
- fiber = Fiber.new do
69
- if layout
70
- layout.render(view, locals, output, &yielder)
71
- else
72
- # If you don't have a layout, just render the thing
73
- # and concatenate the final result. This is the same
74
- # as a layout with just <%= yield %>
75
- output.safe_concat view._layout_for
57
+ def delayed_render(buffer, template, layout, view, locals)
58
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
59
+ # underlying template handler. Now, every time something is concatenated
60
+ # to the buffer, it is not appended to an array, but streamed straight
61
+ # to the client.
62
+ output = ActionView::StreamingBuffer.new(buffer)
63
+ yielder = lambda { |*name| view._layout_for(*name) }
64
+
65
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
66
+ fiber = Fiber.new do
67
+ if layout
68
+ layout.render(view, locals, output, &yielder)
69
+ else
70
+ # If you don't have a layout, just render the thing
71
+ # and concatenate the final result. This is the same
72
+ # as a layout with just <%= yield %>
73
+ output.safe_concat view._layout_for
74
+ end
76
75
  end
77
- end
78
76
 
79
- # Set the view flow to support streaming. It will be aware
80
- # when to stop rendering the layout because it needs to search
81
- # something in the template and vice-versa.
82
- view.view_flow = StreamingFlow.new(view, fiber)
77
+ # Set the view flow to support streaming. It will be aware
78
+ # when to stop rendering the layout because it needs to search
79
+ # something in the template and vice-versa.
80
+ view.view_flow = StreamingFlow.new(view, fiber)
83
81
 
84
- # Yo! Start the fiber!
85
- fiber.resume
82
+ # Yo! Start the fiber!
83
+ fiber.resume
86
84
 
87
- # If the fiber is still alive, it means we need something
88
- # from the template, so start rendering it. If not, it means
89
- # the layout exited without requiring anything from the template.
90
- if fiber.alive?
91
- content = template.render(view, locals, &yielder)
85
+ # If the fiber is still alive, it means we need something
86
+ # from the template, so start rendering it. If not, it means
87
+ # the layout exited without requiring anything from the template.
88
+ if fiber.alive?
89
+ content = template.render(view, locals, &yielder)
92
90
 
93
- # Once rendering the template is done, sets its content in the :layout key.
94
- view.view_flow.set(:layout, content)
91
+ # Once rendering the template is done, sets its content in the :layout key.
92
+ view.view_flow.set(:layout, content)
95
93
 
96
- # In case the layout continues yielding, we need to resume
97
- # the fiber until all yields are handled.
98
- fiber.resume while fiber.alive?
94
+ # In case the layout continues yielding, we need to resume
95
+ # the fiber until all yields are handled.
96
+ fiber.resume while fiber.alive?
97
+ end
99
98
  end
100
99
  end
101
- end
102
100
  end
103
101
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/try'
1
+ require "active_support/core_ext/object/try"
2
2
 
3
3
  module ActionView
4
4
  class TemplateRenderer < AbstractRenderer #:nodoc:
@@ -16,87 +16,85 @@ module ActionView
16
16
 
17
17
  private
18
18
 
19
- # Determine the template to be rendered using the given options.
20
- def determine_template(options)
21
- keys = options.has_key?(:locals) ? options[:locals].keys : []
19
+ # Determine the template to be rendered using the given options.
20
+ def determine_template(options)
21
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
22
22
 
23
- if options.key?(:body)
24
- Template::Text.new(options[:body])
25
- elsif options.key?(:text)
26
- Template::Text.new(options[:text], formats.first)
27
- elsif options.key?(:plain)
28
- Template::Text.new(options[:plain])
29
- elsif options.key?(:html)
30
- Template::HTML.new(options[:html], formats.first)
31
- elsif options.key?(:file)
32
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
33
- elsif options.key?(:inline)
34
- handler = Template.handler_for_extension(options[:type] || "erb")
35
- Template.new(options[:inline], "inline template", handler, :locals => keys)
36
- elsif options.key?(:template)
37
- if options[:template].respond_to?(:render)
38
- options[:template]
23
+ if options.key?(:body)
24
+ Template::Text.new(options[:body])
25
+ elsif options.key?(:plain)
26
+ Template::Text.new(options[:plain])
27
+ elsif options.key?(:html)
28
+ Template::HTML.new(options[:html], formats.first)
29
+ elsif options.key?(:file)
30
+ with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
31
+ elsif options.key?(:inline)
32
+ handler = Template.handler_for_extension(options[:type] || "erb")
33
+ Template.new(options[:inline], "inline template", handler, locals: keys)
34
+ elsif options.key?(:template)
35
+ if options[:template].respond_to?(:render)
36
+ options[:template]
37
+ else
38
+ find_template(options[:template], options[:prefixes], false, keys, @details)
39
+ end
39
40
  else
40
- find_template(options[:template], options[:prefixes], false, keys, @details)
41
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
41
42
  end
42
- else
43
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option."
44
43
  end
45
- end
46
44
 
47
- # Renders the given template. A string representing the layout can be
48
- # supplied as well.
49
- def render_template(template, layout_name = nil, locals = nil) #:nodoc:
50
- view, locals = @view, locals || {}
45
+ # Renders the given template. A string representing the layout can be
46
+ # supplied as well.
47
+ def render_template(template, layout_name = nil, locals = nil)
48
+ view, locals = @view, locals || {}
51
49
 
52
- render_with_layout(layout_name, locals) do |layout|
53
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
54
- template.render(view, locals) { |*name| view._layout_for(*name) }
50
+ render_with_layout(layout_name, locals) do |layout|
51
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
52
+ template.render(view, locals) { |*name| view._layout_for(*name) }
53
+ end
55
54
  end
56
55
  end
57
- end
58
56
 
59
- def render_with_layout(path, locals) #:nodoc:
60
- layout = path && find_layout(path, locals.keys, [formats.first])
61
- content = yield(layout)
57
+ def render_with_layout(path, locals)
58
+ layout = path && find_layout(path, locals.keys, [formats.first])
59
+ content = yield(layout)
62
60
 
63
- if layout
64
- view = @view
65
- view.view_flow.set(:layout, content)
66
- layout.render(view, locals){ |*name| view._layout_for(*name) }
67
- else
68
- content
61
+ if layout
62
+ view = @view
63
+ view.view_flow.set(:layout, content)
64
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
65
+ else
66
+ content
67
+ end
69
68
  end
70
- end
71
69
 
72
- # This is the method which actually finds the layout using details in the lookup
73
- # context object. If no layout is found, it checks if at least a layout with
74
- # the given name exists across all details before raising the error.
75
- def find_layout(layout, keys, formats)
76
- resolve_layout(layout, keys, formats)
77
- end
70
+ # This is the method which actually finds the layout using details in the lookup
71
+ # context object. If no layout is found, it checks if at least a layout with
72
+ # the given name exists across all details before raising the error.
73
+ def find_layout(layout, keys, formats)
74
+ resolve_layout(layout, keys, formats)
75
+ end
78
76
 
79
- def resolve_layout(layout, keys, formats)
80
- details = @details.dup
81
- details[:formats] = formats
77
+ def resolve_layout(layout, keys, formats)
78
+ details = @details.dup
79
+ details[:formats] = formats
82
80
 
83
- case layout
84
- when String
85
- begin
86
- if layout =~ /^\//
87
- with_fallbacks { find_template(layout, nil, false, keys, details) }
88
- else
89
- find_template(layout, nil, false, keys, details)
81
+ case layout
82
+ when String
83
+ begin
84
+ if layout.start_with?("/")
85
+ with_fallbacks { find_template(layout, nil, false, [], details) }
86
+ else
87
+ find_template(layout, nil, false, [], details)
88
+ end
89
+ rescue ActionView::MissingTemplate
90
+ all_details = @details.merge(formats: @lookup_context.default_formats)
91
+ raise unless template_exists?(layout, nil, false, [], all_details)
90
92
  end
91
- rescue ActionView::MissingTemplate
92
- all_details = @details.merge(:formats => @lookup_context.default_formats)
93
- raise unless template_exists?(layout, nil, false, keys, all_details)
93
+ when Proc
94
+ resolve_layout(layout.call(formats), keys, formats)
95
+ else
96
+ layout
94
97
  end
95
- when Proc
96
- resolve_layout(layout.call(formats), keys, formats)
97
- else
98
- layout
99
98
  end
100
- end
101
99
  end
102
100
  end
@@ -84,15 +84,14 @@ module ActionView
84
84
  end
85
85
 
86
86
  def rendered_format
87
- format = lookup_context.rendered_format
88
- Template::Types[format] || format
87
+ Template::Types[lookup_context.rendered_format]
89
88
  end
90
89
 
91
90
  private
92
91
 
93
92
  # Find and render a template based on the options given.
94
93
  # :api: private
95
- def _render_template(options) #:nodoc:
94
+ def _render_template(options)
96
95
  variant = options.delete(:variant)
97
96
  assigns = options.delete(:assigns)
98
97
  context = view_context
@@ -105,7 +104,7 @@ module ActionView
105
104
  end
106
105
 
107
106
  # Assign the rendered format to look up context.
108
- def _process_format(format) #:nodoc:
107
+ def _process_format(format)
109
108
  super
110
109
  lookup_context.formats = [format.to_sym]
111
110
  lookup_context.rendered_format = lookup_context.formats.first
@@ -114,7 +113,7 @@ module ActionView
114
113
  # Normalize args by converting render "foo" to render :action => "foo" and
115
114
  # render "foo/bar" to render :template => "foo/bar".
116
115
  # :api: private
117
- def _normalize_args(action=nil, options={})
116
+ def _normalize_args(action = nil, options = {})
118
117
  options = super(action, options)
119
118
  case action
120
119
  when NilClass
@@ -1,8 +1,7 @@
1
- require 'action_dispatch/routing/polymorphic_routes'
1
+ require "action_dispatch/routing/polymorphic_routes"
2
2
 
3
3
  module ActionView
4
4
  module RoutingUrlFor
5
-
6
5
  # Returns the URL for the set of +options+ provided. This takes the
7
6
  # same options as +url_for+ in Action Controller (see the
8
7
  # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
@@ -123,18 +122,15 @@ module ActionView
123
122
  controller.url_options
124
123
  end
125
124
 
126
- def _routes_context #:nodoc:
127
- controller
128
- end
129
- protected :_routes_context
130
-
131
- def optimize_routes_generation? #:nodoc:
132
- controller.respond_to?(:optimize_routes_generation?, true) ?
133
- controller.optimize_routes_generation? : super
134
- end
135
- protected :optimize_routes_generation?
136
-
137
125
  private
126
+ def _routes_context
127
+ controller
128
+ end
129
+
130
+ def optimize_routes_generation?
131
+ controller.respond_to?(:optimize_routes_generation?, true) ?
132
+ controller.optimize_routes_generation? : super
133
+ end
138
134
 
139
135
  def _generate_paths_by_default
140
136
  true
@@ -1,19 +1,19 @@
1
1
  namespace :cache_digests do
2
- desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
3
- task :nested_dependencies => :environment do
4
- abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
2
+ desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
3
+ task nested_dependencies: :environment do
4
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
5
5
  puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
6
6
  end
7
7
 
8
- desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
9
- task :dependencies => :environment do
10
- abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
8
+ desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
9
+ task dependencies: :environment do
10
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
11
11
  puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
12
12
  end
13
13
 
14
14
  class CacheDigests
15
15
  def self.template_name
16
- ENV['TEMPLATE'].split('.', 2).first
16
+ ENV["TEMPLATE"].split(".", 2).first
17
17
  end
18
18
 
19
19
  def self.finder
@@ -35,10 +35,10 @@ module ActionView
35
35
  prefixes = Array(prefixes)
36
36
  template_type = if partial
37
37
  "partial"
38
- elsif path =~ /layouts/i
39
- 'layout'
38
+ elsif /layouts/i.match?(path)
39
+ "layout"
40
40
  else
41
- 'template'
41
+ "template"
42
42
  end
43
43
 
44
44
  if partial && path.present?
@@ -62,23 +62,13 @@ module ActionView
62
62
  # Override to prevent #cause resetting during re-raise.
63
63
  attr_reader :cause
64
64
 
65
- def initialize(template, original_exception = nil)
66
- if original_exception
67
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
68
- "Exceptions will automatically capture the original exception.", caller)
69
- end
70
-
65
+ def initialize(template)
71
66
  super($!.message)
72
67
  set_backtrace($!.backtrace)
73
68
  @cause = $!
74
69
  @template, @sub_templates = template, nil
75
70
  end
76
71
 
77
- def original_exception
78
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
79
- cause
80
- end
81
-
82
72
  def file_name
83
73
  @template.identifier
84
74
  end
@@ -130,7 +120,7 @@ module ActionView
130
120
  if line_number
131
121
  "on line ##{line_number} of "
132
122
  else
133
- 'in '
123
+ "in "
134
124
  end + file_name
135
125
  end
136
126
 
@@ -7,20 +7,20 @@ module ActionView
7
7
 
8
8
  def call(template)
9
9
  require_engine
10
- "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
10
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" \
11
11
  "self.output_buffer = xml.target!;" +
12
12
  template.source +
13
13
  ";xml.target!;"
14
14
  end
15
15
 
16
- protected
16
+ private
17
17
 
18
- def require_engine
19
- @required ||= begin
20
- require "builder"
21
- true
18
+ def require_engine # :doc:
19
+ @required ||= begin
20
+ require "builder"
21
+ true
22
+ end
22
23
  end
23
- end
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,9 @@
1
+ ::ActiveSupport::Deprecation.warn("ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.")
2
+
3
+ module ActionView
4
+ class Template
5
+ module Handlers
6
+ Erubis = ERB::Erubis
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,81 @@
1
+ require "erubi"
2
+
3
+ module ActionView
4
+ class Template
5
+ module Handlers
6
+ class ERB
7
+ class Erubi < ::Erubi::Engine
8
+ # :nodoc: all
9
+ def initialize(input, properties = {})
10
+ @newline_pending = 0
11
+
12
+ # Dup properties so that we don't modify argument
13
+ properties = Hash[properties]
14
+ properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
15
+ properties[:postamble] = "@output_buffer.to_s"
16
+ properties[:bufvar] = "@output_buffer"
17
+ properties[:escapefunc] = ""
18
+
19
+ super
20
+ end
21
+
22
+ def evaluate(action_view_erb_handler_context)
23
+ pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
24
+ action_view_erb_handler_context.instance_eval(&pr)
25
+ end
26
+
27
+ private
28
+ def add_text(text)
29
+ return if text.empty?
30
+
31
+ if text == "\n"
32
+ @newline_pending += 1
33
+ else
34
+ src << "@output_buffer.safe_append='"
35
+ src << "\n" * @newline_pending if @newline_pending > 0
36
+ src << text.gsub(/['\\]/, '\\\\\&')
37
+ src << "'.freeze;"
38
+
39
+ @newline_pending = 0
40
+ end
41
+ end
42
+
43
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
44
+
45
+ def add_expression(indicator, code)
46
+ flush_newline_if_pending(src)
47
+
48
+ if (indicator == "==") || @escape
49
+ src << "@output_buffer.safe_expr_append="
50
+ else
51
+ src << "@output_buffer.append="
52
+ end
53
+
54
+ if BLOCK_EXPR.match?(code)
55
+ src << " " << code
56
+ else
57
+ src << "(" << code << ");"
58
+ end
59
+ end
60
+
61
+ def add_code(code)
62
+ flush_newline_if_pending(src)
63
+ super
64
+ end
65
+
66
+ def add_postamble(_)
67
+ flush_newline_if_pending(src)
68
+ super
69
+ end
70
+
71
+ def flush_newline_if_pending(src)
72
+ if @newline_pending > 0
73
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
74
+ @newline_pending = 0
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ gem "erubis"
2
+ require "erubis"
3
+
4
+ module ActionView
5
+ class Template
6
+ module Handlers
7
+ class ERB
8
+ class Erubis < ::Erubis::Eruby
9
+ # :nodoc: all
10
+ def add_preamble(src)
11
+ @newline_pending = 0
12
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
13
+ end
14
+
15
+ def add_text(src, text)
16
+ return if text.empty?
17
+
18
+ if text == "\n"
19
+ @newline_pending += 1
20
+ else
21
+ src << "@output_buffer.safe_append='"
22
+ src << "\n" * @newline_pending if @newline_pending > 0
23
+ src << escape_text(text)
24
+ src << "'.freeze;"
25
+
26
+ @newline_pending = 0
27
+ end
28
+ end
29
+
30
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
31
+ # We override to always treat <%== as escaped.
32
+ def add_expr(src, code, indicator)
33
+ case indicator
34
+ when "=="
35
+ add_expr_escaped(src, code)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
42
+
43
+ def add_expr_literal(src, code)
44
+ flush_newline_if_pending(src)
45
+ if BLOCK_EXPR.match?(code)
46
+ src << "@output_buffer.append= " << code
47
+ else
48
+ src << "@output_buffer.append=(" << code << ");"
49
+ end
50
+ end
51
+
52
+ def add_expr_escaped(src, code)
53
+ flush_newline_if_pending(src)
54
+ if BLOCK_EXPR.match?(code)
55
+ src << "@output_buffer.safe_expr_append= " << code
56
+ else
57
+ src << "@output_buffer.safe_expr_append=(" << code << ");"
58
+ end
59
+ end
60
+
61
+ def add_stmt(src, code)
62
+ flush_newline_if_pending(src)
63
+ super
64
+ end
65
+
66
+ def add_postamble(src)
67
+ flush_newline_if_pending(src)
68
+ src << "@output_buffer.to_s"
69
+ end
70
+
71
+ def flush_newline_if_pending(src)
72
+ if @newline_pending > 0
73
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
74
+ @newline_pending = 0
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end