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
@@ -1,53 +1,55 @@
|
|
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
|
-
'Asset type must be text/javascript' unless asset.content_type == 'text/javascript'
|
21
|
-
elsif match[:tag] == 'img'
|
22
|
-
'Asset type must be image/svg+xml' unless asset.content_type == 'image/svg+xml'
|
23
|
-
else
|
24
|
-
"Cannot displace <#{match[:tag]}> tags"
|
25
|
-
end
|
26
|
-
end,
|
27
|
-
|
28
|
-
reference_content: ->(asset, match, format) do
|
29
|
-
case format
|
30
|
-
when 'displace'
|
31
|
-
if match[:tag] == 'link' && asset.content_type == 'text/css'
|
7
|
+
class HTMLDelegate < Delegate
|
8
|
+
REFERENCE_REGEX = /
|
9
|
+
<(?<tag>a|area|audio|base|embed|iframe|img|input|link|script|source|track|video)\s+[^>]*
|
10
|
+
(?<attr>href|src)=#{Asset::REFERENCE_REGEX.source}[^>]*>
|
11
|
+
/x.freeze
|
12
|
+
|
13
|
+
content_type('text/html')
|
14
|
+
|
15
|
+
reference(REFERENCE_REGEX) do |parse_data:, match:, asset:, format:|
|
16
|
+
case format
|
17
|
+
when 'displace'
|
18
|
+
case match[:tag]
|
19
|
+
when 'link'
|
20
|
+
if asset.content_type == 'text/css'
|
32
21
|
"<style>#{asset.content}</style>"
|
33
|
-
|
22
|
+
else
|
23
|
+
error('Asset content type must be text/css')
|
24
|
+
end
|
25
|
+
when 'script'
|
26
|
+
if asset.content_type == 'text/javascript'
|
34
27
|
offset = match.begin(0)
|
35
28
|
|
36
29
|
"#{match[0][0..(match.begin(:attr) - 2 - offset)]}"\
|
37
30
|
"#{match[0][(match.end(:quoted) + match[:quote].size - offset)..(match.end(0) - offset)]}"\
|
38
31
|
"#{asset.content}"
|
39
|
-
|
40
|
-
|
32
|
+
else
|
33
|
+
error('Asset content type must be text/javascript')
|
34
|
+
end
|
35
|
+
when 'img'
|
36
|
+
if asset.content_type == 'image/svg+xml'
|
37
|
+
asset.content(minified: false)
|
38
|
+
else
|
39
|
+
error('Asset content type must be image/svg+xml')
|
41
40
|
end
|
42
|
-
|
43
|
-
|
41
|
+
else
|
42
|
+
error("Cannot displace <#{match[:tag]}> tags")
|
43
|
+
end
|
44
|
+
when 'utf8'
|
45
|
+
quote = match[:quote] == '' ? Asset::DEFAULT_QUOTE : match[:quote]
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
+
content = asset.content.dup
|
48
|
+
content.gsub!('#', '%23')
|
49
|
+
content.gsub!(quote, quote == '"' ? """ : ''')
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
)
|
51
|
+
content
|
52
|
+
end
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
@@ -1,21 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative('../asset')
|
4
3
|
require_relative('html')
|
5
4
|
require_relative('javascript')
|
6
5
|
|
7
6
|
class Darkroom
|
8
|
-
class
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
class HTXDelegate < HTMLDelegate
|
8
|
+
compile(lib: 'htx', delegate: JavaScriptDelegate) do |parse_data:, path:, own_content:|
|
9
|
+
module_supported = false
|
10
|
+
|
11
|
+
if defined?(HTX::VERSION)
|
12
|
+
major, minor, patch = HTX::VERSION.split('.').map(&:to_i)
|
13
|
+
module_supported = major > 0 || minor > 1 || (minor == 1 && patch >= 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
if module_supported
|
17
|
+
HTX.compile(path, own_content, as_module: Darkroom.javascript_iife)
|
18
|
+
else
|
19
|
+
HTX.compile(path, own_content)
|
20
|
+
end
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
@@ -1,14 +1,187 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require('strscan')
|
3
4
|
require_relative('../asset')
|
5
|
+
require_relative('../delegate')
|
4
6
|
|
5
7
|
class Darkroom
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
class JavaScriptDelegate < Delegate
|
9
|
+
IDENTIFIER_REGEX = /[_$a-zA-Z][_$a-zA-Z0-9]*/.freeze
|
10
|
+
COMMA_REGEX = /,/.freeze
|
11
|
+
QUOTED_REGEX = /
|
12
|
+
(?<quoted>
|
13
|
+
(?<quote>['"])(
|
14
|
+
(?<=[^\\])\\(\\\\)*\k<quote> |
|
15
|
+
(?!\k<quote>).
|
16
|
+
)*\k<quote>
|
17
|
+
)
|
18
|
+
/x.freeze
|
19
|
+
|
20
|
+
content_type('text/javascript')
|
21
|
+
|
22
|
+
########################################################################################################
|
23
|
+
## Imports ##
|
24
|
+
########################################################################################################
|
25
|
+
|
26
|
+
IMPORT_REGEX = /
|
27
|
+
(?<=^|;|})[^\S\n]*
|
28
|
+
import\s+(
|
29
|
+
((?<default>#{IDENTIFIER_REGEX.source})(?:\s*,\s*|(?=\s+from\s+)))?
|
30
|
+
(
|
31
|
+
\*\s+as\s+(?<module>#{IDENTIFIER_REGEX.source}) |
|
32
|
+
\{(?<named>[\s\S]+?)\}
|
33
|
+
)?
|
34
|
+
\s+from\s+
|
35
|
+
)?#{Asset::QUOTED_PATH_REGEX.source}
|
36
|
+
[^\S\n]*;?[^\S\n]*(\n|\Z)
|
37
|
+
/x.freeze
|
38
|
+
|
39
|
+
IMPORT_ITEM_REGEX = /
|
40
|
+
\s*
|
41
|
+
(?<name>#{IDENTIFIER_REGEX.source}|#{QUOTED_REGEX.source}(?=\s+as\s+))
|
42
|
+
(\s+as\s+(?<alias>#{IDENTIFIER_REGEX.source}))?
|
43
|
+
\s*
|
44
|
+
/x.freeze
|
45
|
+
|
46
|
+
import(IMPORT_REGEX) do |parse_data:, match:, asset:|
|
47
|
+
items = []
|
48
|
+
items << [match[:default], '.default'] if match[:default]
|
49
|
+
items << [match[:module], ''] if match[:module]
|
50
|
+
|
51
|
+
if match[:named]
|
52
|
+
scanner = StringScanner.new(match[:named])
|
53
|
+
|
54
|
+
while scanner.scan(IMPORT_ITEM_REGEX)
|
55
|
+
items << [
|
56
|
+
scanner[:alias] || scanner[:name],
|
57
|
+
scanner[:quote] ? "[#{scanner[:name]}]" : ".#{scanner[:name]}"
|
58
|
+
]
|
59
|
+
|
60
|
+
break unless scanner.scan(COMMA_REGEX)
|
61
|
+
end
|
62
|
+
|
63
|
+
error('Invalid import statement') unless scanner.eos?
|
64
|
+
end
|
65
|
+
|
66
|
+
((parse_data[:imports] ||= {})[asset.path] ||= []).concat(items) unless items.empty?
|
67
|
+
|
68
|
+
''
|
69
|
+
end
|
70
|
+
|
71
|
+
########################################################################################################
|
72
|
+
## Exports ##
|
73
|
+
########################################################################################################
|
74
|
+
|
75
|
+
EXPORT_REGEX = /
|
76
|
+
(?<=^|;|})[^\S\n]*
|
77
|
+
export\s+(
|
78
|
+
(?<default>default\s+)?
|
79
|
+
(?<keep>(let|const|var|function\*?|class)\s+(?<name>#{IDENTIFIER_REGEX.source}))
|
80
|
+
|
|
81
|
+
\{(?<named>.+?)\}
|
82
|
+
[^\S\n]*;?[^\S\n]*(\n|\Z)
|
83
|
+
)
|
84
|
+
/x.freeze
|
85
|
+
|
86
|
+
EXPORT_ITEM_REGEX = /
|
87
|
+
\s*
|
88
|
+
(?<name>#{IDENTIFIER_REGEX.source})
|
89
|
+
(\s+as\s+(?<alias>#{IDENTIFIER_REGEX.source}|#{QUOTED_REGEX.source}))?
|
90
|
+
\s*
|
91
|
+
/x.freeze
|
92
|
+
|
93
|
+
parse(:export, EXPORT_REGEX) do |parse_data:, match:|
|
94
|
+
items = (parse_data[:exports] ||= [])
|
95
|
+
|
96
|
+
if match[:default]
|
97
|
+
items << ['default', match[:name]]
|
98
|
+
elsif match[:name]
|
99
|
+
items << [match[:name], match[:name]]
|
100
|
+
else
|
101
|
+
scanner = StringScanner.new(match[:named])
|
102
|
+
|
103
|
+
while scanner.scan(EXPORT_ITEM_REGEX)
|
104
|
+
items << [scanner[:alias] || scanner[:name], scanner[:name]]
|
105
|
+
|
106
|
+
break if scanner.eos?
|
107
|
+
break unless scanner.scan(COMMA_REGEX)
|
108
|
+
end
|
109
|
+
|
110
|
+
error('Invalid export statement') unless scanner.eos?
|
111
|
+
end
|
112
|
+
|
113
|
+
match[:keep]
|
114
|
+
end
|
115
|
+
|
116
|
+
########################################################################################################
|
117
|
+
## Compile ##
|
118
|
+
########################################################################################################
|
119
|
+
|
120
|
+
compile do |parse_data:, path:, own_content:|
|
121
|
+
next unless Darkroom.javascript_iife
|
122
|
+
|
123
|
+
(parse_data[:imports] || []).each do |import_path, items|
|
124
|
+
mod_suffix = nil
|
125
|
+
prefix = '{ ' if items.size != 1
|
126
|
+
suffix = ' }' if items.size != 1
|
127
|
+
|
128
|
+
begin
|
129
|
+
mod = "m#{mod_suffix}"
|
130
|
+
mod_suffix = (mod_suffix || 1) + 1
|
131
|
+
end while items.any? { |i| i.first == mod }
|
132
|
+
|
133
|
+
own_content.prepend(
|
134
|
+
"let #{items.map(&:first).join(', ')}; "\
|
135
|
+
"$import('#{import_path}', "\
|
136
|
+
"#{mod} => #{prefix}#{items.map { |(i, e)| "#{i} = #{mod}#{e}" }.join(', ')}#{suffix})\n"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
own_content.prepend("['#{path}', $import => {\n\n")
|
141
|
+
own_content << <<~EOS
|
142
|
+
|
143
|
+
return Object.seal({#{
|
144
|
+
if parse_data[:exports] && !parse_data[:exports].empty?
|
145
|
+
"\n#{parse_data[:exports].map { |k, v| " #{k}: #{v},\n" }.join}"
|
146
|
+
end
|
147
|
+
}})
|
148
|
+
|
149
|
+
}],
|
150
|
+
EOS
|
151
|
+
end
|
152
|
+
|
153
|
+
########################################################################################################
|
154
|
+
## Finalize ##
|
155
|
+
########################################################################################################
|
156
|
+
|
157
|
+
finalize do |parse_data:, path:, content:|
|
158
|
+
next unless Darkroom.javascript_iife
|
159
|
+
|
160
|
+
(content.frozen? ? content.dup : content).prepend(
|
161
|
+
<<~EOS
|
162
|
+
((...bundle) => {
|
163
|
+
const modules = {}
|
164
|
+
const setters = []
|
165
|
+
const $import = (name, setter) =>
|
166
|
+
modules[name] ? setter(modules[name]) : setters.push([setter, name])
|
167
|
+
|
168
|
+
for (const [name, def] of bundle)
|
169
|
+
modules[name] = def($import)
|
170
|
+
|
171
|
+
for (const [setter, name] of setters)
|
172
|
+
setter(modules[name])
|
173
|
+
})(
|
174
|
+
|
175
|
+
EOS
|
176
|
+
) << "\n)\n"
|
177
|
+
end
|
178
|
+
|
179
|
+
########################################################################################################
|
180
|
+
## Minify ##
|
181
|
+
########################################################################################################
|
182
|
+
|
183
|
+
minify(lib: 'terser') do |parse_data:, path:, content:|
|
184
|
+
Terser.compile(content)
|
185
|
+
end
|
13
186
|
end
|
14
187
|
end
|
@@ -10,10 +10,10 @@ class Darkroom
|
|
10
10
|
##
|
11
11
|
# Creates a new instance.
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
13
|
+
# [message] Description of the error.
|
14
|
+
# [detail] Additional detail about the error.
|
15
|
+
# [source_path] Path of the asset that contains the error.
|
16
|
+
# [source_line_num] Line number in the asset where the error is located.
|
17
17
|
#
|
18
18
|
def initialize(message, detail, source_path = nil, source_line_num = nil)
|
19
19
|
super(message)
|
@@ -11,9 +11,9 @@ class Darkroom
|
|
11
11
|
##
|
12
12
|
# Creates a new instance.
|
13
13
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# [path] Path of asset that doesn't exist.
|
15
|
+
# [source_path] Path of the asset that contains the error.
|
16
|
+
# [source_line_num] Line number in the asset where the error is located.
|
17
17
|
#
|
18
18
|
def initialize(path, source_path = nil, source_line_num = nil)
|
19
19
|
super('Asset not found', path, source_path, source_line_num)
|
@@ -10,9 +10,9 @@ class Darkroom
|
|
10
10
|
##
|
11
11
|
# Creates a new instance.
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# [snippet] Snippet showing the reference.
|
14
|
+
# [source_path] Path of the asset that contains the error.
|
15
|
+
# [source_line_num] Line number in the asset where the error is located.
|
16
16
|
#
|
17
17
|
def initialize(snippet, source_path, source_line_num)
|
18
18
|
super('Reference would result in a circular reference chain', snippet, source_path, source_line_num)
|
@@ -10,9 +10,9 @@ class Darkroom
|
|
10
10
|
##
|
11
11
|
# Creates a new instance.
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# [path] Path of the asset that exists under multiple load paths.
|
14
|
+
# [first_load_path] Load path where the asset was first found.
|
15
|
+
# [second_load_path] Load path where the asset was subsequently found.
|
16
16
|
#
|
17
17
|
def initialize(path, first_load_path, second_load_path)
|
18
18
|
@path = path
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative('../asset')
|
4
|
+
|
3
5
|
class Darkroom
|
4
6
|
##
|
5
7
|
# Error class used when an asset's path contains one or more invalid characters.
|
@@ -10,8 +12,8 @@ class Darkroom
|
|
10
12
|
##
|
11
13
|
# Creates a new instance.
|
12
14
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
+
# [path] Path of the asset with the invalid character(s).
|
16
|
+
# [index] Position of the first bad character in the path.
|
15
17
|
#
|
16
18
|
def initialize(path, index)
|
17
19
|
@path = path
|
@@ -22,7 +24,7 @@ class Darkroom
|
|
22
24
|
# Returns a string representation of the error.
|
23
25
|
#
|
24
26
|
def to_s
|
25
|
-
"Asset path contains one or more invalid characters (#{DISALLOWED_PATH_CHARS}): #{@path}"
|
27
|
+
"Asset path contains one or more invalid characters (#{Asset::DISALLOWED_PATH_CHARS}): #{@path}"
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -10,9 +10,9 @@ class Darkroom
|
|
10
10
|
##
|
11
11
|
# Creates a new instance.
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# [library] Name of the library that's missing.
|
14
|
+
# [need] Reason the library is needed ('pre-process', 'post-process', or 'minify').
|
15
|
+
# [extension] Extension of the type of asset that needs the library.
|
16
16
|
#
|
17
17
|
def initialize(library, need, extension)
|
18
18
|
@library = library
|
@@ -8,7 +8,7 @@ class Darkroom
|
|
8
8
|
##
|
9
9
|
# Creates a new instance.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# [errors] Error or array of errors.
|
12
12
|
#
|
13
13
|
def initialize(errors)
|
14
14
|
@errors = Array(errors)
|
@@ -18,7 +18,11 @@ class Darkroom
|
|
18
18
|
# Returns a string representation of the error.
|
19
19
|
#
|
20
20
|
def to_s
|
21
|
-
|
21
|
+
lines = @errors.map do |error|
|
22
|
+
error.kind_of?(AssetError) ? error.to_s : "#{error.inspect} at #{error.backtrace[0]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
"Errors were encountered while processing assets:\n #{lines.join("\n ")}"
|
22
26
|
end
|
23
27
|
|
24
28
|
##
|
@@ -10,9 +10,9 @@ class Darkroom
|
|
10
10
|
##
|
11
11
|
# Creates a new instance.
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# [file] File with the unrecognized extension.
|
14
|
+
# [source_path] Path of the asset that contains the error.
|
15
|
+
# [source_line_num] Line number in the asset where the error is located.
|
16
16
|
#
|
17
17
|
def initialize(file, source_path = nil, source_line_num = nil)
|
18
18
|
super('File extension not recognized', file, source_path, source_line_num)
|
data/lib/darkroom/version.rb
CHANGED
data/lib/darkroom.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require('darkroom/asset')
|
4
4
|
require('darkroom/darkroom')
|
5
|
+
require('darkroom/delegate')
|
5
6
|
require('darkroom/version')
|
6
7
|
|
7
8
|
require('darkroom/delegates/css')
|
@@ -9,12 +10,12 @@ require('darkroom/delegates/html')
|
|
9
10
|
require('darkroom/delegates/htx')
|
10
11
|
require('darkroom/delegates/javascript')
|
11
12
|
|
12
|
-
Darkroom.register('.css', Darkroom::
|
13
|
-
Darkroom.register('.htm', '.html', Darkroom::
|
14
|
-
Darkroom.register('.htx', Darkroom::
|
13
|
+
Darkroom.register('.css', Darkroom::CSSDelegate)
|
14
|
+
Darkroom.register('.htm', '.html', Darkroom::HTMLDelegate)
|
15
|
+
Darkroom.register('.htx', Darkroom::HTXDelegate)
|
15
16
|
Darkroom.register('.ico', 'image/x-icon')
|
16
17
|
Darkroom.register('.jpg', '.jpeg', 'image/jpeg')
|
17
|
-
Darkroom.register('.js', Darkroom::
|
18
|
+
Darkroom.register('.js', Darkroom::JavaScriptDelegate)
|
18
19
|
Darkroom.register('.json', 'application/json')
|
19
20
|
Darkroom.register('.png', 'image/png')
|
20
21
|
Darkroom.register('.svg', 'image/svg+xml')
|
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Pickens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/darkroom.rb
|
60
60
|
- lib/darkroom/asset.rb
|
61
61
|
- lib/darkroom/darkroom.rb
|
62
|
+
- lib/darkroom/delegate.rb
|
62
63
|
- lib/darkroom/delegates/css.rb
|
63
64
|
- lib/darkroom/delegates/html.rb
|
64
65
|
- lib/darkroom/delegates/htx.rb
|
@@ -94,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
95
|
- !ruby/object:Gem::Version
|
95
96
|
version: '0'
|
96
97
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
98
|
+
rubygems_version: 3.4.15
|
98
99
|
signing_key:
|
99
100
|
specification_version: 4
|
100
101
|
summary: A fast, lightweight, and straightforward web asset management library.
|