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.
@@ -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 Asset
7
- HTMLDelegate = Delegate.new(
8
- content_type: 'text/html',
9
- reference_regex: %r{
10
- <(?<tag>a|area|audio|base|embed|iframe|img|input|link|script|source|track|video)\s+[^>]*
11
- (?<attr>href|src)=#{REFERENCE_PATH.source}[^>]*>
12
- }x.freeze,
13
-
14
- validate_reference: ->(asset, match, format) do
15
- return unless format == 'displace'
16
-
17
- if match[:tag] == 'link'
18
- 'Asset type must be text/css' unless asset.content_type == 'text/css'
19
- elsif match[:tag] == 'script'
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
- elsif match[:tag] == 'script' && asset.content_type == 'text/javascript'
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
- elsif match[:tag] == 'img' && asset.content_type == 'image/svg+xml'
40
- asset.content
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
- when 'utf8'
43
- quote = match[:quote] == '' ? Asset::DEFAULT_QUOTE : match[:quote]
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
- content = asset.content.gsub('#', '%23')
46
- content.gsub!(quote, quote == "'" ? '"' : "'")
47
+ content = asset.content.dup
48
+ content.gsub!('#', '%23')
49
+ content.gsub!(quote, quote == '"' ? "&#34;" : '&#39;')
47
50
 
48
- content
49
- end
50
- end,
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 Asset
9
- HTXDelegate = Delegate.new(
10
- content_type: JavaScriptDelegate.content_type,
11
- import_regex: HTMLDelegate.import_regex,
12
- reference_regex: HTMLDelegate.reference_regex,
13
- validate_reference: HTMLDelegate.validate_reference,
14
- reference_content: HTMLDelegate.reference_content,
15
- compile_lib: 'htx',
16
- compile: ->(path, content) { HTX.compile(path, content) },
17
- minify_lib: JavaScriptDelegate.minify_lib,
18
- minify: JavaScriptDelegate.minify,
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 Asset
7
- JavaScriptDelegate = Delegate.new(
8
- content_type: 'text/javascript',
9
- import_regex: /^ *import +#{QUOTED_PATH.source} *;? *(\n|$)/.freeze,
10
- minify_lib: 'terser',
11
- minify: ->(content) { Terser.compile(content) },
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
- # * +message+ - Description of the error.
14
- # * +detail+ - Additional detail about the error.
15
- # * +source_path+ - Path of the asset that contains the error (optional).
16
- # * +source_line_num+ - Line number in the asset where the error is located (optional).
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
- # * +path+ - Path of asset that doesn't exist.
15
- # * +source_path+ - Path of the asset that contains the error (optional).
16
- # * +source_line_num+ - Line number in the asset where the error is located (optional).
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
- # * +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.
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
- # * +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.
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
- # * +path+ - Path of the asset with the invalid character(s).
14
- # * +index+ - Position of the first bad character in the path.
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
- # * +library+ - Name of the library that's missing.
14
- # * +need+ - Reason the library is needed ('compile' or 'minify').
15
- # * +extension+ - Extension of the type of asset that needs the library.
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
- # * +errors+ - Error or array of errors.
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
- "Errors were encountered while processing assets:\n #{@errors.map(&:to_s).join("\n ")}"
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
- # * +file+ - File with the unrecognized extension.
14
- # * +source_path+ - Path of the asset that contains the error (optional).
15
- # * +source_line_num+ - Line number in the asset where the error is located (optional).
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Darkroom
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.7'
5
5
  end
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::Asset::CSSDelegate)
13
- Darkroom.register('.htm', '.html', Darkroom::Asset::HTMLDelegate)
14
- Darkroom.register('.htx', Darkroom::Asset::HTXDelegate)
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::Asset::JavaScriptDelegate)
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.6
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: 2022-05-03 00:00:00.000000000 Z
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.2.32
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.