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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +29 -0
  3. data/lib/klenod/build/asset.rb +235 -0
  4. data/lib/klenod/build/asset_compression.rb +58 -0
  5. data/lib/klenod/build/asset_generation_queue.rb +50 -0
  6. data/lib/klenod/build/cli/application.rb +108 -0
  7. data/lib/klenod/build/cli.rb +3 -0
  8. data/lib/klenod/build/config.rb +142 -0
  9. data/lib/klenod/build/context.rb +370 -0
  10. data/lib/klenod/build/dependency.rb +21 -0
  11. data/lib/klenod/build/errors.rb +175 -0
  12. data/lib/klenod/build/filesystem_resolver.rb +147 -0
  13. data/lib/klenod/build/graph/invalidator.rb +246 -0
  14. data/lib/klenod/build/graph.rb +864 -0
  15. data/lib/klenod/build/graphviz.rb +282 -0
  16. data/lib/klenod/build/hashing.rb +21 -0
  17. data/lib/klenod/build/invalidation_result.rb +55 -0
  18. data/lib/klenod/build/load_result.rb +7 -0
  19. data/lib/klenod/build/loaded_module.rb +38 -0
  20. data/lib/klenod/build/module_id.rb +100 -0
  21. data/lib/klenod/build/module_record.rb +22 -0
  22. data/lib/klenod/build/plugin.rb +35 -0
  23. data/lib/klenod/build/plugins/class_names_runtime.rb +125 -0
  24. data/lib/klenod/build/plugins/component_defaults.rb +12 -0
  25. data/lib/klenod/build/plugins/data_plugin.rb +117 -0
  26. data/lib/klenod/build/plugins/gem_import_plugin.rb +125 -0
  27. data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.json +17687 -0
  28. data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.rb +105 -0
  29. data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.txt +31 -0
  30. data/lib/klenod/build/plugins/google_fonts_plugin.rb +387 -0
  31. data/lib/klenod/build/plugins/haml_plugin/companions.rb +40 -0
  32. data/lib/klenod/build/plugins/haml_plugin/errors.rb +114 -0
  33. data/lib/klenod/build/plugins/haml_plugin/helper_source.rb +123 -0
  34. data/lib/klenod/build/plugins/haml_plugin/parser.rb +85 -0
  35. data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +1039 -0
  36. data/lib/klenod/build/plugins/haml_plugin/transformer.rb +766 -0
  37. data/lib/klenod/build/plugins/haml_plugin.rb +369 -0
  38. data/lib/klenod/build/plugins/image_plugin.rb +401 -0
  39. data/lib/klenod/build/plugins/intl_plugin.rb +34 -0
  40. data/lib/klenod/build/plugins/markdown_compiler.rb +180 -0
  41. data/lib/klenod/build/plugins/markdown_plugin.rb +161 -0
  42. data/lib/klenod/build/plugins/router_plugin.rb +942 -0
  43. data/lib/klenod/build/plugins/ruby_plugin.rb +34 -0
  44. data/lib/klenod/build/plugins/svg_plugin.rb +197 -0
  45. data/lib/klenod/build/plugins.rb +22 -0
  46. data/lib/klenod/build/profiler.rb +79 -0
  47. data/lib/klenod/build/resolution_error_formatter.rb +42 -0
  48. data/lib/klenod/build/resolver.rb +95 -0
  49. data/lib/klenod/build/ruby_import_rewriter.rb +436 -0
  50. data/lib/klenod/build/source_map/editor.rb +110 -0
  51. data/lib/klenod/build/source_map/map.rb +168 -0
  52. data/lib/klenod/build/source_map/vlq.rb +68 -0
  53. data/lib/klenod/build/source_map.rb +12 -0
  54. data/lib/klenod/build/transform_result.rb +12 -0
  55. data/lib/klenod/build/version.rb +7 -0
  56. data/lib/klenod/build/watched_pattern.rb +11 -0
  57. data/lib/klenod/build/watcher.rb +141 -0
  58. data/lib/klenod/build.rb +15 -0
  59. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Klenod
4
+ module Build
5
+ LoadResult = Data.define(:source, :source_hash, :transform)
6
+ end
7
+ 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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Klenod
4
+ module Build
5
+ module Plugins
6
+ module ComponentDefaults
7
+ DEFAULT_COMPONENT_BASE_CLASS = "Object"
8
+ DEFAULT_FACTORY = "Object"
9
+ end
10
+ end
11
+ end
12
+ end