klenod-build 0.0.1
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 +7 -0
- data/README.md +29 -0
- data/lib/klenod/build/asset.rb +235 -0
- data/lib/klenod/build/asset_compression.rb +58 -0
- data/lib/klenod/build/asset_generation_queue.rb +50 -0
- data/lib/klenod/build/cli/application.rb +108 -0
- data/lib/klenod/build/cli.rb +3 -0
- data/lib/klenod/build/config.rb +142 -0
- data/lib/klenod/build/context.rb +370 -0
- data/lib/klenod/build/dependency.rb +21 -0
- data/lib/klenod/build/errors.rb +175 -0
- data/lib/klenod/build/filesystem_resolver.rb +147 -0
- data/lib/klenod/build/graph/invalidator.rb +246 -0
- data/lib/klenod/build/graph.rb +864 -0
- data/lib/klenod/build/graphviz.rb +282 -0
- data/lib/klenod/build/hashing.rb +21 -0
- data/lib/klenod/build/invalidation_result.rb +55 -0
- data/lib/klenod/build/load_result.rb +7 -0
- data/lib/klenod/build/loaded_module.rb +38 -0
- data/lib/klenod/build/module_id.rb +100 -0
- data/lib/klenod/build/module_record.rb +22 -0
- data/lib/klenod/build/plugin.rb +35 -0
- data/lib/klenod/build/plugins/class_names_runtime.rb +125 -0
- data/lib/klenod/build/plugins/component_defaults.rb +12 -0
- data/lib/klenod/build/plugins/data_plugin.rb +117 -0
- data/lib/klenod/build/plugins/gem_import_plugin.rb +125 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.json +17687 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.rb +105 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.txt +31 -0
- data/lib/klenod/build/plugins/google_fonts_plugin.rb +387 -0
- data/lib/klenod/build/plugins/haml_plugin/companions.rb +40 -0
- data/lib/klenod/build/plugins/haml_plugin/errors.rb +114 -0
- data/lib/klenod/build/plugins/haml_plugin/helper_source.rb +123 -0
- data/lib/klenod/build/plugins/haml_plugin/parser.rb +85 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +1039 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +766 -0
- data/lib/klenod/build/plugins/haml_plugin.rb +369 -0
- data/lib/klenod/build/plugins/image_plugin.rb +401 -0
- data/lib/klenod/build/plugins/intl_plugin.rb +34 -0
- data/lib/klenod/build/plugins/markdown_compiler.rb +180 -0
- data/lib/klenod/build/plugins/markdown_plugin.rb +161 -0
- data/lib/klenod/build/plugins/router_plugin.rb +942 -0
- data/lib/klenod/build/plugins/ruby_plugin.rb +34 -0
- data/lib/klenod/build/plugins/svg_plugin.rb +197 -0
- data/lib/klenod/build/plugins.rb +22 -0
- data/lib/klenod/build/profiler.rb +79 -0
- data/lib/klenod/build/resolution_error_formatter.rb +42 -0
- data/lib/klenod/build/resolver.rb +95 -0
- data/lib/klenod/build/ruby_import_rewriter.rb +436 -0
- data/lib/klenod/build/source_map/editor.rb +110 -0
- data/lib/klenod/build/source_map/map.rb +168 -0
- data/lib/klenod/build/source_map/vlq.rb +68 -0
- data/lib/klenod/build/source_map.rb +12 -0
- data/lib/klenod/build/transform_result.rb +12 -0
- data/lib/klenod/build/version.rb +7 -0
- data/lib/klenod/build/watched_pattern.rb +11 -0
- data/lib/klenod/build/watcher.rb +141 -0
- data/lib/klenod/build.rb +15 -0
- metadata +310 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "klenod/runtime"
|
|
4
|
+
|
|
5
|
+
module Klenod
|
|
6
|
+
module Build
|
|
7
|
+
class Graphviz
|
|
8
|
+
MODULE_STYLES = {
|
|
9
|
+
".rb" => {fillcolor: "#d7ecff", color: "#3a77b5"},
|
|
10
|
+
".haml" => {fillcolor: "#f4dcff", color: "#9b52b8"},
|
|
11
|
+
".css" => {fillcolor: "#dcfce7", color: "#31a46a"},
|
|
12
|
+
".json" => {fillcolor: "#fff3bf", color: "#c28f00"},
|
|
13
|
+
".toml" => {fillcolor: "#fff3bf", color: "#c28f00"},
|
|
14
|
+
".yaml" => {fillcolor: "#fff3bf", color: "#c28f00"},
|
|
15
|
+
".yml" => {fillcolor: "#fff3bf", color: "#c28f00"}
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
VIRTUAL_STYLE = {fillcolor: "#eceff4", color: "#667085"}.freeze
|
|
19
|
+
ASSET_STYLE = {fillcolor: "#ffe4d6", color: "#e26d39"}.freeze
|
|
20
|
+
DEFAULT_MODULE_STYLE = {fillcolor: "#f7f7f7", color: "#8a8a8a"}.freeze
|
|
21
|
+
|
|
22
|
+
def self.call(bundle, include_assets: true)
|
|
23
|
+
new(bundle, include_assets: include_assets).to_dot
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(bundle, include_assets: true)
|
|
27
|
+
@bundle = bundle
|
|
28
|
+
@include_assets = include_assets
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_dot
|
|
32
|
+
lines = [
|
|
33
|
+
"digraph klenod {",
|
|
34
|
+
" graph [rankdir=LR, bgcolor=\"transparent\", pad=\"0.4\", nodesep=\"0.45\", ranksep=\"0.8\"];",
|
|
35
|
+
" node [shape=box, style=\"rounded,filled\", fontname=\"Menlo\", fontsize=10, margin=\"0.08,0.05\"];",
|
|
36
|
+
" edge [fontname=\"Menlo\", fontsize=9, color=\"#667085\", arrowsize=0.7];"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
module_ids.each { |id| lines << module_node(id) }
|
|
40
|
+
lines.concat(asset_nodes) if @include_assets
|
|
41
|
+
lines.concat(import_edges)
|
|
42
|
+
lines.concat(asset_edges) if @include_assets
|
|
43
|
+
lines << "}"
|
|
44
|
+
"#{lines.join("\n")}\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def module_ids
|
|
50
|
+
@bundle.modules.keys.sort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def module_node(id)
|
|
54
|
+
spec = @bundle.modules.fetch(id)
|
|
55
|
+
style = module_style(id)
|
|
56
|
+
attributes = {
|
|
57
|
+
label: module_label(spec),
|
|
58
|
+
id: svg_node_id(id),
|
|
59
|
+
class: module_classes(id),
|
|
60
|
+
fillcolor: style.fetch(:fillcolor),
|
|
61
|
+
color: style.fetch(:color),
|
|
62
|
+
penwidth: entrypoint?(id) ? "2.2" : "1.2"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
" #{node_id(id)} [#{dot_attributes(attributes)}];"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def asset_nodes
|
|
69
|
+
@bundle.assets.values.sort_by(&:output_path).map do |asset|
|
|
70
|
+
" #{asset_node_id(asset)} [#{dot_attributes(asset_attributes(asset))}];"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def import_edges
|
|
75
|
+
module_ids.flat_map do |id|
|
|
76
|
+
@bundle.modules.fetch(id).imports.values.map do |import|
|
|
77
|
+
import = import_spec(import)
|
|
78
|
+
next unless @bundle.modules.key?(import.target_id)
|
|
79
|
+
|
|
80
|
+
attributes = {
|
|
81
|
+
id: svg_edge_id(id, import.target_id),
|
|
82
|
+
class: edge_classes(import),
|
|
83
|
+
color: import.eager ? "#475467" : "#7a5af8",
|
|
84
|
+
style: import.eager ? "solid" : "dashed"
|
|
85
|
+
}
|
|
86
|
+
attributes[:label] = "lazy" unless import.eager
|
|
87
|
+
|
|
88
|
+
" #{node_id(id)} -> #{node_id(import.target_id)} [#{dot_attributes(attributes)}];"
|
|
89
|
+
end
|
|
90
|
+
end.compact
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def asset_edges
|
|
94
|
+
@bundle.assets.values.sort_by(&:output_path).filter_map do |asset|
|
|
95
|
+
owner = asset_owner(asset)
|
|
96
|
+
next unless owner
|
|
97
|
+
|
|
98
|
+
attributes = {
|
|
99
|
+
id: svg_edge_id(owner_node_name(owner), asset.output_path),
|
|
100
|
+
class: "edge edge-asset",
|
|
101
|
+
color: "#e26d39",
|
|
102
|
+
style: "dotted",
|
|
103
|
+
label: "asset"
|
|
104
|
+
}
|
|
105
|
+
" #{owner_node_id(owner)} -> #{asset_node_id(asset)} [#{dot_attributes(attributes)}];"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def asset_owner(asset)
|
|
110
|
+
return [:module, asset.logical_name] if @bundle.modules.key?(asset.logical_name)
|
|
111
|
+
|
|
112
|
+
google_fonts_css_asset_owner(asset) || query_module_id_for(asset.logical_name)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def google_fonts_css_asset_owner(asset)
|
|
116
|
+
return nil unless asset.metadata[:google_fonts] && asset.metadata[:type] == :font
|
|
117
|
+
|
|
118
|
+
css_asset = @bundle.assets.values.find do |candidate|
|
|
119
|
+
candidate.metadata[:google_fonts] &&
|
|
120
|
+
candidate.metadata[:type] == :css &&
|
|
121
|
+
Array(candidate.metadata[:font_source_urls]).include?(google_fonts_source_url(asset))
|
|
122
|
+
end
|
|
123
|
+
[:asset, css_asset] if css_asset
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def google_fonts_source_url(asset)
|
|
127
|
+
asset.metadata[:source_url]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def query_module_id_for(logical_name)
|
|
131
|
+
module_id = query_module_ids_by_path.fetch(logical_name, nil)
|
|
132
|
+
[:module, module_id] if module_id
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def query_module_ids_by_path
|
|
136
|
+
@query_module_ids_by_path ||=
|
|
137
|
+
module_ids.each_with_object({}) do |id, index|
|
|
138
|
+
path, query = id.split("?", 2)
|
|
139
|
+
next unless query
|
|
140
|
+
|
|
141
|
+
index[path] ||= id
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def owner_node_id(owner)
|
|
146
|
+
type, value = owner
|
|
147
|
+
case type
|
|
148
|
+
when :module then node_id(value)
|
|
149
|
+
when :asset then asset_node_id(value)
|
|
150
|
+
else raise ArgumentError, "unknown graph owner: #{owner.inspect}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def owner_node_name(owner)
|
|
155
|
+
type, value = owner
|
|
156
|
+
case type
|
|
157
|
+
when :module then value
|
|
158
|
+
when :asset then value.output_path
|
|
159
|
+
else raise ArgumentError, "unknown graph owner: #{owner.inspect}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def import_spec(import)
|
|
164
|
+
case import
|
|
165
|
+
when Runtime::ImportSpec
|
|
166
|
+
import
|
|
167
|
+
else
|
|
168
|
+
Runtime::ImportSpec.new(import.to_s, nil, true)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def module_label(spec)
|
|
173
|
+
label = display_path(spec.source_path || spec.id)
|
|
174
|
+
type = module_type(spec.id)
|
|
175
|
+
entrypoint?(spec.id) ? "#{label}\n#{type} entrypoint" : "#{label}\n#{type}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def asset_attributes(asset)
|
|
179
|
+
ASSET_STYLE.merge(
|
|
180
|
+
label: "#{display_path(asset.output_path)}\n#{asset.content_type || "asset"}",
|
|
181
|
+
id: svg_node_id(asset.output_path),
|
|
182
|
+
class: asset_classes(asset),
|
|
183
|
+
shape: "note"
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def module_style(id)
|
|
188
|
+
return VIRTUAL_STYLE if id.start_with?("virtual:")
|
|
189
|
+
|
|
190
|
+
MODULE_STYLES.fetch(File.extname(id), DEFAULT_MODULE_STYLE)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def module_type(id)
|
|
194
|
+
return "virtual" if id.start_with?("virtual:")
|
|
195
|
+
|
|
196
|
+
ext = File.extname(id)
|
|
197
|
+
ext.empty? ? "module" : ext.delete_prefix(".")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def module_classes(id)
|
|
201
|
+
[
|
|
202
|
+
"node",
|
|
203
|
+
"node-module",
|
|
204
|
+
"module-#{css_identifier(module_type(id))}",
|
|
205
|
+
("entrypoint" if entrypoint?(id))
|
|
206
|
+
].compact.join(" ")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def asset_classes(asset)
|
|
210
|
+
[
|
|
211
|
+
"node",
|
|
212
|
+
"node-asset",
|
|
213
|
+
"asset-#{css_identifier(asset.metadata[:type] || "asset")}"
|
|
214
|
+
].join(" ")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def edge_classes(import)
|
|
218
|
+
["edge", import.eager ? "edge-import" : "edge-import edge-lazy"].join(" ")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def entrypoint?(id)
|
|
222
|
+
entrypoint_ids.include?(id)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def entrypoint_ids
|
|
226
|
+
@entrypoint_ids ||=
|
|
227
|
+
if @bundle.entrypoints.respond_to?(:values)
|
|
228
|
+
@bundle.entrypoints.values
|
|
229
|
+
else
|
|
230
|
+
@bundle.entrypoints
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def display_path(path)
|
|
235
|
+
path = path.to_s
|
|
236
|
+
return path unless @bundle.source_root
|
|
237
|
+
|
|
238
|
+
root = File.expand_path(@bundle.source_root)
|
|
239
|
+
expanded = File.expand_path(path)
|
|
240
|
+
return expanded.delete_prefix("#{root}/") if expanded.start_with?("#{root}/")
|
|
241
|
+
|
|
242
|
+
path
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def node_id(id)
|
|
246
|
+
"mod_#{dot_identifier(id)}"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def asset_node_id(asset)
|
|
250
|
+
"asset_#{dot_identifier(asset.output_path)}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def svg_node_id(id)
|
|
254
|
+
"node-#{dot_identifier(id)}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def svg_edge_id(from, to)
|
|
258
|
+
"edge-#{dot_identifier(from)}-#{dot_identifier(to)}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def css_identifier(value)
|
|
262
|
+
value.to_s.gsub(/[^A-Za-z0-9_-]+/, "-").gsub(/\A-+|-+\z/, "")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def dot_identifier(value)
|
|
266
|
+
value.to_s.bytes.map { |byte| byte.to_s(16).rjust(2, "0") }.join
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def dot_attributes(attributes)
|
|
270
|
+
attributes.map { |key, value| "#{key}=#{dot_value(value)}" }.join(", ")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def dot_value(value)
|
|
274
|
+
%("#{dot_escape(value)}")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def dot_escape(value)
|
|
278
|
+
value.to_s.gsub(/[\\"]/) { |char| "\\#{char}" }.gsub("\n", "\\n")
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Klenod
|
|
6
|
+
module Build
|
|
7
|
+
module Hashing
|
|
8
|
+
def self.hexdigest(value)
|
|
9
|
+
Digest::SHA256.hexdigest(value)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.file_hexdigest(path)
|
|
13
|
+
Digest::SHA256.file(path.to_s).hexdigest
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.short(value, length: 16)
|
|
17
|
+
hexdigest(value)[0, length]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
AssetUpdate = Data.define(:output_path, :previous_asset, :current_asset) do
|
|
6
|
+
def added?
|
|
7
|
+
previous_asset.nil? && !current_asset.nil?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def changed?
|
|
11
|
+
!previous_asset.nil? && !current_asset.nil?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def removed?
|
|
15
|
+
!previous_asset.nil? && current_asset.nil?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
AssetChanges = Data.define(:added, :changed, :removed) do
|
|
20
|
+
def empty?
|
|
21
|
+
added.empty? && changed.empty? && removed.empty?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def paths
|
|
25
|
+
(added + changed + removed).uniq
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
InvalidationResult =
|
|
30
|
+
Data.define(
|
|
31
|
+
:changed_module_ids,
|
|
32
|
+
:removed_module_ids,
|
|
33
|
+
:reloaded_module_ids,
|
|
34
|
+
:reevaluated_module_ids,
|
|
35
|
+
:added_assets,
|
|
36
|
+
:changed_assets,
|
|
37
|
+
:removed_assets,
|
|
38
|
+
:asset_updates,
|
|
39
|
+
:errors
|
|
40
|
+
) do
|
|
41
|
+
def asset_changes
|
|
42
|
+
AssetChanges.new(added_assets, changed_assets, removed_assets)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def empty?
|
|
46
|
+
changed_module_ids.empty? &&
|
|
47
|
+
removed_module_ids.empty? &&
|
|
48
|
+
reloaded_module_ids.empty? &&
|
|
49
|
+
reevaluated_module_ids.empty? &&
|
|
50
|
+
asset_changes.empty? &&
|
|
51
|
+
errors.empty?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
class LoadedModule
|
|
6
|
+
attr_reader :context, :id
|
|
7
|
+
|
|
8
|
+
def initialize(context, id)
|
|
9
|
+
@context = context
|
|
10
|
+
@id = id
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def record
|
|
14
|
+
context.graph.records.fetch(id)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def exports
|
|
18
|
+
context.exports(id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def evaluated?
|
|
22
|
+
context.evaluated?(id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call(...)
|
|
26
|
+
exports.call(...)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def assets(type: nil, content_type: nil, recursive: true)
|
|
30
|
+
context.assets_for_module(id, type: type, content_type: content_type, recursive: recursive)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
id.to_s
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "protocol/url"
|
|
4
|
+
|
|
5
|
+
module Klenod
|
|
6
|
+
module Build
|
|
7
|
+
class ModuleId
|
|
8
|
+
SCHEME_PATTERN = /\A[A-Za-z][A-Za-z0-9+.-]*:/
|
|
9
|
+
|
|
10
|
+
def self.parse(value)
|
|
11
|
+
new(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(value, query = nil)
|
|
15
|
+
@url = absolute_url_for(value.to_s)
|
|
16
|
+
@url = @url.with(query: query) if query
|
|
17
|
+
|
|
18
|
+
@scheme = @url.scheme.to_sym
|
|
19
|
+
@host = @url.authority
|
|
20
|
+
@uri_path = absolute_uri_path(@url.path)
|
|
21
|
+
@query = @url.query
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :scheme, :host, :uri_path, :query
|
|
25
|
+
|
|
26
|
+
def merge(specifier)
|
|
27
|
+
specifier = specifier.to_s
|
|
28
|
+
return self.class.parse(specifier) if explicit_scheme?(specifier)
|
|
29
|
+
|
|
30
|
+
self.class.parse((@url + Protocol::URL[specifier]).to_s)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
@url.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def path
|
|
38
|
+
case scheme
|
|
39
|
+
when :app
|
|
40
|
+
relative_path
|
|
41
|
+
when :virtual
|
|
42
|
+
"virtual:#{relative_path}"
|
|
43
|
+
else
|
|
44
|
+
host ? "#{scheme}://#{host}#{uri_path}" : "#{scheme}:#{relative_path}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def relative_path
|
|
49
|
+
uri_path.delete_prefix("/")
|
|
50
|
+
end
|
|
51
|
+
alias_method :bare_path, :relative_path
|
|
52
|
+
|
|
53
|
+
def dirname
|
|
54
|
+
File.dirname(relative_path)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def extname
|
|
58
|
+
File.extname(uri_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def eql?(other)
|
|
62
|
+
other.is_a?(self.class) && to_s == other.to_s
|
|
63
|
+
end
|
|
64
|
+
alias_method :==, :eql?
|
|
65
|
+
|
|
66
|
+
def hash
|
|
67
|
+
to_s.hash
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def absolute_url_for(value)
|
|
73
|
+
parsed = Protocol::URL[value]
|
|
74
|
+
return absolute_url(parsed) if parsed.respond_to?(:scheme) && parsed.scheme
|
|
75
|
+
|
|
76
|
+
Protocol::URL::Absolute.new("app", nil, absolute_uri_path(parsed.path), parsed.query, parsed.fragment)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def absolute_url(parsed)
|
|
80
|
+
Protocol::URL::Absolute.new(
|
|
81
|
+
parsed.scheme,
|
|
82
|
+
parsed.authority,
|
|
83
|
+
absolute_uri_path(parsed.path),
|
|
84
|
+
parsed.query,
|
|
85
|
+
parsed.fragment
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def absolute_uri_path(path)
|
|
90
|
+
path = path.to_s
|
|
91
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
92
|
+
path
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def explicit_scheme?(value)
|
|
96
|
+
value.match?(SCHEME_PATTERN)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
ModuleRecord =
|
|
6
|
+
Data.define(
|
|
7
|
+
:id,
|
|
8
|
+
:source_hash,
|
|
9
|
+
:transformed_hash,
|
|
10
|
+
:dependencies,
|
|
11
|
+
:resolved_dependencies,
|
|
12
|
+
:source,
|
|
13
|
+
:transformed_source,
|
|
14
|
+
:source_map,
|
|
15
|
+
:assets,
|
|
16
|
+
:watched_patterns,
|
|
17
|
+
:metadata,
|
|
18
|
+
:version,
|
|
19
|
+
:status
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Klenod
|
|
4
|
+
module Build
|
|
5
|
+
class Plugin
|
|
6
|
+
def resolve(_dependency, _context)
|
|
7
|
+
nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def load(_module_id, _context)
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def transform(_module_id, code, _context)
|
|
15
|
+
TransformResult.identity(code)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def finalize(_module_id, result, _resolved_dependencies, _dependency_records, _context)
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def import_value(_resolved_dependency, _record, _context)
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def runtime_import_value(_resolved_dependency, _record, _context)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def invalidate_module_ids(_paths, _context)
|
|
31
|
+
[]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../dependency"
|
|
4
|
+
require_relative "../load_result"
|
|
5
|
+
require_relative "../module_id"
|
|
6
|
+
require_relative "../transform_result"
|
|
7
|
+
|
|
8
|
+
module Klenod
|
|
9
|
+
module Build
|
|
10
|
+
module Plugins
|
|
11
|
+
module ClassNamesRuntime
|
|
12
|
+
CLASS_NAMES_SPECIFIER = "virtual:klenod/class_names"
|
|
13
|
+
CLASS_NAMES_MODULE_ID = ModuleId.new("virtual:klenod/class_names.rb", nil)
|
|
14
|
+
|
|
15
|
+
def resolve_class_names_runtime(dependency)
|
|
16
|
+
return nil unless dependency.specifier == CLASS_NAMES_SPECIFIER
|
|
17
|
+
|
|
18
|
+
ResolvedDependency.new(dependency, CLASS_NAMES_MODULE_ID, {virtual: true})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_class_names_runtime(module_id)
|
|
22
|
+
return nil unless module_id.scheme == :virtual && module_id == CLASS_NAMES_MODULE_ID
|
|
23
|
+
|
|
24
|
+
source = class_names_runtime_source
|
|
25
|
+
LoadResult.new(source, nil, TransformResult.new(source, [], nil, [], [], {}))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def class_names_runtime_dependency(module_id)
|
|
29
|
+
Dependency
|
|
30
|
+
.create(
|
|
31
|
+
specifier: CLASS_NAMES_SPECIFIER,
|
|
32
|
+
importer_id: module_id,
|
|
33
|
+
kind: :class_names_runtime
|
|
34
|
+
)
|
|
35
|
+
.with(id: CLASS_NAMES_SPECIFIER)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def class_names_runtime_import_value(resolved_dependency, record, context)
|
|
39
|
+
return nil unless resolved_dependency.dependency.kind == :class_names_runtime
|
|
40
|
+
|
|
41
|
+
context.mods.fetch(record.id).const_get(:Exports)::Default
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def class_names_runtime_runtime_import_value(resolved_dependency, _record)
|
|
45
|
+
return nil unless resolved_dependency.dependency.kind == :class_names_runtime
|
|
46
|
+
|
|
47
|
+
Runtime::DefaultImport.new(:Default)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def class_names_runtime_source
|
|
51
|
+
<<~RUBY
|
|
52
|
+
# frozen_string_literal: true
|
|
53
|
+
|
|
54
|
+
class ClassNames
|
|
55
|
+
def self.merge(*sources)
|
|
56
|
+
classes = sources.each_with_object({}) do |source, result|
|
|
57
|
+
source.each_pair do |name, class_name|
|
|
58
|
+
result[name] = [result[name], class_name].compact.join(" ")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
new(classes.freeze)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def initialize(classes)
|
|
66
|
+
@classes = classes
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def [](first = nil, *rest)
|
|
70
|
+
return @classes[first] if rest.empty? && first.is_a?(Symbol)
|
|
71
|
+
|
|
72
|
+
class_name(first, *rest)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fetch(...)
|
|
76
|
+
@classes.fetch(...)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def key?(...)
|
|
80
|
+
@classes.key?(...)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def keys
|
|
84
|
+
@classes.keys
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def each_pair(...)
|
|
88
|
+
@classes.each_pair(...)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def class_name(*values)
|
|
92
|
+
classes = []
|
|
93
|
+
values.each { |value| collect_class_names(classes, value) }
|
|
94
|
+
return nil if classes.empty?
|
|
95
|
+
|
|
96
|
+
classes.join(" ")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def collect_class_names(classes, value)
|
|
102
|
+
case value
|
|
103
|
+
when nil, false
|
|
104
|
+
nil
|
|
105
|
+
when Symbol
|
|
106
|
+
collect_class_names(classes, @classes[value])
|
|
107
|
+
when String
|
|
108
|
+
value.split.each { |class_name| classes << class_name }
|
|
109
|
+
when Array
|
|
110
|
+
value.each { |item| collect_class_names(classes, item) }
|
|
111
|
+
when Hash
|
|
112
|
+
value.each { |class_name, enabled| collect_class_names(classes, class_name) if enabled }
|
|
113
|
+
else
|
|
114
|
+
value.to_s.split.each { |class_name| classes << class_name }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
Default = ClassNames
|
|
120
|
+
RUBY
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|