actionview 4.2.11.1 → 7.0.2.4

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.

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -215
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +116 -43
  6. data/lib/action_view/buffers.rb +20 -3
  7. data/lib/action_view/cache_expiry.rb +66 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  10. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  11. data/lib/action_view/dependency_tracker.rb +21 -122
  12. data/lib/action_view/digestor.rb +92 -85
  13. data/lib/action_view/flows.rb +15 -16
  14. data/lib/action_view/gem_version.rb +6 -4
  15. data/lib/action_view/helpers/active_model_helper.rb +17 -12
  16. data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
  17. data/lib/action_view/helpers/asset_url_helper.rb +180 -74
  18. data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
  19. data/lib/action_view/helpers/cache_helper.rb +156 -43
  20. data/lib/action_view/helpers/capture_helper.rb +21 -14
  21. data/lib/action_view/helpers/controller_helper.rb +16 -5
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  24. data/lib/action_view/helpers/date_helper.rb +288 -132
  25. data/lib/action_view/helpers/debug_helper.rb +9 -6
  26. data/lib/action_view/helpers/form_helper.rb +956 -173
  27. data/lib/action_view/helpers/form_options_helper.rb +178 -97
  28. data/lib/action_view/helpers/form_tag_helper.rb +220 -101
  29. data/lib/action_view/helpers/javascript_helper.rb +33 -19
  30. data/lib/action_view/helpers/number_helper.rb +88 -63
  31. data/lib/action_view/helpers/output_safety_helper.rb +38 -6
  32. data/lib/action_view/helpers/rendering_helper.rb +21 -10
  33. data/lib/action_view/helpers/sanitize_helper.rb +31 -32
  34. data/lib/action_view/helpers/tag_helper.rb +332 -71
  35. data/lib/action_view/helpers/tags/base.rb +123 -99
  36. data/lib/action_view/helpers/tags/check_box.rb +21 -20
  37. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  39. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  40. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  41. data/lib/action_view/helpers/tags/collection_select.rb +5 -3
  42. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  43. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  44. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  45. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  46. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  48. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  49. data/lib/action_view/helpers/tags/file_field.rb +18 -0
  50. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  51. data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
  52. data/lib/action_view/helpers/tags/label.rb +7 -2
  53. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  54. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  55. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  56. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  57. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  58. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  59. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  60. data/lib/action_view/helpers/tags/select.rb +11 -10
  61. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  62. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  63. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  64. data/lib/action_view/helpers/tags/time_field.rb +12 -2
  65. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  66. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  67. data/lib/action_view/helpers/tags/translator.rb +15 -16
  68. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  69. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  70. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  71. data/lib/action_view/helpers/tags.rb +5 -2
  72. data/lib/action_view/helpers/text_helper.rb +80 -51
  73. data/lib/action_view/helpers/translation_helper.rb +120 -69
  74. data/lib/action_view/helpers/url_helper.rb +398 -171
  75. data/lib/action_view/helpers.rb +29 -27
  76. data/lib/action_view/layouts.rb +68 -63
  77. data/lib/action_view/log_subscriber.rb +77 -10
  78. data/lib/action_view/lookup_context.rb +137 -113
  79. data/lib/action_view/model_naming.rb +4 -2
  80. data/lib/action_view/path_set.rb +28 -32
  81. data/lib/action_view/railtie.rb +74 -13
  82. data/lib/action_view/record_identifier.rb +53 -26
  83. data/lib/action_view/render_parser.rb +188 -0
  84. data/lib/action_view/renderer/abstract_renderer.rb +152 -15
  85. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  86. data/lib/action_view/renderer/object_renderer.rb +34 -0
  87. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  88. data/lib/action_view/renderer/partial_renderer.rb +51 -333
  89. data/lib/action_view/renderer/renderer.rb +68 -11
  90. data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
  91. data/lib/action_view/renderer/template_renderer.rb +87 -74
  92. data/lib/action_view/rendering.rb +73 -47
  93. data/lib/action_view/ripper_ast_parser.rb +198 -0
  94. data/lib/action_view/routing_url_for.rb +35 -24
  95. data/lib/action_view/tasks/cache_digests.rake +25 -0
  96. data/lib/action_view/template/error.rb +151 -41
  97. data/lib/action_view/template/handlers/builder.rb +12 -13
  98. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  99. data/lib/action_view/template/handlers/erb.rb +29 -89
  100. data/lib/action_view/template/handlers/html.rb +11 -0
  101. data/lib/action_view/template/handlers/raw.rb +4 -4
  102. data/lib/action_view/template/handlers.rb +14 -10
  103. data/lib/action_view/template/html.rb +12 -13
  104. data/lib/action_view/template/inline.rb +22 -0
  105. data/lib/action_view/template/raw_file.rb +25 -0
  106. data/lib/action_view/template/renderable.rb +24 -0
  107. data/lib/action_view/template/resolver.rb +139 -300
  108. data/lib/action_view/template/sources/file.rb +17 -0
  109. data/lib/action_view/template/sources.rb +13 -0
  110. data/lib/action_view/template/text.rb +10 -12
  111. data/lib/action_view/template/types.rb +28 -26
  112. data/lib/action_view/template.rb +123 -91
  113. data/lib/action_view/template_details.rb +66 -0
  114. data/lib/action_view/template_path.rb +64 -0
  115. data/lib/action_view/test_case.rb +70 -53
  116. data/lib/action_view/testing/resolvers.rb +25 -35
  117. data/lib/action_view/unbound_template.rb +57 -0
  118. data/lib/action_view/version.rb +3 -1
  119. data/lib/action_view/view_paths.rb +73 -58
  120. data/lib/action_view.rb +16 -11
  121. data/lib/assets/compiled/rails-ujs.js +746 -0
  122. metadata +52 -32
  123. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  124. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,366 +1,205 @@
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 "thread_safe"
8
+ require "concurrent/map"
8
9
 
