omg-actionview 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- 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 +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- 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 +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- 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 +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- 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 +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- metadata +275 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
|
4
|
+
require "active_support/syntax_error_proxy"
|
|
5
|
+
|
|
6
|
+
module ActionView
|
|
7
|
+
# = Action View Errors
|
|
8
|
+
class ActionViewError < StandardError # :nodoc:
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class EncodingError < StandardError # :nodoc:
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class WrongEncodingError < EncodingError # :nodoc:
|
|
15
|
+
def initialize(string, encoding)
|
|
16
|
+
@string, @encoding = string, encoding
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
@string.force_encoding(Encoding::ASCII_8BIT)
|
|
21
|
+
"Your template was not saved as valid #{@encoding}. Please " \
|
|
22
|
+
"either specify #{@encoding} as the encoding for your template " \
|
|
23
|
+
"in your text editor, or mark the template with its " \
|
|
24
|
+
"encoding by inserting the following as the first line " \
|
|
25
|
+
"of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
|
|
26
|
+
"The source of your template was:\n\n#{@string}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class MissingTemplate < ActionViewError # :nodoc:
|
|
31
|
+
attr_reader :path, :paths, :prefixes, :partial
|
|
32
|
+
|
|
33
|
+
def initialize(paths, path, prefixes, partial, details, *)
|
|
34
|
+
if partial && path.present?
|
|
35
|
+
path = path.sub(%r{([^/]+)$}, "_\\1")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@path = path
|
|
39
|
+
@paths = paths
|
|
40
|
+
@prefixes = Array(prefixes)
|
|
41
|
+
@partial = partial
|
|
42
|
+
template_type = if partial
|
|
43
|
+
"partial"
|
|
44
|
+
elsif /layouts/i.match?(path)
|
|
45
|
+
"layout"
|
|
46
|
+
else
|
|
47
|
+
"template"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
|
|
51
|
+
|
|
52
|
+
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n"
|
|
53
|
+
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
|
|
54
|
+
super out
|
|
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
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class Template
|
|
151
|
+
# The Template::Error exception is raised when the compilation or rendering of the template
|
|
152
|
+
# fails. This exception then gathers a bunch of intimate details and uses it to report a
|
|
153
|
+
# precise exception message.
|
|
154
|
+
class Error < ActionViewError # :nodoc:
|
|
155
|
+
SOURCE_CODE_RADIUS = 3
|
|
156
|
+
|
|
157
|
+
# Override to prevent #cause resetting during re-raise.
|
|
158
|
+
attr_reader :cause
|
|
159
|
+
|
|
160
|
+
attr_reader :template
|
|
161
|
+
|
|
162
|
+
def initialize(template)
|
|
163
|
+
super($!.message)
|
|
164
|
+
@cause = $!
|
|
165
|
+
if @cause.is_a?(SyntaxError)
|
|
166
|
+
@cause = ActiveSupport::SyntaxErrorProxy.new(@cause)
|
|
167
|
+
end
|
|
168
|
+
@template, @sub_templates = template, nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def backtrace
|
|
172
|
+
@cause.backtrace
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def backtrace_locations
|
|
176
|
+
@cause.backtrace_locations
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def file_name
|
|
180
|
+
@template.identifier
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def sub_template_message
|
|
184
|
+
if @sub_templates
|
|
185
|
+
"Trace of template inclusion: " +
|
|
186
|
+
@sub_templates.collect(&:inspect).join(", ")
|
|
187
|
+
else
|
|
188
|
+
""
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def source_extract(indentation = 0)
|
|
193
|
+
return [] unless num = line_number
|
|
194
|
+
num = num.to_i
|
|
195
|
+
|
|
196
|
+
source_code = @template.encode!.split("\n")
|
|
197
|
+
|
|
198
|
+
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
|
|
199
|
+
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
|
200
|
+
|
|
201
|
+
indent = end_on_line.to_s.size + indentation
|
|
202
|
+
return [] unless source_code = source_code[start_on_line..end_on_line]
|
|
203
|
+
|
|
204
|
+
formatted_code_for(source_code, start_on_line, indent)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def sub_template_of(template_path)
|
|
208
|
+
@sub_templates ||= []
|
|
209
|
+
@sub_templates << template_path
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def line_number
|
|
213
|
+
@line_number ||=
|
|
214
|
+
if file_name
|
|
215
|
+
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
|
|
216
|
+
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def annotated_source_code
|
|
221
|
+
source_extract(4)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
def source_location
|
|
226
|
+
if line_number
|
|
227
|
+
"on line ##{line_number} of "
|
|
228
|
+
else
|
|
229
|
+
"in "
|
|
230
|
+
end + file_name
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def formatted_code_for(source_code, line_counter, indent)
|
|
234
|
+
indent_template = "%#{indent}s: %s"
|
|
235
|
+
source_code.map do |line|
|
|
236
|
+
line_counter += 1
|
|
237
|
+
indent_template % [line_counter, line]
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
TemplateError = Template::Error
|
|
244
|
+
|
|
245
|
+
class SyntaxErrorInTemplate < TemplateError # :nodoc:
|
|
246
|
+
def initialize(template, offending_code_string)
|
|
247
|
+
@offending_code_string = offending_code_string
|
|
248
|
+
super(template)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def message
|
|
252
|
+
<<~MESSAGE
|
|
253
|
+
Encountered a syntax error while rendering template: check #{@offending_code_string}
|
|
254
|
+
MESSAGE
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def annotated_source_code
|
|
258
|
+
@offending_code_string.split("\n").map.with_index(1) { |line, index|
|
|
259
|
+
indentation = " " * 4
|
|
260
|
+
"#{index}:#{indentation}#{line}"
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module Template::Handlers
|
|
5
|
+
class Builder
|
|
6
|
+
class_attribute :default_format, default: :xml
|
|
7
|
+
|
|
8
|
+
def call(template, source)
|
|
9
|
+
require_engine
|
|
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
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def require_engine # :doc:
|
|
18
|
+
@required ||= begin
|
|
19
|
+
require "builder"
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erubi"
|
|
4
|
+
|
|
5
|
+
module ActionView
|
|
6
|
+
class Template
|
|
7
|
+
module Handlers
|
|
8
|
+
class ERB
|
|
9
|
+
class Erubi < ::Erubi::Engine
|
|
10
|
+
# :nodoc: all
|
|
11
|
+
def initialize(input, properties = {})
|
|
12
|
+
@newline_pending = 0
|
|
13
|
+
|
|
14
|
+
# Dup properties so that we don't modify argument
|
|
15
|
+
properties = Hash[properties]
|
|
16
|
+
|
|
17
|
+
properties[:bufvar] ||= "@output_buffer"
|
|
18
|
+
properties[:preamble] ||= ""
|
|
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
|
|
23
|
+
|
|
24
|
+
properties[:escapefunc] = ""
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def add_text(text)
|
|
31
|
+
return if text.empty?
|
|
32
|
+
|
|
33
|
+
if text == "\n"
|
|
34
|
+
@newline_pending += 1
|
|
35
|
+
else
|
|
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
|
|
41
|
+
@newline_pending = 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
BLOCK_EXPR = /((\s|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
46
|
+
|
|
47
|
+
def add_expression(indicator, code)
|
|
48
|
+
flush_newline_if_pending(src)
|
|
49
|
+
|
|
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
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def add_code(code)
|
|
66
|
+
flush_newline_if_pending(src)
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_postamble(_)
|
|
71
|
+
flush_newline_if_pending(src)
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def flush_newline_if_pending(src)
|
|
76
|
+
if @newline_pending > 0
|
|
77
|
+
with_buffer { src << ".safe_append='#{"\n" * @newline_pending}" << @text_end }
|
|
78
|
+
@newline_pending = 0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
require "active_support/core_ext/erb/util"
|
|
5
|
+
|
|
6
|
+
module ActionView
|
|
7
|
+
class Template
|
|
8
|
+
module Handlers
|
|
9
|
+
class ERB
|
|
10
|
+
autoload :Erubi, "action_view/template/handlers/erb/erubi"
|
|
11
|
+
|
|
12
|
+
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
|
13
|
+
# See ERB documentation for suitable values.
|
|
14
|
+
class_attribute :erb_trim_mode, default: "-"
|
|
15
|
+
|
|
16
|
+
# Default implementation used.
|
|
17
|
+
class_attribute :erb_implementation, default: Erubi
|
|
18
|
+
|
|
19
|
+
# Do not escape templates of these mime types.
|
|
20
|
+
class_attribute :escape_ignore_list, default: ["text/plain"]
|
|
21
|
+
|
|
22
|
+
# Strip trailing newlines from rendered output
|
|
23
|
+
class_attribute :strip_trailing_newlines, default: false
|
|
24
|
+
|
|
25
|
+
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
|
|
26
|
+
|
|
27
|
+
LocationParsingError = Class.new(StandardError) # :nodoc:
|
|
28
|
+
|
|
29
|
+
def self.call(template, source)
|
|
30
|
+
new.call(template, source)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def supports_streaming?
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handles_encoding?
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Translate an error location returned by ErrorHighlight to the correct
|
|
42
|
+
# source location inside the template.
|
|
43
|
+
def translate_location(spot, backtrace_location, source)
|
|
44
|
+
# Tokenize the source line
|
|
45
|
+
tokens = ::ERB::Util.tokenize(source.lines[backtrace_location.lineno - 1])
|
|
46
|
+
new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
|
|
47
|
+
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
|
|
48
|
+
spot[:first_lineno] -= lineno_delta
|
|
49
|
+
spot[:last_lineno] -= lineno_delta
|
|
50
|
+
|
|
51
|
+
column_delta = spot[:first_column] - new_first_column
|
|
52
|
+
spot[:first_column] -= column_delta
|
|
53
|
+
spot[:last_column] -= column_delta
|
|
54
|
+
spot[:script_lines] = source.lines
|
|
55
|
+
|
|
56
|
+
spot
|
|
57
|
+
rescue NotImplementedError, LocationParsingError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call(template, source)
|
|
62
|
+
# First, convert to BINARY, so in case the encoding is
|
|
63
|
+
# wrong, we can still find an encoding tag
|
|
64
|
+
# (<%# encoding %>) inside the String using a regular
|
|
65
|
+
# expression
|
|
66
|
+
template_source = source.b
|
|
67
|
+
|
|
68
|
+
erb = template_source.gsub(ENCODING_TAG, "")
|
|
69
|
+
encoding = $2
|
|
70
|
+
|
|
71
|
+
erb.force_encoding valid_encoding(source.dup, encoding)
|
|
72
|
+
|
|
73
|
+
# Always make sure we return a String in the default_internal
|
|
74
|
+
erb.encode!
|
|
75
|
+
|
|
76
|
+
# Strip trailing newlines from the template if enabled
|
|
77
|
+
erb.chomp! if strip_trailing_newlines
|
|
78
|
+
|
|
79
|
+
options = {
|
|
80
|
+
escape: (self.class.escape_ignore_list.include? template.type),
|
|
81
|
+
trim: (self.class.erb_trim_mode == "-")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
|
|
85
|
+
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
|
|
86
|
+
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
self.class.erb_implementation.new(erb, options).src
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
def valid_encoding(string, encoding)
|
|
94
|
+
# If a magic encoding comment was found, tag the
|
|
95
|
+
# String with this encoding. This is for a case
|
|
96
|
+
# where the original String was assumed to be,
|
|
97
|
+
# for instance, UTF-8, but a magic comment
|
|
98
|
+
# proved otherwise
|
|
99
|
+
string.force_encoding(encoding) if encoding
|
|
100
|
+
|
|
101
|
+
# If the String is valid, return the encoding we found
|
|
102
|
+
return string.encoding if string.valid_encoding?
|
|
103
|
+
|
|
104
|
+
# Otherwise, raise an exception
|
|
105
|
+
raise WrongEncodingError.new(string, string.encoding)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_offset(compiled, source_tokens, error_column)
|
|
109
|
+
compiled = StringScanner.new(compiled)
|
|
110
|
+
|
|
111
|
+
passed_tokens = []
|
|
112
|
+
|
|
113
|
+
while tok = source_tokens.shift
|
|
114
|
+
tok_name, str = *tok
|
|
115
|
+
case tok_name
|
|
116
|
+
when :TEXT
|
|
117
|
+
loop do
|
|
118
|
+
break if compiled.match?(str)
|
|
119
|
+
compiled.getch
|
|
120
|
+
end
|
|
121
|
+
raise LocationParsingError unless compiled.scan(str)
|
|
122
|
+
when :CODE
|
|
123
|
+
if compiled.pos > error_column
|
|
124
|
+
raise LocationParsingError, "We went too far"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if compiled.pos + str.bytesize >= error_column
|
|
128
|
+
offset = error_column - compiled.pos
|
|
129
|
+
return passed_tokens.map(&:last).join.bytesize + offset
|
|
130
|
+
else
|
|
131
|
+
unless compiled.scan(str)
|
|
132
|
+
raise LocationParsingError, "Couldn't find code snippet"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
when :OPEN
|
|
136
|
+
next_tok = source_tokens.first.last
|
|
137
|
+
loop do
|
|
138
|
+
break if compiled.match?(next_tok)
|
|
139
|
+
compiled.getch
|
|
140
|
+
end
|
|
141
|
+
when :CLOSE
|
|
142
|
+
next_tok = source_tokens.first.last
|
|
143
|
+
loop do
|
|
144
|
+
break if compiled.match?(next_tok)
|
|
145
|
+
compiled.getch
|
|
146
|
+
end
|
|
147
|
+
else
|
|
148
|
+
raise LocationParsingError, "Not implemented: #{tok.first}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
passed_tokens << tok
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView # :nodoc:
|
|
4
|
+
class Template # :nodoc:
|
|
5
|
+
# = Action View Template Handlers
|
|
6
|
+
module Handlers # :nodoc:
|
|
7
|
+
autoload :Raw, "action_view/template/handlers/raw"
|
|
8
|
+
autoload :ERB, "action_view/template/handlers/erb"
|
|
9
|
+
autoload :Html, "action_view/template/handlers/html"
|
|
10
|
+
autoload :Builder, "action_view/template/handlers/builder"
|
|
11
|
+
|
|
12
|
+
def self.extended(base)
|
|
13
|
+
base.register_default_template_handler :raw, Raw.new
|
|
14
|
+
base.register_template_handler :erb, ERB.new
|
|
15
|
+
base.register_template_handler :html, Html.new
|
|
16
|
+
base.register_template_handler :builder, Builder.new
|
|
17
|
+
base.register_template_handler :ruby, lambda { |_, source| source }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@@template_handlers = {}
|
|
21
|
+
@@default_template_handlers = nil
|
|
22
|
+
|
|
23
|
+
def self.extensions
|
|
24
|
+
@@template_extensions ||= @@template_handlers.keys
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Register an object that knows how to handle template files with the given
|
|
28
|
+
# extensions. This can be used to implement new template types.
|
|
29
|
+
# The handler must respond to +:call+, which will be passed the template
|
|
30
|
+
# and should return the rendered template as a String.
|
|
31
|
+
def register_template_handler(*extensions, handler)
|
|
32
|
+
raise(ArgumentError, "Extension is required") if extensions.empty?
|
|
33
|
+
extensions.each do |extension|
|
|
34
|
+
@@template_handlers[extension.to_sym] = handler
|
|
35
|
+
end
|
|
36
|
+
@@template_extensions = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Opposite to register_template_handler.
|
|
40
|
+
def unregister_template_handler(*extensions)
|
|
41
|
+
extensions.each do |extension|
|
|
42
|
+
handler = @@template_handlers.delete extension.to_sym
|
|
43
|
+
@@default_template_handlers = nil if @@default_template_handlers == handler
|
|
44
|
+
end
|
|
45
|
+
@@template_extensions = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def template_handler_extensions
|
|
49
|
+
@@template_handlers.keys.map(&:to_s).sort
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def registered_template_handler(extension)
|
|
53
|
+
extension && @@template_handlers[extension.to_sym]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def register_default_template_handler(extension, klass)
|
|
57
|
+
register_template_handler(extension, klass)
|
|
58
|
+
@@default_template_handlers = klass
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def handler_for_extension(extension)
|
|
62
|
+
registered_template_handler(extension) || @@default_template_handlers
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView # :nodoc:
|
|
4
|
+
class Template # :nodoc:
|
|
5
|
+
# = Action View HTML Template
|
|
6
|
+
class HTML # :nodoc:
|
|
7
|
+
attr_reader :type
|
|
8
|
+
|
|
9
|
+
def initialize(string, type)
|
|
10
|
+
@string = string.to_s
|
|
11
|
+
@type = type
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def identifier
|
|
15
|
+
"html template"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias_method :inspect, :identifier
|
|
19
|
+
|
|
20
|
+
def to_str
|
|
21
|
+
ERB::Util.h(@string)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render(*args)
|
|
25
|
+
to_str
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def format
|
|
29
|
+
@type
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView # :nodoc:
|
|
4
|
+
class Template # :nodoc:
|
|
5
|
+
class Inline < Template # :nodoc:
|
|
6
|
+
# This finalizer is needed (and exactly with a proc inside another proc)
|
|
7
|
+
# otherwise templates leak in development.
|
|
8
|
+
Finalizer = proc do |method_name, mod| # :nodoc:
|
|
9
|
+
proc do
|
|
10
|
+
mod.module_eval do
|
|
11
|
+
remove_possible_method method_name
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def compile(mod)
|
|
17
|
+
super
|
|
18
|
+
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView # :nodoc:
|
|
4
|
+
class Template # :nodoc:
|
|
5
|
+
# = Action View RawFile Template
|
|
6
|
+
class RawFile # :nodoc:
|
|
7
|
+
attr_accessor :type, :format
|
|
8
|
+
|
|
9
|
+
def initialize(filename)
|
|
10
|
+
@filename = filename.to_s
|
|
11
|
+
extname = ::File.extname(filename).delete(".")
|
|
12
|
+
@type = Template::Types[extname] || Template::Types[:text]
|
|
13
|
+
@format = @type.symbol
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def identifier
|
|
17
|
+
@filename
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render(*args)
|
|
21
|
+
::File.read(@filename)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|