actionview 6.0.0
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 +7 -0
- data/CHANGELOG.md +271 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/lib/action_view.rb +98 -0
- data/lib/action_view/base.rb +312 -0
- data/lib/action_view/buffers.rb +67 -0
- data/lib/action_view/cache_expiry.rb +54 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +175 -0
- data/lib/action_view/digestor.rb +126 -0
- data/lib/action_view/flows.rb +76 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers.rb +66 -0
- data/lib/action_view/helpers/active_model_helper.rb +55 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
- data/lib/action_view/helpers/asset_url_helper.rb +470 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +271 -0
- data/lib/action_view/helpers/capture_helper.rb +216 -0
- data/lib/action_view/helpers/controller_helper.rb +36 -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 +1200 -0
- data/lib/action_view/helpers/debug_helper.rb +36 -0
- data/lib/action_view/helpers/form_helper.rb +2569 -0
- data/lib/action_view/helpers/form_options_helper.rb +896 -0
- data/lib/action_view/helpers/form_tag_helper.rb +920 -0
- data/lib/action_view/helpers/javascript_helper.rb +95 -0
- data/lib/action_view/helpers/number_helper.rb +456 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +101 -0
- data/lib/action_view/helpers/sanitize_helper.rb +171 -0
- data/lib/action_view/helpers/tag_helper.rb +314 -0
- data/lib/action_view/helpers/tags.rb +44 -0
- data/lib/action_view/helpers/tags/base.rb +196 -0
- data/lib/action_view/helpers/tags/check_box.rb +66 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +30 -0
- data/lib/action_view/helpers/tags/color_field.rb +27 -0
- data/lib/action_view/helpers/tags/date_field.rb +15 -0
- data/lib/action_view/helpers/tags/date_select.rb +74 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -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 +10 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
- data/lib/action_view/helpers/tags/label.rb +81 -0
- data/lib/action_view/helpers/tags/month_field.rb +15 -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 +33 -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 +43 -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 +34 -0
- data/lib/action_view/helpers/tags/time_field.rb +15 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +22 -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 +15 -0
- data/lib/action_view/helpers/text_helper.rb +486 -0
- data/lib/action_view/helpers/translation_helper.rb +145 -0
- data/lib/action_view/helpers/url_helper.rb +676 -0
- data/lib/action_view/layouts.rb +433 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +96 -0
- data/lib/action_view/lookup_context.rb +316 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_set.rb +95 -0
- data/lib/action_view/railtie.rb +105 -0
- data/lib/action_view/record_identifier.rb +112 -0
- data/lib/action_view/renderer/abstract_renderer.rb +108 -0
- data/lib/action_view/renderer/partial_renderer.rb +563 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
- data/lib/action_view/renderer/renderer.rb +68 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
- data/lib/action_view/renderer/template_renderer.rb +108 -0
- data/lib/action_view/rendering.rb +171 -0
- data/lib/action_view/routing_url_for.rb +146 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template.rb +393 -0
- data/lib/action_view/template/error.rb +161 -0
- data/lib/action_view/template/handlers.rb +92 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb.rb +84 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +87 -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/html.rb +43 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +394 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +35 -0
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/test_case.rb +300 -0
- data/lib/action_view/testing/resolvers.rb +67 -0
- data/lib/action_view/unbound_template.rb +32 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +129 -0
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +260 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/deprecation"
|
4
|
+
|
5
|
+
module ActionView #:nodoc:
|
6
|
+
# = Action View HTML Template
|
7
|
+
class Template #:nodoc:
|
8
|
+
class HTML #:nodoc:
|
9
|
+
attr_reader :type
|
10
|
+
|
11
|
+
def initialize(string, type = nil)
|
12
|
+
unless type
|
13
|
+
ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter"
|
14
|
+
type = :html
|
15
|
+
end
|
16
|
+
|
17
|
+
@string = string.to_s
|
18
|
+
@type = type
|
19
|
+
end
|
20
|
+
|
21
|
+
def identifier
|
22
|
+
"html template"
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :inspect, :identifier
|
26
|
+
|
27
|
+
def to_str
|
28
|
+
ERB::Util.h(@string)
|
29
|
+
end
|
30
|
+
|
31
|
+
def render(*args)
|
32
|
+
to_str
|
33
|
+
end
|
34
|
+
|
35
|
+
def format
|
36
|
+
@type
|
37
|
+
end
|
38
|
+
|
39
|
+
def formats; Array(format); end
|
40
|
+
deprecate :formats
|
41
|
+
end
|
42
|
+
end
|
43
|
+
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,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
# = Action View RawFile Template
|
5
|
+
class Template #:nodoc:
|
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
|
+
|
24
|
+
def formats; Array(format); end
|
25
|
+
deprecate :formats
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "active_support/core_ext/class"
|
5
|
+
require "active_support/core_ext/module/attribute_accessors"
|
6
|
+
require "action_view/template"
|
7
|
+
require "thread"
|
8
|
+
require "concurrent/map"
|
9
|
+
|
10
|
+
module ActionView
|
11
|
+
# = Action View Resolver
|
12
|
+
class Resolver
|
13
|
+
# Keeps all information about view path and builds virtual path.
|
14
|
+
class Path
|
15
|
+
attr_reader :name, :prefix, :partial, :virtual
|
16
|
+
alias_method :partial?, :partial
|
17
|
+
|
18
|
+
def self.build(name, prefix, partial)
|
19
|
+
virtual = +""
|
20
|
+
virtual << "#{prefix}/" unless prefix.empty?
|
21
|
+
virtual << (partial ? "_#{name}" : name)
|
22
|
+
new name, prefix, partial, virtual
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(name, prefix, partial, virtual)
|
26
|
+
@name = name
|
27
|
+
@prefix = prefix
|
28
|
+
@partial = partial
|
29
|
+
@virtual = virtual
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_str
|
33
|
+
@virtual
|
34
|
+
end
|
35
|
+
alias :to_s :to_str
|
36
|
+
end
|
37
|
+
|
38
|
+
# Threadsafe template cache
|
39
|
+
class Cache #:nodoc:
|
40
|
+
class SmallCache < Concurrent::Map
|
41
|
+
def initialize(options = {})
|
42
|
+
super(options.merge(initial_capacity: 2))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# preallocate all the default blocks for performance/memory consumption reasons
|
47
|
+
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
|
48
|
+
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
|
49
|
+
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
|
50
|
+
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
|
51
|
+
|
52
|
+
# usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
53
|
+
NO_TEMPLATES = [].freeze
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@data = SmallCache.new(&KEY_BLOCK)
|
57
|
+
@query_cache = SmallCache.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Cache the templates returned by the block
|
65
|
+
def cache(key, name, prefix, partial, locals)
|
66
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_query(query) # :nodoc:
|
70
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear
|
74
|
+
@data.clear
|
75
|
+
@query_cache.clear
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the cache size. Do not call this
|
79
|
+
# method. This method is not guaranteed to be here ever.
|
80
|
+
def size # :nodoc:
|
81
|
+
size = 0
|
82
|
+
@data.each_value do |v1|
|
83
|
+
v1.each_value do |v2|
|
84
|
+
v2.each_value do |v3|
|
85
|
+
v3.each_value do |v4|
|
86
|
+
size += v4.size
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
size + @query_cache.size
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def canonical_no_templates(templates)
|
98
|
+
templates.empty? ? NO_TEMPLATES : templates
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
cattr_accessor :caching, default: true
|
103
|
+
|
104
|
+
class << self
|
105
|
+
alias :caching? :caching
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize
|
109
|
+
@cache = Cache.new
|
110
|
+
end
|
111
|
+
|
112
|
+
def clear_cache
|
113
|
+
@cache.clear
|
114
|
+
end
|
115
|
+
|
116
|
+
# Normalizes the arguments and passes it on to find_templates.
|
117
|
+
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
118
|
+
locals = locals.map(&:to_s).sort!.freeze
|
119
|
+
|
120
|
+
cached(key, [name, prefix, partial], details, locals) do
|
121
|
+
_find_all(name, prefix, partial, details, key, locals)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
alias :find_all_anywhere :find_all
|
126
|
+
deprecate :find_all_anywhere
|
127
|
+
|
128
|
+
def find_all_with_query(query) # :nodoc:
|
129
|
+
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
135
|
+
find_templates(name, prefix, partial, details, locals)
|
136
|
+
end
|
137
|
+
|
138
|
+
delegate :caching?, to: :class
|
139
|
+
|
140
|
+
# This is what child classes implement. No defaults are needed
|
141
|
+
# because Resolver guarantees that the arguments are present and
|
142
|
+
# normalized.
|
143
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
144
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Handles templates caching. If a key is given and caching is on
|
148
|
+
# always check the cache before hitting the resolver. Otherwise,
|
149
|
+
# it always hits the resolver but if the key is present, check if the
|
150
|
+
# resolver is fresher before returning it.
|
151
|
+
def cached(key, path_info, details, locals)
|
152
|
+
name, prefix, partial = path_info
|
153
|
+
|
154
|
+
if key
|
155
|
+
@cache.cache(key, name, prefix, partial, locals) do
|
156
|
+
yield
|
157
|
+
end
|
158
|
+
else
|
159
|
+
yield
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# An abstract class that implements a Resolver with path semantics.
|
165
|
+
class PathResolver < Resolver #:nodoc:
|
166
|
+
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
167
|
+
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
168
|
+
|
169
|
+
def initialize(pattern = nil)
|
170
|
+
if pattern
|
171
|
+
ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
|
172
|
+
@pattern = pattern
|
173
|
+
else
|
174
|
+
@pattern = DEFAULT_PATTERN
|
175
|
+
end
|
176
|
+
@unbound_templates = Concurrent::Map.new
|
177
|
+
super()
|
178
|
+
end
|
179
|
+
|
180
|
+
def clear_cache
|
181
|
+
@unbound_templates.clear
|
182
|
+
super()
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
188
|
+
path = Path.build(name, prefix, partial)
|
189
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
190
|
+
end
|
191
|
+
|
192
|
+
def query(path, details, formats, locals, cache:)
|
193
|
+
template_paths = find_template_paths_from_details(path, details)
|
194
|
+
template_paths = reject_files_external_to_app(template_paths)
|
195
|
+
|
196
|
+
template_paths.map do |template|
|
197
|
+
unbound_template =
|
198
|
+
if cache
|
199
|
+
@unbound_templates.compute_if_absent([template, path.virtual]) do
|
200
|
+
build_unbound_template(template, path.virtual)
|
201
|
+
end
|
202
|
+
else
|
203
|
+
build_unbound_template(template, path.virtual)
|
204
|
+
end
|
205
|
+
|
206
|
+
unbound_template.bind_locals(locals)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def build_unbound_template(template, virtual_path)
|
211
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
212
|
+
source = Template::Sources::File.new(template)
|
213
|
+
|
214
|
+
UnboundTemplate.new(
|
215
|
+
source,
|
216
|
+
template,
|
217
|
+
handler,
|
218
|
+
virtual_path: virtual_path,
|
219
|
+
format: format,
|
220
|
+
variant: variant,
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
def reject_files_external_to_app(files)
|
225
|
+
files.reject { |filename| !inside_path?(@path, filename) }
|
226
|
+
end
|
227
|
+
|
228
|
+
def find_template_paths_from_details(path, details)
|
229
|
+
query = build_query(path, details)
|
230
|
+
find_template_paths(query)
|
231
|
+
end
|
232
|
+
|
233
|
+
def find_template_paths(query)
|
234
|
+
Dir[query].uniq.reject do |filename|
|
235
|
+
File.directory?(filename) ||
|
236
|
+
# deals with case-insensitive file systems.
|
237
|
+
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def inside_path?(path, filename)
|
242
|
+
filename = File.expand_path(filename)
|
243
|
+
path = File.join(path, "")
|
244
|
+
filename.start_with?(path)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Helper for building query glob string based on resolver's pattern.
|
248
|
+
def build_query(path, details)
|
249
|
+
query = @pattern.dup
|
250
|
+
|
251
|
+
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
|
252
|
+
query.gsub!(/:prefix(\/)?/, prefix)
|
253
|
+
|
254
|
+
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
255
|
+
query.gsub!(/:action/, partial)
|
256
|
+
|
257
|
+
details.each do |ext, candidates|
|
258
|
+
if ext == :variants && candidates == :any
|
259
|
+
query.gsub!(/:#{ext}/, "*")
|
260
|
+
else
|
261
|
+
query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
File.expand_path(query, @path)
|
266
|
+
end
|
267
|
+
|
268
|
+
def escape_entry(entry)
|
269
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
270
|
+
end
|
271
|
+
|
272
|
+
# Extract handler, formats and variant from path. If a format cannot be found neither
|
273
|
+
# from the path, or the handler, we should return the array of formats given
|
274
|
+
# to the resolver.
|
275
|
+
def extract_handler_and_format_and_variant(path)
|
276
|
+
pieces = File.basename(path).split(".")
|
277
|
+
pieces.shift
|
278
|
+
|
279
|
+
extension = pieces.pop
|
280
|
+
|
281
|
+
handler = Template.handler_for_extension(extension)
|
282
|
+
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
283
|
+
format = if format
|
284
|
+
Template::Types[format]&.ref
|
285
|
+
else
|
286
|
+
if handler.respond_to?(:default_format) # default_format can return nil
|
287
|
+
handler.default_format
|
288
|
+
else
|
289
|
+
nil
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Template::Types[format] and handler.default_format can return nil
|
294
|
+
[handler, format, variant]
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# A resolver that loads files from the filesystem.
|
299
|
+
class FileSystemResolver < PathResolver
|
300
|
+
attr_reader :path
|
301
|
+
|
302
|
+
def initialize(path, pattern = nil)
|
303
|
+
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
304
|
+
super(pattern)
|
305
|
+
@path = File.expand_path(path)
|
306
|
+
end
|
307
|
+
|
308
|
+
def to_s
|
309
|
+
@path.to_s
|
310
|
+
end
|
311
|
+
alias :to_path :to_s
|
312
|
+
|
313
|
+
def eql?(resolver)
|
314
|
+
self.class.equal?(resolver.class) && to_path == resolver.to_path
|
315
|
+
end
|
316
|
+
alias :== :eql?
|
317
|
+
end
|
318
|
+
|
319
|
+
# An Optimized resolver for Rails' most common case.
|
320
|
+
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
321
|
+
def initialize(path)
|
322
|
+
super(path)
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
def find_template_paths_from_details(path, details)
|
328
|
+
# Instead of checking for every possible path, as our other globs would
|
329
|
+
# do, scan the directory for files with the right prefix.
|
330
|
+
query = "#{escape_entry(File.join(@path, path))}*"
|
331
|
+
|
332
|
+
regex = build_regex(path, details)
|
333
|
+
|
334
|
+
Dir[query].uniq.reject do |filename|
|
335
|
+
# This regex match does double duty of finding only files which match
|
336
|
+
# details (instead of just matching the prefix) and also filtering for
|
337
|
+
# case-insensitive file systems.
|
338
|
+
!regex.match?(filename) ||
|
339
|
+
File.directory?(filename)
|
340
|
+
end.sort_by do |filename|
|
341
|
+
# Because we scanned the directory, instead of checking for files
|
342
|
+
# one-by-one, they will be returned in an arbitrary order.
|
343
|
+
# We can use the matches found by the regex and sort by their index in
|
344
|
+
# details.
|
345
|
+
match = filename.match(regex)
|
346
|
+
EXTENSIONS.keys.reverse.map do |ext|
|
347
|
+
if ext == :variants && details[ext] == :any
|
348
|
+
match[ext].nil? ? 0 : 1
|
349
|
+
elsif match[ext].nil?
|
350
|
+
# No match should be last
|
351
|
+
details[ext].length
|
352
|
+
else
|
353
|
+
found = match[ext].to_sym
|
354
|
+
details[ext].index(found)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def build_regex(path, details)
|
361
|
+
query = escape_entry(File.join(@path, path))
|
362
|
+
exts = EXTENSIONS.map do |ext, prefix|
|
363
|
+
match =
|
364
|
+
if ext == :variants && details[ext] == :any
|
365
|
+
".*?"
|
366
|
+
else
|
367
|
+
details[ext].compact.uniq.map { |e| Regexp.escape(e) }.join("|")
|
368
|
+
end
|
369
|
+
prefix = Regexp.escape(prefix)
|
370
|
+
"(#{prefix}(?<#{ext}>#{match}))?"
|
371
|
+
end.join
|
372
|
+
|
373
|
+
%r{\A#{query}#{exts}\z}
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# The same as FileSystemResolver but does not allow templates to store
|
378
|
+
# a virtual path since it is invalid for such resolvers.
|
379
|
+
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
380
|
+
private_class_method :new
|
381
|
+
|
382
|
+
def self.instances
|
383
|
+
[new(""), new("/")]
|
384
|
+
end
|
385
|
+
|
386
|
+
def build_unbound_template(template, _)
|
387
|
+
super(template, nil)
|
388
|
+
end
|
389
|
+
|
390
|
+
def reject_files_external_to_app(files)
|
391
|
+
files
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|