actionview 5.2.4 → 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 +4 -4
- data/CHANGELOG.md +189 -77
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/action_view.rb +3 -2
- data/lib/action_view/base.rb +107 -10
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +54 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/digestor.rb +12 -20
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
- data/lib/action_view/helpers/asset_url_helper.rb +4 -3
- data/lib/action_view/helpers/cache_helper.rb +18 -10
- 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 +69 -25
- data/lib/action_view/helpers/form_helper.rb +238 -6
- data/lib/action_view/helpers/form_options_helper.rb +27 -18
- data/lib/action_view/helpers/form_tag_helper.rb +12 -11
- data/lib/action_view/helpers/javascript_helper.rb +9 -8
- data/lib/action_view/helpers/number_helper.rb +5 -0
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +6 -4
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +7 -6
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/color_field.rb +1 -1
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/text_helper.rb +3 -3
- data/lib/action_view/helpers/translation_helper.rb +16 -12
- data/lib/action_view/helpers/url_helper.rb +14 -14
- data/lib/action_view/layouts.rb +5 -5
- data/lib/action_view/log_subscriber.rb +6 -6
- data/lib/action_view/lookup_context.rb +73 -31
- data/lib/action_view/path_set.rb +5 -10
- data/lib/action_view/railtie.rb +24 -1
- data/lib/action_view/record_identifier.rb +2 -2
- data/lib/action_view/renderer/abstract_renderer.rb +56 -3
- data/lib/action_view/renderer/partial_renderer.rb +66 -55
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
- data/lib/action_view/renderer/renderer.rb +16 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
- data/lib/action_view/renderer/template_renderer.rb +24 -18
- data/lib/action_view/rendering.rb +51 -31
- data/lib/action_view/routing_url_for.rb +12 -11
- data/lib/action_view/template.rb +102 -70
- data/lib/action_view/template/error.rb +21 -1
- data/lib/action_view/template/handlers.rb +27 -1
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb.rb +17 -7
- data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
- 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/html.rb +14 -5
- 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 +136 -133
- 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 +5 -3
- data/lib/action_view/test_case.rb +1 -1
- data/lib/action_view/testing/resolvers.rb +33 -20
- data/lib/action_view/unbound_template.rb +32 -0
- data/lib/action_view/view_paths.rb +25 -1
- data/lib/assets/compiled/rails-ujs.js +30 -4
- metadata +23 -18
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -109,7 +109,7 @@ module ActionView
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def
|
112
|
+
def annotated_source_code
|
113
113
|
source_extract(4)
|
114
114
|
end
|
115
115
|
|
@@ -138,4 +138,24 @@ module ActionView
|
|
138
138
|
end
|
139
139
|
|
140
140
|
TemplateError = Template::Error
|
141
|
+
|
142
|
+
class SyntaxErrorInTemplate < TemplateError #:nodoc
|
143
|
+
def initialize(template, offending_code_string)
|
144
|
+
@offending_code_string = offending_code_string
|
145
|
+
super(template)
|
146
|
+
end
|
147
|
+
|
148
|
+
def message
|
149
|
+
<<~MESSAGE
|
150
|
+
Encountered a syntax error while rendering template: check #{@offending_code_string}
|
151
|
+
MESSAGE
|
152
|
+
end
|
153
|
+
|
154
|
+
def annotated_source_code
|
155
|
+
@offending_code_string.split("\n").map.with_index(1) { |line, index|
|
156
|
+
indentation = " " * 4
|
157
|
+
"#{index}:#{indentation}#{line}"
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
141
161
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/deprecation"
|
4
|
+
|
3
5
|
module ActionView #:nodoc:
|
4
6
|
# = Action View Template Handlers
|
5
7
|
class Template #:nodoc:
|
@@ -14,7 +16,7 @@ module ActionView #:nodoc:
|
|
14
16
|
base.register_template_handler :erb, ERB.new
|
15
17
|
base.register_template_handler :html, Html.new
|
16
18
|
base.register_template_handler :builder, Builder.new
|
17
|
-
base.register_template_handler :ruby,
|
19
|
+
base.register_template_handler :ruby, lambda { |_, source| source }
|
18
20
|
end
|
19
21
|
|
20
22
|
@@template_handlers = {}
|
@@ -24,11 +26,35 @@ module ActionView #:nodoc:
|
|
24
26
|
@@template_extensions ||= @@template_handlers.keys
|
25
27
|
end
|
26
28
|
|
29
|
+
class LegacyHandlerWrapper < SimpleDelegator # :nodoc:
|
30
|
+
def call(view, source)
|
31
|
+
__getobj__.call(ActionView::Template::LegacyTemplate.new(view, source))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
27
35
|
# Register an object that knows how to handle template files with the given
|
28
36
|
# extensions. This can be used to implement new template types.
|
29
37
|
# The handler must respond to +:call+, which will be passed the template
|
30
38
|
# and should return the rendered template as a String.
|
31
39
|
def register_template_handler(*extensions, handler)
|
40
|
+
params = if handler.is_a?(Proc)
|
41
|
+
handler.parameters
|
42
|
+
else
|
43
|
+
handler.method(:call).parameters
|
44
|
+
end
|
45
|
+
|
46
|
+
unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2
|
47
|
+
ActiveSupport::Deprecation.warn <<~eowarn
|
48
|
+
Single arity template handlers are deprecated. Template handlers must
|
49
|
+
now accept two parameters, the view object and the source for the view object.
|
50
|
+
Change:
|
51
|
+
>> #{handler}.call(#{params.map(&:last).join(", ")})
|
52
|
+
To:
|
53
|
+
>> #{handler}.call(#{params.map(&:last).join(", ")}, source)
|
54
|
+
eowarn
|
55
|
+
handler = LegacyHandlerWrapper.new(handler)
|
56
|
+
end
|
57
|
+
|
32
58
|
raise(ArgumentError, "Extension is required") if extensions.empty?
|
33
59
|
extensions.each do |extension|
|
34
60
|
@@template_handlers[extension.to_sym] = handler
|
@@ -5,11 +5,11 @@ module ActionView
|
|
5
5
|
class Builder
|
6
6
|
class_attribute :default_format, default: :xml
|
7
7
|
|
8
|
-
def call(template)
|
8
|
+
def call(template, source)
|
9
9
|
require_engine
|
10
10
|
"xml = ::Builder::XmlMarkup.new(:indent => 2);" \
|
11
11
|
"self.output_buffer = xml.target!;" +
|
12
|
-
|
12
|
+
source +
|
13
13
|
";xml.target!;"
|
14
14
|
end
|
15
15
|
|
@@ -14,12 +14,22 @@ module ActionView
|
|
14
14
|
class_attribute :erb_implementation, default: Erubi
|
15
15
|
|
16
16
|
# Do not escape templates of these mime types.
|
17
|
-
class_attribute :
|
17
|
+
class_attribute :escape_ignore_list, default: ["text/plain"]
|
18
|
+
|
19
|
+
[self, singleton_class].each do |base|
|
20
|
+
base.alias_method :escape_whitelist, :escape_ignore_list
|
21
|
+
base.alias_method :escape_whitelist=, :escape_ignore_list=
|
22
|
+
|
23
|
+
base.deprecate(
|
24
|
+
escape_whitelist: "use #escape_ignore_list instead",
|
25
|
+
:escape_whitelist= => "use #escape_ignore_list= instead"
|
26
|
+
)
|
27
|
+
end
|
18
28
|
|
19
29
|
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
|
20
30
|
|
21
|
-
def self.call(template)
|
22
|
-
new.call(template)
|
31
|
+
def self.call(template, source)
|
32
|
+
new.call(template, source)
|
23
33
|
end
|
24
34
|
|
25
35
|
def supports_streaming?
|
@@ -30,24 +40,24 @@ module ActionView
|
|
30
40
|
true
|
31
41
|
end
|
32
42
|
|
33
|
-
def call(template)
|
43
|
+
def call(template, source)
|
34
44
|
# First, convert to BINARY, so in case the encoding is
|
35
45
|
# wrong, we can still find an encoding tag
|
36
46
|
# (<%# encoding %>) inside the String using a regular
|
37
47
|
# expression
|
38
|
-
template_source =
|
48
|
+
template_source = source.dup.force_encoding(Encoding::ASCII_8BIT)
|
39
49
|
|
40
50
|
erb = template_source.gsub(ENCODING_TAG, "")
|
41
51
|
encoding = $2
|
42
52
|
|
43
|
-
erb.force_encoding valid_encoding(
|
53
|
+
erb.force_encoding valid_encoding(source.dup, encoding)
|
44
54
|
|
45
55
|
# Always make sure we return a String in the default_internal
|
46
56
|
erb.encode!
|
47
57
|
|
48
58
|
self.class.erb_implementation.new(
|
49
59
|
erb,
|
50
|
-
escape: (self.class.
|
60
|
+
escape: (self.class.escape_ignore_list.include? template.type),
|
51
61
|
trim: (self.class.erb_trim_mode == "-")
|
52
62
|
).src
|
53
63
|
end
|
@@ -13,7 +13,7 @@ module ActionView
|
|
13
13
|
|
14
14
|
# Dup properties so that we don't modify argument
|
15
15
|
properties = Hash[properties]
|
16
|
-
properties[:preamble] = "
|
16
|
+
properties[:preamble] = ""
|
17
17
|
properties[:postamble] = "@output_buffer.to_s"
|
18
18
|
properties[:bufvar] = "@output_buffer"
|
19
19
|
properties[:escapefunc] = ""
|
@@ -22,8 +22,12 @@ module ActionView
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def evaluate(action_view_erb_handler_context)
|
25
|
-
|
26
|
-
|
25
|
+
src = @src
|
26
|
+
view = Class.new(ActionView::Base) {
|
27
|
+
include action_view_erb_handler_context._routes.url_helpers
|
28
|
+
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
|
29
|
+
}.empty
|
30
|
+
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
|
27
31
|
end
|
28
32
|
|
29
33
|
private
|
@@ -1,15 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/deprecation"
|
4
|
+
|
3
5
|
module ActionView #:nodoc:
|
4
6
|
# = Action View HTML Template
|
5
7
|
class Template #:nodoc:
|
6
8
|
class HTML #:nodoc:
|
7
|
-
|
9
|
+
attr_reader :type
|
8
10
|
|
9
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
|
+
|
10
17
|
@string = string.to_s
|
11
|
-
@type =
|
12
|
-
@type ||= Types[:html]
|
18
|
+
@type = type
|
13
19
|
end
|
14
20
|
|
15
21
|
def identifier
|
@@ -26,9 +32,12 @@ module ActionView #:nodoc:
|
|
26
32
|
to_str
|
27
33
|
end
|
28
34
|
|
29
|
-
def
|
30
|
-
|
35
|
+
def format
|
36
|
+
@type
|
31
37
|
end
|
38
|
+
|
39
|
+
def formats; Array(format); end
|
40
|
+
deprecate :formats
|
32
41
|
end
|
33
42
|
end
|
34
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
|
@@ -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
|
@@ -63,26 +63,11 @@ module ActionView
|
|
63
63
|
|
64
64
|
# Cache the templates returned by the block
|
65
65
|
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
|
66
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
78
67
|
end
|
79
68
|
|
80
69
|
def cache_query(query) # :nodoc:
|
81
|
-
|
82
|
-
@query_cache[query] ||= canonical_no_templates(yield)
|
83
|
-
else
|
84
|
-
yield
|
85
|
-
end
|
70
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
86
71
|
end
|
87
72
|
|
88
73
|
def clear
|
@@ -112,19 +97,6 @@ module ActionView
|
|
112
97
|
def canonical_no_templates(templates)
|
113
98
|
templates.empty? ? NO_TEMPLATES : templates
|
114
99
|
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
100
|
end
|
129
101
|
|
130
102
|
cattr_accessor :caching, default: true
|
@@ -143,35 +115,33 @@ module ActionView
|
|
143
115
|
|
144
116
|
# Normalizes the arguments and passes it on to find_templates.
|
145
117
|
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
146
|
-
|
147
|
-
find_templates(name, prefix, partial, details)
|
148
|
-
end
|
149
|
-
end
|
118
|
+
locals = locals.map(&:to_s).sort!.freeze
|
150
119
|
|
151
|
-
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
|
152
120
|
cached(key, [name, prefix, partial], details, locals) do
|
153
|
-
|
121
|
+
_find_all(name, prefix, partial, details, key, locals)
|
154
122
|
end
|
155
123
|
end
|
156
124
|
|
125
|
+
alias :find_all_anywhere :find_all
|
126
|
+
deprecate :find_all_anywhere
|
127
|
+
|
157
128
|
def find_all_with_query(query) # :nodoc:
|
158
129
|
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
159
130
|
end
|
160
131
|
|
161
132
|
private
|
162
133
|
|
134
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
135
|
+
find_templates(name, prefix, partial, details, locals)
|
136
|
+
end
|
137
|
+
|
163
138
|
delegate :caching?, to: :class
|
164
139
|
|
165
140
|
# This is what child classes implement. No defaults are needed
|
166
141
|
# because Resolver guarantees that the arguments are present and
|
167
142
|
# 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)
|
143
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
144
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
175
145
|
end
|
176
146
|
|
177
147
|
# Handles templates caching. If a key is given and caching is on
|
@@ -180,25 +150,13 @@ module ActionView
|
|
180
150
|
# resolver is fresher before returning it.
|
181
151
|
def cached(key, path_info, details, locals)
|
182
152
|
name, prefix, partial = path_info
|
183
|
-
locals = locals.map(&:to_s).sort!
|
184
153
|
|
185
154
|
if key
|
186
155
|
@cache.cache(key, name, prefix, partial, locals) do
|
187
|
-
|
156
|
+
yield
|
188
157
|
end
|
189
158
|
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))
|
159
|
+
yield
|
202
160
|
end
|
203
161
|
end
|
204
162
|
end
|
@@ -209,40 +167,69 @@ module ActionView
|
|
209
167
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
210
168
|
|
211
169
|
def initialize(pattern = nil)
|
212
|
-
|
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
|
213
182
|
super()
|
214
183
|
end
|
215
184
|
|
216
185
|
private
|
217
186
|
|
218
|
-
def
|
187
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
219
188
|
path = Path.build(name, prefix, partial)
|
220
|
-
query(path, details, details[:formats],
|
189
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
221
190
|
end
|
222
191
|
|
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
|
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)
|
228
195
|
|
229
196
|
template_paths.map do |template|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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)
|
239
207
|
end
|
240
208
|
end
|
241
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
|
+
|
242
224
|
def reject_files_external_to_app(files)
|
243
225
|
files.reject { |filename| !inside_path?(@path, filename) }
|
244
226
|
end
|
245
227
|
|
228
|
+
def find_template_paths_from_details(path, details)
|
229
|
+
query = build_query(path, details)
|
230
|
+
find_template_paths(query)
|
231
|
+
end
|
232
|
+
|
246
233
|
def find_template_paths(query)
|
247
234
|
Dir[query].uniq.reject do |filename|
|
248
235
|
File.directory?(filename) ||
|
@@ -279,70 +266,39 @@ module ActionView
|
|
279
266
|
end
|
280
267
|
|
281
268
|
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)
|
269
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
288
270
|
end
|
289
271
|
|
290
272
|
# Extract handler, formats and variant from path. If a format cannot be found neither
|
291
273
|
# from the path, or the handler, we should return the array of formats given
|
292
274
|
# to the resolver.
|
293
275
|
def extract_handler_and_format_and_variant(path)
|
294
|
-
pieces = File.basename(path).split("."
|
276
|
+
pieces = File.basename(path).split(".")
|
295
277
|
pieces.shift
|
296
278
|
|
297
279
|
extension = pieces.pop
|
298
280
|
|
299
281
|
handler = Template.handler_for_extension(extension)
|
300
282
|
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
301
|
-
format
|
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
|
302
292
|
|
293
|
+
# Template::Types[format] and handler.default_format can return nil
|
303
294
|
[handler, format, variant]
|
304
295
|
end
|
305
296
|
end
|
306
297
|
|
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
|
-
#
|
298
|
+
# A resolver that loads files from the filesystem.
|
345
299
|
class FileSystemResolver < PathResolver
|
300
|
+
attr_reader :path
|
301
|
+
|
346
302
|
def initialize(path, pattern = nil)
|
347
303
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
348
304
|
super(pattern)
|
@@ -362,30 +318,77 @@ module ActionView
|
|
362
318
|
|
363
319
|
# An Optimized resolver for Rails' most common case.
|
364
320
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
365
|
-
def
|
366
|
-
|
321
|
+
def initialize(path)
|
322
|
+
super(path)
|
323
|
+
end
|
367
324
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
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
|
373
357
|
end
|
374
|
-
end
|
358
|
+
end
|
375
359
|
|
376
|
-
|
377
|
-
|
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
|
378
375
|
end
|
379
376
|
|
380
377
|
# The same as FileSystemResolver but does not allow templates to store
|
381
378
|
# a virtual path since it is invalid for such resolvers.
|
382
379
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
380
|
+
private_class_method :new
|
381
|
+
|
383
382
|
def self.instances
|
384
383
|
[new(""), new("/")]
|
385
384
|
end
|
386
385
|
|
387
|
-
def
|
388
|
-
super
|
386
|
+
def build_unbound_template(template, _)
|
387
|
+
super(template, nil)
|
388
|
+
end
|
389
|
+
|
390
|
+
def reject_files_external_to_app(files)
|
391
|
+
files
|
389
392
|
end
|
390
393
|
end
|
391
394
|
end
|