omg-actionview 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,449 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/redefine_method"
4
+ require "action_controller"
5
+ require "action_controller/test_case"
6
+ require "action_view"
7
+
8
+ require "rails-dom-testing"
9
+
10
+ module ActionView
11
+ # = Action View Test Case
12
+ #
13
+ # Read more about <tt>ActionView::TestCase</tt> in {Testing Rails Applications}[https://guides.rubyonrails.org/testing.html#testing-view-partials]
14
+ # in the guides.
15
+ class TestCase < ActiveSupport::TestCase
16
+ class TestController < ActionController::Base
17
+ include ActionDispatch::TestProcess
18
+
19
+ attr_accessor :request, :response, :params
20
+
21
+ class << self
22
+ # Overrides AbstractController::Base#controller_path
23
+ attr_accessor :controller_path
24
+ end
25
+
26
+ def controller_path=(path)
27
+ self.class.controller_path = path
28
+ end
29
+
30
+ def self.controller_name
31
+ "test"
32
+ end
33
+
34
+ def initialize
35
+ super
36
+ self.class.controller_path = ""
37
+ @request = ActionController::TestRequest.create(self.class)
38
+ @response = ActionDispatch::TestResponse.new
39
+
40
+ @request.env.delete("PATH_INFO")
41
+ @params = ActionController::Parameters.new
42
+ end
43
+ end
44
+
45
+ module Behavior
46
+ extend ActiveSupport::Concern
47
+
48
+ include ActionDispatch::Assertions, ActionDispatch::TestProcess
49
+ include Rails::Dom::Testing::Assertions
50
+ include ActionController::TemplateAssertions
51
+ include ActionView::Context
52
+
53
+ include ActionDispatch::Routing::PolymorphicRoutes
54
+
55
+ include AbstractController::Helpers
56
+ include ActionView::Helpers
57
+ include ActionView::RecordIdentifier
58
+ include ActionView::RoutingUrlFor
59
+
60
+ include ActiveSupport::Testing::ConstantLookup
61
+
62
+ delegate :lookup_context, to: :controller
63
+ attr_accessor :controller, :request, :output_buffer, :rendered
64
+
65
+ module ClassMethods
66
+ def inherited(descendant) # :nodoc:
67
+ super
68
+
69
+ descendant_content_class = content_class.dup
70
+
71
+ if descendant_content_class.respond_to?(:set_temporary_name)
72
+ descendant_content_class.set_temporary_name("rendered_content")
73
+ end
74
+
75
+ descendant.content_class = descendant_content_class
76
+ end
77
+
78
+ # Register a callable to parse rendered content for a given template
79
+ # format.
80
+ #
81
+ # Each registered parser will also define a +#rendered.[FORMAT]+ helper
82
+ # method, where +[FORMAT]+ corresponds to the value of the
83
+ # +format+ argument.
84
+ #
85
+ # By default, ActionView::TestCase defines parsers for:
86
+ #
87
+ # * +:html+ - returns an instance of +Nokogiri::XML::Node+
88
+ # * +:json+ - returns an instance of ActiveSupport::HashWithIndifferentAccess
89
+ #
90
+ # These pre-registered parsers also define corresponding helpers:
91
+ #
92
+ # * +:html+ - defines +rendered.html+
93
+ # * +:json+ - defines +rendered.json+
94
+ #
95
+ # ==== Parameters
96
+ #
97
+ # [+format+]
98
+ # The name (as a +Symbol+) of the format used to render the content.
99
+ #
100
+ # [+callable+]
101
+ # The parser. A callable object that accepts the rendered string as
102
+ # its sole argument. Alternatively, the parser can be specified as a
103
+ # block.
104
+ #
105
+ # ==== Examples
106
+ #
107
+ # test "renders HTML" do
108
+ # article = Article.create!(title: "Hello, world")
109
+ #
110
+ # render partial: "articles/article", locals: { article: article }
111
+ #
112
+ # assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
113
+ # end
114
+ #
115
+ # test "renders JSON" do
116
+ # article = Article.create!(title: "Hello, world")
117
+ #
118
+ # render formats: :json, partial: "articles/article", locals: { article: article }
119
+ #
120
+ # assert_pattern { rendered.json => { title: "Hello, world" } }
121
+ # end
122
+ #
123
+ # To parse the rendered content into RSS, register a call to +RSS::Parser.parse+:
124
+ #
125
+ # register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }
126
+ #
127
+ # test "renders RSS" do
128
+ # article = Article.create!(title: "Hello, world")
129
+ #
130
+ # render formats: :rss, partial: article
131
+ #
132
+ # assert_equal "Hello, world", rendered.rss.items.last.title
133
+ # end
134
+ #
135
+ # To parse the rendered content into a +Capybara::Simple::Node+,
136
+ # re-register an +:html+ parser with a call to +Capybara.string+:
137
+ #
138
+ # register_parser :html, -> rendered { Capybara.string(rendered) }
139
+ #
140
+ # test "renders HTML" do
141
+ # article = Article.create!(title: "Hello, world")
142
+ #
143
+ # render partial: article
144
+ #
145
+ # rendered.html.assert_css "h1", text: "Hello, world"
146
+ # end
147
+ #
148
+ def register_parser(format, callable = nil, &block)
149
+ parser = callable || block || :itself.to_proc
150
+ content_class.redefine_method(format) do
151
+ parser.call(to_s)
152
+ end
153
+ end
154
+
155
+ def tests(helper_class)
156
+ case helper_class
157
+ when String, Symbol
158
+ self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
159
+ when Module
160
+ self.helper_class = helper_class
161
+ end
162
+ end
163
+
164
+ def determine_default_helper_class(name)
165
+ determine_constant_from_test_name(name) do |constant|
166
+ Module === constant && !(Class === constant)
167
+ end
168
+ end
169
+
170
+ def helper_method(*methods)
171
+ # Almost a duplicate from ActionController::Helpers
172
+ methods.flatten.each do |method|
173
+ _helpers_for_modification.module_eval <<~end_eval, __FILE__, __LINE__ + 1
174
+ def #{method}(...) # def current_user(...)
175
+ _test_case.send(:'#{method}', ...) # _test_case.send(:'current_user', ...)
176
+ end # end
177
+ end_eval
178
+ end
179
+ end
180
+
181
+ attr_writer :helper_class
182
+
183
+ def helper_class
184
+ @helper_class ||= determine_default_helper_class(name)
185
+ end
186
+
187
+ def new(*)
188
+ include_helper_modules!
189
+ super
190
+ end
191
+
192
+ private
193
+ def include_helper_modules!
194
+ helper(helper_class) if helper_class
195
+ include _helpers
196
+ end
197
+ end
198
+
199
+ included do
200
+ class_attribute :content_class, instance_accessor: false, default: RenderedViewContent
201
+
202
+ setup :setup_with_controller
203
+
204
+ register_parser :html, -> rendered { Rails::Dom::Testing.html_document_fragment.parse(rendered) }
205
+ register_parser :json, -> rendered { JSON.parse(rendered, object_class: ActiveSupport::HashWithIndifferentAccess) }
206
+
207
+ ActiveSupport.run_load_hooks(:action_view_test_case, self)
208
+
209
+ helper do
210
+ def protect_against_forgery?
211
+ false
212
+ end
213
+
214
+ def _test_case
215
+ controller._test_case
216
+ end
217
+ end
218
+ end
219
+
220
+ def setup_with_controller
221
+ controller_class = Class.new(ActionView::TestCase::TestController)
222
+ @controller = controller_class.new
223
+ @request = @controller.request
224
+ @view_flow = ActionView::OutputFlow.new
225
+ @output_buffer = ActionView::OutputBuffer.new
226
+ @rendered = self.class.content_class.new(+"")
227
+
228
+ test_case_instance = self
229
+ controller_class.define_method(:_test_case) { test_case_instance }
230
+ end
231
+
232
+ def config
233
+ @controller.config if @controller.respond_to?(:config)
234
+ end
235
+
236
+ def render(options = {}, local_assigns = {}, &block)
237
+ view.assign(view_assigns)
238
+ @rendered << output = view.render(options, local_assigns, &block)
239
+ output
240
+ end
241
+
242
+ def rendered_views
243
+ @_rendered_views ||= RenderedViewsCollection.new
244
+ end
245
+
246
+ ##
247
+ # :method: rendered
248
+ #
249
+ # Returns the content rendered by the last +render+ call.
250
+ #
251
+ # The returned object behaves like a string but also exposes a number of methods
252
+ # that allows you to parse the content string in formats registered using
253
+ # <tt>.register_parser</tt>.
254
+ #
255
+ # By default includes the following parsers:
256
+ #
257
+ # +.html+
258
+ #
259
+ # Parse the <tt>rendered</tt> content String into HTML. By default, this means
260
+ # a <tt>Nokogiri::XML::Node</tt>.
261
+ #
262
+ # test "renders HTML" do
263
+ # article = Article.create!(title: "Hello, world")
264
+ #
265
+ # render partial: "articles/article", locals: { article: article }
266
+ #
267
+ # assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
268
+ # end
269
+ #
270
+ # To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
271
+ # re-register an <tt>:html</tt> parser with a call to
272
+ # <tt>Capybara.string</tt>:
273
+ #
274
+ # register_parser :html, -> rendered { Capybara.string(rendered) }
275
+ #
276
+ # test "renders HTML" do
277
+ # article = Article.create!(title: "Hello, world")
278
+ #
279
+ # render partial: article
280
+ #
281
+ # rendered.html.assert_css "h1", text: "Hello, world"
282
+ # end
283
+ #
284
+ # +.json+
285
+ #
286
+ # Parse the <tt>rendered</tt> content String into JSON. By default, this means
287
+ # a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
288
+ #
289
+ # test "renders JSON" do
290
+ # article = Article.create!(title: "Hello, world")
291
+ #
292
+ # render formats: :json, partial: "articles/article", locals: { article: article }
293
+ #
294
+ # assert_pattern { rendered.json => { title: "Hello, world" } }
295
+ # end
296
+
297
+ def _routes
298
+ @controller._routes if @controller.respond_to?(:_routes)
299
+ end
300
+
301
+ class RenderedViewContent < String # :nodoc:
302
+ end
303
+
304
+ # Need to experiment if this priority is the best one: rendered => output_buffer
305
+ class RenderedViewsCollection
306
+ def initialize
307
+ @rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
308
+ end
309
+
310
+ def add(view, locals)
311
+ @rendered_views[view] ||= []
312
+ @rendered_views[view] << locals
313
+ end
314
+
315
+ def locals_for(view)
316
+ @rendered_views[view]
317
+ end
318
+
319
+ def rendered_views
320
+ @rendered_views.keys
321
+ end
322
+
323
+ def view_rendered?(view, expected_locals)
324
+ locals_for(view).any? do |actual_locals|
325
+ expected_locals.all? { |key, value| value == actual_locals[key] }
326
+ end
327
+ end
328
+ end
329
+
330
+ private
331
+ # Need to experiment if this priority is the best one: rendered => output_buffer
332
+ def document_root_element
333
+ Rails::Dom::Testing.html_document.parse(@rendered.blank? ? @output_buffer.to_str : @rendered).root
334
+ end
335
+
336
+ module Locals
337
+ attr_accessor :rendered_views
338
+
339
+ def render(options = {}, local_assigns = {})
340
+ case options
341
+ when Hash
342
+ if block_given?
343
+ rendered_views.add options[:layout], options[:locals]
344
+ elsif options.key?(:partial)
345
+ rendered_views.add options[:partial], options[:locals]
346
+ end
347
+ else
348
+ rendered_views.add options, local_assigns
349
+ end
350
+
351
+ super
352
+ end
353
+ end
354
+
355
+ # The instance of ActionView::Base that is used by +render+.
356
+ def view
357
+ @view ||= begin
358
+ view = @controller.view_context
359
+ view.singleton_class.include(_helpers)
360
+ view.extend(Locals)
361
+ view.rendered_views = rendered_views
362
+ view.output_buffer = output_buffer
363
+ view
364
+ end
365
+ end
366
+
367
+ alias_method :_view, :view
368
+
369
+ INTERNAL_IVARS = [
370
+ :@NAME,
371
+ :@failures,
372
+ :@assertions,
373
+ :@__io__,
374
+ :@_assertion_wrapped,
375
+ :@_assertions,
376
+ :@_result,
377
+ :@_routes,
378
+ :@controller,
379
+ :@_controller,
380
+ :@_request,
381
+ :@_config,
382
+ :@_default_form_builder,
383
+ :@_layouts,
384
+ :@_files,
385
+ :@_rendered_views,
386
+ :@method_name,
387
+ :@output_buffer,
388
+ :@_partials,
389
+ :@passed,
390
+ :@rendered,
391
+ :@request,
392
+ :@routes,
393
+ :@tagged_logger,
394
+ :@_templates,
395
+ :@options,
396
+ :@test_passed,
397
+ :@view,
398
+ :@view_context_class,
399
+ :@view_flow,
400
+ :@_subscribers,
401
+ :@html_document,
402
+ ]
403
+
404
+ def _user_defined_ivars
405
+ instance_variables - INTERNAL_IVARS
406
+ end
407
+
408
+ # Returns a Hash of instance variables and their values, as defined by
409
+ # the user in the test case, which are then assigned to the view being
410
+ # rendered. This is generally intended for internal use and extension
411
+ # frameworks.
412
+ def view_assigns
413
+ Hash[_user_defined_ivars.map do |ivar|
414
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
415
+ end]
416
+ end
417
+
418
+ def method_missing(selector, ...)
419
+ begin
420
+ routes = @controller.respond_to?(:_routes) && @controller._routes
421
+ rescue
422
+ # Don't call routes, if there is an error on _routes call
423
+ end
424
+
425
+ if routes &&
426
+ (routes.named_routes.route_defined?(selector) ||
427
+ routes.mounted_helpers.method_defined?(selector))
428
+ @controller.__send__(selector, ...)
429
+ else
430
+ super
431
+ end
432
+ end
433
+
434
+ def respond_to_missing?(name, include_private = false)
435
+ begin
436
+ routes = @controller.respond_to?(:_routes) && @controller._routes
437
+ rescue
438
+ # Don't call routes, if there is an error on _routes call
439
+ end
440
+
441
+ routes &&
442
+ (routes.named_routes.route_defined?(name) ||
443
+ routes.mounted_helpers.method_defined?(name))
444
+ end
445
+ end
446
+
447
+ include Behavior
448
+ end
449
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/template/resolver"
4
+
5
+ module ActionView # :nodoc:
6
+ # Use FixtureResolver in your tests to simulate the presence of files on the
7
+ # file system. This is used internally by Rails' own test suite, and is
8
+ # useful for testing extensions that have no way of knowing what the file
9
+ # system will look like at runtime.
10
+ class FixtureResolver < FileSystemResolver
11
+ def initialize(hash = {})
12
+ super("")
13
+ @hash = hash
14
+ @path = ""
15
+ end
16
+
17
+ def data
18
+ @hash
19
+ end
20
+
21
+ def to_s
22
+ @hash.keys.join(", ")
23
+ end
24
+
25
+ private
26
+ def template_glob(glob)
27
+ @hash.keys.filter_map do |path|
28
+ "/#{path}" if File.fnmatch(glob, path)
29
+ end
30
+ end
31
+
32
+ def source_for_template(template)
33
+ @hash[template.from(1)]
34
+ end
35
+ end
36
+
37
+ class NullResolver < Resolver
38
+ def find_templates(name, prefix, partial, details, locals = [])
39
+ path = TemplatePath.build(name, prefix, partial)
40
+ handler = ActionView::Template::Handlers::Raw
41
+ [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: nil, variant: nil, locals: locals)]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+
5
+ module ActionView
6
+ class UnboundTemplate
7
+ attr_reader :virtual_path, :details
8
+ delegate :locale, :format, :variant, :handler, to: :@details
9
+
10
+ def initialize(source, identifier, details:, virtual_path:)
11
+ @source = source
12
+ @identifier = identifier
13
+ @details = details
14
+ @virtual_path = virtual_path
15
+
16
+ @templates = Concurrent::Map.new(initial_capacity: 2)
17
+ @write_lock = Mutex.new
18
+ end
19
+
20
+ def bind_locals(locals)
21
+ unless template = @templates[locals]
22
+ @write_lock.synchronize do
23
+ normalized_locals = normalize_locals(locals)
24
+
25
+ # We need ||=, both to dedup on the normalized locals and to check
26
+ # while holding the lock.
27
+ template = (@templates[normalized_locals] ||= build_template(normalized_locals))
28
+
29
+ if template.strict_locals?
30
+ # Under strict locals, we only need one template.
31
+ # This replaces the @templates Concurrent::Map with a hash which
32
+ # returns this template for every key.
33
+ @templates = Hash.new(template).freeze
34
+ else
35
+ # This may have already been assigned, but we've already de-dup'd so
36
+ # reassignment is fine.
37
+ @templates[locals.dup] = template
38
+ end
39
+ end
40
+ end
41
+ template
42
+ end
43
+
44
+ def built_templates # :nodoc:
45
+ @templates.values
46
+ end
47
+
48
+ private
49
+ def build_template(locals)
50
+ Template.new(
51
+ @source,
52
+ @identifier,
53
+ details.handler_class,
54
+
55
+ format: details.format_or_default,
56
+ variant: variant&.to_s,
57
+ virtual_path: @virtual_path,
58
+
59
+ locals: locals.map(&:to_s)
60
+ )
61
+ end
62
+
63
+ def normalize_locals(locals)
64
+ locals.map(&:to_sym).sort!.freeze
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_version"
4
+
5
+ module ActionView
6
+ # Returns the currently loaded version of Action View as a +Gem::Version+.
7
+ def self.version
8
+ gem_version
9
+ end
10
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module ViewPaths
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ ActionView::PathRegistry.set_view_paths(self, ActionView::PathSet.new.freeze)
9
+ end
10
+
11
+ delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
12
+ :locale, :locale=, to: :lookup_context
13
+
14
+ module ClassMethods
15
+ def _view_paths
16
+ ActionView::PathRegistry.get_view_paths(self)
17
+ end
18
+
19
+ def _view_paths=(paths)
20
+ ActionView::PathRegistry.set_view_paths(self, paths)
21
+ end
22
+
23
+ def _prefixes # :nodoc:
24
+ @_prefixes ||= begin
25
+ return local_prefixes if superclass.abstract?
26
+
27
+ local_prefixes + superclass._prefixes
28
+ end
29
+ end
30
+
31
+ def _build_view_paths(paths) # :nodoc:
32
+ return paths if ActionView::PathSet === paths
33
+
34
+ paths = ActionView::PathRegistry.cast_file_system_resolvers(paths)
35
+ ActionView::PathSet.new(paths)
36
+ end
37
+
38
+ # Append a path to the list of view paths for this controller.
39
+ #
40
+ # ==== Parameters
41
+ # * <tt>path</tt> - If a String is provided, it gets converted into
42
+ # the default view path. You may also provide a custom view path
43
+ # (see ActionView::PathSet for more information)
44
+ def append_view_path(path)
45
+ self._view_paths = view_paths + _build_view_paths(path)
46
+ end
47
+
48
+ # Prepend a path to the list of view paths for this controller.
49
+ #
50
+ # ==== Parameters
51
+ # * <tt>path</tt> - If a String is provided, it gets converted into
52
+ # the default view path. You may also provide a custom view path
53
+ # (see ActionView::PathSet for more information)
54
+ def prepend_view_path(path)
55
+ self._view_paths = _build_view_paths(path) + view_paths
56
+ end
57
+
58
+ # A list of all of the default view paths for this controller.
59
+ def view_paths
60
+ _view_paths
61
+ end
62
+
63
+ # Set the view paths.
64
+ #
65
+ # ==== Parameters
66
+ # * <tt>paths</tt> - If a PathSet is provided, use that;
67
+ # otherwise, process the parameter into a PathSet.
68
+ def view_paths=(paths)
69
+ self._view_paths = _build_view_paths(paths)
70
+ end
71
+
72
+ private
73
+ # Override this method in your controller if you want to change paths prefixes for finding views.
74
+ # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
75
+ def local_prefixes
76
+ [controller_path]
77
+ end
78
+ end
79
+
80
+ # The prefixes used in render "foo" shortcuts.
81
+ def _prefixes # :nodoc:
82
+ self.class._prefixes
83
+ end
84
+
85
+ # LookupContext is the object responsible for holding all
86
+ # information required for looking up templates, i.e. view paths and
87
+ # details. Check ActionView::LookupContext for more information.
88
+ def lookup_context
89
+ @_lookup_context ||=
90
+ ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
91
+ end
92
+
93
+ def details_for_lookup
94
+ {}
95
+ end
96
+
97
+ # Append a path to the list of view paths for the current LookupContext.
98
+ #
99
+ # ==== Parameters
100
+ # * <tt>path</tt> - If a String is provided, it gets converted into
101
+ # the default view path. You may also provide a custom view path
102
+ # (see ActionView::PathSet for more information)
103
+ def append_view_path(path)
104
+ lookup_context.append_view_paths(self.class._build_view_paths(path))
105
+ end
106
+
107
+ # Prepend a path to the list of view paths for the current LookupContext.
108
+ #
109
+ # ==== Parameters
110
+ # * <tt>path</tt> - If a String is provided, it gets converted into
111
+ # the default view path. You may also provide a custom view path
112
+ # (see ActionView::PathSet for more information)
113
+ def prepend_view_path(path)
114
+ lookup_context.prepend_view_paths(self.class._build_view_paths(path))
115
+ end
116
+ end
117
+ end