darkroom 0.0.6 → 0.0.7
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/README.md +3 -3
- data/VERSION +1 -1
- data/lib/darkroom/asset.rb +358 -249
- data/lib/darkroom/darkroom.rb +135 -59
- data/lib/darkroom/delegate.rb +237 -0
- data/lib/darkroom/delegates/css.rb +34 -32
- data/lib/darkroom/delegates/html.rb +39 -37
- data/lib/darkroom/delegates/htx.rb +15 -13
- data/lib/darkroom/delegates/javascript.rb +180 -7
- data/lib/darkroom/errors/asset_error.rb +4 -4
- data/lib/darkroom/errors/asset_not_found_error.rb +3 -3
- data/lib/darkroom/errors/circular_reference_error.rb +3 -3
- data/lib/darkroom/errors/duplicate_asset_error.rb +3 -3
- data/lib/darkroom/errors/invalid_path_error.rb +5 -3
- data/lib/darkroom/errors/missing_library_error.rb +3 -3
- data/lib/darkroom/errors/processing_error.rb +6 -2
- data/lib/darkroom/errors/unrecognized_extension_error.rb +3 -3
- data/lib/darkroom/version.rb +1 -1
- data/lib/darkroom.rb +5 -4
- metadata +4 -3
data/lib/darkroom/darkroom.rb
CHANGED
@@ -12,32 +12,41 @@ require_relative('errors/processing_error')
|
|
12
12
|
# Main class providing fast, lightweight, and straightforward web asset management.
|
13
13
|
#
|
14
14
|
class Darkroom
|
15
|
-
|
16
|
-
|
15
|
+
DEFAULT_MINIFIED = /(\.|-)min\.\w+$/.freeze
|
16
|
+
TRAILING_SLASHES = /\/+$/.freeze
|
17
17
|
PRISTINE = Set.new(%w[/favicon.ico /mask-icon.svg /humans.txt /robots.txt]).freeze
|
18
18
|
MIN_PROCESS_INTERVAL = 0.5
|
19
19
|
|
20
|
-
DISALLOWED_PATH_CHARS = '\'"`=<>? '
|
21
|
-
INVALID_PATH = /[#{DISALLOWED_PATH_CHARS}]/.freeze
|
22
|
-
TRAILING_SLASHES = /\/+$/.freeze
|
23
|
-
|
24
20
|
@@delegates = {}
|
25
21
|
@@glob = ''
|
26
22
|
|
27
23
|
attr_reader(:error, :errors, :process_key)
|
28
24
|
|
25
|
+
class << self; attr_accessor(:javascript_iife) end
|
26
|
+
|
29
27
|
##
|
30
28
|
# Registers an asset delegate.
|
31
29
|
#
|
32
|
-
# *
|
33
|
-
#
|
30
|
+
# [*extensions] One or more file extension(s) to associate with this delegate.
|
31
|
+
# [delegate] An HTTP MIME type string or a Delegate subclass.
|
34
32
|
#
|
35
|
-
def self.register(*extensions, delegate)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
def self.register(*extensions, delegate, &block)
|
34
|
+
if delegate.kind_of?(String)
|
35
|
+
content_type = delegate
|
36
|
+
|
37
|
+
if delegate[0] == '.'
|
38
|
+
extensions << delegate
|
39
|
+
content_type = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
delegate = Class.new(Delegate, &block)
|
43
|
+
delegate.content_type(content_type) if content_type && !delegate.content_type
|
44
|
+
elsif delegate.kind_of?(Hash)
|
45
|
+
deprecated("#{self.name}.register with a Hash is deprecated: use the Delegate DSL inside a block "\
|
46
|
+
'instead')
|
47
|
+
delegate = Delegate.deprecated_from_hash(**delegate)
|
48
|
+
elsif delegate && delegate < Delegate
|
49
|
+
delegate = block ? Class.new(delegate, &block) : delegate
|
41
50
|
end
|
42
51
|
|
43
52
|
extensions.each do |extension|
|
@@ -52,38 +61,63 @@ class Darkroom
|
|
52
61
|
##
|
53
62
|
# Returns the delegate associated with a file extension.
|
54
63
|
#
|
55
|
-
#
|
64
|
+
# [extension] File extension of the desired delegate.
|
56
65
|
#
|
57
66
|
def self.delegate(extension)
|
58
67
|
@@delegates[extension]
|
59
68
|
end
|
60
69
|
|
70
|
+
##
|
71
|
+
# Utility method that prints a warning with file and line number of a deprecated call.
|
72
|
+
#
|
73
|
+
def self.deprecated(message)
|
74
|
+
location = caller_locations(2, 1).first
|
75
|
+
|
76
|
+
warn("#{location.path}:#{location.lineno}: #{message}")
|
77
|
+
end
|
78
|
+
|
61
79
|
##
|
62
80
|
# Creates a new instance.
|
63
81
|
#
|
64
|
-
# *
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
82
|
+
# [*load_paths] One or more paths where assets are located on disk.
|
83
|
+
# [host:] Host(s) to prepend to paths (useful when serving from a CDN in production). If multiple hosts
|
84
|
+
# are specified, they will be round-robined within each thread for each call to +#asset_path+.
|
85
|
+
# [hosts:] Alias of +host:+.
|
86
|
+
# [prefix:] Prefix to prepend to asset paths (e.g. +/assets+).
|
87
|
+
# [pristine:] Path(s) that should not include prefix and for which unversioned form should be provided by
|
88
|
+
# default (e.g. +/favicon.ico+).
|
89
|
+
# [entries:] String, regex, or array of strings and regexes specifying entry point paths / path patterns.
|
90
|
+
# [minify:] Boolean specifying whether or not to minify assets.
|
91
|
+
# [minified:] String, regex, or array of strings and regexes specifying paths of assets that are already
|
92
|
+
# minified and thus should be skipped for minification.
|
93
|
+
# [minified_pattern:] DEPRECATED: use +minified:+ instead. Regex used against asset paths to determine if
|
94
|
+
# they are already minified and should therefore be skipped over for minification.
|
95
|
+
# [internal_pattern:] DEPRECATED: use +entries:+ instead. Regex used against asset paths to determine if
|
96
|
+
# they should be marked as internal and therefore made inaccessible externally.
|
97
|
+
# [min_process_interval:] Minimum time required between one run of asset processing and another.
|
77
98
|
#
|
78
|
-
def initialize(*load_paths, host: nil, hosts: nil, prefix: nil, pristine: nil,
|
79
|
-
minified_pattern:
|
99
|
+
def initialize(*load_paths, host: nil, hosts: nil, prefix: nil, pristine: nil, entries: nil,
|
100
|
+
minify: false, minified: DEFAULT_MINIFIED, minified_pattern: nil, internal_pattern: nil,
|
80
101
|
min_process_interval: MIN_PROCESS_INTERVAL)
|
81
102
|
@load_paths = load_paths.map { |load_path| File.expand_path(load_path) }
|
82
103
|
|
83
104
|
@hosts = (Array(host) + Array(hosts)).map! { |host| host.sub(TRAILING_SLASHES, '') }
|
105
|
+
@entries = Array(entries)
|
84
106
|
@minify = minify
|
107
|
+
@minified = Array(minified)
|
85
108
|
@internal_pattern = internal_pattern
|
86
|
-
|
109
|
+
|
110
|
+
if minified_pattern
|
111
|
+
self.class.deprecated("#{self.class.name} :minified_pattern is deprecated: use :minified instead "\
|
112
|
+
'and pass a string, regex, or array of strings and regexes')
|
113
|
+
@minified = [minified_pattern]
|
114
|
+
end
|
115
|
+
|
116
|
+
if @internal_pattern
|
117
|
+
self.class.deprecated("#{self.class.name} :internal_pattern is deprecated: use :entries to instead "\
|
118
|
+
'specify which assets are entry points (i.e. available externally) and pass a string, regex, or '\
|
119
|
+
'array of strings and regexes')
|
120
|
+
end
|
87
121
|
|
88
122
|
@prefix = prefix&.sub(TRAILING_SLASHES, '')
|
89
123
|
@prefix = nil if @prefix && @prefix.empty?
|
@@ -106,14 +140,15 @@ class Darkroom
|
|
106
140
|
|
107
141
|
##
|
108
142
|
# Walks all load paths and refreshes any assets that have been modified on disk since the last call to
|
109
|
-
# this method.
|
143
|
+
# this method. Returns false if processing was skipped due to previous call happening less than
|
144
|
+
# min_process_interval ago or because another thread was already processing; returns true otherwise.
|
110
145
|
#
|
111
146
|
def process
|
112
|
-
return if Time.now.to_f - @last_processed_at < @min_process_interval
|
147
|
+
return false if Time.now.to_f - @last_processed_at < @min_process_interval
|
113
148
|
|
114
149
|
if @mutex.locked?
|
115
150
|
@mutex.synchronize {}
|
116
|
-
return
|
151
|
+
return false
|
117
152
|
end
|
118
153
|
|
119
154
|
@mutex.synchronize do
|
@@ -125,18 +160,22 @@ class Darkroom
|
|
125
160
|
Dir.glob(File.join(load_path, @@glob)).sort.each do |file|
|
126
161
|
path = file.sub(load_path, '')
|
127
162
|
|
128
|
-
if index = (path =~
|
163
|
+
if index = (path =~ Asset::INVALID_PATH_REGEX)
|
129
164
|
@errors << InvalidPathError.new(path, index)
|
130
165
|
elsif found.key?(path)
|
131
166
|
@errors << DuplicateAssetError.new(path, found[path], load_path)
|
132
167
|
else
|
133
168
|
found[path] = load_path
|
134
169
|
|
135
|
-
@manifest
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
170
|
+
unless @manifest.key?(path)
|
171
|
+
entry = entry?(path)
|
172
|
+
|
173
|
+
@manifest[path] = Asset.new(path, file, self,
|
174
|
+
prefix: (@prefix unless @pristine.include?(path)),
|
175
|
+
entry: entry,
|
176
|
+
minify: entry && @minify && !minified?(path),
|
177
|
+
)
|
178
|
+
end
|
140
179
|
end
|
141
180
|
end
|
142
181
|
end
|
@@ -148,13 +187,15 @@ class Darkroom
|
|
148
187
|
@manifest.each do |path, asset|
|
149
188
|
asset.process
|
150
189
|
|
151
|
-
|
190
|
+
if asset.entry?
|
152
191
|
@manifest_unversioned[asset.path_unversioned] = asset
|
153
192
|
@manifest_versioned[asset.path_versioned] = asset
|
154
193
|
end
|
155
194
|
|
156
|
-
@errors
|
195
|
+
@errors.concat(asset.errors)
|
157
196
|
end
|
197
|
+
|
198
|
+
true
|
158
199
|
ensure
|
159
200
|
@last_processed_at = Time.now.to_f
|
160
201
|
@error = @errors.empty? ? nil : ProcessingError.new(@errors)
|
@@ -162,12 +203,13 @@ class Darkroom
|
|
162
203
|
end
|
163
204
|
|
164
205
|
##
|
165
|
-
#
|
206
|
+
# Calls #process. If processing was skipped, returns false. If processing was performed, raises an
|
207
|
+
# exception if any errors were encountered and returns true otherwise.
|
166
208
|
#
|
167
209
|
def process!
|
168
|
-
process
|
210
|
+
result = process
|
169
211
|
|
170
|
-
|
212
|
+
(result && @error) ? raise(@error) : result
|
171
213
|
end
|
172
214
|
|
173
215
|
##
|
@@ -186,7 +228,7 @@ class Darkroom
|
|
186
228
|
# darkroom.asset('/assets/js/app.<hash>.js')
|
187
229
|
# darkroom.asset('/assets/js/app.js')
|
188
230
|
#
|
189
|
-
#
|
231
|
+
# [path] External path of the asset.
|
190
232
|
#
|
191
233
|
def asset(path)
|
192
234
|
@manifest_versioned[path] || @manifest_unversioned[path]
|
@@ -203,8 +245,8 @@ class Darkroom
|
|
203
245
|
#
|
204
246
|
# Raises an AssetNotFoundError if the asset doesn't exist.
|
205
247
|
#
|
206
|
-
#
|
207
|
-
#
|
248
|
+
# [path] Internal path of the asset.
|
249
|
+
# [versioned:] Boolean indicating whether the versioned or unversioned path should be returned.
|
208
250
|
#
|
209
251
|
def asset_path(path, versioned: !@pristine.include?(path))
|
210
252
|
asset = @manifest[path] or raise(AssetNotFoundError.new(path))
|
@@ -219,8 +261,8 @@ class Darkroom
|
|
219
261
|
# Returns an asset's subresource integrity string. Raises an AssetNotFoundError if the asset doesn't
|
220
262
|
# exist.
|
221
263
|
#
|
222
|
-
#
|
223
|
-
#
|
264
|
+
# [path] Internal path of the asset.
|
265
|
+
# [algorithm] Hash algorithm to use to generate the integrity string (see Asset#integrity).
|
224
266
|
#
|
225
267
|
def asset_integrity(path, algorithm = nil)
|
226
268
|
asset = @manifest[path] or raise(AssetNotFoundError.new(path))
|
@@ -231,7 +273,7 @@ class Darkroom
|
|
231
273
|
##
|
232
274
|
# Returns the asset from the manifest hash associated with the given path.
|
233
275
|
#
|
234
|
-
#
|
276
|
+
# [path] Internal path of the asset.
|
235
277
|
#
|
236
278
|
def manifest(path)
|
237
279
|
@manifest[path]
|
@@ -241,14 +283,16 @@ class Darkroom
|
|
241
283
|
# Writes assets to disk. This is useful when deploying to a production environment where assets will be
|
242
284
|
# uploaded to and served from a CDN or proxy server.
|
243
285
|
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
286
|
+
# [dir] Directory to write the assets to.
|
287
|
+
# [clear:] Boolean indicating whether or not the existing contents of the directory should be deleted
|
288
|
+
# before performing the dump.
|
289
|
+
# [include_pristine:] Boolean indicating whether or not to include pristine assets (when dumping for the
|
290
|
+
# purpose of uploading to a CDN, assets such as /robots.txt and /favicon.ico don't
|
291
|
+
# need to be included).
|
250
292
|
#
|
251
293
|
def dump(dir, clear: false, include_pristine: true)
|
294
|
+
raise(@error) if @error
|
295
|
+
|
252
296
|
require('fileutils')
|
253
297
|
|
254
298
|
dir = File.expand_path(dir)
|
@@ -257,7 +301,6 @@ class Darkroom
|
|
257
301
|
Dir.each_child(dir) { |child| FileUtils.rm_rf(File.join(dir, child)) } if clear
|
258
302
|
|
259
303
|
@manifest_versioned.each do |path, asset|
|
260
|
-
next if asset.internal?
|
261
304
|
next if @pristine.include?(asset.path) && !include_pristine
|
262
305
|
|
263
306
|
file_path = File.join(dir,
|
@@ -274,17 +317,50 @@ class Darkroom
|
|
274
317
|
#
|
275
318
|
def inspect
|
276
319
|
"#<#{self.class}: "\
|
320
|
+
"@entries=#{@entries.inspect}, "\
|
277
321
|
"@errors=#{@errors.inspect}, "\
|
278
322
|
"@hosts=#{@hosts.inspect}, "\
|
279
323
|
"@internal_pattern=#{@internal_pattern.inspect}, "\
|
280
324
|
"@last_processed_at=#{@last_processed_at.inspect}, "\
|
281
325
|
"@load_paths=#{@load_paths.inspect}, "\
|
282
326
|
"@min_process_interval=#{@min_process_interval.inspect}, "\
|
283
|
-
"@
|
327
|
+
"@minified=#{@minified.inspect}, "\
|
284
328
|
"@minify=#{@minify.inspect}, "\
|
285
329
|
"@prefix=#{@prefix.inspect}, "\
|
286
330
|
"@pristine=#{@pristine.inspect}, "\
|
287
331
|
"@process_key=#{@process_key.inspect}"\
|
288
332
|
'>'
|
289
333
|
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
##
|
338
|
+
# Returns boolean indicating whether or not the provided path is an entry point.
|
339
|
+
#
|
340
|
+
# [path] Path to check.
|
341
|
+
#
|
342
|
+
def entry?(path)
|
343
|
+
if @pristine.include?(path)
|
344
|
+
true
|
345
|
+
elsif @internal_pattern && @entries.empty?
|
346
|
+
!path.match?(@internal_pattern)
|
347
|
+
elsif @entries.empty?
|
348
|
+
true
|
349
|
+
else
|
350
|
+
@entries.any? do |entry|
|
351
|
+
path == entry || (entry.kind_of?(Regexp) && path.match?(entry))
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
##
|
357
|
+
# Returns boolean indicating whether or not the asset with the provided path is already minified.
|
358
|
+
#
|
359
|
+
# [path] Path to check.
|
360
|
+
#
|
361
|
+
def minified?(path)
|
362
|
+
@minified.any? do |minified|
|
363
|
+
path == minified || (minified.kind_of?(Regexp) && path.match?(minified))
|
364
|
+
end
|
365
|
+
end
|
290
366
|
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
class Darkroom
|
2
|
+
##
|
3
|
+
# Holds asset type-specific information and functionality.
|
4
|
+
#
|
5
|
+
# [minify_lib:] Name of a library to +require+ that is needed by the +minify+ lambda (optional).
|
6
|
+
# [minify:] Lambda to call that will return the minified version of the asset's content (optional). One
|
7
|
+
# argument is passed when called:
|
8
|
+
# * +content+ - Content to minify.
|
9
|
+
#
|
10
|
+
class Delegate
|
11
|
+
[
|
12
|
+
:content_type, :parsers, :compile_lib, :compile_delegate, :compile_handler, :finalize_lib,
|
13
|
+
:finalize_handler, :minify_lib, :minify_handler
|
14
|
+
].each do |name|
|
15
|
+
var = :"@#{name}"
|
16
|
+
instance_variable_set(var, nil)
|
17
|
+
|
18
|
+
define_singleton_method(name) do
|
19
|
+
instance_variable_defined?(var) ? instance_variable_get(var) : superclass.send(name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self; alias :get_content_type :content_type end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Sets or returns HTTP MIME type string.
|
27
|
+
#
|
28
|
+
def self.content_type(content_type = (get = true; nil))
|
29
|
+
get ? get_content_type : (@content_type = content_type)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Configures how imports are handled.
|
34
|
+
#
|
35
|
+
# [regex] Regex for finding import statements. Must contain a named component called +path+ (e.g.
|
36
|
+
# <tt>/^import (?<path>.*)/</tt>).
|
37
|
+
# [&handler] Block for special handling of import statements (optional). Should
|
38
|
+
# <tt>throw(:error, '...')</tt> on error. Passed three arguments:
|
39
|
+
# * +parse_data:+ - Hash for storing data across calls to this and other parsing handlers.
|
40
|
+
# * +match:+ - MatchData object from the match against +regex+.
|
41
|
+
# * +asset:+ - Asset object of the asset being imported.
|
42
|
+
# Return value is used as the substitution for the import statement, with optional second and
|
43
|
+
# third values as integers representing the start and end indexes of the match to replace.
|
44
|
+
#
|
45
|
+
def self.import(regex, &handler)
|
46
|
+
parse(:import, regex, &handler)
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Configures how references are handled.
|
51
|
+
#
|
52
|
+
# [regex] Regex for finding references. Must contain three named components:
|
53
|
+
# * +path+ - Path of the asset being referenced.
|
54
|
+
# * +entity+ - Desired entity ('path' or 'content').
|
55
|
+
# * +format+ - Format to use (see Asset::REFERENCE_FORMATS).
|
56
|
+
# [&handler] Block for special handling of references (optional). Should <tt>throw(:error, '...')</tt>
|
57
|
+
# on error. Passed four arguments:
|
58
|
+
# * +parse_data:+ - Hash for storing data across calls to this and other parsing handlers.
|
59
|
+
# * +match:+ - MatchData object from the match against +regex+.
|
60
|
+
# * +asset:+ - Asset object of the asset being imported.
|
61
|
+
# * +format:+ - Format of the reference (see Asset::REFERENCE_FORMATS).
|
62
|
+
# Return value is used as the substitution for the reference, with optional second and third
|
63
|
+
# values as integers representing the start and end indexes of the match to replace.
|
64
|
+
#
|
65
|
+
def self.reference(regex, &handler)
|
66
|
+
parse(:reference, regex, &handler)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Configures a parser.
|
71
|
+
#
|
72
|
+
# [kind] A name to describe what is being parsed. Should be unique across all +parse+ calls. When
|
73
|
+
# subclassing another Delegate, can be used to override the parent class's regex and handler.
|
74
|
+
# [regex] Regex to match against.
|
75
|
+
# [&handler] Block for handling matches of the regex. Should <tt>throw(:error, '...')</tt>
|
76
|
+
# on error. Passed two arguments:
|
77
|
+
# * +parse_data:+ - Hash for storing data across calls to this and other parsing handlers.
|
78
|
+
# * +match:+ - MatchData object from the match against +regex+.
|
79
|
+
# Return value is used as the substitution for the reference, with optional second and third
|
80
|
+
# values as integers representing the start and end indexes of the match to replace.
|
81
|
+
#
|
82
|
+
def self.parse(kind, regex, &handler)
|
83
|
+
@parsers = parsers&.dup || {} unless @parsers
|
84
|
+
@parsers[kind] = [regex, handler]
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Configures compilation.
|
89
|
+
#
|
90
|
+
# [lib:] Name of a library to +require+ that is needed by the handler (optional).
|
91
|
+
# [delegate:] Another Delegate to be used after the asset is compiled (optional).
|
92
|
+
# [&handler] Block to call that will return the compiled version of the asset's own content. Passed
|
93
|
+
# three arguments when called:
|
94
|
+
#. * +parse_data:+ - Hash of data collected during parsing.
|
95
|
+
# * +path+ - Path of the asset being compiled.
|
96
|
+
# * +own_content+ - Asset's own content.
|
97
|
+
# Asset's own content is set to the value returned.
|
98
|
+
#
|
99
|
+
def self.compile(lib: nil, delegate: nil, &handler)
|
100
|
+
@compile_lib = lib
|
101
|
+
@compile_delegate = delegate
|
102
|
+
@compile_handler = handler
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Configures finalize behavior.
|
107
|
+
#
|
108
|
+
# [lib:] Name of a library to +require+ that is needed by the handler (optional).
|
109
|
+
# [&handler] Block to call that will return the completed version of the asset's overall content. Passed
|
110
|
+
# three arguments when called:
|
111
|
+
#. * +parse_data:+ - Hash of data collected during parsing.
|
112
|
+
# * +path+ - Path of the asset being finalized.
|
113
|
+
# * +content+ - Asset's content (with imports prepended).
|
114
|
+
# Asset's content is set to the value returned.
|
115
|
+
#
|
116
|
+
def self.finalize(lib: nil, &handler)
|
117
|
+
@finalize_lib = lib
|
118
|
+
@finalize_handler = handler
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Configures minification.
|
123
|
+
#
|
124
|
+
# [lib:] Name of a library to +require+ that is needed by the handler (optional).
|
125
|
+
# [&handler] Block to call that will return the minified version of the asset's overall content. Passed
|
126
|
+
# three arguments when called:
|
127
|
+
#. * +parse_data:+ - Hash of data collected during parsing.
|
128
|
+
# * +path+ - Path of the asset being finalized.
|
129
|
+
# * +content+ - Finalized asset's content.
|
130
|
+
# Asset's minified content is set to the value returned.
|
131
|
+
#
|
132
|
+
def self.minify(lib: nil, &handler)
|
133
|
+
@minify_lib = lib
|
134
|
+
@minify_handler = handler
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Throws +:error+ with a message.
|
139
|
+
#
|
140
|
+
# [message] Message to include with the throw.
|
141
|
+
#
|
142
|
+
def self.error(message)
|
143
|
+
throw(:error, message)
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Returns regex for a parser.
|
148
|
+
#
|
149
|
+
# [kind] Name of the parser.
|
150
|
+
#
|
151
|
+
def self.regex(kind)
|
152
|
+
parsers[kind]&.first
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Returns handler for a parser.
|
157
|
+
#
|
158
|
+
# [kind] Name of the parser.
|
159
|
+
#
|
160
|
+
def self.handler(kind)
|
161
|
+
parsers[kind]&.last
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Iterates over each parser and yields its kind, regex, and handler.
|
166
|
+
#
|
167
|
+
def self.each_parser
|
168
|
+
parsers&.each do |kind, (regex, handler)|
|
169
|
+
yield(kind, regex, handler)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# DEPRECATED: subclass Delegate and use its DSL instead. Returns a subclass of Delegate configured using
|
175
|
+
# the supplied Hash.
|
176
|
+
#
|
177
|
+
def self.new(**params)
|
178
|
+
Darkroom.deprecated("#{self.name}::new is deprecated: use the DSL inside a child class or a block "\
|
179
|
+
'passed to Darkroom.register')
|
180
|
+
|
181
|
+
deprecated_from_hash(**params)
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# DEPRECATED: subclass Delegate and use its DSL instead. Returns a subclass of Delegate configured using
|
186
|
+
# the supplied Hash.
|
187
|
+
#
|
188
|
+
def self.deprecated_from_hash(content_type:, import_regex: nil, reference_regex: nil,
|
189
|
+
validate_reference: nil, reference_content: nil, compile_lib: nil, compile: nil, compiled: nil,
|
190
|
+
minify_lib: nil, minify: nil)
|
191
|
+
Class.new(Delegate) do
|
192
|
+
self.content_type(content_type)
|
193
|
+
|
194
|
+
@import_regex = import_regex
|
195
|
+
@reference_regex = reference_regex
|
196
|
+
|
197
|
+
self.import(import_regex) if import_regex
|
198
|
+
|
199
|
+
if validate_reference || reference_content
|
200
|
+
@validate_reference = validate_reference
|
201
|
+
@reference_content = reference_content
|
202
|
+
|
203
|
+
self.reference(reference_regex) do |parse_data:, match:, asset:, format:|
|
204
|
+
error_message = validate_reference&.call(asset, match, format)
|
205
|
+
error(error_message) if error_message
|
206
|
+
|
207
|
+
reference_content&.call(asset, match, format)
|
208
|
+
end
|
209
|
+
elsif reference_regex
|
210
|
+
self.reference(reference_regex)
|
211
|
+
end
|
212
|
+
|
213
|
+
if compile
|
214
|
+
self.compile(lib: compile_lib, delegate: compiled) do |parse_data:, path:, own_content:|
|
215
|
+
compile.call(path, own_content)
|
216
|
+
end
|
217
|
+
elsif compile_lib || compiled
|
218
|
+
self.compile(lib: compile_lib, delegate: compiled)
|
219
|
+
end
|
220
|
+
|
221
|
+
if minify
|
222
|
+
self.minify(lib: minify_lib) do |parse_data:, path:, content:|
|
223
|
+
minify.call(content)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# DEPRECATED: subclass Delegate and use its DSL instead.
|
231
|
+
#
|
232
|
+
def self.import_regex() @import_regex end
|
233
|
+
def self.reference_regex() @reference_regex end
|
234
|
+
def self.validate_reference() @validate_reference end
|
235
|
+
def self.reference_content() @reference_content end
|
236
|
+
end
|
237
|
+
end
|
@@ -1,39 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative('../asset')
|
4
|
+
require_relative('../delegate')
|
4
5
|
|
5
6
|
class Darkroom
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
7
|
+
class CSSDelegate < Delegate
|
8
|
+
IMPORT_REGEX = /
|
9
|
+
(?<=^|;)[^\S\n]*
|
10
|
+
@import\s+#{Asset::QUOTED_PATH_REGEX.source}
|
11
|
+
[^\S\n]*;[^\S\n]*(\n|\Z)
|
12
|
+
/x.freeze
|
13
|
+
|
14
|
+
REFERENCE_REGEX = /url\(\s*#{Asset::REFERENCE_REGEX.source}\s*\)/x.freeze
|
15
|
+
|
16
|
+
content_type('text/css')
|
17
|
+
|
18
|
+
import(IMPORT_REGEX)
|
19
|
+
|
20
|
+
reference(REFERENCE_REGEX) do |parse_data:, match:, asset:, format:|
|
21
|
+
if format == 'displace'
|
22
|
+
error('Cannot displace in CSS files')
|
23
|
+
elsif !asset.image? && !asset.font?
|
24
|
+
error('Referenced asset must be an image or font type')
|
25
|
+
elsif format == 'utf8'
|
26
|
+
content = asset.content.dup
|
27
|
+
|
28
|
+
content.gsub!('#', '%23')
|
29
|
+
content.gsub!('\'', '\\\\\'')
|
30
|
+
content.gsub!('"', '\\"')
|
31
|
+
content.gsub!("\n", "\\\n")
|
32
|
+
|
33
|
+
content
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
minify(lib: 'sassc') do |parse_data:, path:, content:|
|
38
|
+
SassC::Engine.new(content, style: :compressed).render
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|