hanami 2.0.3 → 2.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/LICENSE.md +1 -1
  4. data/README.md +26 -10
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/app.rb +5 -0
  7. data/lib/hanami/config/actions.rb +4 -11
  8. data/lib/hanami/config/assets.rb +84 -0
  9. data/lib/hanami/config/null_config.rb +3 -0
  10. data/lib/hanami/config/views.rb +0 -4
  11. data/lib/hanami/config.rb +71 -5
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  13. data/lib/hanami/extensions/action.rb +8 -6
  14. data/lib/hanami/extensions/router/errors.rb +58 -0
  15. data/lib/hanami/extensions/view/context.rb +129 -60
  16. data/lib/hanami/extensions/view/part.rb +26 -0
  17. data/lib/hanami/extensions/view/scope.rb +26 -0
  18. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  19. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  21. data/lib/hanami/extensions/view/standard_helpers.rb +18 -0
  22. data/lib/hanami/extensions.rb +10 -3
  23. data/lib/hanami/helpers/assets_helper.rb +752 -0
  24. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  25. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  26. data/lib/hanami/helpers/form_helper.rb +213 -0
  27. data/lib/hanami/middleware/assets.rb +21 -0
  28. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  29. data/lib/hanami/middleware/render_errors.rb +90 -0
  30. data/lib/hanami/providers/assets.rb +44 -0
  31. data/lib/hanami/rake_tasks.rb +19 -18
  32. data/lib/hanami/settings.rb +1 -1
  33. data/lib/hanami/slice.rb +48 -2
  34. data/lib/hanami/slice_configurable.rb +3 -2
  35. data/lib/hanami/version.rb +1 -1
  36. data/lib/hanami/web/rack_logger.rb +1 -1
  37. data/lib/hanami.rb +3 -3
  38. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  39. data/spec/integration/action/view_rendering_spec.rb +0 -18
  40. data/spec/integration/assets/assets_spec.rb +101 -0
  41. data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
  42. data/spec/integration/logging/exception_logging_spec.rb +115 -0
  43. data/spec/integration/logging/notifications_spec.rb +68 -0
  44. data/spec/integration/logging/request_logging_spec.rb +128 -0
  45. data/spec/integration/rack_app/middleware_spec.rb +22 -22
  46. data/spec/integration/rack_app/rack_app_spec.rb +3 -220
  47. data/spec/integration/rake_tasks_spec.rb +107 -0
  48. data/spec/integration/view/config/default_context_spec.rb +149 -0
  49. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  50. data/spec/integration/view/config/part_class_spec.rb +147 -0
  51. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  52. data/spec/integration/view/config/paths_spec.rb +119 -0
  53. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  54. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  55. data/spec/integration/view/config/template_spec.rb +38 -0
  56. data/spec/integration/view/context/assets_spec.rb +3 -9
  57. data/spec/integration/view/context/request_spec.rb +3 -7
  58. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  59. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  60. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  61. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  62. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  63. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  64. data/spec/integration/web/render_detailed_errors_spec.rb +107 -0
  65. data/spec/integration/web/render_errors_spec.rb +242 -0
  66. data/spec/spec_helper.rb +1 -1
  67. data/spec/support/app_integration.rb +46 -2
  68. data/spec/support/matchers.rb +32 -0
  69. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
  70. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
  71. data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -6
  72. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  73. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  74. data/spec/unit/hanami/config/views_spec.rb +0 -18
  75. data/spec/unit/hanami/env_spec.rb +11 -25
  76. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  77. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
  78. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +132 -0
  79. data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
  80. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
  81. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
  82. data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
  83. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -0
  84. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  85. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  86. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  87. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  88. data/spec/unit/hanami/version_spec.rb +1 -1
  89. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  90. metadata +95 -35
  91. data/lib/hanami/assets/app_config.rb +0 -61
  92. data/lib/hanami/assets/config.rb +0 -53
  93. data/spec/integration/action/view_integration_spec.rb +0 -165
  94. data/spec/integration/view/part_namespace_spec.rb +0 -96
  95. data/spec/integration/view/path_spec.rb +0 -56
  96. data/spec/integration/view/template_spec.rb +0 -68
  97. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  98. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  99. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  100. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  101. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  102. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  103. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,752 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "hanami/view"
