hanami 2.1.0.beta1 → 2.1.0.beta2.1

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