actionview 4.2.11.1 → 6.1.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +232 -186
- data/MIT-LICENSE +1 -2
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +115 -39
- data/lib/action_view/buffers.rb +18 -1
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -85
- data/lib/action_view/flows.rb +11 -12
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +282 -83
- data/lib/action_view/helpers/asset_url_helper.rb +175 -69
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +107 -43
- data/lib/action_view/helpers/capture_helper.rb +20 -13
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- 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 +232 -130
- data/lib/action_view/helpers/debug_helper.rb +7 -6
- data/lib/action_view/helpers/form_helper.rb +808 -146
- data/lib/action_view/helpers/form_options_helper.rb +124 -78
- data/lib/action_view/helpers/form_tag_helper.rb +120 -74
- data/lib/action_view/helpers/javascript_helper.rb +33 -17
- data/lib/action_view/helpers/number_helper.rb +87 -62
- data/lib/action_view/helpers/output_safety_helper.rb +36 -4
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +30 -31
- data/lib/action_view/helpers/tag_helper.rb +269 -68
- data/lib/action_view/helpers/tags/base.rb +141 -97
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- 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 +4 -2
- 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 +2 -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 +3 -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.rb +3 -1
- data/lib/action_view/helpers/text_helper.rb +56 -38
- data/lib/action_view/helpers/translation_helper.rb +150 -68
- data/lib/action_view/helpers/url_helper.rb +284 -117
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +134 -91
- data/lib/action_view/model_naming.rb +3 -1
- data/lib/action_view/path_set.rb +26 -24
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +151 -14
- 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 +55 -303
- data/lib/action_view/renderer/renderer.rb +66 -9
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +82 -73
- data/lib/action_view/rendering.rb +71 -45
- data/lib/action_view/routing_url_for.rb +34 -23
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- 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 +23 -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 +12 -8
- data/lib/action_view/template/html.rb +10 -11
- 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 +263 -197
- 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 +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +108 -92
- data/lib/action_view/test_case.rb +66 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +14 -8
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +42 -29
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pathname"
|
2
4
|
require "active_support/core_ext/class"
|
3
5
|
require "active_support/core_ext/module/attribute_accessors"
|
4
|
-
require 'active_support/core_ext/string/filters'
|
5
6
|
require "action_view/template"
|
6
7
|
require "thread"
|
7
|
-
require "
|
8
|
+
require "concurrent/map"
|
8
9
|
|
9
10
|
module ActionView
|
10
11
|
# = Action View Resolver
|
@@ -15,7 +16,7 @@ module ActionView
|
|
15
16
|
alias_method :partial?, :partial
|
16
17
|
|
17
18
|
def self.build(name, prefix, partial)
|
18
|
-
virtual = ""
|
19
|
+
virtual = +""
|
19
20
|
virtual << "#{prefix}/" unless prefix.empty?
|
20
21
|
virtual << (partial ? "_#{name}" : name)
|
21
22
|
new name, prefix, partial, virtual
|
@@ -34,69 +35,105 @@ module ActionView
|
|
34
35
|
alias :to_s :to_str
|
35
36
|
end
|
36
37
|
|
38
|
+
class PathParser # :nodoc:
|
39
|
+
def build_path_regex
|
40
|
+
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
|
41
|
+
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
|
42
|
+
locales = "[a-z]{2}(?:-[A-Z]{2})?"
|
43
|
+
variants = "[^.]*"
|
44
|
+
|
45
|
+
%r{
|
46
|
+
\A
|
47
|
+
(?:(?<prefix>.*)/)?
|
48
|
+
(?<partial>_)?
|
49
|
+
(?<action>.*?)
|
50
|
+
(?:\.(?<locale>#{locales}))??
|
51
|
+
(?:\.(?<format>#{formats}))??
|
52
|
+
(?:\+(?<variant>#{variants}))??
|
53
|
+
(?:\.(?<handler>#{handlers}))?
|
54
|
+
\z
|
55
|
+
}x
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse(path)
|
59
|
+
@regex ||= build_path_regex
|
60
|
+
match = @regex.match(path)
|
61
|
+
{
|
62
|
+
prefix: match[:prefix] || "",
|
63
|
+
action: match[:action],
|
64
|
+
partial: !!match[:partial],
|
65
|
+
locale: match[:locale]&.to_sym,
|
66
|
+
handler: match[:handler]&.to_sym,
|
67
|
+
format: match[:format]&.to_sym,
|
68
|
+
variant: match[:variant]
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
37
73
|
# Threadsafe template cache
|
38
74
|
class Cache #:nodoc:
|
39
|
-
class SmallCache <
|
75
|
+
class SmallCache < Concurrent::Map
|
40
76
|
def initialize(options = {})
|
41
|
-
super(options.merge(:
|
77
|
+
super(options.merge(initial_capacity: 2))
|
42
78
|
end
|
43
79
|
end
|
44
80
|
|
45
|
-
#
|
46
|
-
PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
|
47
|
-
PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
|
48
|
-
NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
|
49
|
-
KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
|
81
|
+
# Preallocate all the default blocks for performance/memory consumption reasons
|
82
|
+
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
|
83
|
+
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
|
84
|
+
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
|
85
|
+
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
|
50
86
|
|
51
|
-
#
|
87
|
+
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
52
88
|
NO_TEMPLATES = [].freeze
|
53
89
|
|
54
90
|
def initialize
|
55
91
|
@data = SmallCache.new(&KEY_BLOCK)
|
92
|
+
@query_cache = SmallCache.new
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
|
56
97
|
end
|
57
98
|
|
58
99
|
# Cache the templates returned by the block
|
59
100
|
def cache(key, name, prefix, partial, locals)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if templates_have_changed?(cached_templates, fresh_templates)
|
67
|
-
@data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
|
68
|
-
else
|
69
|
-
cached_templates || NO_TEMPLATES
|
70
|
-
end
|
71
|
-
end
|
101
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
102
|
+
end
|
103
|
+
|
104
|
+
def cache_query(query) # :nodoc:
|
105
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
72
106
|
end
|
73
107
|
|
74
108
|
def clear
|
75
109
|
@data.clear
|
110
|
+
@query_cache.clear
|
76
111
|
end
|
77
112
|
|
78
|
-
|
113
|
+
# Get the cache size. Do not call this
|
114
|
+
# method. This method is not guaranteed to be here ever.
|
115
|
+
def size # :nodoc:
|
116
|
+
size = 0
|
117
|
+
@data.each_value do |v1|
|
118
|
+
v1.each_value do |v2|
|
119
|
+
v2.each_value do |v3|
|
120
|
+
v3.each_value do |v4|
|
121
|
+
size += v4.size
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
79
126
|
|
80
|
-
|
81
|
-
templates.empty? ? NO_TEMPLATES : templates
|
127
|
+
size + @query_cache.size
|
82
128
|
end
|
83
129
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
if cached_templates.blank? || fresh_templates.blank?
|
88
|
-
return fresh_templates.blank? != cached_templates.blank?
|
130
|
+
private
|
131
|
+
def canonical_no_templates(templates)
|
132
|
+
templates.empty? ? NO_TEMPLATES : templates
|
89
133
|
end
|
90
|
-
|
91
|
-
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
|
92
|
-
|
93
|
-
# if a template has changed, it will be now be newer than all the cached templates
|
94
|
-
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
|
95
|
-
end
|
96
134
|
end
|
97
135
|
|
98
|
-
cattr_accessor :caching
|
99
|
-
self.caching = true
|
136
|
+
cattr_accessor :caching, default: true
|
100
137
|
|
101
138
|
class << self
|
102
139
|
alias :caching? :caching
|
@@ -111,220 +148,183 @@ module ActionView
|
|
111
148
|
end
|
112
149
|
|
113
150
|
# Normalizes the arguments and passes it on to find_templates.
|
114
|
-
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
|
151
|
+
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
152
|
+
locals = locals.map(&:to_s).sort!.freeze
|
153
|
+
|
115
154
|
cached(key, [name, prefix, partial], details, locals) do
|
116
|
-
|
155
|
+
_find_all(name, prefix, partial, details, key, locals)
|
117
156
|
end
|
118
157
|
end
|
119
158
|
|
120
|
-
def
|
121
|
-
|
122
|
-
find_templates(name, prefix, partial, details, true)
|
123
|
-
end
|
159
|
+
def find_all_with_query(query) # :nodoc:
|
160
|
+
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
124
161
|
end
|
125
162
|
|
126
163
|
private
|
164
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
165
|
+
find_templates(name, prefix, partial, details, locals)
|
166
|
+
end
|
127
167
|
|
128
168
|
delegate :caching?, to: :class
|
129
169
|
|
130
170
|
# This is what child classes implement. No defaults are needed
|
131
171
|
# because Resolver guarantees that the arguments are present and
|
132
172
|
# normalized.
|
133
|
-
def find_templates(name, prefix, partial, details,
|
134
|
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details,
|
135
|
-
end
|
136
|
-
|
137
|
-
# Helpers that builds a path. Useful for building virtual paths.
|
138
|
-
def build_path(name, prefix, partial)
|
139
|
-
Path.build(name, prefix, partial)
|
173
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
174
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
140
175
|
end
|
141
176
|
|
142
177
|
# Handles templates caching. If a key is given and caching is on
|
143
178
|
# always check the cache before hitting the resolver. Otherwise,
|
144
179
|
# it always hits the resolver but if the key is present, check if the
|
145
180
|
# resolver is fresher before returning it.
|
146
|
-
def cached(key, path_info, details, locals)
|
181
|
+
def cached(key, path_info, details, locals)
|
147
182
|
name, prefix, partial = path_info
|
148
|
-
locals = locals.map { |x| x.to_s }.sort!
|
149
183
|
|
150
184
|
if key
|
151
185
|
@cache.cache(key, name, prefix, partial, locals) do
|
152
|
-
|
186
|
+
yield
|
153
187
|
end
|
154
188
|
else
|
155
|
-
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# Ensures all the resolver information is set in the template.
|
160
|
-
def decorate(templates, path_info, details, locals) #:nodoc:
|
161
|
-
cached = nil
|
162
|
-
templates.each do |t|
|
163
|
-
t.locals = locals
|
164
|
-
t.formats = details[:formats] || [:html] if t.formats.empty?
|
165
|
-
t.variants = details[:variants] || [] if t.variants.empty?
|
166
|
-
t.virtual_path ||= (cached ||= build_path(*path_info))
|
189
|
+
yield
|
167
190
|
end
|
168
191
|
end
|
169
192
|
end
|
170
193
|
|
171
194
|
# An abstract class that implements a Resolver with path semantics.
|
172
195
|
class PathResolver < Resolver #:nodoc:
|
173
|
-
EXTENSIONS = { :
|
196
|
+
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
174
197
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
175
198
|
|
176
|
-
def initialize
|
177
|
-
@pattern =
|
178
|
-
|
199
|
+
def initialize
|
200
|
+
@pattern = DEFAULT_PATTERN
|
201
|
+
@unbound_templates = Concurrent::Map.new
|
202
|
+
@path_parser = PathParser.new
|
203
|
+
super
|
179
204
|
end
|
180
205
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
query(path, details, details[:formats], outside_app_allowed)
|
206
|
+
def clear_cache
|
207
|
+
@unbound_templates.clear
|
208
|
+
@path_parser = PathParser.new
|
209
|
+
super
|
186
210
|
end
|
187
211
|
|
188
|
-
|
189
|
-
|
212
|
+
private
|
213
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
214
|
+
path = Path.build(name, prefix, partial)
|
215
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
216
|
+
end
|
190
217
|
|
191
|
-
|
192
|
-
|
218
|
+
def query(path, details, formats, locals, cache:)
|
219
|
+
template_paths = find_template_paths_from_details(path, details)
|
220
|
+
template_paths = reject_files_external_to_app(template_paths)
|
221
|
+
|
222
|
+
template_paths.map do |template|
|
223
|
+
unbound_template =
|
224
|
+
if cache
|
225
|
+
@unbound_templates.compute_if_absent([template, path.virtual]) do
|
226
|
+
build_unbound_template(template, path.virtual)
|
227
|
+
end
|
228
|
+
else
|
229
|
+
build_unbound_template(template, path.virtual)
|
230
|
+
end
|
231
|
+
|
232
|
+
unbound_template.bind_locals(locals)
|
233
|
+
end
|
234
|
+
end
|
193
235
|
|
194
|
-
|
195
|
-
|
196
|
-
|
236
|
+
def source_for_template(template)
|
237
|
+
Template::Sources::File.new(template)
|
238
|
+
end
|
197
239
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
240
|
+
def build_unbound_template(template, virtual_path)
|
241
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
242
|
+
source = source_for_template(template)
|
243
|
+
|
244
|
+
UnboundTemplate.new(
|
245
|
+
source,
|
246
|
+
template,
|
247
|
+
handler,
|
248
|
+
virtual_path: virtual_path,
|
249
|
+
format: format,
|
250
|
+
variant: variant,
|
203
251
|
)
|
204
|
-
|
205
|
-
end
|
252
|
+
end
|
206
253
|
|
207
|
-
|
208
|
-
|
209
|
-
|
254
|
+
def reject_files_external_to_app(files)
|
255
|
+
files.reject { |filename| !inside_path?(@path, filename) }
|
256
|
+
end
|
257
|
+
|
258
|
+
def find_template_paths_from_details(path, details)
|
259
|
+
if path.name.include?(".")
|
260
|
+
ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
|
261
|
+
end
|
262
|
+
|
263
|
+
query = build_query(path, details)
|
264
|
+
find_template_paths(query)
|
265
|
+
end
|
210
266
|
|
211
|
-
if RUBY_VERSION >= '2.2.0'
|
212
267
|
def find_template_paths(query)
|
213
|
-
Dir[query].reject
|
268
|
+
Dir[query].uniq.reject do |filename|
|
214
269
|
File.directory?(filename) ||
|
215
270
|
# deals with case-insensitive file systems.
|
216
271
|
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
|
217
|
-
|
272
|
+
end
|
218
273
|
end
|
219
|
-
else
|
220
|
-
def find_template_paths(query)
|
221
|
-
# deals with case-insensitive file systems.
|
222
|
-
sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
|
223
274
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
275
|
+
def inside_path?(path, filename)
|
276
|
+
filename = File.expand_path(filename)
|
277
|
+
path = File.join(path, "")
|
278
|
+
filename.start_with?(path)
|
228
279
|
end
|
229
|
-
end
|
230
280
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
filename.start_with?(path)
|
235
|
-
end
|
281
|
+
# Helper for building query glob string based on resolver's pattern.
|
282
|
+
def build_query(path, details)
|
283
|
+
query = @pattern.dup
|
236
284
|
|
237
|
-
|
238
|
-
|
239
|
-
query = @pattern.dup
|
285
|
+
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
|
286
|
+
query.gsub!(/:prefix(\/)?/, prefix)
|
240
287
|
|
241
|
-
|
242
|
-
|
288
|
+
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
289
|
+
query.gsub!(":action", partial)
|
243
290
|
|
244
|
-
|
245
|
-
|
291
|
+
details.each do |ext, candidates|
|
292
|
+
if ext == :variants && candidates == :any
|
293
|
+
query.gsub!(/:#{ext}/, "*")
|
294
|
+
else
|
295
|
+
query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
|
296
|
+
end
|
297
|
+
end
|
246
298
|
|
247
|
-
|
248
|
-
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
|
299
|
+
File.expand_path(query, @path)
|
249
300
|
end
|
250
301
|
|
251
|
-
|
252
|
-
|
302
|
+
def escape_entry(entry)
|
303
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
304
|
+
end
|
253
305
|
|
254
|
-
|
255
|
-
|
256
|
-
|
306
|
+
# Extract handler, formats and variant from path. If a format cannot be found neither
|
307
|
+
# from the path, or the handler, we should return the array of formats given
|
308
|
+
# to the resolver.
|
309
|
+
def extract_handler_and_format_and_variant(path)
|
310
|
+
details = @path_parser.parse(path)
|
257
311
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
end
|
312
|
+
handler = Template.handler_for_extension(details[:handler])
|
313
|
+
format = details[:format] || handler.try(:default_format)
|
314
|
+
variant = details[:variant]
|
262
315
|
|
263
|
-
|
264
|
-
|
265
|
-
# to the resolver.
|
266
|
-
def extract_handler_and_format_and_variant(path, default_formats)
|
267
|
-
pieces = File.basename(path).split(".")
|
268
|
-
pieces.shift
|
269
|
-
|
270
|
-
extension = pieces.pop
|
271
|
-
unless extension
|
272
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
273
|
-
The file #{path} did not specify a template handler. The default is
|
274
|
-
currently ERB, but will change to RAW in the future.
|
275
|
-
MSG
|
316
|
+
# Template::Types[format] and handler.default_format can return nil
|
317
|
+
[handler, format, variant]
|
276
318
|
end
|
277
|
-
|
278
|
-
handler = Template.handler_for_extension(extension)
|
279
|
-
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
280
|
-
format &&= Template::Types[format]
|
281
|
-
|
282
|
-
[handler, format, variant]
|
283
|
-
end
|
284
319
|
end
|
285
320
|
|
286
|
-
# A resolver that loads files from the filesystem.
|
287
|
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
|
288
|
-
#
|
289
|
-
# ==== Examples
|
290
|
-
#
|
291
|
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
|
292
|
-
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
|
293
|
-
#
|
294
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
295
|
-
#
|
296
|
-
# This one allows you to keep files with different formats in separate subdirectories,
|
297
|
-
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
|
298
|
-
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
|
299
|
-
#
|
300
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
301
|
-
#
|
302
|
-
# If you don't specify a pattern then the default will be used.
|
303
|
-
#
|
304
|
-
# In order to use any of the customized resolvers above in a Rails application, you just need
|
305
|
-
# to configure ActionController::Base.view_paths in an initializer, for example:
|
306
|
-
#
|
307
|
-
# ActionController::Base.view_paths = FileSystemResolver.new(
|
308
|
-
# Rails.root.join("app/views"),
|
309
|
-
# ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}"
|
310
|
-
# )
|
311
|
-
#
|
312
|
-
# ==== Pattern format and variables
|
313
|
-
#
|
314
|
-
# Pattern has to be a valid glob string, and it allows you to use the
|
315
|
-
# following variables:
|
316
|
-
#
|
317
|
-
# * <tt>:prefix</tt> - usually the controller path
|
318
|
-
# * <tt>:action</tt> - name of the action
|
319
|
-
# * <tt>:locale</tt> - possible locale versions
|
320
|
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
|
321
|
-
# * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
|
322
|
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
323
|
-
#
|
321
|
+
# A resolver that loads files from the filesystem.
|
324
322
|
class FileSystemResolver < PathResolver
|
325
|
-
|
323
|
+
attr_reader :path
|
324
|
+
|
325
|
+
def initialize(path)
|
326
326
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
327
|
-
super(
|
327
|
+
super()
|
328
328
|
@path = File.expand_path(path)
|
329
329
|
end
|
330
330
|
|
@@ -341,26 +341,92 @@ module ActionView
|
|
341
341
|
|
342
342
|
# An Optimized resolver for Rails' most common case.
|
343
343
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
344
|
-
def
|
345
|
-
|
344
|
+
def initialize(path)
|
345
|
+
super(path)
|
346
|
+
end
|
346
347
|
|
347
|
-
|
348
|
-
|
349
|
-
|
348
|
+
private
|
349
|
+
def find_candidate_template_paths(path)
|
350
|
+
# Instead of checking for every possible path, as our other globs would
|
351
|
+
# do, scan the directory for files with the right prefix.
|
352
|
+
query = "#{escape_entry(File.join(@path, path))}*"
|
350
353
|
|
351
|
-
|
352
|
-
|
354
|
+
Dir[query].reject do |filename|
|
355
|
+
File.directory?(filename)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def find_template_paths_from_details(path, details)
|
360
|
+
if path.name.include?(".")
|
361
|
+
# Fall back to the unoptimized resolver, which will warn
|
362
|
+
return super
|
363
|
+
end
|
364
|
+
|
365
|
+
candidates = find_candidate_template_paths(path)
|
366
|
+
|
367
|
+
regex = build_regex(path, details)
|
368
|
+
|
369
|
+
candidates.uniq.reject do |filename|
|
370
|
+
# This regex match does double duty of finding only files which match
|
371
|
+
# details (instead of just matching the prefix) and also filtering for
|
372
|
+
# case-insensitive file systems.
|
373
|
+
!regex.match?(filename) ||
|
374
|
+
File.directory?(filename)
|
375
|
+
end.sort_by do |filename|
|
376
|
+
# Because we scanned the directory, instead of checking for files
|
377
|
+
# one-by-one, they will be returned in an arbitrary order.
|
378
|
+
# We can use the matches found by the regex and sort by their index in
|
379
|
+
# details.
|
380
|
+
match = filename.match(regex)
|
381
|
+
EXTENSIONS.keys.map do |ext|
|
382
|
+
if ext == :variants && details[ext] == :any
|
383
|
+
match[ext].nil? ? 0 : 1
|
384
|
+
elsif match[ext].nil?
|
385
|
+
# No match should be last
|
386
|
+
details[ext].length
|
387
|
+
else
|
388
|
+
found = match[ext].to_sym
|
389
|
+
details[ext].index(found)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def build_regex(path, details)
|
396
|
+
query = Regexp.escape(File.join(@path, path))
|
397
|
+
exts = EXTENSIONS.map do |ext, prefix|
|
398
|
+
match =
|
399
|
+
if ext == :variants && details[ext] == :any
|
400
|
+
".*?"
|
401
|
+
else
|
402
|
+
arr = details[ext].compact
|
403
|
+
arr.uniq!
|
404
|
+
arr.map! { |e| Regexp.escape(e) }
|
405
|
+
arr.join("|")
|
406
|
+
end
|
407
|
+
prefix = Regexp.escape(prefix)
|
408
|
+
"(#{prefix}(?<#{ext}>#{match}))?"
|
409
|
+
end.join
|
410
|
+
|
411
|
+
%r{\A#{query}#{exts}\z}
|
412
|
+
end
|
353
413
|
end
|
354
414
|
|
355
415
|
# The same as FileSystemResolver but does not allow templates to store
|
356
416
|
# a virtual path since it is invalid for such resolvers.
|
357
417
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
418
|
+
private_class_method :new
|
419
|
+
|
358
420
|
def self.instances
|
359
421
|
[new(""), new("/")]
|
360
422
|
end
|
361
423
|
|
362
|
-
def
|
363
|
-
super
|
424
|
+
def build_unbound_template(template, _)
|
425
|
+
super(template, nil)
|
426
|
+
end
|
427
|
+
|
428
|
+
def reject_files_external_to_app(files)
|
429
|
+
files
|
364
430
|
end
|
365
431
|
end
|
366
432
|
end
|
@@ -1,22 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView #:nodoc:
|
2
4
|
# = Action View Text Template
|
3
|
-
class Template
|
5
|
+
class Template #:nodoc:
|
4
6
|
class Text #:nodoc:
|
5
7
|
attr_accessor :type
|
6
8
|
|
7
|
-
def initialize(string
|
9
|
+
def initialize(string)
|
8
10
|
@string = string.to_s
|
9
|
-
@type = Types[type] || type if type
|
10
|
-
@type ||= Types[:text]
|
11
11
|
end
|
12
12
|
|
13
13
|
def identifier
|
14
|
-
|
14
|
+
"text template"
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
'text template'
|
19
|
-
end
|
17
|
+
alias_method :inspect, :identifier
|
20
18
|
|
21
19
|
def to_str
|
22
20
|
@string
|
@@ -26,8 +24,8 @@ module ActionView #:nodoc:
|
|
26
24
|
to_str
|
27
25
|
end
|
28
26
|
|
29
|
-
def
|
30
|
-
|
27
|
+
def format
|
28
|
+
:text
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|