actionview 6.1.7.2 → 7.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -277
- data/MIT-LICENSE +2 -1
- data/README.rdoc +3 -3
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +37 -19
- data/lib/action_view/buffers.rb +107 -9
- data/lib/action_view/cache_expiry.rb +48 -37
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +8 -5
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
- data/lib/action_view/helpers/asset_url_helper.rb +22 -21
- data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
- data/lib/action_view/helpers/cache_helper.rb +55 -12
- data/lib/action_view/helpers/capture_helper.rb +34 -14
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +8 -2
- data/lib/action_view/helpers/csp_helper.rb +3 -3
- data/lib/action_view/helpers/csrf_helper.rb +4 -4
- data/lib/action_view/helpers/date_helper.rb +123 -57
- data/lib/action_view/helpers/debug_helper.rb +6 -4
- data/lib/action_view/helpers/form_helper.rb +253 -97
- data/lib/action_view/helpers/form_options_helper.rb +72 -34
- data/lib/action_view/helpers/form_tag_helper.rb +189 -58
- data/lib/action_view/helpers/javascript_helper.rb +4 -5
- data/lib/action_view/helpers/number_helper.rb +43 -335
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +6 -7
- data/lib/action_view/helpers/sanitize_helper.rb +54 -24
- data/lib/action_view/helpers/tag_helper.rb +42 -35
- data/lib/action_view/helpers/tags/base.rb +16 -77
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +4 -1
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +4 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +11 -2
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +180 -97
- data/lib/action_view/helpers/translation_helper.rb +14 -45
- data/lib/action_view/helpers/url_helper.rb +230 -132
- data/lib/action_view/helpers.rb +27 -25
- data/lib/action_view/layouts.rb +15 -10
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +58 -61
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +28 -35
- data/lib/action_view/railtie.rb +44 -9
- data/lib/action_view/record_identifier.rb +16 -9
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +3 -3
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
- data/lib/action_view/renderer/partial_renderer.rb +3 -36
- data/lib/action_view/renderer/renderer.rb +6 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
- data/lib/action_view/renderer/template_renderer.rb +9 -4
- data/lib/action_view/rendering.rb +25 -7
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +8 -5
- data/lib/action_view/template/error.rb +122 -14
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +79 -1
- data/lib/action_view/template/handlers.rb +4 -4
- data/lib/action_view/template/html.rb +4 -4
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +4 -4
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +96 -313
- data/lib/action_view/template/text.rb +4 -4
- data/lib/action_view/template/types.rb +25 -32
- data/lib/action_view/template.rb +245 -41
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +182 -23
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +43 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +19 -28
- data/lib/action_view.rb +6 -4
- data/lib/assets/compiled/rails-ujs.js +36 -5
- metadata +32 -25
|
@@ -7,11 +7,11 @@ module ActionView
|
|
|
7
7
|
#
|
|
8
8
|
# * Support streaming from child templates, partials and so on.
|
|
9
9
|
# * Rack::Cache needs to support streaming bodies
|
|
10
|
-
class StreamingTemplateRenderer < TemplateRenderer
|
|
10
|
+
class StreamingTemplateRenderer < TemplateRenderer # :nodoc:
|
|
11
11
|
# A valid Rack::Body (i.e. it responds to each).
|
|
12
12
|
# It is initialized with a block that, when called, starts
|
|
13
13
|
# rendering the template.
|
|
14
|
-
class Body
|
|
14
|
+
class Body # :nodoc:
|
|
15
15
|
def initialize(&start)
|
|
16
16
|
@start = start
|
|
17
17
|
end
|
|
@@ -42,11 +42,11 @@ module ActionView
|
|
|
42
42
|
# For streaming, instead of rendering a given a template, we return a Body
|
|
43
43
|
# object that responds to each. This object is initialized with a block
|
|
44
44
|
# that knows how to render the template.
|
|
45
|
-
def render_template(view, template, layout_name = nil, locals = {})
|
|
45
|
+
def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
|
|
46
46
|
return [super.body] unless layout_name && template.supports_streaming?
|
|
47
47
|
|
|
48
48
|
locals ||= {}
|
|
49
|
-
layout =
|
|
49
|
+
layout = find_layout(layout_name, locals.keys, [formats.first])
|
|
50
50
|
|
|
51
51
|
Body.new do |buffer|
|
|
52
52
|
delayed_render(buffer, template, layout, view, locals)
|
|
@@ -65,7 +65,8 @@ module ActionView
|
|
|
65
65
|
ActiveSupport::Notifications.instrument(
|
|
66
66
|
"render_template.action_view",
|
|
67
67
|
identifier: template.identifier,
|
|
68
|
-
layout: layout && layout.virtual_path
|
|
68
|
+
layout: layout && layout.virtual_path,
|
|
69
|
+
locals: locals
|
|
69
70
|
) do
|
|
70
71
|
outer_config = I18n.config
|
|
71
72
|
fiber = Fiber.new do
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActionView
|
|
4
|
-
class TemplateRenderer < AbstractRenderer
|
|
4
|
+
class TemplateRenderer < AbstractRenderer # :nodoc:
|
|
5
5
|
def render(context, options)
|
|
6
6
|
@details = extract_details(options)
|
|
7
7
|
template = determine_template(options)
|
|
@@ -26,7 +26,11 @@ module ActionView
|
|
|
26
26
|
if File.exist?(options[:file])
|
|
27
27
|
Template::RawFile.new(options[:file])
|
|
28
28
|
else
|
|
29
|
-
|
|
29
|
+
if Pathname.new(options[:file]).absolute?
|
|
30
|
+
raise ArgumentError, "File #{options[:file]} does not exist"
|
|
31
|
+
else
|
|
32
|
+
raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
|
|
33
|
+
end
|
|
30
34
|
end
|
|
31
35
|
elsif options.key?(:inline)
|
|
32
36
|
handler = Template.handler_for_extension(options[:type] || "erb")
|
|
@@ -45,7 +49,7 @@ module ActionView
|
|
|
45
49
|
@lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
|
|
46
50
|
end
|
|
47
51
|
else
|
|
48
|
-
raise ArgumentError, "You invoked render but did not give any of :
|
|
52
|
+
raise ArgumentError, "You invoked render but did not give any of :body, :file, :html, :inline, :partial, :plain, :renderable, or :template option."
|
|
49
53
|
end
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -56,7 +60,8 @@ module ActionView
|
|
|
56
60
|
ActiveSupport::Notifications.instrument(
|
|
57
61
|
"render_template.action_view",
|
|
58
62
|
identifier: template.identifier,
|
|
59
|
-
layout: layout && layout.virtual_path
|
|
63
|
+
layout: layout && layout.virtual_path,
|
|
64
|
+
locals: locals
|
|
60
65
|
) do
|
|
61
66
|
template.render(view, locals) { |*name| view._layout_for(*name) }
|
|
62
67
|
end
|
|
@@ -5,12 +5,13 @@ require "action_view/view_paths"
|
|
|
5
5
|
module ActionView
|
|
6
6
|
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
|
|
7
7
|
# it will trigger the lookup_context and consequently expire the cache.
|
|
8
|
-
class I18nProxy < ::I18n::Config
|
|
8
|
+
class I18nProxy < ::I18n::Config # :nodoc:
|
|
9
9
|
attr_reader :original_config, :lookup_context
|
|
10
10
|
|
|
11
11
|
def initialize(original_config, lookup_context)
|
|
12
12
|
original_config = original_config.original_config if original_config.respond_to?(:original_config)
|
|
13
|
-
@original_config
|
|
13
|
+
@original_config = original_config
|
|
14
|
+
@lookup_context = lookup_context
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def locale
|
|
@@ -33,8 +34,8 @@ module ActionView
|
|
|
33
34
|
super
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
#
|
|
37
|
-
def process(
|
|
37
|
+
# Override process to set up I18n proxy.
|
|
38
|
+
def process(...) # :nodoc:
|
|
38
39
|
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
|
|
39
40
|
super
|
|
40
41
|
ensure
|
|
@@ -48,7 +49,18 @@ module ActionView
|
|
|
48
49
|
def _helpers
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
def inherit_view_context_class?
|
|
53
|
+
superclass.respond_to?(:view_context_class) &&
|
|
54
|
+
supports_path? == superclass.supports_path? &&
|
|
55
|
+
_routes.equal?(superclass._routes) &&
|
|
56
|
+
_helpers.equal?(superclass._helpers)
|
|
57
|
+
end
|
|
58
|
+
|
|
51
59
|
def build_view_context_class(klass, supports_path, routes, helpers)
|
|
60
|
+
if inherit_view_context_class?
|
|
61
|
+
return superclass.view_context_class
|
|
62
|
+
end
|
|
63
|
+
|
|
52
64
|
Class.new(klass) do
|
|
53
65
|
if routes
|
|
54
66
|
include routes.url_helpers(supports_path)
|
|
@@ -61,8 +73,14 @@ module ActionView
|
|
|
61
73
|
end
|
|
62
74
|
end
|
|
63
75
|
|
|
76
|
+
def eager_load!
|
|
77
|
+
super
|
|
78
|
+
view_context_class
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
64
82
|
def view_context_class
|
|
65
|
-
klass = ActionView::LookupContext::DetailsKey.view_context_class
|
|
83
|
+
klass = ActionView::LookupContext::DetailsKey.view_context_class
|
|
66
84
|
|
|
67
85
|
@view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
|
|
68
86
|
|
|
@@ -129,8 +147,8 @@ module ActionView
|
|
|
129
147
|
lookup_context.formats = [format.to_sym] if format.to_sym
|
|
130
148
|
end
|
|
131
149
|
|
|
132
|
-
# Normalize args by converting render "foo" to render :
|
|
133
|
-
# render "foo/bar" to render :
|
|
150
|
+
# Normalize args by converting render "foo" to render action: "foo" and
|
|
151
|
+
# render "foo/bar" to render template: "foo/bar".
|
|
134
152
|
def _normalize_args(action = nil, options = {})
|
|
135
153
|
options = super(action, options)
|
|
136
154
|
case action
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ripper"
|
|
4
|
+
|
|
5
|
+
module ActionView
|
|
6
|
+
class RenderParser
|
|
7
|
+
module RipperASTParser # :nodoc:
|
|
8
|
+
class Node < ::Array # :nodoc:
|
|
9
|
+
attr_reader :type
|
|
10
|
+
|
|
11
|
+
def initialize(type, arr, opts = {})
|
|
12
|
+
@type = type
|
|
13
|
+
super(arr)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def children
|
|
17
|
+
to_a
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect
|
|
21
|
+
typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
|
|
22
|
+
"s(" + typeinfo + map(&:inspect).join(", ") + ")"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fcall?
|
|
26
|
+
type == :command || type == :fcall
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fcall_named?(name)
|
|
30
|
+
fcall? &&
|
|
31
|
+
self[0].type == :@ident &&
|
|
32
|
+
self[0][0] == name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def argument_nodes
|
|
36
|
+
raise unless fcall?
|
|
37
|
+
return [] if self[1].nil?
|
|
38
|
+
if self[1].last == false || self[1].last.type == :vcall
|
|
39
|
+
self[1][0...-1]
|
|
40
|
+
else
|
|
41
|
+
self[1][0..-1]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def string?
|
|
46
|
+
type == :string_literal
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def variable_reference?
|
|
50
|
+
type == :var_ref
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def vcall?
|
|
54
|
+
type == :vcall
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def call?
|
|
58
|
+
type == :call
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def variable_name
|
|
62
|
+
self[0][0]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def call_method_name
|
|
66
|
+
self[2].first
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_string
|
|
70
|
+
raise unless string?
|
|
71
|
+
self[0][0][0]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def hash?
|
|
75
|
+
type == :bare_assoc_hash || type == :hash
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_hash
|
|
79
|
+
if type == :bare_assoc_hash
|
|
80
|
+
hash_from_body(self[0])
|
|
81
|
+
elsif type == :hash && self[0] == nil
|
|
82
|
+
{}
|
|
83
|
+
elsif type == :hash && self[0].type == :assoclist_from_args
|
|
84
|
+
hash_from_body(self[0][0])
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def hash_from_body(body)
|
|
89
|
+
body.to_h do |hash_node|
|
|
90
|
+
return nil if hash_node.type != :assoc_new
|
|
91
|
+
|
|
92
|
+
[hash_node[0], hash_node[1]]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def symbol?
|
|
97
|
+
type == :@label || type == :symbol_literal
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_symbol
|
|
101
|
+
if type == :@label && self[0] =~ /\A(.+):\z/
|
|
102
|
+
$1.to_sym
|
|
103
|
+
elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
|
|
104
|
+
self[0][0][0].to_sym
|
|
105
|
+
else
|
|
106
|
+
raise "not a symbol?: #{self.inspect}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class NodeParser < ::Ripper # :nodoc:
|
|
112
|
+
PARSER_EVENTS.each do |event|
|
|
113
|
+
arity = PARSER_EVENT_TABLE[event]
|
|
114
|
+
if arity == 0 && event.to_s.end_with?("_new")
|
|
115
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
116
|
+
def on_#{event}(*args)
|
|
117
|
+
Node.new(:list, args, lineno: lineno(), column: column())
|
|
118
|
+
end
|
|
119
|
+
eof
|
|
120
|
+
elsif event.to_s.match?(/_add(_.+)?\z/)
|
|
121
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
122
|
+
begin; undef on_#{event}; rescue NameError; end
|
|
123
|
+
def on_#{event}(list, item)
|
|
124
|
+
list.push(item)
|
|
125
|
+
list
|
|
126
|
+
end
|
|
127
|
+
eof
|
|
128
|
+
else
|
|
129
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
|
130
|
+
begin; undef on_#{event}; rescue NameError; end
|
|
131
|
+
def on_#{event}(*args)
|
|
132
|
+
Node.new(:#{event}, args, lineno: lineno(), column: column())
|
|
133
|
+
end
|
|
134
|
+
eof
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
SCANNER_EVENTS.each do |event|
|
|
139
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
|
140
|
+
def on_#{event}(tok)
|
|
141
|
+
Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
|
|
142
|
+
end
|
|
143
|
+
End
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class RenderCallExtractor < NodeParser # :nodoc:
|
|
148
|
+
attr_reader :render_calls
|
|
149
|
+
|
|
150
|
+
METHODS_TO_PARSE = %w(render render_to_string)
|
|
151
|
+
|
|
152
|
+
def initialize(*args)
|
|
153
|
+
super
|
|
154
|
+
|
|
155
|
+
@render_calls = []
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
def on_fcall(name, *args)
|
|
160
|
+
on_render_call(super)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def on_command(name, *args)
|
|
164
|
+
on_render_call(super)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def on_render_call(node)
|
|
168
|
+
METHODS_TO_PARSE.each do |method|
|
|
169
|
+
if node.fcall_named?(method)
|
|
170
|
+
@render_calls << [method, node]
|
|
171
|
+
return node
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
node
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def on_arg_paren(content)
|
|
178
|
+
content
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def on_paren(content)
|
|
182
|
+
content.size == 1 ? content.first : content
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
extend self
|
|
187
|
+
|
|
188
|
+
def parse_render_nodes(code)
|
|
189
|
+
parser = RenderCallExtractor.new(code)
|
|
190
|
+
parser.parse
|
|
191
|
+
|
|
192
|
+
parser.render_calls.group_by(&:first).to_h do |method, nodes|
|
|
193
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -6,14 +6,14 @@ module ActionView
|
|
|
6
6
|
module RoutingUrlFor
|
|
7
7
|
# Returns the URL for the set of +options+ provided. This takes the
|
|
8
8
|
# same options as +url_for+ in Action Controller (see the
|
|
9
|
-
# documentation for
|
|
10
|
-
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
|
|
11
|
-
# instead of the fully qualified URL like "http://example.com/controller/action"
|
|
9
|
+
# documentation for ActionDispatch::Routing::UrlFor#url_for). Note that by default
|
|
10
|
+
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative <tt>"/controller/action"</tt>
|
|
11
|
+
# instead of the fully qualified URL like <tt>"http://example.com/controller/action"</tt>.
|
|
12
12
|
#
|
|
13
13
|
# ==== Options
|
|
14
14
|
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
|
|
15
15
|
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
|
|
16
|
-
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/"
|
|
16
|
+
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in <tt>"/archive/2005/"</tt>. Note that this
|
|
17
17
|
# is currently not recommended since it breaks caching.
|
|
18
18
|
# * <tt>:host</tt> - Overrides the default (current) host if provided.
|
|
19
19
|
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
|
|
@@ -47,6 +47,9 @@ module ActionView
|
|
|
47
47
|
# <%= url_for(action: 'jump', anchor: 'tax&ship') %>
|
|
48
48
|
# # => /testing/jump/#tax&ship
|
|
49
49
|
#
|
|
50
|
+
# <%= url_for(Workshop) %>
|
|
51
|
+
# # => /workshops
|
|
52
|
+
#
|
|
50
53
|
# <%= url_for(Workshop.new) %>
|
|
51
54
|
# # relies on Workshop answering a persisted? call (and in this case returning false)
|
|
52
55
|
# # => /workshops
|
|
@@ -118,7 +121,7 @@ module ActionView
|
|
|
118
121
|
end
|
|
119
122
|
end
|
|
120
123
|
|
|
121
|
-
def url_options
|
|
124
|
+
def url_options # :nodoc:
|
|
122
125
|
return super unless controller.respond_to?(:url_options)
|
|
123
126
|
controller.url_options
|
|
124
127
|
end
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
|
4
|
+
require "active_support/syntax_error_proxy"
|
|
4
5
|
|
|
5
6
|
module ActionView
|
|
6
7
|
# = Action View Errors
|
|
7
|
-
class ActionViewError < StandardError
|
|
8
|
+
class ActionViewError < StandardError # :nodoc:
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
class EncodingError < StandardError
|
|
11
|
+
class EncodingError < StandardError # :nodoc:
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
class WrongEncodingError < EncodingError
|
|
14
|
+
class WrongEncodingError < EncodingError # :nodoc:
|
|
14
15
|
def initialize(string, encoding)
|
|
15
16
|
@string, @encoding = string, encoding
|
|
16
17
|
end
|
|
@@ -26,12 +27,18 @@ module ActionView
|
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
class MissingTemplate < ActionViewError
|
|
30
|
-
attr_reader :path
|
|
30
|
+
class MissingTemplate < ActionViewError # :nodoc:
|
|
31
|
+
attr_reader :path, :paths, :prefixes, :partial
|
|
31
32
|
|
|
32
33
|
def initialize(paths, path, prefixes, partial, details, *)
|
|
34
|
+
if partial && path.present?
|
|
35
|
+
path = path.sub(%r{([^/]+)$}, "_\\1")
|
|
36
|
+
end
|
|
37
|
+
|
|
33
38
|
@path = path
|
|
34
|
-
|
|
39
|
+
@paths = paths
|
|
40
|
+
@prefixes = Array(prefixes)
|
|
41
|
+
@partial = partial
|
|
35
42
|
template_type = if partial
|
|
36
43
|
"partial"
|
|
37
44
|
elsif /layouts/i.match?(path)
|
|
@@ -40,34 +47,135 @@ module ActionView
|
|
|
40
47
|
"template"
|
|
41
48
|
end
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
path = path.sub(%r{([^/]+)$}, "_\\1")
|
|
45
|
-
end
|
|
46
|
-
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
|
|
50
|
+
searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
|
|
47
51
|
|
|
48
|
-
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}
|
|
52
|
+
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n"
|
|
49
53
|
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
|
|
50
54
|
super out
|
|
51
55
|
end
|
|
56
|
+
|
|
57
|
+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro)
|
|
58
|
+
include DidYouMean::Correctable
|
|
59
|
+
|
|
60
|
+
class Results # :nodoc:
|
|
61
|
+
Result = Struct.new(:path, :score)
|
|
62
|
+
|
|
63
|
+
def initialize(size)
|
|
64
|
+
@size = size
|
|
65
|
+
@results = []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_a
|
|
69
|
+
@results.map(&:path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def should_record?(score)
|
|
73
|
+
if @results.size < @size
|
|
74
|
+
true
|
|
75
|
+
else
|
|
76
|
+
score < @results.last.score
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add(path, score)
|
|
81
|
+
if should_record?(score)
|
|
82
|
+
@results << Result.new(path, score)
|
|
83
|
+
@results.sort_by!(&:score)
|
|
84
|
+
@results.pop if @results.size > @size
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Apps may have thousands of candidate templates so we attempt to
|
|
90
|
+
# generate the suggestions as efficiently as possible.
|
|
91
|
+
# First we split templates into prefixes and basenames, so that those can
|
|
92
|
+
# be matched separately.
|
|
93
|
+
def corrections
|
|
94
|
+
candidates = paths.flat_map(&:all_template_paths).uniq
|
|
95
|
+
|
|
96
|
+
if partial
|
|
97
|
+
candidates.select!(&:partial?)
|
|
98
|
+
else
|
|
99
|
+
candidates.reject!(&:partial?)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Group by possible prefixes
|
|
103
|
+
files_by_dir = candidates.group_by(&:prefix)
|
|
104
|
+
files_by_dir.transform_values! do |files|
|
|
105
|
+
files.map do |file|
|
|
106
|
+
# Remove prefix
|
|
107
|
+
File.basename(file.to_s)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# No suggestions if there's an exact match, but wrong details
|
|
112
|
+
if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) }
|
|
113
|
+
return []
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
cached_distance = Hash.new do |h, args|
|
|
117
|
+
h[args] = -DidYouMean::Jaro.distance(*args)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
results = Results.new(6)
|
|
121
|
+
|
|
122
|
+
files_by_dir.keys.index_with do |dirname|
|
|
123
|
+
prefixes.map do |prefix|
|
|
124
|
+
cached_distance[[prefix, dirname]]
|
|
125
|
+
end.min
|
|
126
|
+
end.sort_by(&:last).each do |dirname, dirweight|
|
|
127
|
+
# If our directory's score makes it impossible to find a better match
|
|
128
|
+
# we can prune this search branch.
|
|
129
|
+
next unless results.should_record?(dirweight - 1.0)
|
|
130
|
+
|
|
131
|
+
files = files_by_dir[dirname]
|
|
132
|
+
|
|
133
|
+
files.each do |file|
|
|
134
|
+
fileweight = cached_distance[[path, file]]
|
|
135
|
+
score = dirweight + fileweight
|
|
136
|
+
|
|
137
|
+
results.add(File.join(dirname, file), score)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if partial
|
|
142
|
+
results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") }
|
|
143
|
+
else
|
|
144
|
+
results.to_a
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
52
148
|
end
|
|
53
149
|
|
|
54
150
|
class Template
|
|
55
151
|
# The Template::Error exception is raised when the compilation or rendering of the template
|
|
56
152
|
# fails. This exception then gathers a bunch of intimate details and uses it to report a
|
|
57
153
|
# precise exception message.
|
|
58
|
-
class Error < ActionViewError
|
|
154
|
+
class Error < ActionViewError # :nodoc:
|
|
59
155
|
SOURCE_CODE_RADIUS = 3
|
|
60
156
|
|
|
61
157
|
# Override to prevent #cause resetting during re-raise.
|
|
62
158
|
attr_reader :cause
|
|
63
159
|
|
|
160
|
+
attr_reader :template
|
|
161
|
+
|
|
64
162
|
def initialize(template)
|
|
65
163
|
super($!.message)
|
|
66
|
-
set_backtrace($!.backtrace)
|
|
67
164
|
@cause = $!
|
|
165
|
+
if @cause.is_a?(SyntaxError)
|
|
166
|
+
@cause = ActiveSupport::SyntaxErrorProxy.new(@cause)
|
|
167
|
+
end
|
|
68
168
|
@template, @sub_templates = template, nil
|
|
69
169
|
end
|
|
70
170
|
|
|
171
|
+
def backtrace
|
|
172
|
+
@cause.backtrace
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def backtrace_locations
|
|
176
|
+
@cause.backtrace_locations
|
|
177
|
+
end
|
|
178
|
+
|
|
71
179
|
def file_name
|
|
72
180
|
@template.identifier
|
|
73
181
|
end
|
|
@@ -134,7 +242,7 @@ module ActionView
|
|
|
134
242
|
|
|
135
243
|
TemplateError = Template::Error
|
|
136
244
|
|
|
137
|
-
class SyntaxErrorInTemplate < TemplateError
|
|
245
|
+
class SyntaxErrorInTemplate < TemplateError # :nodoc:
|
|
138
246
|
def initialize(template, offending_code_string)
|
|
139
247
|
@offending_code_string = offending_code_string
|
|
140
248
|
super(template)
|
|
@@ -7,10 +7,10 @@ module ActionView
|
|
|
7
7
|
|
|
8
8
|
def call(template, source)
|
|
9
9
|
require_engine
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
source
|
|
13
|
-
"
|
|
10
|
+
# the double assignment is to silence "assigned but unused variable" warnings
|
|
11
|
+
"xml = xml = ::Builder::XmlMarkup.new(indent: 2, target: output_buffer.raw);" \
|
|
12
|
+
"#{source};" \
|
|
13
|
+
"output_buffer.to_s"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
private
|
|
@@ -16,22 +16,16 @@ module ActionView
|
|
|
16
16
|
|
|
17
17
|
properties[:bufvar] ||= "@output_buffer"
|
|
18
18
|
properties[:preamble] ||= ""
|
|
19
|
-
properties[:postamble] ||= "#{properties[:bufvar]}
|
|
19
|
+
properties[:postamble] ||= "#{properties[:bufvar]}"
|
|
20
|
+
|
|
21
|
+
# Tell Eruby that whether template will be compiled with `frozen_string_literal: true`
|
|
22
|
+
properties[:freeze_template_literals] = !Template.frozen_string_literal
|
|
20
23
|
|
|
21
24
|
properties[:escapefunc] = ""
|
|
22
25
|
|
|
23
26
|
super
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def evaluate(action_view_erb_handler_context)
|
|
27
|
-
src = @src
|
|
28
|
-
view = Class.new(ActionView::Base) {
|
|
29
|
-
include action_view_erb_handler_context._routes.url_helpers
|
|
30
|
-
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
|
|
31
|
-
}.empty
|
|
32
|
-
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
29
|
private
|
|
36
30
|
def add_text(text)
|
|
37
31
|
return if text.empty?
|
|
@@ -39,30 +33,32 @@ module ActionView
|
|
|
39
33
|
if text == "\n"
|
|
40
34
|
@newline_pending += 1
|
|
41
35
|
else
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
with_buffer do
|
|
37
|
+
src << ".safe_append='"
|
|
38
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
|
39
|
+
src << text.gsub(/['\\]/, '\\\\\&') << @text_end
|
|
40
|
+
end
|
|
47
41
|
@newline_pending = 0
|
|
48
42
|
end
|
|
49
43
|
end
|
|
50
44
|
|
|
51
|
-
BLOCK_EXPR =
|
|
45
|
+
BLOCK_EXPR = /((\s|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
52
46
|
|
|
53
47
|
def add_expression(indicator, code)
|
|
54
48
|
flush_newline_if_pending(src)
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
with_buffer do
|
|
51
|
+
if (indicator == "==") || @escape
|
|
52
|
+
src << ".safe_expr_append="
|
|
53
|
+
else
|
|
54
|
+
src << ".append="
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if BLOCK_EXPR.match?(code)
|
|
58
|
+
src << " " << code
|
|
59
|
+
else
|
|
60
|
+
src << "(" << code << ")"
|
|
61
|
+
end
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
64
|
|
|
@@ -78,7 +74,7 @@ module ActionView
|
|
|
78
74
|
|
|
79
75
|
def flush_newline_if_pending(src)
|
|
80
76
|
if @newline_pending > 0
|
|
81
|
-
|
|
77
|
+
with_buffer { src << ".safe_append='#{"\n" * @newline_pending}" << @text_end }
|
|
82
78
|
@newline_pending = 0
|
|
83
79
|
end
|
|
84
80
|
end
|