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,473 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module ActionView
6
+ module Helpers # :nodoc:
7
+ # = Action View Asset URL \Helpers
8
+ #
9
+ # This module provides methods for generating asset paths and
10
+ # URLs.
11
+ #
12
+ # image_path("rails.png")
13
+ # # => "/assets/rails.png"
14
+ #
15
+ # image_url("rails.png")
16
+ # # => "http://www.example.com/assets/rails.png"
17
+ #
18
+ # === Using asset hosts
19
+ #
20
+ # By default, \Rails links to these assets on the current host in the public
21
+ # folder, but you can direct \Rails to link to assets from a dedicated asset
22
+ # server by setting <tt>ActionController::Base.asset_host</tt> in the application
23
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
24
+ # For example, you'd define <tt>assets.example.com</tt> to be your asset
25
+ # host this way, inside the <tt>configure</tt> block of your environment-specific
26
+ # configuration files or <tt>config/application.rb</tt>:
27
+ #
28
+ # config.action_controller.asset_host = "assets.example.com"
29
+ #
30
+ # Helpers take that into account:
31
+ #
32
+ # image_tag("rails.png")
33
+ # # => <img src="http://assets.example.com/assets/rails.png" />
34
+ # stylesheet_link_tag("application")
35
+ # # => <link href="http://assets.example.com/assets/application.css" rel="stylesheet" />
36
+ #
37
+ # Browsers open a limited number of simultaneous connections to a single
38
+ # host. The exact number varies by browser and version. This limit may cause
39
+ # some asset downloads to wait for previous assets to finish before they can
40
+ # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
41
+ # distribute the requests over four hosts. For example,
42
+ # <tt>assets%d.example.com</tt> will spread the asset requests over
43
+ # "assets0.example.com", ..., "assets3.example.com".
44
+ #
45
+ # image_tag("rails.png")
46
+ # # => <img src="http://assets0.example.com/assets/rails.png" />
47
+ # stylesheet_link_tag("application")
48
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
49
+ #
50
+ # This may improve the asset loading performance of your application.
51
+ # It is also possible the combination of additional connection overhead
52
+ # (DNS, SSL) and the overall browser connection limits may result in this
53
+ # solution being slower. You should be sure to measure your actual
54
+ # performance across targeted browsers both before and after this change.
55
+ #
56
+ # To implement the corresponding hosts you can either set up four actual
57
+ # hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
58
+ # You can read more about setting up your DNS CNAME records from your ISP.
59
+ #
60
+ # Note: This is purely a browser performance optimization and is not meant
61
+ # for server load balancing. See https://www.die.net/musings/page_load_time/
62
+ # for background and https://www.browserscope.org/?category=network for
63
+ # connection limit data.
64
+ #
65
+ # Alternatively, you can exert more control over the asset host by setting
66
+ # +asset_host+ to a proc like this:
67
+ #
68
+ # ActionController::Base.asset_host = Proc.new { |source|
69
+ # "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com"
70
+ # }
71
+ # image_tag("rails.png")
72
+ # # => <img src="http://assets1.example.com/assets/rails.png" />
73
+ # stylesheet_link_tag("application")
74
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
75
+ #
76
+ # The example above generates "http://assets1.example.com" and
77
+ # "http://assets2.example.com". This option is useful for example if
78
+ # you need fewer/more than four hosts, custom host names, etc.
79
+ #
80
+ # As you see the proc takes a +source+ parameter. That's a string with the
81
+ # absolute path of the asset, for example "/assets/rails.png".
82
+ #
83
+ # ActionController::Base.asset_host = Proc.new { |source|
84
+ # if source.end_with?('.css')
85
+ # "http://stylesheets.example.com"
86
+ # else
87
+ # "http://assets.example.com"
88
+ # end
89
+ # }
90
+ # image_tag("rails.png")
91
+ # # => <img src="http://assets.example.com/assets/rails.png" />
92
+ # stylesheet_link_tag("application")
93
+ # # => <link href="http://stylesheets.example.com/assets/application.css" rel="stylesheet" />
94
+ #
95
+ # Alternatively you may ask for a second parameter +request+. That one is
96
+ # particularly useful for serving assets from an SSL-protected page. The
97
+ # example proc below disables asset hosting for HTTPS connections, while
98
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
99
+ # have SSL certificates for each of the asset hosts this technique allows you
100
+ # to avoid warnings in the client about mixed media.
101
+ # Note that the +request+ parameter might not be supplied, e.g. when the assets
102
+ # are precompiled with the command <tt>bin/rails assets:precompile</tt>. Make sure to use a
103
+ # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
104
+ # to +nil+.
105
+ #
106
+ # config.action_controller.asset_host = Proc.new { |source, request|
107
+ # if request && request.ssl?
108
+ # "#{request.protocol}#{request.host_with_port}"
109
+ # else
110
+ # "#{request.protocol}assets.example.com"
111
+ # end
112
+ # }
113
+ #
114
+ # You can also implement a custom asset host object that responds to +call+
115
+ # and takes either one or two parameters just like the proc.
116
+ #
117
+ # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
118
+ # "http://asset%d.example.com", "https://asset1.example.com"
119
+ # )
120
+ #
121
+ module AssetUrlHelper
122
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
123
+
124
+ # This is the entry point for all assets.
125
+ # When using an asset pipeline gem (e.g. propshaft or sprockets-rails), the
126
+ # behavior is "enhanced". You can bypass the asset pipeline by passing in
127
+ # <tt>skip_pipeline: true</tt> to the options.
128
+ #
129
+ # All other asset *_path helpers delegate through this method.
130
+ #
131
+ # === With the asset pipeline
132
+ #
133
+ # All options passed to +asset_path+ will be passed to +compute_asset_path+
134
+ # which is implemented by asset pipeline gems.
135
+ #
136
+ # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
137
+ # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
138
+ # asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
139
+ #
140
+ # === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
141
+ #
142
+ # Accepts a <tt>type</tt> option that can specify the asset's extension. No error
143
+ # checking is done to verify the source passed into +asset_path+ is valid
144
+ # and that the file exists on disk.
145
+ #
146
+ # asset_path("application.js", skip_pipeline: true) # => "application.js"
147
+ # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png"
148
+ # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js"
149
+ # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css"
150
+ #
151
+ # === Options applying to all assets
152
+ #
153
+ # Below lists scenarios that apply to +asset_path+ whether or not you're
154
+ # using the asset pipeline.
155
+ #
156
+ # - All fully qualified URLs are returned immediately. This bypasses the
157
+ # asset pipeline and all other behavior described.
158
+ #
159
+ # asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
160
+ #
161
+ # - All assets that begin with a forward slash are assumed to be full
162
+ # URLs and will not be expanded. This will bypass the asset pipeline.
163
+ #
164
+ # asset_path("/foo.png") # => "/foo.png"
165
+ #
166
+ # - All blank strings will be returned immediately. This bypasses the
167
+ # asset pipeline and all other behavior described.
168
+ #
169
+ # asset_path("") # => ""
170
+ #
171
+ # - If <tt>config.relative_url_root</tt> is specified, all assets will have that
172
+ # root prepended.
173
+ #
174
+ # Rails.application.config.relative_url_root = "bar"
175
+ # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js"
176
+ #
177
+ # - A different asset host can be specified via <tt>config.action_controller.asset_host</tt>
178
+ # this is commonly used in conjunction with a CDN.
179
+ #
180
+ # Rails.application.config.action_controller.asset_host = "assets.example.com"
181
+ # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js"
182
+ #
183
+ # - An extension name can be specified manually with <tt>extname</tt>.
184
+ #
185
+ # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js"
186
+ # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js"
187
+ def asset_path(source, options = {})
188
+ raise ArgumentError, "nil is not a valid asset source" if source.nil?
189
+
190
+ source = source.to_s
191
+ return "" if source.blank?
192
+ return source if URI_REGEXP.match?(source)
193
+
194
+ tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "")
195
+
196
+ if extname = compute_asset_extname(source, options)
197
+ source = "#{source}#{extname}"
198
+ end
199
+
200
+ unless source.start_with?(?/)
201
+ if options[:skip_pipeline]
202
+ source = public_compute_asset_path(source, options)
203
+ else
204
+ source = compute_asset_path(source, options)
205
+ end
206
+ end
207
+
208
+ relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
209
+ if relative_url_root
210
+ source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/")
211
+ end
212
+
213
+ if host = compute_asset_host(source, options)
214
+ source = File.join(host, source)
215
+ end
216
+
217
+ "#{source}#{tail}"
218
+ end
219
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
220
+
221
+ # Computes the full URL to an asset in the public directory. This
222
+ # will use +asset_path+ internally, so most of their behaviors
223
+ # will be the same. If +:host+ options is set, it overwrites global
224
+ # +config.action_controller.asset_host+ setting.
225
+ #
226
+ # All other options provided are forwarded to +asset_path+ call.
227
+ #
228
+ # asset_url "application.js" # => http://example.com/assets/application.js
229
+ # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
230
+ #
231
+ def asset_url(source, options = {})
232
+ path_to_asset(source, options.merge(protocol: :request))
233
+ end
234
+ alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
235
+
236
+ ASSET_EXTENSIONS = {
237
+ javascript: ".js",
238
+ stylesheet: ".css"
239
+ }
240
+
241
+ # Compute extname to append to asset path. Returns +nil+ if
242
+ # nothing should be added.
243
+ def compute_asset_extname(source, options = {})
244
+ return if options[:extname] == false
245
+ extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
246
+ if extname && File.extname(source) != extname
247
+ extname
248
+ else
249
+ nil
250
+ end
251
+ end
252
+
253
+ # Maps asset types to public directory.
254
+ ASSET_PUBLIC_DIRECTORIES = {
255
+ audio: "/audios",
256
+ font: "/fonts",
257
+ image: "/images",
258
+ javascript: "/javascripts",
259
+ stylesheet: "/stylesheets",
260
+ video: "/videos"
261
+ }
262
+
263
+ # Computes asset path to public directory. Plugins and
264
+ # extensions can override this method to point to custom assets
265
+ # or generate digested paths or query strings.
266
+ def compute_asset_path(source, options = {})
267
+ dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
268
+ File.join(dir, source)
269
+ end
270
+ alias :public_compute_asset_path :compute_asset_path
271
+
272
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
273
+ # the host if no wildcard is set, the host interpolated with the
274
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
275
+ # or the value returned from invoking call on an object responding to call
276
+ # (proc or otherwise).
277
+ def compute_asset_host(source = "", options = {})
278
+ request = self.request if respond_to?(:request)
279
+ host = options[:host]
280
+ host ||= config.asset_host if defined? config.asset_host
281
+
282
+ if host
283
+ if host.respond_to?(:call)
284
+ arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
285
+ args = [source]
286
+ args << request if request && (arity > 1 || arity < 0)
287
+ host = host.call(*args)
288
+ elsif host.include?("%d")
289
+ host = host % (Zlib.crc32(source) % 4)
290
+ end
291
+ end
292
+
293
+ host ||= request.base_url if request && options[:protocol] == :request
294
+ return unless host
295
+
296
+ if URI_REGEXP.match?(host)
297
+ host
298
+ else
299
+ protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
300
+ case protocol
301
+ when :relative
302
+ "//#{host}"
303
+ when :request
304
+ "#{request.protocol}#{host}"
305
+ else
306
+ "#{protocol}://#{host}"
307
+ end
308
+ end
309
+ end
310
+
311
+ # Computes the path to a JavaScript asset in the public javascripts directory.
312
+ # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
313
+ # Full paths from the document root will be passed through.
314
+ # Used internally by +javascript_include_tag+ to build the script path.
315
+ #
316
+ # javascript_path "xmlhr" # => /assets/xmlhr.js
317
+ # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
318
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
319
+ # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
320
+ # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
321
+ def javascript_path(source, options = {})
322
+ path_to_asset(source, { type: :javascript }.merge!(options))
323
+ end
324
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
325
+
326
+ # Computes the full URL to a JavaScript asset in the public javascripts directory.
327
+ # This will use +javascript_path+ internally, so most of their behaviors will be the same.
328
+ # Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
329
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
330
+ #
331
+ # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
332
+ #
333
+ def javascript_url(source, options = {})
334
+ url_to_asset(source, { type: :javascript }.merge!(options))
335
+ end
336
+ alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
337
+
338
+ # Computes the path to a stylesheet asset in the public stylesheets directory.
339
+ # If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
340
+ # Full paths from the document root will be passed through.
341
+ # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
342
+ #
343
+ # stylesheet_path "style" # => /assets/style.css
344
+ # stylesheet_path "dir/style.css" # => /assets/dir/style.css
345
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
346
+ # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
347
+ # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
348
+ def stylesheet_path(source, options = {})
349
+ path_to_asset(source, { type: :stylesheet }.merge!(options))
350
+ end
351
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
352
+
353
+ # Computes the full URL to a stylesheet asset in the public stylesheets directory.
354
+ # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
355
+ # Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
356
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
357
+ #
358
+ # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
359
+ #
360
+ def stylesheet_url(source, options = {})
361
+ url_to_asset(source, { type: :stylesheet }.merge!(options))
362
+ end
363
+ alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
364
+
365
+ # Computes the path to an image asset.
366
+ # Full paths from the document root will be passed through.
367
+ # Used internally by +image_tag+ to build the image path:
368
+ #
369
+ # image_path("edit") # => "/assets/edit"
370
+ # image_path("edit.png") # => "/assets/edit.png"
371
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
372
+ # image_path("/icons/edit.png") # => "/icons/edit.png"
373
+ # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
374
+ #
375
+ # If you have images as application resources this method may conflict with their named routes.
376
+ # The alias +path_to_image+ is provided to avoid that. \Rails uses the alias internally, and
377
+ # plugin authors are encouraged to do so.
378
+ def image_path(source, options = {})
379
+ path_to_asset(source, { type: :image }.merge!(options))
380
+ end
381
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
382
+
383
+ # Computes the full URL to an image asset.
384
+ # This will use +image_path+ internally, so most of their behaviors will be the same.
385
+ # Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
386
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
387
+ #
388
+ # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
389
+ #
390
+ def image_url(source, options = {})
391
+ url_to_asset(source, { type: :image }.merge!(options))
392
+ end
393
+ alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
394
+
395
+ # Computes the path to a video asset in the public videos directory.
396
+ # Full paths from the document root will be passed through.
397
+ # Used internally by +video_tag+ to build the video path.
398
+ #
399
+ # video_path("hd") # => /videos/hd
400
+ # video_path("hd.avi") # => /videos/hd.avi
401
+ # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
402
+ # video_path("/trailers/hd.avi") # => /trailers/hd.avi
403
+ # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
404
+ def video_path(source, options = {})
405
+ path_to_asset(source, { type: :video }.merge!(options))
406
+ end
407
+ alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
408
+
409
+ # Computes the full URL to a video asset in the public videos directory.
410
+ # This will use +video_path+ internally, so most of their behaviors will be the same.
411
+ # Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
412
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
413
+ #
414
+ # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
415
+ #
416
+ def video_url(source, options = {})
417
+ url_to_asset(source, { type: :video }.merge!(options))
418
+ end
419
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route
420
+
421
+ # Computes the path to an audio asset in the public audios directory.
422
+ # Full paths from the document root will be passed through.
423
+ # Used internally by +audio_tag+ to build the audio path.
424
+ #
425
+ # audio_path("horse") # => /audios/horse
426
+ # audio_path("horse.wav") # => /audios/horse.wav
427
+ # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
428
+ # audio_path("/sounds/horse.wav") # => /sounds/horse.wav
429
+ # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
430
+ def audio_path(source, options = {})
431
+ path_to_asset(source, { type: :audio }.merge!(options))
432
+ end
433
+ alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
434
+
435
+ # Computes the full URL to an audio asset in the public audios directory.
436
+ # This will use +audio_path+ internally, so most of their behaviors will be the same.
437
+ # Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
438
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
439
+ #
440
+ # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
441
+ #
442
+ def audio_url(source, options = {})
443
+ url_to_asset(source, { type: :audio }.merge!(options))
444
+ end
445
+ alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
446
+
447
+ # Computes the path to a font asset.
448
+ # Full paths from the document root will be passed through.
449
+ #
450
+ # font_path("font") # => /fonts/font
451
+ # font_path("font.ttf") # => /fonts/font.ttf
452
+ # font_path("dir/font.ttf") # => /fonts/dir/font.ttf
453
+ # font_path("/dir/font.ttf") # => /dir/font.ttf
454
+ # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
455
+ def font_path(source, options = {})
456
+ path_to_asset(source, { type: :font }.merge!(options))
457
+ end
458
+ alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route
459
+
460
+ # Computes the full URL to a font asset.
461
+ # This will use +font_path+ internally, so most of their behaviors will be the same.
462
+ # Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
463
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
464
+ #
465
+ # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
466
+ #
467
+ def font_url(source, options = {})
468
+ url_to_asset(source, { type: :font }.merge!(options))
469
+ end
470
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route
471
+ end
472
+ end
473
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActionView
6
+ module Helpers # :nodoc:
7
+ # = Action View Atom Feed \Helpers
8
+ module AtomFeedHelper
9
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
10
+ # template languages).
11
+ #
12
+ # Full usage example:
13
+ #
14
+ # config/routes.rb:
15
+ # Rails.application.routes.draw do
16
+ # resources :posts
17
+ # root to: "posts#index"
18
+ # end
19
+ #
20
+ # app/controllers/posts_controller.rb:
21
+ # class PostsController < ApplicationController
22
+ # # GET /posts.html
23
+ # # GET /posts.atom
24
+ # def index
25
+ # @posts = Post.all
26
+ #
27
+ # respond_to do |format|
28
+ # format.html
29
+ # format.atom
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # app/views/posts/index.atom.builder:
35
+ # atom_feed do |feed|
36
+ # feed.title("My great blog!")
37
+ # feed.updated(@posts[0].created_at) if @posts.length > 0
38
+ #
39
+ # @posts.each do |post|
40
+ # feed.entry(post) do |entry|
41
+ # entry.title(post.title)
42
+ # entry.content(post.body, type: 'html')
43
+ #
44
+ # entry.author do |author|
45
+ # author.name("DHH")
46
+ # end
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # The options for atom_feed are:
52
+ #
53
+ # * <tt>:language</tt>: Defaults to "en-US".
54
+ # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
55
+ # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
56
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
57
+ # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
58
+ # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
59
+ # 2005 is used (as an "I don't care" value).
60
+ # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
61
+ #
62
+ # Other namespaces can be added to the root element:
63
+ #
64
+ # app/views/posts/index.atom.builder:
65
+ # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
66
+ # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
67
+ # feed.title("My great blog!")
68
+ # feed.updated((@posts.first.created_at))
69
+ # feed.tag!('openSearch:totalResults', 10)
70
+ #
71
+ # @posts.each do |post|
72
+ # feed.entry(post) do |entry|
73
+ # entry.title(post.title)
74
+ # entry.content(post.body, type: 'html')
75
+ # entry.tag!('app:edited', Time.now)
76
+ #
77
+ # entry.author do |author|
78
+ # author.name("DHH")
79
+ # end
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # The Atom spec defines five elements (content rights title subtitle
85
+ # summary) which may directly contain XHTML content if type: 'xhtml'
86
+ # is specified as an attribute. If so, this helper will take care of
87
+ # the enclosing div and XHTML namespace declaration. Example usage:
88
+ #
89
+ # entry.summary type: 'xhtml' do |xhtml|
90
+ # xhtml.p pluralize(order.line_items.count, "line item")
91
+ # xhtml.p "Shipped to #{order.address}"
92
+ # xhtml.p "Paid by #{order.pay_type}"
93
+ # end
94
+ #
95
+ #
96
+ # <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
97
+ # an +AtomBuilder+ instance.
98
+ def atom_feed(options = {}, &block)
99
+ if options[:schema_date]
100
+ options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
101
+ else
102
+ options[:schema_date] = "2005" # The Atom spec copyright date
103
+ end
104
+
105
+ xml = options.delete(:xml) || block.binding.local_variable_get(:xml)
106
+ xml.instruct!
107
+ if options[:instruct]
108
+ options[:instruct].each do |target, attrs|
109
+ if attrs.respond_to?(:keys)
110
+ xml.instruct!(target, attrs)
111
+ elsif attrs.respond_to?(:each)
112
+ attrs.each { |attr_group| xml.instruct!(target, attr_group) }
113
+ end
114
+ end
115
+ end
116
+
117
+ feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
118
+ feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") }
119
+
120
+ xml.feed(feed_opts) do
121
+ xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
122
+ xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port))
123
+ xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url)
124
+
125
+ yield AtomFeedBuilder.new(xml, self, options)
126
+ end
127
+ end
128
+
129
+ class AtomBuilder # :nodoc:
130
+ XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
131
+
132
+ def initialize(xml)
133
+ @xml = xml
134
+ end
135
+
136
+ private
137
+ # Delegate to XML Builder, first wrapping the element in an XHTML
138
+ # namespaced div element if the method and arguments indicate
139
+ # that an xhtml_block? is desired.
140
+ def method_missing(method, *arguments, &block)
141
+ if xhtml_block?(method, arguments)
142
+ @xml.__send__(method, *arguments) do
143
+ @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml|
144
+ block.call(xhtml)
145
+ end
146
+ end
147
+ else
148
+ @xml.__send__(method, *arguments, &block)
149
+ end
150
+ end
151
+
152
+ # True if the method name matches one of the five elements defined
153
+ # in the Atom spec as potentially containing XHTML content and
154
+ # if type: 'xhtml' is, in fact, specified.
155
+ def xhtml_block?(method, arguments)
156
+ if XHTML_TAG_NAMES.include?(method.to_s)
157
+ last = arguments.last
158
+ last.is_a?(Hash) && last[:type].to_s == "xhtml"
159
+ end
160
+ end
161
+ end
162
+
163
+ class AtomFeedBuilder < AtomBuilder # :nodoc:
164
+ def initialize(xml, view, feed_options = {})
165
+ @xml, @view, @feed_options = xml, view, feed_options
166
+ end
167
+
168
+ # Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used.
169
+ def updated(date_or_time = nil)
170
+ @xml.updated((date_or_time || Time.now.utc).xmlschema)
171
+ end
172
+
173
+ # Creates an entry tag for a specific record and prefills the id using class and id.
174
+ #
175
+ # Options:
176
+ #
177
+ # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
178
+ # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
179
+ # * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record.
180
+ # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
181
+ # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
182
+ def entry(record, options = {})
183
+ @xml.entry do
184
+ @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
185
+
186
+ if options[:published] || (record.respond_to?(:created_at) && record.created_at)
187
+ @xml.published((options[:published] || record.created_at).xmlschema)
188
+ end
189
+
190
+ if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
191
+ @xml.updated((options[:updated] || record.updated_at).xmlschema)
192
+ end
193
+
194
+ type = options.fetch(:type, "text/html")
195
+
196
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
197
+ @xml.link(rel: "alternate", type: type, href: url) if url
198
+
199
+ yield AtomBuilder.new(@xml)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end