actionview 4.2.11.1 → 7.0.2.4
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.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -215
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +116 -43
- data/lib/action_view/buffers.rb +20 -3
- data/lib/action_view/cache_expiry.rb +66 -0
- data/lib/action_view/context.rb +8 -12
- 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 +21 -122
- data/lib/action_view/digestor.rb +92 -85
- data/lib/action_view/flows.rb +15 -16
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +17 -12
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
- data/lib/action_view/helpers/asset_url_helper.rb +180 -74
- data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
- data/lib/action_view/helpers/cache_helper.rb +156 -43
- data/lib/action_view/helpers/capture_helper.rb +21 -14
- data/lib/action_view/helpers/controller_helper.rb +16 -5
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +288 -132
- data/lib/action_view/helpers/debug_helper.rb +9 -6
- data/lib/action_view/helpers/form_helper.rb +956 -173
- data/lib/action_view/helpers/form_options_helper.rb +178 -97
- data/lib/action_view/helpers/form_tag_helper.rb +220 -101
- data/lib/action_view/helpers/javascript_helper.rb +33 -19
- data/lib/action_view/helpers/number_helper.rb +88 -63
- data/lib/action_view/helpers/output_safety_helper.rb +38 -6
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +31 -32
- data/lib/action_view/helpers/tag_helper.rb +332 -71
- data/lib/action_view/helpers/tags/base.rb +123 -99
- data/lib/action_view/helpers/tags/check_box.rb +21 -20
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +5 -3
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +18 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
- data/lib/action_view/helpers/tags/label.rb +7 -2
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +14 -9
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +4 -2
- data/lib/action_view/helpers/tags/text_field.rb +8 -8
- data/lib/action_view/helpers/tags/time_field.rb +12 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +15 -16
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +80 -51
- data/lib/action_view/helpers/translation_helper.rb +120 -69
- data/lib/action_view/helpers/url_helper.rb +398 -171
- data/lib/action_view/helpers.rb +29 -27
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +137 -113
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +28 -32
- data/lib/action_view/railtie.rb +74 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +152 -15
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +51 -333
- data/lib/action_view/renderer/renderer.rb +68 -11
- data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
- data/lib/action_view/renderer/template_renderer.rb +87 -74
- data/lib/action_view/rendering.rb +73 -47
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +35 -24
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +151 -41
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +29 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +14 -10
- data/lib/action_view/template/html.rb +12 -13
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +139 -300
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +10 -12
- data/lib/action_view/template/types.rb +28 -26
- data/lib/action_view/template.rb +123 -91
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +70 -53
- data/lib/action_view/testing/resolvers.rb +25 -35
- data/lib/action_view/unbound_template.rb +57 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +16 -11
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +52 -32
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -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.last.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.map do |hash_node|
|
90
|
+
return nil if hash_node.type != :assoc_new
|
91
|
+
|
92
|
+
[hash_node[0], hash_node[1]]
|
93
|
+
end.to_h
|
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
|
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).collect do |method, nodes|
|
193
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
194
|
+
end.to_h
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/routing/polymorphic_routes"
|
2
4
|
|
3
5
|
module ActionView
|
4
6
|
module RoutingUrlFor
|
5
|
-
|
6
7
|
# Returns the URL for the set of +options+ provided. This takes the
|
7
8
|
# same options as +url_for+ in Action Controller (see the
|
8
9
|
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
@@ -32,7 +33,7 @@ module ActionView
|
|
32
33
|
#
|
33
34
|
# ==== Examples
|
34
35
|
# <%= url_for(action: 'index') %>
|
35
|
-
# # => /
|
36
|
+
# # => /blogs/
|
36
37
|
#
|
37
38
|
# <%= url_for(action: 'find', controller: 'books') %>
|
38
39
|
# # => /books/find
|
@@ -83,27 +84,28 @@ module ActionView
|
|
83
84
|
super(only_path: _generate_paths_by_default)
|
84
85
|
when Hash
|
85
86
|
options = options.symbolize_keys
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
end
|
87
|
+
ensure_only_path_option(options)
|
88
|
+
|
89
|
+
super(options)
|
90
|
+
when ActionController::Parameters
|
91
|
+
ensure_only_path_option(options)
|
93
92
|
|
94
93
|
super(options)
|
95
94
|
when :back
|
96
95
|
_back_url
|
97
96
|
when Array
|
98
97
|
components = options.dup
|
99
|
-
|
100
|
-
|
98
|
+
options = components.extract_options!
|
99
|
+
ensure_only_path_option(options)
|
100
|
+
|
101
|
+
if options[:only_path]
|
102
|
+
polymorphic_path(components, options)
|
101
103
|
else
|
102
|
-
polymorphic_url(components,
|
104
|
+
polymorphic_url(components, options)
|
103
105
|
end
|
104
106
|
else
|
105
107
|
method = _generate_paths_by_default ? :path : :url
|
106
|
-
builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.
|
108
|
+
builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.public_send(method)
|
107
109
|
|
108
110
|
case options
|
109
111
|
when Symbol
|
@@ -116,20 +118,29 @@ module ActionView
|
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
119
|
-
def url_options
|
121
|
+
def url_options # :nodoc:
|
120
122
|
return super unless controller.respond_to?(:url_options)
|
121
123
|
controller.url_options
|
122
124
|
end
|
123
125
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
126
|
+
private
|
127
|
+
def _routes_context
|
128
|
+
controller
|
129
|
+
end
|
128
130
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
131
|
+
def optimize_routes_generation?
|
132
|
+
controller.respond_to?(:optimize_routes_generation?, true) ?
|
133
|
+
controller.optimize_routes_generation? : super
|
134
|
+
end
|
135
|
+
|
136
|
+
def _generate_paths_by_default
|
137
|
+
true
|
138
|
+
end
|
139
|
+
|
140
|
+
def ensure_only_path_option(options)
|
141
|
+
unless options.key?(:only_path)
|
142
|
+
options[:only_path] = _generate_paths_by_default unless options[:host]
|
143
|
+
end
|
144
|
+
end
|
134
145
|
end
|
135
146
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :cache_digests do
|
4
|
+
desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
|
5
|
+
task nested_dependencies: :environment do
|
6
|
+
abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
|
7
|
+
puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
|
11
|
+
task dependencies: :environment do
|
12
|
+
abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
|
13
|
+
puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
|
14
|
+
end
|
15
|
+
|
16
|
+
class CacheDigests
|
17
|
+
def self.template_name
|
18
|
+
ENV["TEMPLATE"].split(".", 2).first
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.finder
|
22
|
+
ApplicationController.new.lookup_context
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,17 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/enumerable"
|
2
4
|
|
3
5
|
module ActionView
|
4
6
|
# = Action View Errors
|
5
|
-
class ActionViewError < StandardError
|
6
|
-
end
|
7
|
-
|
8
|
-
class EncodingError < StandardError #:nodoc:
|
7
|
+
class ActionViewError < StandardError # :nodoc:
|
9
8
|
end
|
10
9
|
|
11
|
-
class
|
10
|
+
class EncodingError < StandardError # :nodoc:
|
12
11
|
end
|
13
12
|
|
14
|
-
class WrongEncodingError < EncodingError
|
13
|
+
class WrongEncodingError < EncodingError # :nodoc:
|
15
14
|
def initialize(string, encoding)
|
16
15
|
@string, @encoding = string, encoding
|
17
16
|
end
|
@@ -27,45 +26,141 @@ module ActionView
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
class MissingTemplate < ActionViewError
|
31
|
-
attr_reader :path
|
29
|
+
class MissingTemplate < ActionViewError # :nodoc:
|
30
|
+
attr_reader :path, :paths, :prefixes, :partial
|
32
31
|
|
33
32
|
def initialize(paths, path, prefixes, partial, details, *)
|
33
|
+
if partial && path.present?
|
34
|
+
path = path.sub(%r{([^/]+)$}, "_\\1")
|
35
|
+
end
|
36
|
+
|
34
37
|
@path = path
|
35
|
-
|
38
|
+
@paths = paths
|
39
|
+
@prefixes = Array(prefixes)
|
40
|
+
@partial = partial
|
36
41
|
template_type = if partial
|
37
42
|
"partial"
|
38
|
-
elsif
|
39
|
-
|
43
|
+
elsif /layouts/i.match?(path)
|
44
|
+
"layout"
|
40
45
|
else
|
41
|
-
|
46
|
+
"template"
|
42
47
|
end
|
43
48
|
|
44
|
-
|
45
|
-
path = path.sub(%r{([^/]+)$}, "_\\1")
|
46
|
-
end
|
47
|
-
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
|
49
|
+
searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
|
48
50
|
|
49
|
-
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}
|
51
|
+
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n"
|
50
52
|
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
|
51
53
|
super out
|
52
54
|
end
|
55
|
+
|
56
|
+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro)
|
57
|
+
include DidYouMean::Correctable
|
58
|
+
|
59
|
+
class Results # :nodoc:
|
60
|
+
Result = Struct.new(:path, :score)
|
61
|
+
|
62
|
+
def initialize(size)
|
63
|
+
@size = size
|
64
|
+
@results = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_a
|
68
|
+
@results.map(&:path)
|
69
|
+
end
|
70
|
+
|
71
|
+
def should_record?(score)
|
72
|
+
if @results.size < @size
|
73
|
+
true
|
74
|
+
else
|
75
|
+
score < @results.last.score
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def add(path, score)
|
80
|
+
if should_record?(score)
|
81
|
+
@results << Result.new(path, score)
|
82
|
+
@results.sort_by!(&:score)
|
83
|
+
@results.pop if @results.size > @size
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Apps may have thousands of candidate templates so we attempt to
|
89
|
+
# generate the suggestions as efficiently as possible.
|
90
|
+
# First we split templates into prefixes and basenames, so that those can
|
91
|
+
# be matched separately.
|
92
|
+
def corrections
|
93
|
+
candidates = paths.flat_map(&:all_template_paths).uniq
|
94
|
+
|
95
|
+
if partial
|
96
|
+
candidates.select!(&:partial?)
|
97
|
+
else
|
98
|
+
candidates.reject!(&:partial?)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Group by possible prefixes
|
102
|
+
files_by_dir = candidates.group_by(&:prefix)
|
103
|
+
files_by_dir.transform_values! do |files|
|
104
|
+
files.map do |file|
|
105
|
+
# Remove prefix
|
106
|
+
File.basename(file.to_s)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# No suggestions if there's an exact match, but wrong details
|
111
|
+
if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) }
|
112
|
+
return []
|
113
|
+
end
|
114
|
+
|
115
|
+
cached_distance = Hash.new do |h, args|
|
116
|
+
h[args] = -DidYouMean::Jaro.distance(*args)
|
117
|
+
end
|
118
|
+
|
119
|
+
results = Results.new(6)
|
120
|
+
|
121
|
+
files_by_dir.keys.index_with do |dirname|
|
122
|
+
prefixes.map do |prefix|
|
123
|
+
cached_distance[[prefix, dirname]]
|
124
|
+
end.min
|
125
|
+
end.sort_by(&:last).each do |dirname, dirweight|
|
126
|
+
# If our directory's score makes it impossible to find a better match
|
127
|
+
# we can prune this search branch.
|
128
|
+
next unless results.should_record?(dirweight - 1.0)
|
129
|
+
|
130
|
+
files = files_by_dir[dirname]
|
131
|
+
|
132
|
+
files.each do |file|
|
133
|
+
fileweight = cached_distance[[path, file]]
|
134
|
+
score = dirweight + fileweight
|
135
|
+
|
136
|
+
results.add(File.join(dirname, file), score)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if partial
|
141
|
+
results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") }
|
142
|
+
else
|
143
|
+
results.to_a
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
53
147
|
end
|
54
148
|
|
55
149
|
class Template
|
56
150
|
# The Template::Error exception is raised when the compilation or rendering of the template
|
57
151
|
# fails. This exception then gathers a bunch of intimate details and uses it to report a
|
58
152
|
# precise exception message.
|
59
|
-
class Error < ActionViewError
|
153
|
+
class Error < ActionViewError # :nodoc:
|
60
154
|
SOURCE_CODE_RADIUS = 3
|
61
155
|
|
62
|
-
|
156
|
+
# Override to prevent #cause resetting during re-raise.
|
157
|
+
attr_reader :cause
|
63
158
|
|
64
|
-
def initialize(template
|
65
|
-
super(
|
66
|
-
|
67
|
-
@
|
68
|
-
|
159
|
+
def initialize(template)
|
160
|
+
super($!.message)
|
161
|
+
set_backtrace($!.backtrace)
|
162
|
+
@cause = $!
|
163
|
+
@template, @sub_templates = template, nil
|
69
164
|
end
|
70
165
|
|
71
166
|
def file_name
|
@@ -75,25 +170,25 @@ module ActionView
|
|
75
170
|
def sub_template_message
|
76
171
|
if @sub_templates
|
77
172
|
"Trace of template inclusion: " +
|
78
|
-
@sub_templates.collect
|
173
|
+
@sub_templates.collect(&:inspect).join(", ")
|
79
174
|
else
|
80
175
|
""
|
81
176
|
end
|
82
177
|
end
|
83
178
|
|
84
|
-
def source_extract(indentation = 0
|
85
|
-
return unless num = line_number
|
179
|
+
def source_extract(indentation = 0)
|
180
|
+
return [] unless num = line_number
|
86
181
|
num = num.to_i
|
87
182
|
|
88
|
-
source_code = @template.
|
183
|
+
source_code = @template.encode!.split("\n")
|
89
184
|
|
90
185
|
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
|
91
186
|
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
92
187
|
|
93
188
|
indent = end_on_line.to_s.size + indentation
|
94
|
-
return unless source_code = source_code[start_on_line..end_on_line]
|
189
|
+
return [] unless source_code = source_code[start_on_line..end_on_line]
|
95
190
|
|
96
|
-
formatted_code_for(source_code, start_on_line, indent
|
191
|
+
formatted_code_for(source_code, start_on_line, indent)
|
97
192
|
end
|
98
193
|
|
99
194
|
def sub_template_of(template_path)
|
@@ -109,33 +204,48 @@ module ActionView
|
|
109
204
|
end
|
110
205
|
end
|
111
206
|
|
112
|
-
def
|
207
|
+
def annotated_source_code
|
113
208
|
source_extract(4)
|
114
209
|
end
|
115
210
|
|
116
211
|
private
|
117
|
-
|
118
212
|
def source_location
|
119
213
|
if line_number
|
120
214
|
"on line ##{line_number} of "
|
121
215
|
else
|
122
|
-
|
216
|
+
"in "
|
123
217
|
end + file_name
|
124
218
|
end
|
125
219
|
|
126
|
-
def formatted_code_for(source_code, line_counter, indent
|
127
|
-
|
128
|
-
source_code.
|
220
|
+
def formatted_code_for(source_code, line_counter, indent)
|
221
|
+
indent_template = "%#{indent}s: %s"
|
222
|
+
source_code.map do |line|
|
129
223
|
line_counter += 1
|
130
|
-
|
131
|
-
result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
|
132
|
-
else
|
133
|
-
result << "%#{indent}s: %s\n" % [line_counter, line]
|
134
|
-
end
|
224
|
+
indent_template % [line_counter, line]
|
135
225
|
end
|
136
226
|
end
|
137
227
|
end
|
138
228
|
end
|
139
229
|
|
140
230
|
TemplateError = Template::Error
|
231
|
+
|
232
|
+
class SyntaxErrorInTemplate < TemplateError # :nodoc:
|
233
|
+
def initialize(template, offending_code_string)
|
234
|
+
@offending_code_string = offending_code_string
|
235
|
+
super(template)
|
236
|
+
end
|
237
|
+
|
238
|
+
def message
|
239
|
+
<<~MESSAGE
|
240
|
+
Encountered a syntax error while rendering template: check #{@offending_code_string}
|
241
|
+
MESSAGE
|
242
|
+
end
|
243
|
+
|
244
|
+
def annotated_source_code
|
245
|
+
@offending_code_string.split("\n").map.with_index(1) { |line, index|
|
246
|
+
indentation = " " * 4
|
247
|
+
"#{index}:#{indentation}#{line}"
|
248
|
+
}
|
249
|
+
end
|
250
|
+
end
|
141
251
|
end
|
@@ -1,26 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Template::Handlers
|
3
5
|
class Builder
|
4
|
-
|
5
|
-
class_attribute :default_format
|
6
|
-
self.default_format = :xml
|
6
|
+
class_attribute :default_format, default: :xml
|
7
7
|
|
8
|
-
def call(template)
|
8
|
+
def call(template, source)
|
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
|
+
source +
|
13
13
|
";xml.target!;"
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
private
|
17
|
+
def require_engine # :doc:
|
18
|
+
@required ||= begin
|
19
|
+
require "builder"
|
20
|
+
true
|
21
|
+
end
|
22
22
|
end
|
23
|
-
end
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|