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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/README.md +429 -7
- data/bin/lotus-assets +22 -0
- data/lib/lotus/assets.rb +153 -2
- data/lib/lotus/assets/bundler.rb +173 -0
- data/lib/lotus/assets/cache.rb +58 -0
- data/lib/lotus/assets/compiler.rb +212 -0
- data/lib/lotus/assets/compressors/abstract.rb +119 -0
- data/lib/lotus/assets/compressors/builtin_javascript.rb +36 -0
- data/lib/lotus/assets/compressors/builtin_stylesheet.rb +57 -0
- data/lib/lotus/assets/compressors/closure_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/javascript.rb +77 -0
- data/lib/lotus/assets/compressors/jsmin.rb +283 -0
- data/lib/lotus/assets/compressors/null_compressor.rb +19 -0
- data/lib/lotus/assets/compressors/sass_stylesheet.rb +38 -0
- data/lib/lotus/assets/compressors/stylesheet.rb +77 -0
- data/lib/lotus/assets/compressors/uglifier_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/yui_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/yui_stylesheet.rb +25 -0
- data/lib/lotus/assets/config/global_sources.rb +50 -0
- data/lib/lotus/assets/config/manifest.rb +112 -0
- data/lib/lotus/assets/config/sources.rb +77 -0
- data/lib/lotus/assets/configuration.rb +539 -0
- data/lib/lotus/assets/helpers.rb +733 -0
- data/lib/lotus/assets/precompiler.rb +67 -0
- data/lib/lotus/assets/version.rb +4 -1
- data/lotus-assets.gemspec +25 -11
- metadata +192 -15
- data/.gitignore +0 -22
- data/Gemfile +0 -4
- data/Rakefile +0 -2
@@ -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
|