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,539 @@
1
+ require 'pathname'
2
+ require 'json'
3
+ require 'lotus/utils/string'
4
+ require 'lotus/utils/class'
5
+ require 'lotus/utils/path_prefix'
6
+ require 'lotus/utils/basic_object'
7
+ require 'lotus/assets/config/manifest'
8
+ require 'lotus/assets/config/sources'
9
+
10
+ module Lotus
11
+ module Assets
12
+ # Framework configuration
13
+ #
14
+ # @since 0.1.0
15
+ class Configuration
16
+ # @since 0.1.0
17
+ # @api private
18
+ DEFAULT_SCHEME = 'http'.freeze
19
+
20
+ # @since 0.1.0
21
+ # @api private
22
+ DEFAULT_HOST = 'localhost'.freeze
23
+
24
+ # @since 0.1.0
25
+ # @api private
26
+ DEFAULT_PORT = '2300'.freeze
27
+
28
+ # @since 0.1.0
29
+ # @api private
30
+ DEFAULT_PUBLIC_DIRECTORY = 'public'.freeze
31
+
32
+ # @since 0.1.0
33
+ # @api private
34
+ DEFAULT_MANIFEST = 'assets.json'.freeze
35
+
36
+ # @since 0.1.0
37
+ # @api private
38
+ DEFAULT_PREFIX = '/assets'.freeze
39
+
40
+ # @since 0.1.0
41
+ # @api private
42
+ URL_SEPARATOR = '/'.freeze
43
+
44
+ # @since 0.1.0
45
+ # @api private
46
+ HTTP_SCHEME = 'http'.freeze
47
+
48
+ # @since 0.1.0
49
+ # @api private
50
+ HTTP_PORT = '80'.freeze
51
+
52
+ # @since 0.1.0
53
+ # @api private
54
+ HTTPS_SCHEME = 'https'.freeze
55
+
56
+ # @since 0.1.0
57
+ # @api private
58
+ HTTPS_PORT = '443'.freeze
59
+
60
+ # Return a copy of the configuration of the framework instance associated
61
+ # with the given class.
62
+ #
63
+ # When multiple instances of Lotus::Assets are used in the same
64
+ # application, we want to make sure that a controller or an action will
65
+ # receive the expected configuration.
66
+ #
67
+ # @param base [Class, Module] a controller or an action
68
+ #
69
+ # @return [Lotus::Assets::Configuration] the configuration associated
70
+ # to the given class.
71
+ #
72
+ # @since 0.1.0
73
+ # @api private
74
+ def self.for(base)
75
+ # TODO this implementation is similar to Lotus::Controller::Configuration consider to extract it into Lotus::Utils
76
+ namespace = Utils::String.new(base).namespace
77
+ framework = Utils::Class.load_from_pattern!("(#{namespace}|Lotus)::Assets")
78
+ framework.configuration
79
+ end
80
+
81
+ # @since 0.1.0
82
+ # @api private
83
+ attr_reader :digest_manifest
84
+
85
+ # Return a new instance
86
+ #
87
+ # @return [Lotus::Assets::Configuration] a new instance
88
+ #
89
+ # @since 0.1.0
90
+ # @api private
91
+ def initialize
92
+ reset!
93
+ end
94
+
95
+ # Compile mode
96
+ #
97
+ # Determine if compile assets from sources to destination.
98
+ # Usually this is turned off in production mode.
99
+ #
100
+ # @since 0.1.0
101
+ def compile(value = nil)
102
+ if value.nil?
103
+ @compile
104
+ else
105
+ @compile = value
106
+ end
107
+ end
108
+
109
+ # Digest mode
110
+ #
111
+ # Determine if the helpers should generate the digest path for an asset.
112
+ # Usually this is turned on in production mode.
113
+ #
114
+ # @since 0.1.0
115
+ def digest(value = nil)
116
+ if value.nil?
117
+ @digest
118
+ else
119
+ @digest = value
120
+ end
121
+ end
122
+
123
+ # CDN mode
124
+ #
125
+ # Determine if the helpers should always generate absolute URL.
126
+ # This is useful in production mode.
127
+ #
128
+ # @since 0.1.0
129
+ def cdn(value = nil)
130
+ if value.nil?
131
+ @cdn
132
+ else
133
+ @cdn = !!value
134
+ end
135
+ end
136
+
137
+ # JavaScript compressor
138
+ #
139
+ # Determine which compressor to use for JavaScript files during deploy.
140
+ #
141
+ # By default it's <tt>nil</tt>, that means it doesn't compress JavaScripts at deploy time.
142
+ #
143
+ # It accepts a <tt>Symbol</tt> or an object that respond to <tt>#compress(file)</tt>.
144
+ #
145
+ # The following symbols are accepted:
146
+ #
147
+ # * <tt>:builtin</tt> - Ruby based implementation of jsmin. It doesn't require any external gem.
148
+ # * <tt>:yui</tt> - YUI Compressor, it depends on <tt>yui-compressor</tt> gem and it requires Java 1.4+
149
+ # * <tt>:uglifier</tt> - UglifyJS, it depends on <tt>uglifier</tt> gem and it requires Node.js
150
+ # * <tt>:closure</tt> - Google Closure Compiler, it depends on <tt>closure-compiler</tt> gem and it requires Java
151
+ #
152
+ # @param value [Symbol,#compress] the compressor
153
+ #
154
+ # @since 0.1.0
155
+ #
156
+ # @see http://yui.github.io/yuicompressor
157
+ # @see https://rubygems.org/gems/yui-compressor
158
+ #
159
+ # @see http://lisperator.net/uglifyjs
160
+ # @see https://rubygems.org/gems/uglifier
161
+ #
162
+ # @see https://developers.google.com/closure/compiler
163
+ # @see https://rubygems.org/gems/closure-compiler
164
+ #
165
+ # @example YUI Compressor
166
+ # require 'lotus/assets'
167
+ #
168
+ # Lotus::Assets.configure do
169
+ # # ...
170
+ # javascript_compressor :yui
171
+ # end.load!
172
+ #
173
+ # @example Custom Compressor
174
+ # require 'lotus/assets'
175
+ #
176
+ # Lotus::Assets.configure do
177
+ # # ...
178
+ # javascript_compressor MyCustomJavascriptCompressor.new
179
+ # end.load!
180
+ def javascript_compressor(value = nil)
181
+ if value.nil?
182
+ @javascript_compressor
183
+ else
184
+ @javascript_compressor = value
185
+ end
186
+ end
187
+
188
+ # Stylesheet compressor
189
+ #
190
+ # Determine which compressor to use for Stylesheet files during deploy.
191
+ #
192
+ # By default it's <tt>nil</tt>, that means it doesn't compress Stylesheets at deploy time.
193
+ #
194
+ # It accepts a <tt>Symbol</tt> or an object that respond to <tt>#compress(file)</tt>.
195
+ #
196
+ # The following symbols are accepted:
197
+ #
198
+ # * <tt>:builtin</tt> - Ruby based compressor. It doesn't require any external gem. It's fast, but not an efficient compressor.
199
+ # * <tt>:yui</tt> - YUI-Compressor, it depends on <tt>yui-compressor</tt> gem and requires Java 1.4+
200
+ # * <tt>:sass</tt> - Sass, it depends on <tt>sass</tt> gem
201
+ #
202
+ # @param value [Symbol,#compress] the compressor
203
+ #
204
+ # @since 0.1.0
205
+ #
206
+ # @see http://yui.github.io/yuicompressor
207
+ # @see https://rubygems.org/gems/yui-compressor
208
+ #
209
+ # @see http://sass-lang.com
210
+ # @see https://rubygems.org/gems/sass
211
+ #
212
+ # @example YUI Compressor
213
+ # require 'lotus/assets'
214
+ #
215
+ # Lotus::Assets.configure do
216
+ # # ...
217
+ # stylesheet_compressor :yui
218
+ # end.load!
219
+ #
220
+ # @example Custom Compressor
221
+ # require 'lotus/assets'
222
+ #
223
+ # Lotus::Assets.configure do
224
+ # # ...
225
+ # stylesheet_compressor MyCustomStylesheetCompressor.new
226
+ # end.load!
227
+ def stylesheet_compressor(value = nil)
228
+ if value.nil?
229
+ @stylesheet_compressor
230
+ else
231
+ @stylesheet_compressor = value
232
+ end
233
+ end
234
+
235
+ # URL scheme for the application
236
+ #
237
+ # This is used to generate absolute URL from helpers.
238
+ #
239
+ # @since 0.1.0
240
+ def scheme(value = nil)
241
+ if value.nil?
242
+ @scheme
243
+ else
244
+ @scheme = value
245
+ end
246
+ end
247
+
248
+ # URL host for the application
249
+ #
250
+ # This is used to generate absolute URL from helpers.
251
+ #
252
+ # @since 0.1.0
253
+ def host(value = nil)
254
+ if value.nil?
255
+ @host
256
+ else
257
+ @host = value
258
+ end
259
+ end
260
+
261
+ # URL port for the application
262
+ #
263
+ # This is used to generate absolute URL from helpers.
264
+ #
265
+ # @since 0.1.0
266
+ def port(value = nil)
267
+ if value.nil?
268
+ @port
269
+ else
270
+ @port = value.to_s
271
+ end
272
+ end
273
+
274
+ # URL port for the application
275
+ #
276
+ # This is used to generate absolute or relative URL from helpers.
277
+ #
278
+ # @since 0.1.0
279
+ def prefix(value = nil)
280
+ if value.nil?
281
+ @prefix
282
+ else
283
+ @prefix = Utils::PathPrefix.new(value)
284
+ end
285
+ end
286
+
287
+ # Sources root
288
+ #
289
+ # @since 0.1.0
290
+ def root(value = nil)
291
+ if value.nil?
292
+ @root
293
+ else
294
+ @root = Pathname.new(value).realpath
295
+ sources.root = @root
296
+ end
297
+ end
298
+
299
+ # Application public directory
300
+ #
301
+ # @since 0.1.0
302
+ def public_directory(value = nil)
303
+ if value.nil?
304
+ @public_directory
305
+ else
306
+ @public_directory = Pathname.new(::File.expand_path(value))
307
+ end
308
+ end
309
+
310
+ # Destination directory
311
+ #
312
+ # It's the combination of <tt>public_directory</tt> and <tt>prefix</tt>.
313
+ #
314
+ # @since 0.1.0
315
+ # @api private
316
+ def destination_directory
317
+ @destination_directory ||= public_directory.join(*prefix.split(URL_SEPARATOR))
318
+ end
319
+
320
+ # Manifest path from public directory
321
+ #
322
+ # @since 0.1.0
323
+ def manifest(value = nil)
324
+ if value.nil?
325
+ @manifest
326
+ else
327
+ @manifest = value.to_s
328
+ end
329
+ end
330
+
331
+ # Absolute manifest path
332
+ #
333
+ # @since 0.1.0
334
+ # @api private
335
+ def manifest_path
336
+ public_directory.join(manifest)
337
+ end
338
+
339
+ # Application's assets sources
340
+ #
341
+ # @since 0.1.0
342
+ # @api private
343
+ def sources
344
+ @sources ||= Lotus::Assets::Config::Sources.new(root)
345
+ end
346
+
347
+ # Application's assets
348
+ #
349
+ # @since 0.1.0
350
+ # @api private
351
+ def files
352
+ sources.files
353
+ end
354
+
355
+ # Find a file from sources
356
+ #
357
+ # @since 0.1.0
358
+ # @api private
359
+ def find(file)
360
+ @sources.find(file)
361
+ end
362
+
363
+ # Relative URL
364
+ #
365
+ # @since 0.1.0
366
+ # @api private
367
+ def asset_path(source)
368
+ cdn ?
369
+ asset_url(source) :
370
+ compile_path(source)
371
+ end
372
+
373
+ # Absolute URL
374
+ #
375
+ # @since 0.1.0
376
+ # @api private
377
+ def asset_url(source)
378
+ "#{ @base_url }#{ compile_path(source) }"
379
+ end
380
+
381
+ # Load Javascript compressor
382
+ #
383
+ # @return [Lotus::Assets::Compressors::Javascript] a compressor
384
+ #
385
+ # @raise [Lotus::Assets::Compressors::UnknownCompressorError] when the
386
+ # given name refers to an unknown compressor engine
387
+ #
388
+ # @since 0.1.0
389
+ # @api private
390
+ #
391
+ # @see Lotus::Assets::Configuration#javascript_compressor
392
+ # @see Lotus::Assets::Compressors::Javascript#for
393
+ def js_compressor
394
+ require 'lotus/assets/compressors/javascript'
395
+ Lotus::Assets::Compressors::Javascript.for(javascript_compressor)
396
+ end
397
+
398
+ # Load Stylesheet compressor
399
+ #
400
+ # @return [Lotus::Assets::Compressors::Stylesheet] a compressor
401
+ #
402
+ # @raise [Lotus::Assets::Compressors::UnknownCompressorError] when the
403
+ # given name refers to an unknown compressor engine
404
+ #
405
+ # @since 0.1.0
406
+ # @api private
407
+ #
408
+ # @see Lotus::Assets::Configuration#stylesheet_compressor
409
+ # @see Lotus::Assets::Compressors::Stylesheet#for
410
+ def css_compressor
411
+ require 'lotus/assets/compressors/stylesheet'
412
+ Lotus::Assets::Compressors::Stylesheet.for(stylesheet_compressor)
413
+ end
414
+
415
+ # @since 0.1.0
416
+ # @api private
417
+ def duplicate
418
+ Configuration.new.tap do |c|
419
+ c.root = root
420
+ c.scheme = scheme
421
+ c.host = host
422
+ c.port = port
423
+ c.prefix = prefix
424
+ c.cdn = cdn
425
+ c.compile = compile
426
+ c.public_directory = public_directory
427
+ c.manifest = manifest
428
+ c.sources = sources.dup
429
+ c.javascript_compressor = javascript_compressor
430
+ c.stylesheet_compressor = stylesheet_compressor
431
+ end
432
+ end
433
+
434
+ # @since 0.1.0
435
+ # @api private
436
+ def reset!
437
+ @scheme = DEFAULT_SCHEME
438
+ @host = DEFAULT_HOST
439
+ @port = DEFAULT_PORT
440
+
441
+ @prefix = Utils::PathPrefix.new(DEFAULT_PREFIX)
442
+ @cdn = false
443
+ @compile = false
444
+ @destination_directory = nil
445
+ @digest_manifest = Config::NullDigestManifest.new(self)
446
+
447
+ @javascript_compressor = nil
448
+ @stylesheet_compressor = nil
449
+
450
+ root Dir.pwd
451
+ public_directory root.join(DEFAULT_PUBLIC_DIRECTORY)
452
+ manifest DEFAULT_MANIFEST
453
+ end
454
+
455
+ # Load the configuration
456
+ #
457
+ # This MUST be executed before to accept the first HTTP request
458
+ #
459
+ # @since 0.1.0
460
+ def load!
461
+ if digest && manifest_path.exist?
462
+ @digest_manifest = Config::DigestManifest.new(
463
+ JSON.load(manifest_path.read),
464
+ manifest_path
465
+ )
466
+ end
467
+
468
+ @base_url = URI::Generic.build(scheme: scheme, host: host, port: url_port).to_s
469
+ end
470
+
471
+ protected
472
+
473
+ # @since 0.1.0
474
+ # @api private
475
+ attr_writer :cdn
476
+
477
+ # @since 0.1.0
478
+ # @api private
479
+ attr_writer :compile
480
+
481
+ # @since 0.1.0
482
+ # @api private
483
+ attr_writer :scheme
484
+
485
+ # @since 0.1.0
486
+ # @api private
487
+ attr_writer :host
488
+
489
+ # @since 0.1.0
490
+ # @api private
491
+ attr_writer :port
492
+
493
+ # @since 0.1.0
494
+ # @api private
495
+ attr_writer :prefix
496
+
497
+ # @since 0.1.0
498
+ # @api private
499
+ attr_writer :root
500
+
501
+ # @since 0.1.0
502
+ # @api private
503
+ attr_writer :public_directory
504
+
505
+ # @since 0.1.0
506
+ # @api private
507
+ attr_writer :manifest
508
+
509
+ # @since 0.1.0
510
+ # @api private
511
+ attr_writer :sources
512
+
513
+ # @since 0.1.0
514
+ # @api private
515
+ attr_writer :javascript_compressor
516
+
517
+ # @since 0.1.0
518
+ # @api private
519
+ attr_writer :stylesheet_compressor
520
+
521
+ private
522
+
523
+ # @since 0.1.0
524
+ # @api private
525
+ def compile_path(source)
526
+ result = prefix.join(source)
527
+ result = digest_manifest.resolve(result) if digest
528
+ result.to_s
529
+ end
530
+
531
+ # @since 0.1.0
532
+ # @api private
533
+ def url_port
534
+ ( (scheme == HTTP_SCHEME && port == HTTP_PORT ) ||
535
+ (scheme == HTTPS_SCHEME && port == HTTPS_PORT ) ) ? nil : port.to_i
536
+ end
537
+ end
538
+ end
539
+ end