9
10
  module ActionView
10
11
  # = Action View Resolver
11
12
  class Resolver
12
- # Keeps all information about view path and builds virtual path.
13
- class Path
14
- attr_reader :name, :prefix, :partial, :virtual
15
- alias_method :partial?, :partial
16
-
17
- def self.build(name, prefix, partial)
18
- virtual = ""
19
- virtual << "#{prefix}/" unless prefix.empty?
20
- virtual << (partial ? "_#{name}" : name)
21
- new name, prefix, partial, virtual
22
- end
23
-
24
- def initialize(name, prefix, partial, virtual)
25
- @name = name
26
- @prefix = prefix
27
- @partial = partial
28
- @virtual = virtual
29
- end
30
-
31
- def to_str
32
- @virtual
33
- end
34
- alias :to_s :to_str
35
- end
36
-
37
- # Threadsafe template cache
38
- class Cache #:nodoc:
39
- class SmallCache < ThreadSafe::Cache
40
- def initialize(options = {})
41
- super(options.merge(:initial_capacity => 2))
42
- end
43
- end
44
-
45
- # preallocate all the default blocks for performance/memory consumption reasons
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)}
50
-
51
- # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
52
- NO_TEMPLATES = [].freeze
53
-
54
- def initialize
55
- @data = SmallCache.new(&KEY_BLOCK)
56
- end
57
-
58
- # Cache the templates returned by the block
59
- def cache(key, name, prefix, partial, locals)
60
- if Resolver.caching?
61
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
62
- else
63
- fresh_templates = yield
64
- cached_templates = @data[key][name][prefix][partial][locals]
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
72
- end
73
-
74
- def clear
75
- @data.clear
76
- end
77
-
78
- private
79
-
80
- def canonical_no_templates(templates)
81
- templates.empty? ? NO_TEMPLATES : templates
82
- end
83
-
84
- def templates_have_changed?(cached_templates, fresh_templates)
85
- # if either the old or new template list is empty, we don't need to (and can't)
86
- # compare modification times, and instead just check whether the lists are different
87
- if cached_templates.blank? || fresh_templates.blank?
88
- return fresh_templates.blank? != cached_templates.blank?
89
- 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 }
13
+ Path = ActionView::TemplatePath
14
+ deprecate_constant :Path
15
+
16
+ class PathParser # :nodoc:
17
+ ParsedPath = Struct.new(:path, :details)
18
+
19
+ def build_path_regex
20
+ handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
21
+ formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
22
+ locales = "[a-z]{2}(?:-[A-Z]{2})?"
23
+ variants = "[^.]*"
24
+
25
+ %r{
26
+ \A
27
+ (?:(?<prefix>.*)/)?
28
+ (?<partial>_)?
29
+ (?<action>.*?)
30
+ (?:\.(?<locale>#{locales}))??
31
+ (?:\.(?<format>#{formats}))??
32
+ (?:\+(?<variant>#{variants}))??
33
+ (?:\.(?<handler>#{handlers}))?
34
+ \z
35
+ }x
36
+ end
37
+
38
+ def parse(path)
39
+ @regex ||= build_path_regex
40
+ match = @regex.match(path)
41
+ path = TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial])
42
+ details = TemplateDetails.new(
43
+ match[:locale]&.to_sym,
44
+ match[:handler]&.to_sym,
45
+ match[:format]&.to_sym,
46
+ match[:variant]&.to_sym
47
+ )
48
+ ParsedPath.new(path, details)
95
49
  end
96
50
  end
97
51
 
98
- cattr_accessor :caching
99
- self.caching = true
52
+ cattr_accessor :caching, default: true
100
53
 
101
54
  class << self
102
55
  alias :caching? :caching
103
56
  end
104
57
 
105
- def initialize
106
- @cache = Cache.new
107
- end
108
-
109
58
  def clear_cache
110
- @cache.clear
111
59
  end
112
60
 
113
61
  # Normalizes the arguments and passes it on to find_templates.
114
- def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
115
- cached(key, [name, prefix, partial], details, locals) do
116
- find_templates(name, prefix, partial, details, false)
117
- end
62
+ def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
63
+ _find_all(name, prefix, partial, details, key, locals)
118
64
  end
119
65
 
120
- def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[])
121
- cached(key, [name, prefix, partial], details, locals) do
122
- find_templates(name, prefix, partial, details, true)
123
- end
66
+ def all_template_paths # :nodoc:
67
+ # Not implemented by default
68
+ []
124
69
  end
125
70
 
126
71
  private
72
+ def _find_all(name, prefix, partial, details, key, locals)
73
+ find_templates(name, prefix, partial, details, locals)
74
+ end
127
75
 
128
76
  delegate :caching?, to: :class
129
77
 
130
78
  # This is what child classes implement. No defaults are needed
131
79
  # because Resolver guarantees that the arguments are present and
132
80
  # normalized.
133
- def find_templates(name, prefix, partial, details, outside_app_allowed)
134
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed) method"
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)
140
- end
141
-
142
- # Handles templates caching. If a key is given and caching is on
143
- # always check the cache before hitting the resolver. Otherwise,
144
- # it always hits the resolver but if the key is present, check if the
145
- # resolver is fresher before returning it.
146
- def cached(key, path_info, details, locals) #:nodoc:
147
- name, prefix, partial = path_info
148
- locals = locals.map { |x| x.to_s }.sort!
149
-
150
- if key
151
- @cache.cache(key, name, prefix, partial, locals) do
152
- decorate(yield, path_info, details, locals)
153
- end
154
- else
155
- decorate(yield, path_info, details, locals)
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))
167
- end
81
+ def find_templates(name, prefix, partial, details, locals = [])
82
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
168
83
  end
169
84
  end
170
85
 
171
- # An abstract class that implements a Resolver with path semantics.
172
- class PathResolver < Resolver #:nodoc:
173
- EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
174
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
86
+ # A resolver that loads files from the filesystem.
87
+ class FileSystemResolver < Resolver
88
+ attr_reader :path
175
89
 
176
- def initialize(pattern=nil)
177
- @pattern = pattern || DEFAULT_PATTERN
90
+ def initialize(path)
91
+ raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
92
+ @unbound_templates = Concurrent::Map.new
93
+ @path_parser = PathParser.new
94
+ @path = File.expand_path(path)
178
95
  super()
179
96
  end
180
97
 
181
- private
182
-
183
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
184
- path = Path.build(name, prefix, partial)
185
- query(path, details, details[:formats], outside_app_allowed)
98
+ def clear_cache
99
+ @unbound_templates.clear
100
+ @path_parser = PathParser.new
101
+ super
186
102
  end
187
103
 
188
- def query(path, details, formats, outside_app_allowed)
189
- query = build_query(path, details)
190
-
191
- template_paths = find_template_paths query
192
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
193
-
194
- template_paths.map { |template|
195
- handler, format, variant = extract_handler_and_format_and_variant(template, formats)
196
- contents = File.binread(template)
197
-
198
- Template.new(contents, File.expand_path(template), handler,
199
- :virtual_path => path.virtual,
200
- :format => format,
201
- :variant => variant,
202
- :updated_at => mtime(template)
203
- )
204
- }
104
+ def to_s
105
+ @path.to_s
205
106
  end
107
+ alias :to_path :to_s
206
108
 
207
- def reject_files_external_to_app(files)
208
- files.reject { |filename| !inside_path?(@path, filename) }
109
+ def eql?(resolver)
110
+ self.class.equal?(resolver.class) && to_path == resolver.to_path
209
111
  end
112
+ alias :== :eql?
210
113
 
211
- if RUBY_VERSION >= '2.2.0'
212
- def find_template_paths(query)
213
- Dir[query].reject { |filename|
214
- File.directory?(filename) ||
215
- # deals with case-insensitive file systems.
216
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
217
- }
114
+ def all_template_paths # :nodoc:
115
+ paths = template_glob("**/*")
116
+ paths.map do |filename|
117
+ filename.from(@path.size + 1).remove(/\.[^\/]*\z/)
118
+ end.uniq.map do |filename|
119
+ TemplatePath.parse(filename)
218
120
  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
-
224
- Dir[query].reject { |filename|
225
- File.directory?(filename) ||
226
- !sanitizer[File.dirname(filename)].include?(filename)
227
- }
228
- end
229
- end
230
-
231
- def inside_path?(path, filename)
232
- filename = File.expand_path(filename)
233
- path = File.join(path, '')
234
- filename.start_with?(path)
235
121
  end
236
122
 
237
- # Helper for building query glob string based on resolver's pattern.
238
- def build_query(path, details)
239
- query = @pattern.dup
240
-
241
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
242
- query.gsub!(/\:prefix(\/)?/, prefix)
243
-
244
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
245
- query.gsub!(/\:action/, partial)
123
+ private
124
+ def _find_all(name, prefix, partial, details, key, locals)
125
+ requested_details = key || TemplateDetails::Requested.new(**details)
126
+ cache = key ? @unbound_templates : Concurrent::Map.new
127
+
128
+ unbound_templates =
129
+ cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do
130
+ path = TemplatePath.build(name, prefix, partial)
131
+ unbound_templates_from_path(path)
132
+ end
246
133
 
247
- details.each do |ext, variants|
248
- query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
134
+ filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template|
135
+ unbound_template.bind_locals(locals)
136
+ end
249
137
  end
250
138
 
251
- File.expand_path(query, @path)
252
- end
253
-
254
- def escape_entry(entry)
255
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
256
- end
139
+ def source_for_template(template)
140
+ Template::Sources::File.new(template)
141
+ end
257
142
 
258
- # Returns the file mtime from the filesystem.
259
- def mtime(p)
260
- File.mtime(p)
261
- end
143
+ def build_unbound_template(template)
144
+ parsed = @path_parser.parse(template.from(@path.size + 1))
145
+ details = parsed.details
146
+ source = source_for_template(template)
262
147
 
263
- # Extract handler, formats and variant from path. If a format cannot be found neither
264
- # from the path, or the handler, we should return the array of formats given
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
148
+ UnboundTemplate.new(
149
+ source,
150
+ template,
151
+ details: details,
152
+ virtual_path: parsed.path.virtual,
153
+ )
276
154
  end
277
155
 
278
- handler = Template.handler_for_extension(extension)
279
- format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
280
- format &&= Template::Types[format]
156
+ def unbound_templates_from_path(path)
157
+ if path.name.include?(".")
158
+ return []
159
+ end
281
160
 
282
- [handler, format, variant]
283
- end
284
- end
161
+ # Instead of checking for every possible path, as our other globs would
162
+ # do, scan the directory for files with the right prefix.
163
+ paths = template_glob("#{escape_entry(path.to_s)}*")
285
164
 
286
- # A resolver that loads files from the filesystem. It allows setting your own
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
- #
324
- class FileSystemResolver < PathResolver
325
- def initialize(path, pattern=nil)
326
- raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
327
- super(pattern)
328
- @path = File.expand_path(path)
329
- end
165
+ paths.map do |path|
166
+ build_unbound_template(path)
167
+ end.select do |template|
168
+ # Select for exact virtual path match, including case sensitivity
169
+ template.virtual_path == path.virtual
170
+ end
171
+ end
330
172
 
331
- def to_s
332
- @path.to_s
333
- end
334
- alias :to_path :to_s
173
+ def filter_and_sort_by_details(templates, requested_details)
174
+ filtered_templates = templates.select do |template|
175
+ template.details.matches?(requested_details)
176
+ end
335
177
 
336
- def eql?(resolver)
337
- self.class.equal?(resolver.class) && to_path == resolver.to_path
338
- end
339
- alias :== :eql?
340
- end
178
+ if filtered_templates.count > 1
179
+ filtered_templates.sort_by! do |template|
180
+ template.details.sort_key_for(requested_details)
181
+ end
182
+ end
341
183
 
342
- # An Optimized resolver for Rails' most common case.
343
- class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
344
- def build_query(path, details)
345
- query = escape_entry(File.join(@path, path))
184
+ filtered_templates
185
+ end
346
186
 
347
- exts = EXTENSIONS.map do |ext, prefix|
348
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
349
- end.join
187
+ # Safe glob within @path
188
+ def template_glob(glob)
189
+ query = File.join(escape_entry(@path), glob)
190
+ path_with_slash = File.join(@path, "")
350
191
 
351
- query + exts
352
- end
353
- end
192
+ Dir.glob(query).filter_map do |filename|
193
+ filename = File.expand_path(filename)
194
+ next if File.directory?(filename)
195
+ next unless filename.start_with?(path_with_slash)
354
196
 
355
- # The same as FileSystemResolver but does not allow templates to store
356
- # a virtual path since it is invalid for such resolvers.
357
- class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
358
- def self.instances
359
- [new(""), new("/")]
360
- end
197
+ filename
198
+ end
199
+ end
361
200
 
362
- def decorate(*)
363
- super.each { |t| t.virtual_path = nil }
364
- end
201
+ def escape_entry(entry)
202
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
203
+ end
365
204
  end
366
205
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class Template
5
+ module Sources
6
+ class File
7
+ def initialize(filename)
8
+ @filename = filename
9
+ end
10
+
11
+ def to_s
12
+ ::File.binread @filename
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class Template
5
+ module Sources
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :File
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,22 +1,20 @@
1
- module ActionView #:nodoc:
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView # :nodoc:
2
4
  # = Action View Text Template
3
- class Template
4
- class Text #:nodoc:
5
+ class Template # :nodoc:
6
+ class Text # :nodoc:
5
7
  attr_accessor :type
6
8
 
7
- def initialize(string, type = nil)
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
- 'text template'
14
+ "text template"
15
15
  end
16
16
 
17
- def inspect
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 formats
30
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
27
+ def format
28
+ :text
31
29
  end
32
30
  end
33
31
  end
@@ -1,23 +1,17 @@
1
- require 'set'
2
- require 'active_support/core_ext/module/attribute_accessors'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
3
4
 
4
5
  module ActionView
5
- class Template
6
- class Types
6
+ class Template # :nodoc:
7
+ module Types
7
8
  class Type
8
- cattr_accessor :types
9
- self.types = Set.new
10
-
11
- def self.register(*t)
12
- types.merge(t.map { |type| type.to_s })
13
- end
14
-
15
- register :html, :text, :js, :css, :xml, :json
9
+ SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
16
10
 
17
11
  def self.[](type)
18
- return type if type.is_a?(self)
19
-
20
- if type.is_a?(Symbol) || types.member?(type.to_s)
12
+ if type.is_a?(self)
13
+ type
14
+ else
21
15
  new(type)
22
16
  end
23
17
  end
@@ -28,30 +22,38 @@ module ActionView
28
22
  @symbol = symbol.to_sym
29
23
  end
30
24
 
31
- delegate :to_s, :to_sym, :to => :symbol
25
+ def to_s
26
+ @symbol.to_s
27
+ end
32
28
  alias to_str to_s
33
29
 
34
30
  def ref
35
- to_sym || to_s
31
+ @symbol
36
32
  end
33
+ alias to_sym ref
37
34
 
38
35
  def ==(type)
39
- return false if type.blank?
40
- symbol.to_sym == type.to_sym
36
+ @symbol == type.to_sym unless type.blank?
41
37
  end
42
38
  end
43
39
 
44
- cattr_accessor :type_klass
40
+ class << self
41
+ attr_accessor :type_klass
45
42
 
46
- def self.delegate_to(klass)
47
- self.type_klass = klass
48
- end
43
+ def delegate_to(klass)
44
+ self.type_klass = klass
45
+ end
49
46
 
50
- delegate_to Type
47
+ def [](type)
48
+ type_klass[type]
49
+ end
51
50
 
52
- def self.[](type)
53
- type_klass[type]
51
+ def symbols
52
+ type_klass::SET.symbols
53
+ end
54
54
  end
55
+
56
+ delegate_to Type
55
57
  end
56
58
  end
57
59
  end