lotus-assets 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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