lotus-assets 0.0.0 → 0.1.0

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.
@@ -0,0 +1,733 @@
1
+ require 'uri'
2
+ require 'set'
3
+ require 'thread'
4
+ require 'lotus/helpers/html_helper'
5
+ require 'lotus/utils/escape'
6
+
7
+ module Lotus
8
+ module Assets
9
+ # HTML assets helpers
10
+ #
11
+ # Include this helper in a view
12
+ #
13
+ # @since 0.1.0
14
+ #
15
+ # @see http://www.rubydoc.info/gems/lotus-helpers/Lotus/Helpers/HtmlHelper
16
+ module Helpers
17
+ # @since 0.1.0
18
+ # @api private
19
+ NEW_LINE_SEPARATOR = "\n".freeze
20
+
21
+ # @since 0.1.0
22
+ # @api private
23
+ WILDCARD_EXT = '.*'.freeze
24
+
25
+ # @since 0.1.0
26
+ # @api private
27
+ JAVASCRIPT_EXT = '.js'.freeze
28
+
29
+ # @since 0.1.0
30
+ # @api private
31
+ STYLESHEET_EXT = '.css'.freeze
32
+
33
+ # @since 0.1.0
34
+ # @api private
35
+ JAVASCRIPT_MIME_TYPE = 'text/javascript'.freeze
36
+
37
+ # @since 0.1.0
38
+ # @api private
39
+ STYLESHEET_MIME_TYPE = 'text/css'.freeze
40
+
41
+ # @since 0.1.0
42
+ # @api private
43
+ FAVICON_MIME_TYPE = 'image/x-icon'.freeze
44
+
45
+ # @since 0.1.0
46
+ # @api private
47
+ STYLESHEET_REL = 'stylesheet'.freeze
48
+
49
+ # @since 0.1.0
50
+ # @api private
51
+ FAVICON_REL = 'shortcut icon'.freeze
52
+
53
+ # @since 0.1.0
54
+ # @api private
55
+ DEFAULT_FAVICON = 'favicon.ico'.freeze
56
+
57
+ include Lotus::Helpers::HtmlHelper
58
+
59
+ # Inject helpers into the given class
60
+ #
61
+ # @since 0.1.0
62
+ # @api private
63
+ def self.included(base)
64
+ conf = ::Lotus::Assets::Configuration.for(base)
65
+ base.class_eval do
66
+ include Utils::ClassAttribute
67
+
68
+ class_attribute :assets_configuration
69
+ self.assets_configuration = conf
70
+ end
71
+ end
72
+
73
+ # Generate <tt>script</tt> tag for given source(s)
74
+ #
75
+ # It accepts one or more strings representing the name of the asset, if it
76
+ # comes from the application or third party gems. It also accepts strings
77
+ # representing absolute URLs in case of public CDN (eg. jQuery CDN).
78
+ #
79
+ # If the "digest mode" is on, <tt>src</tt> is the digest version of the
80
+ # relative URL.
81
+ #
82
+ # If the "CDN mode" is on, the <tt>src</tt> is an absolute URL of the
83
+ # application CDN.
84
+ #
85
+ # @param sources [Array<String>] one or more assets by name or absolute URL
86
+ #
87
+ # @return [Lotus::Utils::Escape::SafeString] the markup
88
+ #
89
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
90
+ # at least one of the given sources is missing from the manifest
91
+ #
92
+ # @since 0.1.0
93
+ #
94
+ # @see Lotus::Assets::Configuration#digest
95
+ # @see Lotus::Assets::Configuration#cdn
96
+ # @see Lotus::Assets::Helpers#asset_path
97
+ #
98
+ # @example Single Asset
99
+ #
100
+ # <%= javascript 'application' %>
101
+ #
102
+ # # <script src="/assets/application.js" type="text/javascript"></script>
103
+ #
104
+ # @example Multiple Assets
105
+ #
106
+ # <%= javascript 'application', 'dashboard' %>
107
+ #
108
+ # # <script src="/assets/application.js" type="text/javascript"></script>
109
+ # # <script src="/assets/dashboard.js" type="text/javascript"></script>
110
+ #
111
+ # @example Absolute URL
112
+ #
113
+ # <%= javascript 'https://code.jquery.com/jquery-2.1.4.min.js' %>
114
+ #
115
+ # # <script src="https://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript"></script>
116
+ #
117
+ # @example Digest Mode
118
+ #
119
+ # <%= javascript 'application' %>
120
+ #
121
+ # # <script src="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js" type="text/javascript"></script>
122
+ #
123
+ # @example CDN Mode
124
+ #
125
+ # <%= javascript 'application' %>
126
+ #
127
+ # # <script src="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js" type="text/javascript"></script>
128
+ def javascript(*sources)
129
+ _safe_tags(*sources) do |source|
130
+ html.script(src: _typed_asset_path(source, JAVASCRIPT_EXT), type: JAVASCRIPT_MIME_TYPE).to_s
131
+ end
132
+ end
133
+
134
+ # Generate <tt>link</tt> tag for given source(s)
135
+ #
136
+ # It accepts one or more strings representing the name of the asset, if it
137
+ # comes from the application or third party gems. It also accepts strings
138
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
139
+ #
140
+ # If the "digest mode" is on, <tt>href</tt> is the digest version of the
141
+ # relative URL.
142
+ #
143
+ # If the "CDN mode" is on, the <tt>href</tt> is an absolute URL of the
144
+ # application CDN.
145
+ #
146
+ # @param sources [Array<String>] one or more assets by name or absolute URL
147
+ #
148
+ # @return [Lotus::Utils::Escape::SafeString] the markup
149
+ #
150
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
151
+ # at least one of the given sources is missing from the manifest
152
+ #
153
+ # @since 0.1.0
154
+ #
155
+ # @see Lotus::Assets::Configuration#digest
156
+ # @see Lotus::Assets::Configuration#cdn
157
+ # @see Lotus::Assets::Helpers#asset_path
158
+ #
159
+ # @example Single Asset
160
+ #
161
+ # <%= stylesheet 'application' %>
162
+ #
163
+ # # <link href="/assets/application.css" type="text/css" rel="stylesheet">
164
+ #
165
+ # @example Multiple Assets
166
+ #
167
+ # <%= stylesheet 'application', 'dashboard' %>
168
+ #
169
+ # # <link href="/assets/application.css" type="text/css" rel="stylesheet">
170
+ # # <link href="/assets/dashboard.css" type="text/css" rel="stylesheet">
171
+ #
172
+ # @example Absolute URL
173
+ #
174
+ # <%= stylesheet 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' %>
175
+ #
176
+ # # <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" rel="stylesheet">
177
+ #
178
+ # @example Digest Mode
179
+ #
180
+ # <%= stylesheet 'application' %>
181
+ #
182
+ # # <link href="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css" type="text/css" rel="stylesheet">
183
+ #
184
+ # @example CDN Mode
185
+ #
186
+ # <%= stylesheet 'application' %>
187
+ #
188
+ # # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css" type="text/css" rel="stylesheet">
189
+ def stylesheet(*sources)
190
+ _safe_tags(*sources) do |source|
191
+ html.link(href: _typed_asset_path(source, STYLESHEET_EXT), type: STYLESHEET_MIME_TYPE, rel: STYLESHEET_REL).to_s
192
+ end
193
+ end
194
+
195
+ # Generate <tt>img</tt> tag for given source
196
+ #
197
+ # It accepts one string representing the name of the asset, if it comes
198
+ # from the application or third party gems. It also accepts string
199
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
200
+ #
201
+ # <tt>alt</tt> Attribute is auto generated from <tt>src</tt>.
202
+ # You can specify a different value, by passing the <tt>:src</tt> option.
203
+ #
204
+ # If the "digest mode" is on, <tt>src</tt> is the digest version of the
205
+ # relative URL.
206
+ #
207
+ # If the "CDN mode" is on, the <tt>src</tt> is an absolute URL of the
208
+ # application CDN.
209
+ #
210
+ # @param source [String] asset name or absolute URL
211
+ #
212
+ # @return [Lotus::Utils::Helpers::HtmlBuilder] the builder
213
+ #
214
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
215
+ # the image is missing from the manifest
216
+ #
217
+ # @since 0.1.0
218
+ #
219
+ # @see Lotus::Assets::Configuration#digest
220
+ # @see Lotus::Assets::Configuration#cdn
221
+ # @see Lotus::Assets::Helpers#asset_path
222
+ #
223
+ # @example Basic Usage
224
+ #
225
+ # <%= image 'logo.png' %>
226
+ #
227
+ # # <img src="/assets/logo.png" alt="Logo">
228
+ #
229
+ # @example Custom alt Attribute
230
+ #
231
+ # <%= image 'logo.png', alt: 'Application Logo' %>
232
+ #
233
+ # # <img src="/assets/logo.png" alt="Application Logo">
234
+ #
235
+ # @example Custom HTML Attributes
236
+ #
237
+ # <%= image 'logo.png', id: 'logo', class: 'image' %>
238
+ #
239
+ # # <img src="/assets/logo.png" alt="Logo" id="logo" class="image">
240
+ #
241
+ # @example Absolute URL
242
+ #
243
+ # <%= image 'https://example-cdn.com/images/logo.png' %>
244
+ #
245
+ # # <img src="https://example-cdn.com/images/logo.png" alt="Logo">
246
+ #
247
+ # @example Digest Mode
248
+ #
249
+ # <%= image 'logo.png' %>
250
+ #
251
+ # # <img src="/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
252
+ #
253
+ # @example CDN Mode
254
+ #
255
+ # <%= image 'logo.png' %>
256
+ #
257
+ # # <img src="https://assets.bookshelf.org/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
258
+ def image(source, options = {})
259
+ options[:src] = asset_path(source)
260
+ options[:alt] ||= Utils::String.new(::File.basename(source, WILDCARD_EXT)).titleize
261
+
262
+ html.img(options)
263
+ end
264
+
265
+ # Generate <tt>link</tt> tag application favicon.
266
+ #
267
+ # If no argument is given, it assumes <tt>favico.ico</tt> from the application.
268
+ #
269
+ # It accepts one string representing the name of the asset.
270
+ #
271
+ # If the "digest mode" is on, <tt>href</tt> is the digest version of the
272
+ # relative URL.
273
+ #
274
+ # If the "CDN mode" is on, the <tt>href</tt> is an absolute URL of the
275
+ # application CDN.
276
+ #
277
+ # @param source [String] asset name
278
+ #
279
+ # @return [Lotus::Utils::Helpers::HtmlBuilder] the builder
280
+ #
281
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
282
+ # the favicon is missing from the manifest
283
+ #
284
+ # @since 0.1.0
285
+ #
286
+ # @see Lotus::Assets::Configuration#digest
287
+ # @see Lotus::Assets::Configuration#cdn
288
+ # @see Lotus::Assets::Helpers#asset_path
289
+ #
290
+ # @example Basic Usage
291
+ #
292
+ # <%= favicon %>
293
+ #
294
+ # # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
295
+ #
296
+ # @example Custom Path
297
+ #
298
+ # <%= favicon 'fav.ico' %>
299
+ #
300
+ # # <link href="/assets/fav.ico" rel="shortcut icon" type="image/x-icon">
301
+ #
302
+ # @example Custom HTML Attributes
303
+ #
304
+ # <%= favicon id: 'fav' %>
305
+ #
306
+ # # <link id: "fav" href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
307
+ #
308
+ # @example Digest Mode
309
+ #
310
+ # <%= favicon %>
311
+ #
312
+ # # <link href="/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico" rel="shortcut icon" type="image/x-icon">
313
+ #
314
+ # @example CDN Mode
315
+ #
316
+ # <%= favicon %>
317
+ #
318
+ # # <link href="https://assets.bookshelf.org/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico" rel="shortcut icon" type="image/x-icon">
319
+ def favicon(source = DEFAULT_FAVICON, options = {})
320
+ options[:href] = asset_path(source)
321
+ options[:rel] ||= FAVICON_REL
322
+ options[:type] ||= FAVICON_MIME_TYPE
323
+
324
+ html.link(options)
325
+ end
326
+
327
+ # Generate <tt>video</tt> tag for given source
328
+ #
329
+ # It accepts one string representing the name of the asset, if it comes
330
+ # from the application or third party gems. It also accepts string
331
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
332
+ #
333
+ # Alternatively, it accepts a block that allows to specify one or more
334
+ # sources via the <tt>source</tt> tag.
335
+ #
336
+ # If the "digest mode" is on, <tt>src</tt> is the digest version of the
337
+ # relative URL.
338
+ #
339
+ # If the "CDN mode" is on, the <tt>src</tt> is an absolute URL of the
340
+ # application CDN.
341
+ #
342
+ # @param source [String] asset name or absolute URL
343
+ #
344
+ # @return [Lotus::Utils::Helpers::HtmlBuilder] the builder
345
+ #
346
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
347
+ # the image is missing from the manifest
348
+ #
349
+ # @raise [ArgumentError] if source isn't specified both as argument or
350
+ # tag inside the given block
351
+ #
352
+ # @since 0.1.0
353
+ #
354
+ # @see Lotus::Assets::Configuration#digest
355
+ # @see Lotus::Assets::Configuration#cdn
356
+ # @see Lotus::Assets::Helpers#asset_path
357
+ #
358
+ # @example Basic Usage
359
+ #
360
+ # <%= video 'movie.mp4' %>
361
+ #
362
+ # # <video src="/assets/movie.mp4"></video>
363
+ #
364
+ # @example Absolute URL
365
+ #
366
+ # <%= video 'https://example-cdn.com/assets/movie.mp4' %>
367
+ #
368
+ # # <video src="https://example-cdn.com/assets/movie.mp4"></video>
369
+ #
370
+ # @example Custom HTML Attributes
371
+ #
372
+ # <%= video('movie.mp4', autoplay: true, controls: true) %>
373
+ #
374
+ # # <video src="/assets/movie.mp4" autoplay="autoplay" controls="controls"></video>
375
+ #
376
+ # @example Fallback Content
377
+ #
378
+ # <%=
379
+ # video('movie.mp4') do
380
+ # "Your browser does not support the video tag"
381
+ # end
382
+ # %>
383
+ #
384
+ # # <video src="/assets/movie.mp4">
385
+ # # Your browser does not support the video tag
386
+ # # </video>
387
+ #
388
+ # @example Tracks
389
+ #
390
+ # <%=
391
+ # video('movie.mp4') do
392
+ # track(kind: 'captions', src: asset_path('movie.en.vtt'),
393
+ # srclang: 'en', label: 'English')
394
+ # end
395
+ # %>
396
+ #
397
+ # # <video src="/assets/movie.mp4">
398
+ # # <track kind="captions" src="/assets/movie.en.vtt" srclang="en" label="English">
399
+ # # </video>
400
+ #
401
+ # @example Sources
402
+ #
403
+ # <%=
404
+ # video do
405
+ # text "Your browser does not support the video tag"
406
+ # source(src: asset_path('movie.mp4'), type: 'video/mp4')
407
+ # source(src: asset_path('movie.ogg'), type: 'video/ogg')
408
+ # end
409
+ # %>
410
+ #
411
+ # # <video>
412
+ # # Your browser does not support the video tag
413
+ # # <source src="/assets/movie.mp4" type="video/mp4">
414
+ # # <source src="/assets/movie.ogg" type="video/ogg">
415
+ # # </video>
416
+ #
417
+ # @example Without Any Argument
418
+ #
419
+ # <%= video %>
420
+ #
421
+ # # ArgumentError
422
+ #
423
+ # @example Without src And Without Block
424
+ #
425
+ # <%= video(content: true) %>
426
+ #
427
+ # # ArgumentError
428
+ #
429
+ # @example Digest Mode
430
+ #
431
+ # <%= video 'movie.mp4' %>
432
+ #
433
+ # # <video src="/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
434
+ #
435
+ # @example CDN Mode
436
+ #
437
+ # <%= video 'movie.mp4' %>
438
+ #
439
+ # # <video src="https://assets.bookshelf.org/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
440
+ def video(source = nil, options = {}, &blk)
441
+ options = _source_options(source, options, &blk)
442
+ html.video(blk, options)
443
+ end
444
+
445
+ # Generate <tt>audio</tt> tag for given source
446
+ #
447
+ # It accepts one string representing the name of the asset, if it comes
448
+ # from the application or third party gems. It also accepts string
449
+ # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
450
+ #
451
+ # Alternatively, it accepts a block that allows to specify one or more
452
+ # sources via the <tt>source</tt> tag.
453
+ #
454
+ # If the "digest mode" is on, <tt>src</tt> is the digest version of the
455
+ # relative URL.
456
+ #
457
+ # If the "CDN mode" is on, the <tt>src</tt> is an absolute URL of the
458
+ # application CDN.
459
+ #
460
+ # @param source [String] asset name or absolute URL
461
+ #
462
+ # @return [Lotus::Utils::Helpers::HtmlBuilder] the builder
463
+ #
464
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
465
+ # the image is missing from the manifest
466
+ #
467
+ # @raise [ArgumentError] if source isn't specified both as argument or
468
+ # tag inside the given block
469
+ #
470
+ # @since 0.1.0
471
+ #
472
+ # @see Lotus::Assets::Configuration#digest
473
+ # @see Lotus::Assets::Configuration#cdn
474
+ # @see Lotus::Assets::Helpers#asset_path
475
+ #
476
+ # @example Basic Usage
477
+ #
478
+ # <%= audio 'song.ogg' %>
479
+ #
480
+ # # <audio src="/assets/song.ogg"></audio>
481
+ #
482
+ # @example Absolute URL
483
+ #
484
+ # <%= audio 'https://example-cdn.com/assets/song.ogg' %>
485
+ #
486
+ # # <audio src="https://example-cdn.com/assets/song.ogg"></audio>
487
+ #
488
+ # @example Custom HTML Attributes
489
+ #
490
+ # <%= audio('song.ogg', autoplay: true, controls: true) %>
491
+ #
492
+ # # <audio src="/assets/song.ogg" autoplay="autoplay" controls="controls"></audio>
493
+ #
494
+ # @example Fallback Content
495
+ #
496
+ # <%=
497
+ # audio('song.ogg') do
498
+ # "Your browser does not support the audio tag"
499
+ # end
500
+ # %>
501
+ #
502
+ # # <audio src="/assets/song.ogg">
503
+ # # Your browser does not support the audio tag
504
+ # # </audio>
505
+ #
506
+ # @example Tracks
507
+ #
508
+ # <%=
509
+ # audio('song.ogg') do
510
+ # track(kind: 'captions', src: asset_path('song.pt-BR.vtt'),
511
+ # srclang: 'pt-BR', label: 'Portuguese')
512
+ # end
513
+ # %>
514
+ #
515
+ # # <audio src="/assets/song.ogg">
516
+ # # <track kind="captions" src="/assets/song.pt-BR.vtt" srclang="pt-BR" label="Portuguese">
517
+ # # </audio>
518
+ #
519
+ # @example Sources
520
+ #
521
+ # <%=
522
+ # audio do
523
+ # text "Your browser does not support the audio tag"
524
+ # source(src: asset_path('song.ogg'), type: 'audio/ogg')
525
+ # source(src: asset_path('song.wav'), type: 'auido/wav')
526
+ # end
527
+ # %>
528
+ #
529
+ # # <audio>
530
+ # # Your browser does not support the audio tag
531
+ # # <source src="/assets/song.ogg" type="audio/ogg">
532
+ # # <source src="/assets/song.wav" type="auido/wav">
533
+ # # </audio>
534
+ #
535
+ # @example Without Any Argument
536
+ #
537
+ # <%= audio %>
538
+ #
539
+ # # ArgumentError
540
+ #
541
+ # @example Without src And Without Block
542
+ #
543
+ # <%= audio(controls: true) %>
544
+ #
545
+ # # ArgumentError
546
+ #
547
+ # @example Digest Mode
548
+ #
549
+ # <%= audio 'song.ogg' %>
550
+ #
551
+ # # <audio src="/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
552
+ #
553
+ # @example CDN Mode
554
+ #
555
+ # <%= audio 'song.ogg' %>
556
+ #
557
+ # # <audio src="https://assets.bookshelf.org/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
558
+ def audio(source = nil, options = {}, &blk)
559
+ options = _source_options(source, options, &blk)
560
+ html.audio(blk, options)
561
+ end
562
+
563
+ # It generates the relative URL for the given source.
564
+ #
565
+ # It can be the name of the asset, coming from the sources or third party
566
+ # gems.
567
+ #
568
+ # Absolute URLs are returned as they are.
569
+ #
570
+ # If Digest mode is on, it returns the digest path of the source
571
+ #
572
+ # If CDN mode is on, it returns the absolute URL of the asset.
573
+ #
574
+ # @param source [String] the asset name
575
+ #
576
+ # @return [String] the asset path
577
+ #
578
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
579
+ # the asset is missing from the manifest
580
+ #
581
+ # @since 0.1.0
582
+ #
583
+ # @example Basic Usage
584
+ #
585
+ # <%= asset_path 'application.js' %>
586
+ #
587
+ # # "/assets/application.js"
588
+ #
589
+ # @example Absolute URL
590
+ #
591
+ # <%= asset_path 'https://code.jquery.com/jquery-2.1.4.min.js' %>
592
+ #
593
+ # # "https://code.jquery.com/jquery-2.1.4.min.js"
594
+ #
595
+ # @example Digest Mode
596
+ #
597
+ # <%= asset_path 'application.js' %>
598
+ #
599
+ # # "/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
600
+ #
601
+ # @example CDN Mode
602
+ #
603
+ # <%= asset_path 'application.js' %>
604
+ #
605
+ # # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
606
+ def asset_path(source)
607
+ _asset_url(source) { _relative_url(source) }
608
+ end
609
+
610
+ # It generates the absolute URL for the given source.
611
+ #
612
+ # It can be the name of the asset, coming from the sources or third party
613
+ # gems.
614
+ #
615
+ # Absolute URLs are returned as they are.
616
+ #
617
+ # If Digest mode is on, it returns the digest URL of the source
618
+ #
619
+ # If CDN mode is on, it returns the absolute URL of the asset.
620
+ #
621
+ # @param source [String] the asset name
622
+ #
623
+ # @return [String] the asset URL
624
+ #
625
+ # @raise [Lotus::Assets::MissingDigestAssetError] if digest mode is on and
626
+ # the asset is missing from the manifest
627
+ #
628
+ # @since 0.1.0
629
+ #
630
+ # @example Basic Usage
631
+ #
632
+ # <%= asset_url 'application.js' %>
633
+ #
634
+ # # "https://bookshelf.org/assets/application.js"
635
+ #
636
+ # @example Absolute URL
637
+ #
638
+ # <%= asset_url 'https://code.jquery.com/jquery-2.1.4.min.js' %>
639
+ #
640
+ # # "https://code.jquery.com/jquery-2.1.4.min.js"
641
+ #
642
+ # @example Digest Mode
643
+ #
644
+ # <%= asset_url 'application.js' %>
645
+ #
646
+ # # "https://bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
647
+ #
648
+ # @example CDN Mode
649
+ #
650
+ # <%= asset_url 'application.js' %>
651
+ #
652
+ # # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
653
+ def asset_url(source)
654
+ _asset_url(source) { _absolute_url(source) }
655
+ end
656
+
657
+ private
658
+
659
+ # @since 0.1.0
660
+ # @api private
661
+ def _safe_tags(*sources)
662
+ ::Lotus::Utils::Escape::SafeString.new(
663
+ sources.map do |source|
664
+ yield source
665
+ end.join(NEW_LINE_SEPARATOR)
666
+ )
667
+ end
668
+
669
+ # @since 0.1.0
670
+ # @api private
671
+ def _asset_url(source)
672
+ _push_promise(
673
+ _absolute_url?(source) ?
674
+ source : yield
675
+ )
676
+ end
677
+
678
+ # @since 0.1.0
679
+ # @api private
680
+ def _typed_asset_path(source, ext)
681
+ source = "#{ source }#{ ext }" unless source.match(/#{ Regexp.escape(ext) }\z/)
682
+ asset_path(source)
683
+ end
684
+
685
+ # @since 0.1.0
686
+ # @api private
687
+ def _absolute_url?(source)
688
+ URI.regexp.match(source)
689
+ end
690
+
691
+ # @since 0.1.0
692
+ # @api private
693
+ def _relative_url(source)
694
+ self.class.assets_configuration.asset_path(source)
695
+ end
696
+
697
+ # @since 0.1.0
698
+ # @api private
699
+ def _absolute_url(source)
700
+ self.class.assets_configuration.asset_url(source)
701
+ end
702
+
703
+ # @since 0.1.0
704
+ # @api private
705
+ def _source_options(src, options, &blk)
706
+ options ||= {}
707
+
708
+ if src.respond_to?(:to_hash)
709
+ options = src.to_hash
710
+ elsif src
711
+ options[:src] = asset_path(src)
712
+ end
713
+
714
+ if !options[:src] && !block_given?
715
+ raise ArgumentError.new('You should provide a source via `src` option or with a `source` HTML tag')
716
+ end
717
+
718
+ options
719
+ end
720
+
721
+ # @since 0.1.0
722
+ # @api private
723
+ def _push_promise(url)
724
+ Mutex.new.synchronize do
725
+ Thread.current[:__lotus_assets] ||= Set.new
726
+ Thread.current[:__lotus_assets].add(url.to_s)
727
+ end
728
+
729
+ url
730
+ end
731
+ end
732
+ end
733
+ end