darkroom 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/darkroom/asset.rb +104 -64
- data/lib/darkroom/darkroom.rb +48 -33
- data/lib/darkroom/errors/missing_library_error.rb +2 -1
- data/lib/darkroom/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 518b5ff3d82631d41125029a7292c2bfdd8942990a7e92e2be08f99217fe8d02
|
4
|
+
data.tar.gz: 388cf8db580df8de6d85a9d196786ef7400a97a096dfbbb316de4b0dbca8e295
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 439b783c5d7afcb20c6a5bde715f686e42236bd70251af909938784f3c4e855f3e22b8fa779b1de19478e64e17462dc4fe37d28f043c0a62f95d13cc8e40829a
|
7
|
+
data.tar.gz: 3bb350472717b4a656e71ef3c7c1b8c200b88c7414c3f7feecfee46b19327e7d7bb5bb061878474d584c65afa84777cdf784fe76fd0cada86cecfbb20485fd13
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/darkroom/asset.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require('base64')
|
3
4
|
require('digest')
|
4
5
|
|
5
6
|
class Darkroom
|
@@ -12,7 +13,7 @@ class Darkroom
|
|
12
13
|
|
13
14
|
@@specs = {}
|
14
15
|
|
15
|
-
attr_reader(:content, :error, :errors, :path, :path_versioned)
|
16
|
+
attr_reader(:content, :error, :errors, :path, :path_unversioned, :path_versioned)
|
16
17
|
|
17
18
|
##
|
18
19
|
# Holds information about how to handle a particular asset type.
|
@@ -72,18 +73,21 @@ class Darkroom
|
|
72
73
|
#
|
73
74
|
# * +file+ - The path to the file on disk.
|
74
75
|
# * +path+ - The path this asset will be referenced by (e.g. /js/app.js).
|
75
|
-
# * +
|
76
|
+
# * +darkroom+ - Darkroom instance that the asset is a member of.
|
77
|
+
# * +prefix+ - Prefix to apply to unversioned and versioned paths.
|
76
78
|
# * +minify+ - Boolean specifying whether or not the asset should be minified when processed.
|
77
79
|
# * +internal+ - Boolean indicating whether or not the asset is only accessible internally (i.e. as a
|
78
80
|
# dependency).
|
79
81
|
#
|
80
|
-
def initialize(path, file,
|
82
|
+
def initialize(path, file, darkroom, prefix: nil, minify: false, internal: false)
|
81
83
|
@path = path
|
82
84
|
@file = file
|
83
|
-
@
|
85
|
+
@darkroom = darkroom
|
86
|
+
@prefix = prefix
|
84
87
|
@minify = minify
|
85
88
|
@internal = internal
|
86
89
|
|
90
|
+
@path_unversioned = "#{@prefix}#{@path}"
|
87
91
|
@extension = File.extname(@path).downcase
|
88
92
|
@spec = self.class.spec(@extension) or raise(SpecNotDefinedError.new(@extension, @file))
|
89
93
|
|
@@ -92,35 +96,26 @@ class Darkroom
|
|
92
96
|
end
|
93
97
|
|
94
98
|
##
|
95
|
-
# Processes the asset if
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
+
# Processes the asset if modified (see #modified? for how modification is determined). File is read from
|
100
|
+
# disk, any dependencies are merged into its content (if spec for the asset type allows for it), the
|
101
|
+
# content is compiled (if the asset type requires compilation), and minified (if specified for this
|
102
|
+
# Asset). Returns true if asset was modified since it was last processed and false otherwise.
|
99
103
|
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
key == @process_key ? (return @modified) : (@process_key = key)
|
104
|
-
|
105
|
-
begin
|
106
|
-
@modified = @mtime != (@mtime = File.mtime(@file))
|
107
|
-
@modified ||= @dependencies.any? { |d| d.process(key) }
|
108
|
-
|
109
|
-
return false unless @modified
|
110
|
-
rescue Errno::ENOENT
|
111
|
-
clear
|
112
|
-
return @modified = true
|
113
|
-
end
|
104
|
+
def process
|
105
|
+
@process_key == @darkroom.process_key ? (return @processed) : (@process_key = @darkroom.process_key)
|
106
|
+
modified? ? (@processed = true) : (return @processed = false)
|
114
107
|
|
115
108
|
clear
|
116
|
-
read
|
109
|
+
read
|
117
110
|
compile
|
118
111
|
minify
|
119
112
|
|
120
113
|
@fingerprint = Digest::MD5.hexdigest(@content)
|
121
|
-
@path_versioned = @path.sub(EXTENSION_REGEX, "-#{@fingerprint}")
|
114
|
+
@path_versioned = "#{@prefix}#{@path.sub(EXTENSION_REGEX, "-#{@fingerprint}")}"
|
122
115
|
|
123
|
-
@
|
116
|
+
@processed
|
117
|
+
rescue Errno::ENOENT
|
118
|
+
# File was deleted. Do nothing.
|
124
119
|
ensure
|
125
120
|
@error = @errors.empty? ? nil : ProcessingError.new(@errors)
|
126
121
|
end
|
@@ -145,6 +140,23 @@ class Darkroom
|
|
145
140
|
}.compact!
|
146
141
|
end
|
147
142
|
|
143
|
+
##
|
144
|
+
# Returns subresource integrity string.
|
145
|
+
#
|
146
|
+
# * +algorithm+ - The hash algorithm to use to generate the integrity string (one of sha256, sha384, or
|
147
|
+
# sha512).
|
148
|
+
#
|
149
|
+
def integrity(algorithm = :sha384)
|
150
|
+
@integrity[algorithm] ||= "#{algorithm}-#{Base64.strict_encode64(
|
151
|
+
case algorithm
|
152
|
+
when :sha256 then Digest::SHA256.digest(@content)
|
153
|
+
when :sha384 then Digest::SHA384.digest(@content)
|
154
|
+
when :sha512 then Digest::SHA512.digest(@content)
|
155
|
+
else raise("Unrecognized integrity algorithm: #{algorithm}")
|
156
|
+
end
|
157
|
+
)}".freeze
|
158
|
+
end
|
159
|
+
|
148
160
|
##
|
149
161
|
# Returns boolean indicating whether or not the asset is marked as internal.
|
150
162
|
#
|
@@ -173,24 +185,57 @@ class Darkroom
|
|
173
185
|
"@minify=#{@minify.inspect}, "\
|
174
186
|
"@mtime=#{@mtime.inspect}, "\
|
175
187
|
"@path=#{@path.inspect}, "\
|
176
|
-
"@
|
188
|
+
"@path_unversioned=#{@path_unversioned.inspect}, "\
|
189
|
+
"@path_versioned=#{@path_versioned.inspect}, "\
|
190
|
+
"@prefix=#{@prefix.inspect}"\
|
177
191
|
'>'
|
178
192
|
end
|
179
193
|
|
180
194
|
protected
|
181
195
|
|
182
196
|
##
|
183
|
-
# Returns the
|
197
|
+
# Returns true if the asset or any of its dependencies were modified since last processed, or if an
|
198
|
+
# error was recorded during the last processing run.
|
184
199
|
#
|
185
|
-
def
|
186
|
-
@
|
200
|
+
def modified?
|
201
|
+
@modified_key == @darkroom.process_key ? (return @modified) : (@modified_key = @darkroom.process_key)
|
202
|
+
|
203
|
+
begin
|
204
|
+
@modified = !!@error
|
205
|
+
@modified ||= @mtime != (@mtime = File.mtime(@file))
|
206
|
+
@modified ||= dependencies.any? { |d| d.modified? }
|
207
|
+
rescue Errno::ENOENT
|
208
|
+
@modified = true
|
209
|
+
end
|
187
210
|
end
|
188
211
|
|
189
212
|
##
|
190
|
-
# Returns
|
213
|
+
# Returns all dependencies (including dependencies of dependencies).
|
214
|
+
#
|
215
|
+
# * +ancestors+ - Ancestor chain followed to get to this asset as a dependency.
|
191
216
|
#
|
192
|
-
def dependencies
|
193
|
-
@dependencies
|
217
|
+
def dependencies(ancestors = Set.new)
|
218
|
+
@dependencies ||= @own_dependencies.inject([]) do |dependencies, own_dependency|
|
219
|
+
next dependencies if ancestors.include?(self)
|
220
|
+
|
221
|
+
ancestors << self
|
222
|
+
own_dependency.process
|
223
|
+
|
224
|
+
dependencies |= own_dependency.dependencies(ancestors)
|
225
|
+
dependencies |= [own_dependency]
|
226
|
+
|
227
|
+
dependencies.delete(self)
|
228
|
+
ancestors.delete(self)
|
229
|
+
|
230
|
+
dependencies
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Returns the processed content of the asset without dependencies concatenated.
|
236
|
+
#
|
237
|
+
def own_content
|
238
|
+
@own_content
|
194
239
|
end
|
195
240
|
|
196
241
|
private
|
@@ -199,48 +244,40 @@ class Darkroom
|
|
199
244
|
# Clears content, dependencies, and errors so asset is ready for (re)processing.
|
200
245
|
#
|
201
246
|
def clear
|
247
|
+
@dependencies = nil
|
248
|
+
@error = nil
|
249
|
+
@fingerprint = nil
|
250
|
+
@path_versioned = nil
|
251
|
+
|
202
252
|
(@errors ||= []).clear
|
203
|
-
(@
|
253
|
+
(@own_dependencies ||= []).clear
|
204
254
|
(@content ||= +'').clear
|
205
255
|
(@own_content ||= +'').clear
|
256
|
+
(@integrity ||= {}).clear
|
206
257
|
end
|
207
258
|
|
208
259
|
##
|
209
260
|
# Reads the asset file, building dependency array if dependencies are supported for the asset's type.
|
210
261
|
#
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
else
|
222
|
-
@errors << AssetNotFoundError.new(path, @path, line_num + 1)
|
223
|
-
end
|
262
|
+
def read
|
263
|
+
unless @spec.dependency_regex
|
264
|
+
@own_content = File.read(@file)
|
265
|
+
return
|
266
|
+
end
|
267
|
+
|
268
|
+
File.new(@file).each.with_index do |line, line_num|
|
269
|
+
if (path = line[@spec.dependency_regex, :path])
|
270
|
+
if (dependency = @darkroom.manifest(path))
|
271
|
+
@own_dependencies << dependency
|
224
272
|
else
|
225
|
-
@
|
273
|
+
@errors << AssetNotFoundError.new(path, @path, line_num + 1)
|
226
274
|
end
|
275
|
+
else
|
276
|
+
@own_content << line
|
227
277
|
end
|
228
|
-
|
229
|
-
dependencies.each do |dependency|
|
230
|
-
dependency.process(key)
|
231
|
-
|
232
|
-
@dependencies += dependency.dependencies
|
233
|
-
@dependencies << dependency
|
234
|
-
end
|
235
|
-
|
236
|
-
@dependencies.uniq!
|
237
|
-
@dependencies.delete_if { |d| d.path == @path }
|
238
|
-
|
239
|
-
@content << @dependencies.map { |d| d.own_content }.join(DEPENDENCY_JOINER)
|
240
|
-
@own_content
|
241
|
-
else
|
242
|
-
@own_content = File.read(@file)
|
243
278
|
end
|
279
|
+
|
280
|
+
@content << dependencies.map { |d| d.own_content }.join(DEPENDENCY_JOINER)
|
244
281
|
end
|
245
282
|
|
246
283
|
##
|
@@ -287,14 +324,17 @@ class Darkroom
|
|
287
324
|
begin
|
288
325
|
require(@spec.compile_lib) if @spec.compile_lib
|
289
326
|
rescue LoadError
|
290
|
-
|
327
|
+
compile_load_error = true
|
291
328
|
end
|
292
329
|
|
293
330
|
begin
|
294
331
|
require(@spec.minify_lib) if @spec.minify_lib && @minify
|
295
332
|
rescue LoadError
|
296
|
-
|
333
|
+
minify_load_error = true
|
297
334
|
end
|
335
|
+
|
336
|
+
raise(MissingLibraryError.new(@spec.compile_lib, 'compile', @extension)) if compile_load_error
|
337
|
+
raise(MissingLibraryError.new(@spec.minify_lib, 'minify', @extension)) if minify_load_error
|
298
338
|
end
|
299
339
|
end
|
300
340
|
end
|
data/lib/darkroom/darkroom.rb
CHANGED
@@ -13,7 +13,7 @@ class Darkroom
|
|
13
13
|
|
14
14
|
TRAILING_SLASHES = /\/+$/.freeze
|
15
15
|
|
16
|
-
attr_reader(:error, :errors)
|
16
|
+
attr_reader(:error, :errors, :process_key)
|
17
17
|
|
18
18
|
##
|
19
19
|
# Creates a new instance.
|
@@ -51,8 +51,12 @@ class Darkroom
|
|
51
51
|
|
52
52
|
@min_process_interval = min_process_interval
|
53
53
|
@last_processed_at = 0
|
54
|
+
@process_key = 0
|
54
55
|
@mutex = Mutex.new
|
56
|
+
|
55
57
|
@manifest = {}
|
58
|
+
@manifest_unversioned = {}
|
59
|
+
@manifest_versioned = {}
|
56
60
|
|
57
61
|
Thread.current[:darkroom_host_index] = -1 unless @hosts.empty?
|
58
62
|
end
|
@@ -70,6 +74,7 @@ class Darkroom
|
|
70
74
|
end
|
71
75
|
|
72
76
|
@mutex.synchronize do
|
77
|
+
@process_key += 1
|
73
78
|
@errors = []
|
74
79
|
found = {}
|
75
80
|
|
@@ -82,21 +87,28 @@ class Darkroom
|
|
82
87
|
else
|
83
88
|
found[path] = load_path
|
84
89
|
|
85
|
-
@manifest[path] ||= Asset.new(path, file,
|
86
|
-
|
87
|
-
|
90
|
+
@manifest[path] ||= Asset.new(path, file, self,
|
91
|
+
prefix: (@prefix unless @pristine.include?(path)),
|
92
|
+
internal: @internal_pattern && path =~ @internal_pattern,
|
93
|
+
minify: @minify && path !~ @minified_pattern,
|
88
94
|
)
|
89
95
|
end
|
90
96
|
end
|
91
97
|
end
|
92
98
|
|
93
99
|
@manifest.select! { |path, _| found.key?(path) }
|
100
|
+
@manifest_unversioned.clear
|
101
|
+
@manifest_versioned.clear
|
102
|
+
|
103
|
+
@manifest.each do |path, asset|
|
104
|
+
asset.process
|
94
105
|
|
95
|
-
|
96
|
-
|
97
|
-
|
106
|
+
unless asset.internal?
|
107
|
+
@manifest_unversioned[asset.path_unversioned] = asset
|
108
|
+
@manifest_versioned[asset.path_versioned] = asset
|
109
|
+
end
|
98
110
|
|
99
|
-
@errors +=
|
111
|
+
@errors += asset.errors
|
100
112
|
end
|
101
113
|
ensure
|
102
114
|
@last_processed_at = Time.now.to_f
|
@@ -132,14 +144,7 @@ class Darkroom
|
|
132
144
|
# * +path+ - The external path of the asset.
|
133
145
|
#
|
134
146
|
def asset(path)
|
135
|
-
|
136
|
-
|
137
|
-
return nil if asset.nil?
|
138
|
-
return nil if asset.internal?
|
139
|
-
return nil if @prefix && !path.start_with?(@prefix) && !@pristine.include?(asset.path)
|
140
|
-
return nil if @prefix && path.start_with?(@prefix) && @pristine.include?(asset.path)
|
141
|
-
|
142
|
-
asset
|
147
|
+
@manifest_versioned[path] || @manifest_unversioned[path]
|
143
148
|
end
|
144
149
|
|
145
150
|
##
|
@@ -151,28 +156,40 @@ class Darkroom
|
|
151
156
|
# darkroom.asset_path('/js/app.js') # => /assets/js/app.<hash>.js
|
152
157
|
# darkroom.asset_path('/js/app.js', versioned: false) # => /assets/js/app.js
|
153
158
|
#
|
159
|
+
# Raises an AssetNotFoundError if the asset doesn't exist.
|
160
|
+
#
|
154
161
|
# * +path+ - The internal path of the asset.
|
155
162
|
# * +versioned+ - Boolean indicating whether the versioned or unversioned path should be returned.
|
156
163
|
#
|
157
164
|
def asset_path(path, versioned: !@pristine.include?(path))
|
158
|
-
asset = @manifest[path] or
|
159
|
-
|
165
|
+
asset = @manifest[path] or raise(AssetNotFoundError.new(path))
|
160
166
|
host = @hosts.empty? ? '' : @hosts[
|
161
167
|
Thread.current[:darkroom_host_index] = (Thread.current[:darkroom_host_index] + 1) % @hosts.size
|
162
168
|
]
|
163
|
-
prefix = @prefix unless @pristine.include?(path)
|
164
169
|
|
165
|
-
"#{host}#{
|
170
|
+
"#{host}#{versioned ? asset.path_versioned : asset.path_unversioned}"
|
166
171
|
end
|
167
172
|
|
168
173
|
##
|
169
|
-
#
|
170
|
-
#
|
174
|
+
# Returns an asset's subresource integrity string. Raises an AssetNotFoundError if the asset doesn't
|
175
|
+
# exist.
|
171
176
|
#
|
172
|
-
# * +
|
177
|
+
# * +path+ - The internal path of the asset.
|
178
|
+
# * +algorithm+ - The hash algorithm to use to generate the integrity string (see Asset#integrity).
|
173
179
|
#
|
174
|
-
def
|
175
|
-
|
180
|
+
def asset_integrity(path, algorithm = nil)
|
181
|
+
asset = @manifest[path] or raise(AssetNotFoundError.new(path))
|
182
|
+
|
183
|
+
algorithm ? asset.integrity(algorithm) : asset.integrity
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Returns the asset from the manifest hash associated with the given path.
|
188
|
+
#
|
189
|
+
# * +path+ - The internal path of the asset.
|
190
|
+
#
|
191
|
+
def manifest(path)
|
192
|
+
@manifest[path]
|
176
193
|
end
|
177
194
|
|
178
195
|
##
|
@@ -188,23 +205,20 @@ class Darkroom
|
|
188
205
|
#
|
189
206
|
def dump(dir, clear: false, include_pristine: true)
|
190
207
|
dir = File.expand_path(dir)
|
191
|
-
written = Set.new
|
192
208
|
|
193
209
|
FileUtils.mkdir_p(dir)
|
194
210
|
Dir.each_child(dir) { |child| FileUtils.rm_rf(File.join(dir, child)) } if clear
|
195
211
|
|
196
|
-
@
|
212
|
+
@manifest_versioned.each do |path, asset|
|
197
213
|
next if asset.internal?
|
198
|
-
next if written.include?(asset.path)
|
199
214
|
next if @pristine.include?(asset.path) && !include_pristine
|
200
215
|
|
201
|
-
|
202
|
-
|
216
|
+
file_path = File.join(dir,
|
217
|
+
@pristine.include?(asset.path) ? asset.path_unversioned : path
|
218
|
+
)
|
203
219
|
|
204
220
|
FileUtils.mkdir_p(File.dirname(file_path))
|
205
221
|
File.write(file_path, asset.content)
|
206
|
-
|
207
|
-
written << asset.path
|
208
222
|
end
|
209
223
|
end
|
210
224
|
|
@@ -222,7 +236,8 @@ class Darkroom
|
|
222
236
|
"@minified_pattern=#{@minified_pattern.inspect}, "\
|
223
237
|
"@minify=#{@minify.inspect}, "\
|
224
238
|
"@prefix=#{@prefix.inspect}, "\
|
225
|
-
"@pristine=#{@pristine.inspect}"\
|
239
|
+
"@pristine=#{@pristine.inspect}, "\
|
240
|
+
"@process_key=#{@process_key.inspect}"\
|
226
241
|
'>'
|
227
242
|
end
|
228
243
|
end
|
@@ -24,7 +24,8 @@ class Darkroom
|
|
24
24
|
# Returns a string representation of the error.
|
25
25
|
#
|
26
26
|
def to_s
|
27
|
-
"Cannot #{@need} #{@extension} file(s): #{@library} library not available"
|
27
|
+
"Cannot #{@need} #{@extension} file(s): #{@library} library not available [hint: try adding "\
|
28
|
+
"gem('#{@library}') to your Gemfile]"
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
data/lib/darkroom/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: darkroom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Pickens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|