darkroom 0.0.1 → 0.0.2
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/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
|