actionview 5.2.7.1 → 6.1.4.6
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 +250 -112
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +15 -22
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
- data/lib/action_view/helpers/asset_url_helper.rb +9 -6
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +23 -22
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +73 -30
- data/lib/action_view/helpers/form_helper.rb +305 -37
- data/lib/action_view/helpers/form_options_helper.rb +23 -23
- data/lib/action_view/helpers/form_tag_helper.rb +19 -16
- data/lib/action_view/helpers/javascript_helper.rb +12 -11
- data/lib/action_view/helpers/number_helper.rb +14 -8
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +17 -7
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +100 -55
- data/lib/action_view/helpers/tags/base.rb +18 -11
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -3
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
- data/lib/action_view/helpers/tags/label.rb +4 -1
- data/lib/action_view/helpers/tags/month_field.rb +1 -2
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +1 -2
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -2
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +4 -5
- data/lib/action_view/helpers/translation_helper.rb +94 -54
- data/lib/action_view/helpers/url_helper.rb +136 -28
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +30 -15
- data/lib/action_view/lookup_context.rb +63 -35
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +42 -26
- data/lib/action_view/record_identifier.rb +2 -3
- data/lib/action_view/renderer/abstract_renderer.rb +142 -11
- 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 +61 -16
- data/lib/action_view/renderer/partial_renderer.rb +21 -273
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +54 -33
- data/lib/action_view/routing_url_for.rb +13 -12
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- data/lib/action_view/template/handlers/erb.rb +16 -11
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +5 -6
- 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 +191 -150
- 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 +2 -3
- data/lib/action_view/template.rb +66 -75
- data/lib/action_view/test_case.rb +21 -29
- data/lib/action_view/testing/resolvers.rb +18 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +59 -38
- data/lib/action_view.rb +7 -2
- data/lib/assets/compiled/rails-ujs.js +32 -6
- metadata +29 -18
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -14,7 +14,7 @@ module ActionView #:nodoc:
|
|
14
14
|
base.register_template_handler :erb, ERB.new
|
15
15
|
base.register_template_handler :html, Html.new
|
16
16
|
base.register_template_handler :builder, Builder.new
|
17
|
-
base.register_template_handler :ruby,
|
17
|
+
base.register_template_handler :ruby, lambda { |_, source| source }
|
18
18
|
end
|
19
19
|
|
20
20
|
@@template_handlers = {}
|
@@ -4,12 +4,11 @@ module ActionView #:nodoc:
|
|
4
4
|
# = Action View HTML Template
|
5
5
|
class Template #:nodoc:
|
6
6
|
class HTML #:nodoc:
|
7
|
-
|
7
|
+
attr_reader :type
|
8
8
|
|
9
|
-
def initialize(string, type
|
9
|
+
def initialize(string, type)
|
10
10
|
@string = string.to_s
|
11
|
-
@type =
|
12
|
-
@type ||= Types[:html]
|
11
|
+
@type = type
|
13
12
|
end
|
14
13
|
|
15
14
|
def identifier
|
@@ -26,8 +25,8 @@ module ActionView #:nodoc:
|
|
26
25
|
to_str
|
27
26
|
end
|
28
27
|
|
29
|
-
def
|
30
|
-
|
28
|
+
def format
|
29
|
+
@type
|
31
30
|
end
|
32
31
|
end
|
33
32
|
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
|
+
# = 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
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
# = Action View Renderable Template for objects that respond to #render_in
|
5
|
+
class Template
|
6
|
+
class Renderable # :nodoc:
|
7
|
+
def initialize(renderable)
|
8
|
+
@renderable = renderable
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifier
|
12
|
+
@renderable.class.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(context, *args)
|
16
|
+
@renderable.render_in(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
def format
|
20
|
+
@renderable.format
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -16,7 +16,7 @@ module ActionView
|
|
16
16
|
alias_method :partial?, :partial
|
17
17
|
|
18
18
|
def self.build(name, prefix, partial)
|
19
|
-
virtual = ""
|
19
|
+
virtual = +""
|
20
20
|
virtual << "#{prefix}/" unless prefix.empty?
|
21
21
|
virtual << (partial ? "_#{name}" : name)
|
22
22
|
new name, prefix, partial, virtual
|
@@ -35,6 +35,41 @@ module ActionView
|
|
35
35
|
alias :to_s :to_str
|
36
36
|
end
|
37
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
|
+
|
38
73
|
# Threadsafe template cache
|
39
74
|
class Cache #:nodoc:
|
40
75
|
class SmallCache < Concurrent::Map
|
@@ -43,13 +78,13 @@ module ActionView
|
|
43
78
|
end
|
44
79
|
end
|
45
80
|
|
46
|
-
#
|
81
|
+
# Preallocate all the default blocks for performance/memory consumption reasons
|
47
82
|
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
|
48
83
|
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
|
49
84
|
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
|
50
85
|
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
|
51
86
|
|
52
|
-
#
|
87
|
+
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
53
88
|
NO_TEMPLATES = [].freeze
|
54
89
|
|
55
90
|
def initialize
|
@@ -58,31 +93,16 @@ module ActionView
|
|
58
93
|
end
|
59
94
|
|
60
95
|
def inspect
|
61
|
-
"
|
96
|
+
"#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
|
62
97
|
end
|
63
98
|
|
64
99
|
# Cache the templates returned by the block
|
65
100
|
def cache(key, name, prefix, partial, locals)
|
66
|
-
|
67
|
-
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
68
|
-
else
|
69
|
-
fresh_templates = yield
|
70
|
-
cached_templates = @data[key][name][prefix][partial][locals]
|
71
|
-
|
72
|
-
if templates_have_changed?(cached_templates, fresh_templates)
|
73
|
-
@data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
|
74
|
-
else
|
75
|
-
cached_templates || NO_TEMPLATES
|
76
|
-
end
|
77
|
-
end
|
101
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
78
102
|
end
|
79
103
|
|
80
104
|
def cache_query(query) # :nodoc:
|
81
|
-
|
82
|
-
@query_cache[query] ||= canonical_no_templates(yield)
|
83
|
-
else
|
84
|
-
yield
|
85
|
-
end
|
105
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
86
106
|
end
|
87
107
|
|
88
108
|
def clear
|
@@ -90,7 +110,7 @@ module ActionView
|
|
90
110
|
@query_cache.clear
|
91
111
|
end
|
92
112
|
|
93
|
-
# Get the cache size.
|
113
|
+
# Get the cache size. Do not call this
|
94
114
|
# method. This method is not guaranteed to be here ever.
|
95
115
|
def size # :nodoc:
|
96
116
|
size = 0
|
@@ -108,23 +128,9 @@ module ActionView
|
|
108
128
|
end
|
109
129
|
|
110
130
|
private
|
111
|
-
|
112
131
|
def canonical_no_templates(templates)
|
113
132
|
templates.empty? ? NO_TEMPLATES : templates
|
114
133
|
end
|
115
|
-
|
116
|
-
def templates_have_changed?(cached_templates, fresh_templates)
|
117
|
-
# if either the old or new template list is empty, we don't need to (and can't)
|
118
|
-
# compare modification times, and instead just check whether the lists are different
|
119
|
-
if cached_templates.blank? || fresh_templates.blank?
|
120
|
-
return fresh_templates.blank? != cached_templates.blank?
|
121
|
-
end
|
122
|
-
|
123
|
-
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
|
124
|
-
|
125
|
-
# if a template has changed, it will be now be newer than all the cached templates
|
126
|
-
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
|
127
|
-
end
|
128
134
|
end
|
129
135
|
|
130
136
|
cattr_accessor :caching, default: true
|
@@ -143,14 +149,10 @@ module ActionView
|
|
143
149
|
|
144
150
|
# Normalizes the arguments and passes it on to find_templates.
|
145
151
|
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
146
|
-
|
147
|
-
find_templates(name, prefix, partial, details)
|
148
|
-
end
|
149
|
-
end
|
152
|
+
locals = locals.map(&:to_s).sort!.freeze
|
150
153
|
|
151
|
-
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
|
152
154
|
cached(key, [name, prefix, partial], details, locals) do
|
153
|
-
|
155
|
+
_find_all(name, prefix, partial, details, key, locals)
|
154
156
|
end
|
155
157
|
end
|
156
158
|
|
@@ -159,19 +161,17 @@ module ActionView
|
|
159
161
|
end
|
160
162
|
|
161
163
|
private
|
164
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
165
|
+
find_templates(name, prefix, partial, details, locals)
|
166
|
+
end
|
162
167
|
|
163
168
|
delegate :caching?, to: :class
|
164
169
|
|
165
170
|
# This is what child classes implement. No defaults are needed
|
166
171
|
# because Resolver guarantees that the arguments are present and
|
167
172
|
# normalized.
|
168
|
-
def find_templates(name, prefix, partial, details,
|
169
|
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details,
|
170
|
-
end
|
171
|
-
|
172
|
-
# Helpers that builds a path. Useful for building virtual paths.
|
173
|
-
def build_path(name, prefix, partial)
|
174
|
-
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"
|
175
175
|
end
|
176
176
|
|
177
177
|
# Handles templates caching. If a key is given and caching is on
|
@@ -180,25 +180,13 @@ module ActionView
|
|
180
180
|
# resolver is fresher before returning it.
|
181
181
|
def cached(key, path_info, details, locals)
|
182
182
|
name, prefix, partial = path_info
|
183
|
-
locals = locals.map(&:to_s).sort!
|
184
183
|
|
185
184
|
if key
|
186
185
|
@cache.cache(key, name, prefix, partial, locals) do
|
187
|
-
|
186
|
+
yield
|
188
187
|
end
|
189
188
|
else
|
190
|
-
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
# Ensures all the resolver information is set in the template.
|
195
|
-
def decorate(templates, path_info, details, locals)
|
196
|
-
cached = nil
|
197
|
-
templates.each do |t|
|
198
|
-
t.locals = locals
|
199
|
-
t.formats = details[:formats] || [:html] if t.formats.empty?
|
200
|
-
t.variants = details[:variants] || [] if t.variants.empty?
|
201
|
-
t.virtual_path ||= (cached ||= build_path(*path_info))
|
189
|
+
yield
|
202
190
|
end
|
203
191
|
end
|
204
192
|
end
|
@@ -208,41 +196,74 @@ module ActionView
|
|
208
196
|
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
209
197
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
210
198
|
|
211
|
-
def initialize
|
212
|
-
@pattern =
|
213
|
-
|
199
|
+
def initialize
|
200
|
+
@pattern = DEFAULT_PATTERN
|
201
|
+
@unbound_templates = Concurrent::Map.new
|
202
|
+
@path_parser = PathParser.new
|
203
|
+
super
|
214
204
|
end
|
215
205
|
|
216
|
-
|
206
|
+
def clear_cache
|
207
|
+
@unbound_templates.clear
|
208
|
+
@path_parser = PathParser.new
|
209
|
+
super
|
210
|
+
end
|
217
211
|
|
218
|
-
|
212
|
+
private
|
213
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
219
214
|
path = Path.build(name, prefix, partial)
|
220
|
-
query(path, details, details[:formats],
|
215
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
221
216
|
end
|
222
217
|
|
223
|
-
def query(path, details, formats,
|
224
|
-
|
225
|
-
|
226
|
-
template_paths = find_template_paths(query)
|
227
|
-
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
|
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)
|
228
221
|
|
229
222
|
template_paths.map do |template|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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)
|
239
233
|
end
|
240
234
|
end
|
241
235
|
|
236
|
+
def source_for_template(template)
|
237
|
+
Template::Sources::File.new(template)
|
238
|
+
end
|
239
|
+
|
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,
|
251
|
+
)
|
252
|
+
end
|
253
|
+
|
242
254
|
def reject_files_external_to_app(files)
|
243
255
|
files.reject { |filename| !inside_path?(@path, filename) }
|
244
256
|
end
|
245
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
|
266
|
+
|
246
267
|
def find_template_paths(query)
|
247
268
|
Dir[query].uniq.reject do |filename|
|
248
269
|
File.directory?(filename) ||
|
@@ -265,7 +286,7 @@ module ActionView
|
|
265
286
|
query.gsub!(/:prefix(\/)?/, prefix)
|
266
287
|
|
267
288
|
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
268
|
-
query.gsub!(
|
289
|
+
query.gsub!(":action", partial)
|
269
290
|
|
270
291
|
details.each do |ext, candidates|
|
271
292
|
if ext == :variants && candidates == :any
|
@@ -279,73 +300,31 @@ module ActionView
|
|
279
300
|
end
|
280
301
|
|
281
302
|
def escape_entry(entry)
|
282
|
-
entry.gsub(/[*?{}\[\]]/, '\\\\\\&'
|
283
|
-
end
|
284
|
-
|
285
|
-
# Returns the file mtime from the filesystem.
|
286
|
-
def mtime(p)
|
287
|
-
File.mtime(p)
|
303
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
288
304
|
end
|
289
305
|
|
290
306
|
# Extract handler, formats and variant from path. If a format cannot be found neither
|
291
307
|
# from the path, or the handler, we should return the array of formats given
|
292
308
|
# to the resolver.
|
293
309
|
def extract_handler_and_format_and_variant(path)
|
294
|
-
|
295
|
-
pieces.shift
|
296
|
-
|
297
|
-
extension = pieces.pop
|
310
|
+
details = @path_parser.parse(path)
|
298
311
|
|
299
|
-
handler = Template.handler_for_extension(
|
300
|
-
format
|
301
|
-
|
312
|
+
handler = Template.handler_for_extension(details[:handler])
|
313
|
+
format = details[:format] || handler.try(:default_format)
|
314
|
+
variant = details[:variant]
|
302
315
|
|
316
|
+
# Template::Types[format] and handler.default_format can return nil
|
303
317
|
[handler, format, variant]
|
304
318
|
end
|
305
319
|
end
|
306
320
|
|
307
|
-
# A resolver that loads files from the filesystem.
|
308
|
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
|
309
|
-
#
|
310
|
-
# ==== Examples
|
311
|
-
#
|
312
|
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
|
313
|
-
# looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
|
314
|
-
#
|
315
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
316
|
-
#
|
317
|
-
# This one allows you to keep files with different formats in separate subdirectories,
|
318
|
-
# eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
|
319
|
-
# <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
|
320
|
-
#
|
321
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
322
|
-
#
|
323
|
-
# If you don't specify a pattern then the default will be used.
|
324
|
-
#
|
325
|
-
# In order to use any of the customized resolvers above in a Rails application, you just need
|
326
|
-
# to configure ActionController::Base.view_paths in an initializer, for example:
|
327
|
-
#
|
328
|
-
# ActionController::Base.view_paths = FileSystemResolver.new(
|
329
|
-
# Rails.root.join("app/views"),
|
330
|
-
# ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
|
331
|
-
# )
|
332
|
-
#
|
333
|
-
# ==== Pattern format and variables
|
334
|
-
#
|
335
|
-
# Pattern has to be a valid glob string, and it allows you to use the
|
336
|
-
# following variables:
|
337
|
-
#
|
338
|
-
# * <tt>:prefix</tt> - usually the controller path
|
339
|
-
# * <tt>:action</tt> - name of the action
|
340
|
-
# * <tt>:locale</tt> - possible locale versions
|
341
|
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
|
342
|
-
# * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
|
343
|
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
344
|
-
#
|
321
|
+
# A resolver that loads files from the filesystem.
|
345
322
|
class FileSystemResolver < PathResolver
|
346
|
-
|
323
|
+
attr_reader :path
|
324
|
+
|
325
|
+
def initialize(path)
|
347
326
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
348
|
-
super(
|
327
|
+
super()
|
349
328
|
@path = File.expand_path(path)
|
350
329
|
end
|
351
330
|
|
@@ -362,30 +341,92 @@ module ActionView
|
|
362
341
|
|
363
342
|
# An Optimized resolver for Rails' most common case.
|
364
343
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
365
|
-
def
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
344
|
+
def initialize(path)
|
345
|
+
super(path)
|
346
|
+
end
|
347
|
+
|
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))}*"
|
353
|
+
|
354
|
+
Dir[query].reject do |filename|
|
355
|
+
File.directory?(filename)
|
373
356
|
end
|
374
|
-
end
|
357
|
+
end
|
375
358
|
|
376
|
-
|
377
|
-
|
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
|
378
413
|
end
|
379
414
|
|
380
415
|
# The same as FileSystemResolver but does not allow templates to store
|
381
416
|
# a virtual path since it is invalid for such resolvers.
|
382
417
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
418
|
+
private_class_method :new
|
419
|
+
|
383
420
|
def self.instances
|
384
421
|
[new(""), new("/")]
|
385
422
|
end
|
386
423
|
|
387
|
-
def
|
388
|
-
super
|
424
|
+
def build_unbound_template(template, _)
|
425
|
+
super(template, nil)
|
426
|
+
end
|
427
|
+
|
428
|
+
def reject_files_external_to_app(files)
|
429
|
+
files
|
389
430
|
end
|
390
431
|
end
|
391
432
|
end
|
@@ -8,7 +8,6 @@ module ActionView #:nodoc:
|
|
8
8
|
|
9
9
|
def initialize(string)
|
10
10
|
@string = string.to_s
|
11
|
-
@type = Types[:text]
|
12
11
|
end
|
13
12
|
|
14
13
|
def identifier
|
@@ -25,8 +24,8 @@ module ActionView #:nodoc:
|
|
25
24
|
to_str
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
29
|
-
|
27
|
+
def format
|
28
|
+
:text
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|