5
+
6
+ # rubocop:disable Metrics/ModuleLength
7
+
8
+ module Hanami
9
+ module Helpers
10
+ # HTML assets helpers
11
+ #
12
+ # Inject these helpers in a view
13
+ #
14
+ # @since 0.1.0
15
+ #
16
+ # @see http://www.rubydoc.info/gems/hanami-helpers/Hanami/Helpers/HtmlHelper
17
+ module AssetsHelper
18
+ # @since 0.1.0
19
+ # @api private
20
+ NEW_LINE_SEPARATOR = "\n"
21
+
22
+ # @since 0.1.0
23
+ # @api private
24
+ WILDCARD_EXT = ".*"
25
+
26
+ # @since 0.1.0
27
+ # @api private
28
+ JAVASCRIPT_EXT = ".js"
29
+
30
+ # @since 0.1.0
31
+ # @api private
32
+ STYLESHEET_EXT = ".css"
33
+
34
+ # @since 0.1.0
35
+ # @api private
36
+ JAVASCRIPT_MIME_TYPE = "text/javascript"
37
+
38
+ # @since 0.1.0
39
+ # @api private
40
+ STYLESHEET_MIME_TYPE = "text/css"
41
+
42
+ # @since 0.1.0
43
+ # @api private
44
+ FAVICON_MIME_TYPE = "image/x-icon"
45
+
46
+ # @since 0.1.0
47
+ # @api private
48
+ STYLESHEET_REL = "stylesheet"
49
+
50
+ # @since 0.1.0
51
+ # @api private
52
+ FAVICON_REL = "shortcut icon"
53
+
54
+ # @since 0.1.0
55
+ # @api private
56
+ DEFAULT_FAVICON = "favicon.ico"
57
+
58
+ # @since 0.3.0
59
+ # @api private
60
+ CROSSORIGIN_ANONYMOUS = "anonymous"
61
+
62
+ # @since 0.3.0
63
+ # @api private
64
+ ABSOLUTE_URL_MATCHER = URI::DEFAULT_PARSER.make_regexp
65
+
66
+ # @since 1.1.0
67
+ # @api private
68
+ QUERY_STRING_MATCHER = /\?/
69
+
70
+ include Hanami::View::Helpers::TagHelper
71
+
72
+ # Generate `script` tag for given source(s)
73
+ #
74
+ # It accepts one or more strings representing the name of the asset, if it
75
+ # comes from the application or third party gems. It also accepts strings
76
+ # representing absolute URLs in case of public CDN (eg. jQuery CDN).
77
+ #
78
+ # If the "fingerprint mode" is on, `src` is the fingerprinted
79
+ # version of the relative URL.
80
+ #
81
+ # If the "CDN mode" is on, the `src` is an absolute URL of the
82
+ # application CDN.
83
+ #
84
+ # If the "subresource integrity mode" is on, `integriy` is the
85
+ # name of the algorithm, then a hyphen, then the hash value of the file.
86
+ # If more than one algorithm is used, they"ll be separated by a space.
87
+ #
88
+ # @param sources [Array<String>] one or more assets by name or absolute URL
89
+ #
90
+ # @return [Hanami::View::HTML::SafeString] the markup
91
+ #
92
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
93
+ # `subresource_integrity` modes are on and the javascript file is missing
94
+ # from the manifest
95
+ #
96
+ # @since 0.1.0
97
+ #
98
+ # @see Hanami::Assets::Helpers#path
99
+ #
100
+ # @example Single Asset
101
+ #
102
+ # <%= js "application" %>
103
+ #
104
+ # # <script src="/assets/application.js" type="text/javascript"></script>
105
+ #
106
+ # @example Multiple Assets
107
+ #
108
+ # <%= js "application", "dashboard" %>
109
+ #
110
+ # # <script src="/assets/application.js" type="text/javascript"></script>
111
+ # # <script src="/assets/dashboard.js" type="text/javascript"></script>
112
+ #
113
+ # @example Asynchronous Execution
114
+ #
115
+ # <%= js "application", async: true %>
116
+ #
117
+ # # <script src="/assets/application.js" type="text/javascript" async="async"></script>
118
+ #
119
+ # @example Subresource Integrity
120
+ #
121
+ # <%= js "application" %>
122
+ #
123
+ # # <script src="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
124
+ # # type="text/javascript" integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
125
+ #
126
+ # @example Subresource Integrity for 3rd Party Scripts
127
+ #
128
+ # <%= js "https://example.com/assets/example.js", integrity: "sha384-oqVu...Y8wC" %>
129
+ #
130
+ # # <script src="https://example.com/assets/example.js" type="text/javascript"
131
+ # # integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
132
+ #
133
+ # @example Deferred Execution
134
+ #
135
+ # <%= js "application", defer: true %>
136
+ #
137
+ # # <script src="/assets/application.js" type="text/javascript" defer="defer"></script>
138
+ #
139
+ # @example Absolute URL
140
+ #
141
+ # <%= js "https://code.jquery.com/jquery-2.1.4.min.js" %>
142
+ #
143
+ # # <script src="https://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript"></script>
144
+ #
145
+ # @example Fingerprint Mode
146
+ #
147
+ # <%= js "application" %>
148
+ #
149
+ # # <script src="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js" type="text/javascript"></script>
150
+ #
151
+ # @example CDN Mode
152
+ #
153
+ # <%= js "application" %>
154
+ #
155
+ # # <script src="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
156
+ # # type="text/javascript"></script>
157
+ def javascript_tag(*source_paths, **options)
158
+ options = options.reject { |k, _| k.to_sym == :src }
159
+
160
+ _safe_tags(*source_paths) do |source|
161
+ attributes = {
162
+ src: _typed_path(source, JAVASCRIPT_EXT),
163
+ type: JAVASCRIPT_MIME_TYPE
164
+ }
165
+ attributes.merge!(options)
166
+
167
+ if _context.assets.subresource_integrity? || attributes.include?(:integrity)
168
+ attributes[:integrity] ||= _subresource_integrity_value(source, JAVASCRIPT_EXT)
169
+ attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
170
+ end
171
+
172
+ tag.script(**attributes).to_s
173
+ end
174
+ end
175
+
176
+ # @api public
177
+ # @since 2.1.0
178
+ alias_method :js, :javascript_tag
179
+
180
+ # Generate `link` tag for given source(s)
181
+ #
182
+ # It accepts one or more strings representing the name of the asset, if it
183
+ # comes from the application or third party gems. It also accepts strings
184
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
185
+ #
186
+ # If the "fingerprint mode" is on, `href` is the fingerprinted
187
+ # version of the relative URL.
188
+ #
189
+ # If the "CDN mode" is on, the `href` is an absolute URL of the
190
+ # application CDN.
191
+ #
192
+ # If the "subresource integrity mode" is on, `integriy` is the
193
+ # name of the algorithm, then a hyphen, then the hashed value of the file.
194
+ # If more than one algorithm is used, they"ll be separated by a space.
195
+ #
196
+ # @param sources [Array<String>] one or more assets by name or absolute URL
197
+ #
198
+ # @return [Hanami::View::HTML::SafeString] the markup
199
+ #
200
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
201
+ # `subresource_integrity` modes are on and the stylesheet file is missing
202
+ # from the manifest
203
+ #
204
+ # @since 0.1.0
205
+ #
206
+ # @see Hanami::Assets::Helpers#path
207
+ #
208
+ # @example Single Asset
209
+ #
210
+ # <%= css "application" %>
211
+ #
212
+ # # <link href="/assets/application.css" type="text/css" rel="stylesheet">
213
+ #
214
+ # @example Multiple Assets
215
+ #
216
+ # <%= css "application", "dashboard" %>
217
+ #
218
+ # # <link href="/assets/application.css" type="text/css" rel="stylesheet">
219
+ # # <link href="/assets/dashboard.css" type="text/css" rel="stylesheet">
220
+ #
221
+ # @example Subresource Integrity
222
+ #
223
+ # <%= css "application" %>
224
+ #
225
+ # # <link href="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
226
+ # # type="text/css" integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
227
+ #
228
+ # @example Subresource Integrity for 3rd Party Assets
229
+ #
230
+ # <%= css "https://example.com/assets/example.css", integrity: "sha384-oqVu...Y8wC" %>
231
+ #
232
+ # # <link href="https://example.com/assets/example.css"
233
+ # # type="text/css" rel="stylesheet" integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
234
+ #
235
+ # @example Absolute URL
236
+ #
237
+ # <%= css "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" %>
238
+ #
239
+ # # <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
240
+ # # type="text/css" rel="stylesheet">
241
+ #
242
+ # @example Fingerprint Mode
243
+ #
244
+ # <%= css "application" %>
245
+ #
246
+ # # <link href="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css" type="text/css" rel="stylesheet">
247
+ #
248
+ # @example CDN Mode
249
+ #
250
+ # <%= css "application" %>
251
+ #
252
+ # # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
253
+ # # type="text/css" rel="stylesheet">
254
+ def stylesheet_link_tag(*source_paths, **options)
255
+ options = options.reject { |k, _| k.to_sym == :href }
256
+
257
+ _safe_tags(*source_paths) do |source_path|
258
+ attributes = {
259
+ href: _typed_path(source_path, STYLESHEET_EXT),
260
+ type: STYLESHEET_MIME_TYPE,
261
+ rel: STYLESHEET_REL
262
+ }
263
+ attributes.merge!(options)
264
+
265
+ if _context.assets.subresource_integrity? || attributes.include?(:integrity)
266
+ attributes[:integrity] ||= _subresource_integrity_value(source_path, STYLESHEET_EXT)
267
+ attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
268
+ end
269
+
270
+ tag.link(**attributes).to_s
271
+ end
272
+ end
273
+
274
+ # @api public
275
+ # @since 2.1.0
276
+ alias_method :css, :stylesheet_link_tag
277
+
278
+ # Generate `img` tag for given source
279
+ #
280
+ # It accepts one string representing the name of the asset, if it comes
281
+ # from the application or third party gems. It also accepts string
282
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
283
+ #
284
+ # `alt` Attribute is auto generated from `src`.
285
+ # You can specify a different value, by passing the `:src` option.
286
+ #
287
+ # If the "fingerprint mode" is on, `src` is the fingerprinted
288
+ # version of the relative URL.
289
+ #
290
+ # If the "CDN mode" is on, the `src` is an absolute URL of the
291
+ # application CDN.
292
+ #
293
+ # @param source [String] asset name or absolute URL
294
+ # @param options [Hash] HTML 5 attributes
295
+ #
296
+ # @return [Hanami::View::HTML::SafeString] the markup
297
+ #
298
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
299
+ # `subresource_integrity` modes are on and the image file is missing
300
+ # from the manifest
301
+ #
302
+ # @since 0.1.0
303
+ #
304
+ # @see Hanami::Assets::Helpers#path
305
+ #
306
+ # @example Basic Usage
307
+ #
308
+ # <%= image_tag "logo.png" %>
309
+ #
310
+ # # <img src="/assets/logo.png" alt="Logo">
311
+ #
312
+ # @example Custom alt Attribute
313
+ #
314
+ # <%= image_tag "logo.png", alt: "Application Logo" %>
315
+ #
316
+ # # <img src="/assets/logo.png" alt="Application Logo">
317
+ #
318
+ # @example Custom HTML Attributes
319
+ #
320
+ # <%= image_tag "logo.png", id: "logo", class: "image" %>
321
+ #
322
+ # # <img src="/assets/logo.png" alt="Logo" id="logo" class="image">
323
+ #
324
+ # @example Absolute URL
325
+ #
326
+ # <%= image_tag "https://example-cdn.com/images/logo.png" %>
327
+ #
328
+ # # <img src="https://example-cdn.com/images/logo.png" alt="Logo">
329
+ #
330
+ # @example Fingerprint Mode
331
+ #
332
+ # <%= image_tag "logo.png" %>
333
+ #
334
+ # # <img src="/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
335
+ #
336
+ # @example CDN Mode
337
+ #
338
+ # <%= image_tag "logo.png" %>
339
+ #
340
+ # # <img src="https://assets.bookshelf.org/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
341
+ def image_tag(source, options = {})
342
+ options = options.reject { |k, _| k.to_sym == :src }
343
+ attributes = {
344
+ src: asset_url(source),
345
+ alt: _context.inflector.humanize(::File.basename(source, WILDCARD_EXT))
346
+ }
347
+ attributes.merge!(options)
348
+
349
+ tag.img(**attributes)
350
+ end
351
+
352
+ # Generate `link` tag application favicon.
353
+ #
354
+ # If no argument is given, it assumes `favico.ico` from the application.
355
+ #
356
+ # It accepts one string representing the name of the asset.
357
+ #
358
+ # If the "fingerprint mode" is on, `href` is the fingerprinted version
359
+ # of the relative URL.
360
+ #
361
+ # If the "CDN mode" is on, the `href` is an absolute URL of the
362
+ # application CDN.
363
+ #
364
+ # @param source [String] asset name
365
+ # @param options [Hash] HTML 5 attributes
366
+ #
367
+ # @return [Hanami::View::HTML::SafeString] the markup
368
+ #
369
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
370
+ # `subresource_integrity` modes are on and the favicon is file missing
371
+ # from the manifest
372
+ #
373
+ # @since 0.1.0
374
+ #
375
+ # @see Hanami::Assets::Helpers#path
376
+ #
377
+ # @example Basic Usage
378
+ #
379
+ # <%= favicon_link_tag %>
380
+ #
381
+ # # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
382
+ #
383
+ # @example Custom Path
384
+ #
385
+ # <%= favicon_link_tag "fav.ico" %>
386
+ #
387
+ # # <link href="/assets/fav.ico" rel="shortcut icon" type="image/x-icon">
388
+ #
389
+ # @example Custom HTML Attributes
390
+ #
391
+ # <%= favicon_link_tag "favicon.ico", id: "fav" %>
392
+ #
393
+ # # <link id: "fav" href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
394
+ #
395
+ # @example Fingerprint Mode
396
+ #
397
+ # <%= favicon_link_tag %>
398
+ #
399
+ # # <link href="/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico" rel="shortcut icon" type="image/x-icon">
400
+ #
401
+ # @example CDN Mode
402
+ #
403
+ # <%= favicon_link_tag %>
404
+ #
405
+ # # <link href="https://assets.bookshelf.org/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico"
406
+ # rel="shortcut icon" type="image/x-icon">
407
+ def favicon_link_tag(source = DEFAULT_FAVICON, options = {})
408
+ options = options.reject { |k, _| k.to_sym == :href }
409
+
410
+ attributes = {
411
+ href: asset_url(source),
412
+ rel: FAVICON_REL,
413
+ type: FAVICON_MIME_TYPE
414
+ }
415
+ attributes.merge!(options)
416
+
417
+ tag.link(**attributes)
418
+ end
419
+
420
+ # @api public
421
+ # @since 2.1.0
422
+ alias_method :favicon, :favicon_link_tag
423
+
424
+ # Generate `video` tag for given source
425
+ #
426
+ # It accepts one string representing the name of the asset, if it comes
427
+ # from the application or third party gems. It also accepts string
428
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
429
+ #
430
+ # Alternatively, it accepts a block that allows to specify one or more
431
+ # sources via the `source` tag.
432
+ #
433
+ # If the "fingerprint mode" is on, `src` is the fingerprinted
434
+ # version of the relative URL.
435
+ #
436
+ # If the "CDN mode" is on, the `src` is an absolute URL of the
437
+ # application CDN.
438
+ #
439
+ # @param source [String] asset name or absolute URL
440
+ # @param options [Hash] HTML 5 attributes
441
+ #
442
+ # @return [Hanami::View::HTML::SafeString] the markup
443
+ #
444
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
445
+ # `subresource_integrity` modes are on and the video file is missing
446
+ # from the manifest
447
+ #
448
+ # @raise [ArgumentError] if source isn"t specified both as argument or
449
+ # tag inside the given block
450
+ #
451
+ # @since 0.1.0
452
+ #
453
+ # @see Hanami::Assets::Helpers#path
454
+ #
455
+ # @example Basic Usage
456
+ #
457
+ # <%= video_tag "movie.mp4" %>
458
+ #
459
+ # # <video src="/assets/movie.mp4"></video>
460
+ #
461
+ # @example Absolute URL
462
+ #
463
+ # <%= video_tag "https://example-cdn.com/assets/movie.mp4" %>
464
+ #
465
+ # # <video src="https://example-cdn.com/assets/movie.mp4"></video>
466
+ #
467
+ # @example Custom HTML Attributes
468
+ #
469
+ # <%= video_tag("movie.mp4", autoplay: true, controls: true) %>
470
+ #
471
+ # # <video src="/assets/movie.mp4" autoplay="autoplay" controls="controls"></video>
472
+ #
473
+ # @example Fallback Content
474
+ #
475
+ # <%=
476
+ # video_tag("movie.mp4") do
477
+ # "Your browser does not support the video tag"
478
+ # end
479
+ # %>
480
+ #
481
+ # # <video src="/assets/movie.mp4">
482
+ # # Your browser does not support the video tag
483
+ # # </video>
484
+ #
485
+ # @example Tracks
486
+ #
487
+ # <%=
488
+ # video_tag("movie.mp4") do
489
+ # tag.track(kind: "captions", src: asset_url("movie.en.vtt"),
490
+ # srclang: "en", label: "English")
491
+ # end
492
+ # %>
493
+ #
494
+ # # <video src="/assets/movie.mp4">
495
+ # # <track kind="captions" src="/assets/movie.en.vtt" srclang="en" label="English">
496
+ # # </video>
497
+ #
498
+ # @example Without Any Argument
499
+ #
500
+ # <%= video_tag %>
501
+ #
502
+ # # ArgumentError
503
+ #
504
+ # @example Without src And Without Block
505
+ #
506
+ # <%= video_tag(content: true) %>
507
+ #
508
+ # # ArgumentError
509
+ #
510
+ # @example Fingerprint Mode
511
+ #
512
+ # <%= video_tag "movie.mp4" %>
513
+ #
514
+ # # <video src="/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
515
+ #
516
+ # @example CDN Mode
517
+ #
518
+ # <%= video_tag "movie.mp4" %>
519
+ #
520
+ # # <video src="https://assets.bookshelf.org/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
521
+ def video_tag(source = nil, options = {}, &blk)
522
+ options = _source_options(source, options, &blk)
523
+ tag.video(**options, &blk)
524
+ end
525
+
526
+ # Generate `audio` tag for given source
527
+ #
528
+ # It accepts one string representing the name of the asset, if it comes
529
+ # from the application or third party gems. It also accepts string
530
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
531
+ #
532
+ # Alternatively, it accepts a block that allows to specify one or more
533
+ # sources via the `source` tag.
534
+ #
535
+ # If the "fingerprint mode" is on, `src` is the fingerprinted
536
+ # version of the relative URL.
537
+ #
538
+ # If the "CDN mode" is on, the `src` is an absolute URL of the
539
+ # application CDN.
540
+ #
541
+ # @param source [String] asset name or absolute URL
542
+ # @param options [Hash] HTML 5 attributes
543
+ #
544
+ # @return [Hanami::View::HTML::SafeString] the markup
545
+ #
546
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
547
+ # `subresource_integrity` modes are on and the audio file is missing
548
+ # from the manifest
549
+ #
550
+ # @raise [ArgumentError] if source isn"t specified both as argument or
551
+ # tag inside the given block
552
+ #
553
+ # @since 0.1.0
554
+ #
555
+ # @see Hanami::Assets::Helpers#path
556
+ #
557
+ # @example Basic Usage
558
+ #
559
+ # <%= audio_tag "song.ogg" %>
560
+ #
561
+ # # <audio src="/assets/song.ogg"></audio>
562
+ #
563
+ # @example Absolute URL
564
+ #
565
+ # <%= audio_tag "https://example-cdn.com/assets/song.ogg" %>
566
+ #
567
+ # # <audio src="https://example-cdn.com/assets/song.ogg"></audio>
568
+ #
569
+ # @example Custom HTML Attributes
570
+ #
571
+ # <%= audio_tag("song.ogg", autoplay: true, controls: true) %>
572
+ #
573
+ # # <audio src="/assets/song.ogg" autoplay="autoplay" controls="controls"></audio>
574
+ #
575
+ # @example Fallback Content
576
+ #
577
+ # <%=
578
+ # audio_tag("song.ogg") do
579
+ # "Your browser does not support the audio tag"
580
+ # end
581
+ # %>
582
+ #
583
+ # # <audio src="/assets/song.ogg">
584
+ # # Your browser does not support the audio tag
585
+ # # </audio>
586
+ #
587
+ # @example Tracks
588
+ #
589
+ # <%=
590
+ # audio_tag("song.ogg") do
591
+ # tag.track(kind: "captions", src: asset_url("song.pt-BR.vtt"),
592
+ # srclang: "pt-BR", label: "Portuguese")
593
+ # end
594
+ # %>
595
+ #
596
+ # # <audio src="/assets/song.ogg">
597
+ # # <track kind="captions" src="/assets/song.pt-BR.vtt" srclang="pt-BR" label="Portuguese">
598
+ # # </audio>
599
+ #
600
+ # @example Without Any Argument
601
+ #
602
+ # <%= audio_tag %>
603
+ #
604
+ # # ArgumentError
605
+ #
606
+ # @example Without src And Without Block
607
+ #
608
+ # <%= audio_tag(controls: true) %>
609
+ #
610
+ # # ArgumentError
611
+ #
612
+ # @example Fingerprint Mode
613
+ #
614
+ # <%= audio_tag "song.ogg" %>
615
+ #
616
+ # # <audio src="/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
617
+ #
618
+ # @example CDN Mode
619
+ #
620
+ # <%= audio_tag "song.ogg" %>
621
+ #
622
+ # # <audio src="https://assets.bookshelf.org/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
623
+ def audio_tag(source = nil, options = {}, &blk)
624
+ options = _source_options(source, options, &blk)
625
+ tag.audio(**options, &blk)
626
+ end
627
+
628
+ # It generates the relative or absolute URL for the given asset.
629
+ # It automatically decides if it has to use the relative or absolute
630
+ # depending on the configuration and current environment.
631
+ #
632
+ # Absolute URLs are returned as they are.
633
+ #
634
+ # It can be the name of the asset, coming from the sources or third party
635
+ # gems.
636
+ #
637
+ # If Fingerprint mode is on, it returns the fingerprinted path of the source
638
+ #
639
+ # If CDN mode is on, it returns the absolute URL of the asset.
640
+ #
641
+ # @param source [String] the asset name
642
+ #
643
+ # @return [String] the asset path
644
+ #
645
+ # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
646
+ # `subresource_integrity` modes are on and the asset is missing
647
+ # from the manifest
648
+ #
649
+ # @since 0.1.0
650
+ #
651
+ # @example Basic Usage
652
+ #
653
+ # <%= asset_url "application.js" %>
654
+ #
655
+ # # "/assets/application.js"
656
+ #
657
+ # @example Alias
658
+ #
659
+ # <%= asset_url "application.js" %>
660
+ #
661
+ # # "/assets/application.js"
662
+ #
663
+ # @example Absolute URL
664
+ #
665
+ # <%= asset_url "https://code.jquery.com/jquery-2.1.4.min.js" %>
666
+ #
667
+ # # "https://code.jquery.com/jquery-2.1.4.min.js"
668
+ #
669
+ # @example Fingerprint Mode
670
+ #
671
+ # <%= asset_url "application.js" %>
672
+ #
673
+ # # "/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
674
+ #
675
+ # @example CDN Mode
676
+ #
677
+ # <%= asset_url "application.js" %>
678
+ #
679
+ # # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
680
+ def asset_url(source_path)
681
+ return source_path if _absolute_url?(source_path)
682
+
683
+ _context.assets[source_path].url
684
+ end
685
+
686
+ private
687
+
688
+ # @since 0.1.0
689
+ # @api private
690
+ def _safe_tags(*source_paths, &blk)
691
+ ::Hanami::View::HTML::SafeString.new(
692
+ source_paths.map(&blk).join(NEW_LINE_SEPARATOR)
693
+ )
694
+ end
695
+
696
+ # @since 2.1.0
697
+ # @api private
698
+ def _typed_path(source, ext)
699
+ source = "#{source}#{ext}" if _append_extension?(source, ext)
700
+ asset_url(source)
701
+ end
702
+
703
+ # @api private
704
+ def _subresource_integrity_value(source_path, ext)
705
+ return if _absolute_url?(source_path)
706
+
707
+ source_path = "#{source_path}#{ext}" unless /#{Regexp.escape(ext)}\z/.match?(source_path)
708
+ _context.assets[source_path].sri
709
+ end
710
+
711
+ # @since 0.1.0
712
+ # @api private
713
+ def _absolute_url?(source)
714
+ ABSOLUTE_URL_MATCHER.match(source)
715
+ end
716
+
717
+ # @since 1.2.0
718
+ # @api private
719
+ def _crossorigin?(source)
720
+ return false unless _absolute_url?(source)
721
+
722
+ _context.assets.crossorigin?(source)
723
+ end
724
+
725
+ # @since 0.1.0
726
+ # @api private
727
+ def _source_options(src, options, &blk)
728
+ options ||= {}
729
+
730
+ if src.respond_to?(:to_hash)
731
+ options = src.to_hash
732
+ elsif src
733
+ options[:src] = asset_url(src)
734
+ end
735
+
736
+ if !options[:src] && !blk
737
+ raise ArgumentError.new("You should provide a source via `src` option or with a `source` HTML tag")
738
+ end
739
+
740
+ options
741
+ end
742
+
743
+ # @since 1.1.0
744
+ # @api private
745
+ def _append_extension?(source, ext)
746
+ source !~ QUERY_STRING_MATCHER && source !~ /#{Regexp.escape(ext)}\z/
747
+ end
748
+ end
749
+ end
750
+ end
751
+
752
+ # rubocop:enable Metrics/ModuleLength