compex 0.1.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a05ce5e69574029faf2e79599fa9da036407cc795dbdbb0cdf1b61bee7207fa9
4
- data.tar.gz: 2aafbe674927a37b3be6765eadb1728e034f0ccc5eb320c46c81cf67291d9e78
3
+ metadata.gz: 4b7cf8ba1fdb3025ced5fbd9ac131254f434f8c5b0fb9051db7ad99ecebbcff5
4
+ data.tar.gz: 5c999e47eecd15154c8bf51bf537fb579f970d73c19b3649e5f2e0752b195f53
5
5
  SHA512:
6
- metadata.gz: c0b66d2086af516eff2747260fcde3d41442b9efac231b2f91d841f46e1022a1307ca0c3263a5eed4eba6ac94397cbaf73b404caeb346afd301ec2aa6d375321
7
- data.tar.gz: ac8a634d182846d66e175a6fe99fced4ee9f9d0caf8500fe0ae0e6b077175690cff1cc5e6d9b97346d368b779db6a172176ad825ba0c7f7a98a7de051f4a898b
6
+ metadata.gz: 4282c8050c62201e2171969458e13facac1d9e8a02fe376531a671ddecefdd14a8c1d2a4137e9a9ae129fc5ce4b4dbfc5453b57a8f0af05ab4ac90eb67e2ebf5
7
+ data.tar.gz: 59055e79cc4caae458af50b83c296e959c3700b8c8538750494adc6dd9e8cd02b2cbab72dcbac5b0735456bca2e922aa4a8af2e52db3481fb7f5ea7a21232fee
@@ -2,21 +2,18 @@
2
2
 
3
3
  module CompEx
4
4
  class AssetProvider
5
- def self.provide(hashed)
6
- return nil if !hashed.is_a?(String) || hashed.length < 2
7
-
8
- case hashed[0]
9
- when "j"
10
- script = Cache.get_hashed_script(hashed[2...])
11
- return nil unless script
5
+ def self.register(hash, value)
6
+ @assets ||= {}
7
+ CompEx::Cache.cache_asset(hash, value)
8
+ @assets[hash] = value
9
+ end
12
10
 
13
- script[1]
14
- when "c"
15
- style = Cache.get_hashed_style(hashed[2...])
16
- return nil unless style
11
+ def self.reset_cache!
12
+ @assets = {}
13
+ end
17
14
 
18
- style[1]
19
- end
15
+ def self.provide(hashed)
16
+ @assets[hashed] ||= CompEx::Cache.get_asset(hashed)
20
17
  end
21
18
  end
22
19
  end
data/lib/compex/bag.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module CompEx
4
4
  class Bag
5
5
  def initialize(parent, **args)
6
- parent.defined_args.each do |arg|
6
+ parent.args.each do |arg|
7
7
  next unless args.key? arg
8
8
 
9
9
  define_singleton_method(arg) { args[arg] }
@@ -12,8 +12,9 @@ module CompEx
12
12
  @_extra_js_hashes = []
13
13
  end
14
14
 
15
- def extra_css_hashes = @_extra_css_hashes
16
- def extra_js_hashes = @_extra_js_hashes
15
+ def required_hashes
16
+ [@_extra_css_hashes, @_extra_js_hashes].compact.flatten
17
+ end
17
18
 
18
19
  def render(klass, **args)
19
20
  inst = klass.new(**args)
data/lib/compex/base.rb CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  module CompEx
4
4
  class Base
5
+ using CompEx::StringRefinements
6
+ using CompEx::ClassRefinements
7
+
5
8
  class << self
9
+ def descriptor
10
+ @descriptor ||= ComponentDescriptor.new(self)
11
+ end
12
+
6
13
  def args(first, *rest)
7
14
  [first, *rest].each { arg(it) }
8
15
  self
@@ -15,8 +22,7 @@ module CompEx
15
22
  define_method("#{name}=") do |value|
16
23
  instance_variable_set(var_name, UnsafeString.new(value))
17
24
  end
18
- @args ||= []
19
- @args << name
25
+ descriptor.args.add(name)
20
26
 
21
27
  return self if rest.empty?
22
28
 
@@ -26,131 +32,61 @@ module CompEx
26
32
  end
27
33
 
28
34
  def expose(first, *rest)
29
- @exposed ||= []
30
- @exposed << first
31
- @exposed.append(*rest)
32
- @exposed.uniq!
35
+ descriptor.exposed.add(first, *rest)
33
36
  nil
34
37
  end
35
38
 
36
39
  def html(source)
37
- @html = source
40
+ descriptor.raw_html = source
38
41
  self
39
42
  end
40
43
 
41
44
  def style(source)
42
- @style = source
45
+ descriptor.raw_style = source
43
46
  self
44
47
  end
45
48
 
46
49
  def js(source)
47
- @js = source
50
+ descriptor.raw_js = source
48
51
  self
49
52
  end
50
53
 
51
- def raw_component_id
52
- @raw_component_id ||= name || hash.to_s(36)
53
- end
54
-
55
- def component_id
56
- @component_id ||= hash_string(raw_component_id)
57
- end
58
-
59
54
  def js_class_id
60
55
  return @js_class_id if @js_class_id
61
56
 
62
57
  class_id = name.split("::").map do |id|
63
58
  next id unless id.start_with? "#"
64
59
 
65
- hash_string(id)
60
+ id.hashed
66
61
  end.join
67
62
  @js_class_id ||= ["CompEx_", class_id, "_", component_id.upcase].join
68
63
  end
69
64
 
70
- def hash_string(str)
71
- value = 2906
72
- str.chars.map(&:ord).each do |v|
73
- value = (value * 33) ^ v
74
- end
75
-
76
- (value & 0xFFFFFFFF).to_s(36)
77
- end
78
-
79
65
  def inherited(subclass)
80
66
  super
81
67
  CompEx::ComponentRegistry.register(subclass)
82
68
  end
83
-
84
- def stable_class_id(klass)
85
- klass.name || Digest::SHA256.hexdigest(klass.inspect)
86
- end
87
-
88
- def defined_args
89
- @args || []
90
- end
91
- end
92
-
93
- using CompEx::StringRefinements
94
-
95
- def html
96
- @html ||= Cache.html(self.class) do
97
- Template.parse(self.class.instance_variable_get(:@html) || find_template, self.class)
98
- end
99
- end
100
-
101
- def style
102
- self.class.instance_variable_get(:@style) || find_style
103
69
  end
104
70
 
105
- attr_reader :css, :css_hash, :js_hash, :js
71
+ def descriptor = self.class.descriptor
106
72
 
107
73
  def initialize(**args)
108
- @bag = Bag.new(self, **args)
74
+ @bag = Bag.new(descriptor, **args)
109
75
  @extra_hashes = []
110
-
111
- initialize_css
112
- initialize_js
113
76
  end
114
77
 
115
- def exposed = self.class.instance_variable_get(:@exposed) || []
78
+ def exposed = self.class.descriptor.exposed
116
79
 
117
- def initialize_css
118
- css_id, css_source, css_hash = Cache.style(self.class) do
119
- id, css = Style.compile(self, html.root_classes)
120
- # Keeping define_css_prefix here, so we only define it once.
121
- html.define_css_prefix(id)
122
- css_hash = Digest::SHA1.hexdigest(css)
123
- [id, css, "c_#{css_hash}"]
124
- end
125
- @css_id = css_id
126
- @css = css_source
127
- @css_hash = css_hash
128
- end
129
-
130
- def initialize_js
131
- js = self.class.instance_variable_get(:@js) || find_js
132
- return unless js
133
-
134
- hash, src = Cache.script(self.class) do
135
- js_hash = Digest::SHA1.hexdigest(js)
136
- # Keeping define_js_module here, so we only define it once.
137
- html.define_js_module(self.class.js_class_id)
138
- ["j_#{js_hash}", JSRewriter.rewrite(self.class, js)]
139
- end
140
- @js = src
141
- @js_hash = hash
142
- end
143
-
144
- def defined_args = self.class.defined_args
80
+ def defined_args = self.class.descriptor.args
145
81
 
146
- def render_html
147
- html.render do |v|
82
+ def render_html(children: nil)
83
+ self.class.descriptor.html.render do |v|
148
84
  case v
85
+ when :slot
86
+ children
149
87
  when Hash
150
88
  inst = v[:component].new(**v[:args])
151
- ret = inst.render_html do
152
- v[:children]
153
- end
89
+ ret = inst.render_html(children: v[:children])
154
90
  @extra_hashes << inst.required_hashes
155
91
  ret
156
92
 
@@ -163,57 +99,10 @@ module CompEx
163
99
  end
164
100
 
165
101
  def required_hashes
166
- [@css_hash, @js_hash, @bag.extra_css_hashes, @bag.extra_js_hashes, @extra_hashes]
102
+ [descriptor.required_hashes, @bag.required_hashes, @extra_hashes]
167
103
  .flatten
168
104
  .compact
169
105
  .uniq
170
106
  end
171
-
172
- def component_name
173
- return nil if self.class.name.nil?
174
-
175
- @component_name ||= self.class.name.split("::").map do |item|
176
- next item if item[0] == "#"
177
-
178
- item.underscore
179
- end.join("/")
180
- end
181
-
182
- private
183
-
184
- def find_template
185
- path = CompEx.config.lookup_template(self, :html)
186
- begin
187
- File.read(path)
188
- rescue Errno::ENOENT
189
- raise "No template defined by #{self.class.name} (through ::html), nor could be found at #{path}"
190
- end
191
- end
192
-
193
- def find_style
194
- cname = component_name
195
- return nil unless cname
196
-
197
- path = CompEx.config.lookup_template(self, :style)
198
- begin
199
- style = File.read(path)
200
- rescue Errno::ENOENT
201
- return nil
202
- end
203
-
204
- Style.compile(style)
205
- end
206
-
207
- def find_js
208
- cname = component_name
209
- return nil unless cname
210
-
211
- path = CompEx.config.lookup_template(self, :js)
212
- begin
213
- File.read(path)
214
- rescue Errno::ENOENT
215
- nil
216
- end
217
- end
218
107
  end
219
108
  end
@@ -2,16 +2,16 @@
2
2
 
3
3
  module CompEx
4
4
  class Cache
5
- class MemoryBackend < Base
6
- def prepare!(*, **)
7
- @keys = {}
5
+ class MemoryBackend
6
+ def initialize
7
+ @cache = {}
8
8
  end
9
9
 
10
10
  def set(key, value)
11
- @keys[prefix(key)] = value
11
+ @cache[key] = value
12
12
  end
13
13
 
14
- def get(key) = @keys[prefix(key)]
14
+ def get(key) = @keys[key]
15
15
  end
16
16
  end
17
17
  end
data/lib/compex/cache.rb CHANGED
@@ -1,73 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "cache/base"
4
- require_relative "cache/memcached_backend"
5
3
  require_relative "cache/memory_backend"
6
- require_relative "cache/noop_backend"
7
- require_relative "cache/redis_backend"
8
4
 
9
5
  module CompEx
10
6
  class Cache
7
+ using ClassRefinements
8
+
11
9
  class << self
12
10
  def backend = CompEx.config.cache_backend
13
- def stable_class_id(comp) = CompEx::Base.stable_class_id(comp)
14
-
15
- def style(component_class)
16
- stable_id = stable_class_id(component_class)
17
- stable_key = "style:#{stable_id}"
18
- v = backend.get(stable_key)
19
- return JSON.parse(v, symbolize_names: true) if v
20
11
 
21
- r = yield
22
- backend.set("style:#{r.last[2...]}", stable_id)
23
- backend.set(stable_key, r.to_json)
24
-
25
- r
12
+ def cache_asset(key, value)
13
+ backend.set(prefix(key), value)
26
14
  end
27
15
 
28
- def get_hashed_style(hashed)
29
- ptr = backend.get("style:#{hashed}")
30
- return nil unless ptr
31
-
32
- v = backend.get("style:#{ptr}")
33
- return nil unless v
34
-
35
- JSON.parse(v, symbolize_names: true)
16
+ def get_asset(key)
17
+ backend.get(prefix(key))
36
18
  end
37
19
 
38
- def html(component_class)
39
- stable_id = stable_class_id(component_class)
40
- stable_key = "html:#{stable_id}"
41
- v = backend.get(stable_key)
42
- return v if v
43
-
44
- html = yield
45
- backend.set(stable_key, html)
46
-
47
- html
48
- end
49
-
50
- def script(component_class)
51
- stable_id = stable_class_id(component_class)
52
- stable_key = "script:#{stable_id}"
53
- v = backend.get(stable_key)
54
- return JSON.parse(v, symbolize_names: true) if v
55
-
56
- r = yield
57
- backend.set(stable_key, r.to_json)
58
- backend.set("script:#{r.first[2...]}", stable_id)
59
-
60
- r
61
- end
62
-
63
- def get_hashed_script(hashed)
64
- ptr = backend.get("script:#{hashed}")
65
- return nil unless ptr
66
-
67
- v = backend.get("script:#{ptr}")
68
- return nil unless v
69
-
70
- JSON.parse(v, symbolize_names: true)
20
+ def prefix(key)
21
+ "#{CompEx.config.cache_prefix}#{key}"
71
22
  end
72
23
  end
73
24
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CompEx
4
+ class ComponentDescriptor
5
+ using CompEx::ClassRefinements
6
+ using CompEx::StringRefinements
7
+
8
+ attr_accessor :raw_style
9
+ attr_reader :args, :exposed, :css, :css_hash, :css_prefix
10
+ attr_writer :raw_html, :raw_js
11
+
12
+ def initialize(parent)
13
+ @parent = parent
14
+ @args = Set.new
15
+ @exposed = Set.new
16
+ @css_prefix = "cx_#{@parent.component_id}"
17
+ end
18
+
19
+ def html
20
+ prepare!
21
+ @html
22
+ end
23
+
24
+ def has_js? = !@js.nil?
25
+ def has_style? = !@css.nil?
26
+
27
+ def prepare!
28
+ return if @prepared
29
+
30
+ # prime cached templates
31
+ js_template
32
+ css_template
33
+
34
+ prepare_html
35
+ prepare_js
36
+ prepare_css
37
+ update_html_attrs
38
+
39
+ @prepared = true
40
+ end
41
+
42
+ def required_hashes
43
+ [@css_hash, @js_hash].compact
44
+ end
45
+
46
+ def js_class_id
47
+ return @js_class_id if @js_class_id
48
+
49
+ id = @parent.name.split("::")
50
+ .reject { it.start_with?("#") }
51
+ .map(&:hashed).join
52
+ @js_class_id = "CompEx_#{id}_#{@parent.component_id.upcase}"
53
+ end
54
+
55
+ def dynamically_defined?
56
+ @dynamically_defined ||= @parent.name.include?("#")
57
+ end
58
+
59
+ private
60
+
61
+ def find_template(kind)
62
+ if kind == :html && dynamically_defined?
63
+ # This is an error already. #find should only be called in case
64
+ # raw_html isn't called. Bail early.
65
+ raise MissingTemplateError, @parent
66
+ end
67
+
68
+ path = CompEx.config.lookup_template(@parent, kind)
69
+ if path.nil?
70
+ raise MissingTemplateError, @parent if kind == :html
71
+
72
+ return nil
73
+ end
74
+
75
+ begin
76
+ File.read(path)
77
+ rescue Errno::ENOENT
78
+ return nil unless kind == :html
79
+
80
+ raise MissingTemplateError, @parent
81
+ end
82
+ end
83
+
84
+ def prepare_js
85
+ return unless @js_template
86
+
87
+ js_hash = Digest::SHA1.hexdigest(@js_template)
88
+ @js = JSRewriter.rewrite(@parent, @js_template)
89
+ @js_hash = "j_#{js_hash}"
90
+ CompEx::AssetProvider.register(@js_hash, @js)
91
+ end
92
+
93
+ def prepare_html
94
+ @html_template ||= @raw_html || find_template(:html)
95
+ @html = Template.parse(@html_template, @parent)
96
+ end
97
+
98
+ def prepare_css
99
+ return unless css_template
100
+
101
+ @css = Style.compile(@parent, css_template, @html.root_classes)
102
+ @css_hash = "c_#{Digest::SHA1.hexdigest(@css)}"
103
+ CompEx::AssetProvider.register(@css_hash, @css)
104
+ end
105
+
106
+ def update_html_attrs
107
+ @html.define_css_prefix(css_prefix) if @css
108
+ @html.define_js_module(js_class_id) if @js
109
+ end
110
+
111
+ def js_template
112
+ @js_template ||= @raw_js || find_template(:js)
113
+ end
114
+
115
+ def css_template
116
+ @css_template ||= @raw_style || find_template(:style)
117
+ end
118
+ end
119
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module CompEx
4
4
  class ComponentRegistry
5
+ using ClassRefinements
6
+
5
7
  Node = Struct.new(:name, :children, :component) do
6
8
  def initialize(*)
7
9
  super
@@ -12,11 +14,16 @@ module CompEx
12
14
  class << self
13
15
  attr_reader :components, :tree
14
16
 
17
+ def flush!
18
+ @components = {}
19
+ @tree = Struct.new(:children).new(children: [])
20
+ end
21
+
15
22
  def register(component)
16
23
  @components ||= {}
17
24
  @tree ||= Struct.new(:children).new(children: [])
18
25
 
19
- stable_id = CompEx::Base.stable_class_id(component)
26
+ stable_id = component.stable_class_id
20
27
  @components[stable_id] = component
21
28
 
22
29
  names = stable_id.split("::")
@@ -47,6 +54,7 @@ module CompEx
47
54
  end
48
55
 
49
56
  def resolve(name, context: nil)
57
+ puts "resolve #{name}, #{context.inspect}"
50
58
  return nil if name.nil? || name.empty?
51
59
 
52
60
  # Normalize
@@ -76,6 +84,44 @@ module CompEx
76
84
  node = find_by_path(path, @tree)
77
85
  node&.component
78
86
  end
87
+
88
+ def walk(base, &)
89
+ base.children.each do |v|
90
+ if v.respond_to?(:component) && !v.component.nil?
91
+ yield v.component.descriptor
92
+ end
93
+
94
+ walk(v, &) if v.children && v.children.length > 0
95
+ end
96
+ end
97
+
98
+ def gather_scripts
99
+ scripts = []
100
+ walk(@tree) do |comp|
101
+ next unless comp.has_js?
102
+ scripts << {
103
+ component: comp,
104
+ source: comp.js,
105
+ hash: comp.js_hash
106
+ }
107
+ end
108
+
109
+ scripts
110
+ end
111
+
112
+ def gather_styles
113
+ styles = []
114
+ walk(@tree) do |comp|
115
+ next unless comp.has_style?
116
+ styles << {
117
+ component: comp,
118
+ source: comp.css,
119
+ hash: comp.css_hash
120
+ }
121
+ end
122
+
123
+ styles
124
+ end
79
125
  end
80
126
  end
81
127
  end
data/lib/compex/config.rb CHANGED
@@ -16,56 +16,45 @@ module CompEx
16
16
  def config = configure
17
17
 
18
18
  class Config
19
- attr_accessor :on_multiple_root, :multiple_root_wrap_element,
20
- :on_non_element, :cache_args, :cache_kwargs, :cache_prefix,
21
- :template_search_path, :js_search_path, :style_search_path
22
- attr_reader :cache_mode, :cache_backend
19
+ using CompEx::ClassRefinements
23
20
 
24
- CACHE_MODES = {
25
- memcached: Cache::MemcachedBackend,
26
- redis: Cache::RedisBackend,
27
- memory: Cache::MemoryBackend,
28
- disabled: Cache::NoopBackend
29
- }.freeze
21
+ attr_accessor :on_multiple_root, :multiple_root_wrap_element,
22
+ :on_non_element, :template_search_path, :js_search_path,
23
+ :style_search_path, :cache_backend, :cache_prefix
30
24
 
31
25
  def initialize
32
26
  @on_multiple_root = :wrap
33
27
  @multiple_root_wrap_element = "div"
34
28
  @on_non_element = :wrap
35
- self.cache_mode = :memory
29
+ self.cache_backend = Cache::MemoryBackend.new
30
+ self.cache_prefix = "compex.cache"
36
31
 
37
- return unless defined?(Rails)
32
+ return unless defined?(::Rails)
38
33
 
39
- root_path = File.join(Rails.root, "app", "views", "components")
34
+ root_path = File.join(::Rails.root, "app", "views", "components")
40
35
  @template_search_path = root_path
41
36
  @style_search_path = root_path
42
37
  @js_search_path = root_path
43
38
  end
44
39
 
45
- def cache_mode=(value)
46
- value = value.to_sym if value.is_a? String
47
- raise ArgumentError, "Unknown cache mode #{value}, valid options are #{CACHE_MODES.keys.map(&:to_s).join(", ")}" unless CACHE_MODES.key? value
48
-
49
- @cache_backend = CACHE_MODES[value].new
50
- @cache_backend.prepare!(*cache_args, **cache_kwargs)
51
- end
52
-
53
40
  def lookup_template(component, type)
54
41
  @lookup_cache ||= {}
55
42
  lookup_key = [component, type]
56
43
  return @lookup_cache[lookup_key] if @lookup_cache.key? lookup_key
57
44
 
58
- cname = component.component_name
59
- raise MissingTemplate, "No template defined by anonymous class #{self.class} (through ::#{type})" if type == :html && !cname
45
+ dynamically_defined = component.descriptor.dynamically_defined?
46
+ raise MissingTemplateError, "No template defined by anonymous class #{self.class} (through ::#{type})" if dynamically_defined && type == :html
60
47
 
61
48
  # Anonymous classes can't be looked up since we can't define its path
62
- unless cname
49
+ if dynamically_defined
63
50
  @lookup_cache[lookup_key] = nil
64
51
  return nil
65
52
  end
66
53
 
54
+ cname = component.name
55
+
67
56
  ret = case type
68
- when :template
57
+ when :html
69
58
  File.join(@template_search_path || "", "#{cname}.html.crb")
70
59
  when :style
71
60
  File.join(@style_search_path || "", "#{cname}.css")
@@ -5,7 +5,7 @@ module CompEx
5
5
  def self.rewrite(component, raw_js)
6
6
  return nil unless raw_js
7
7
 
8
- class_name = component.js_class_id
8
+ class_name = component.descriptor.js_class_id
9
9
  source = raw_js.strip.gsub(/\Aclass\s+[^\s]+\s*{/, "").gsub(/}\z/, "")
10
10
  <<~JS
11
11
  ((Runtime, Component) => {
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CompEx
4
+ module ClassRefinements
5
+ refine Class do
6
+ using CompEx::StringRefinements
7
+
8
+ def stable_class_id
9
+ @stable_class_id ||= name || Digest::SHA256.hexdigest(inspect)
10
+ end
11
+
12
+ def raw_component_id
13
+ @raw_component_id ||= name || hash.to_s(36)
14
+ end
15
+
16
+ def component_id
17
+ @component_id ||= raw_component_id.hashed
18
+ end
19
+ end
20
+ end
21
+ end
@@ -10,6 +10,15 @@ module CompEx
10
10
  .tr("-", "_")
11
11
  .downcase
12
12
  end
13
+
14
+ def hashed
15
+ value = 2906
16
+ chars.map(&:ord).each do |v|
17
+ value = (value * 33) ^ v
18
+ end
19
+
20
+ (value & 0xFFFFFFFF).to_s(36)
21
+ end
13
22
  end
14
23
  end
15
24
  end
data/lib/compex/style.rb CHANGED
@@ -2,35 +2,20 @@
2
2
 
3
3
  module CompEx
4
4
  class Style
5
- def self.compile(component_class, root_classes = nil)
6
- [
7
- component_prefix(component_class),
8
- new(component_class, root_classes).compile
9
- ]
10
- end
11
-
12
- def self.component_prefix(component) = "cx_#{hash_string(comp_id(component))}"
13
-
14
- def self.comp_id(comp) = comp.class.name || comp.class.hash.to_s(36)
5
+ using CompEx::StringRefinements
6
+ using CompEx::ClassRefinements
15
7
 
16
- def self.hash_string(str)
17
- value = 2906
18
- str.chars.map(&:ord).each do |v|
19
- value = (value * 33) ^ v
20
- end
21
-
22
- (value & 0xFFFFFFFF).to_s(36)
8
+ def self.compile(component_class, source, root_classes = nil)
9
+ new(component_class, source, root_classes).compile
23
10
  end
24
11
 
25
- def initialize(klass, root_classes = nil)
26
- @comp = klass
27
- @style = klass.style
12
+ def initialize(klass, source, root_classes = nil)
13
+ @style = source
28
14
  @ast = MiniCSS.parse(@style)
29
15
  @root_classes = root_classes || []
16
+ @component_prefix = klass.descriptor.css_prefix
30
17
  end
31
18
 
32
- def id_for_component = self.class.component_prefix(@comp)
33
-
34
19
  def compile
35
20
  @ast.each do |v|
36
21
  case v
@@ -61,28 +46,33 @@ module CompEx
61
46
  {
62
47
  type: :complex,
63
48
  combinator: @root_classes.include?(sel[:content].gsub(/^\./, "")) ? "" : " ",
64
- left: { type: :class, content: ".#{id_for_component}" },
49
+ left: { type: :class, content: ".#{@component_prefix}" },
65
50
  right: sel
66
51
  }
52
+
67
53
  when :type, :attribute, :universal, :id
68
54
  {
69
55
  type: :complex,
70
56
  combinator: " ",
71
- left: { type: :class, content: ".#{id_for_component}" },
57
+ left: { type: :class, content: ".#{@component_prefix}" },
72
58
  right: sel
73
59
  }
60
+
74
61
  when :list
75
62
  sel.tap do |s|
76
63
  s[:list].map! { prefix_selector(it) }
77
64
  end
65
+
78
66
  when :complex
79
67
  sel.tap do |s|
80
68
  s[:left] = prefix_selector(s[:left])
81
69
  end
70
+
82
71
  when :compound
83
72
  sel.tap do |s|
84
73
  s[:list][0] = prefix_selector(s[:list].first)
85
74
  end
75
+
86
76
  else
87
77
  raise "Unexpected selector type #{sel[:type]}"
88
78
  end
@@ -102,9 +102,10 @@ module CompEx
102
102
 
103
103
  when MiniHTML::AST::Executable
104
104
  push_literal
105
- id = @embedding_id
105
+ id = "__cx_embedding_quoted_#{@context_id}_#{@embedding_id}__"
106
106
  @embedding_id += 1
107
- @result << { kind: :executable, id: "__cx_embedding_quoted_#{@context_id}_#{id}__", source: at.value.source }
107
+ @embeddings[id] = at.value.source
108
+ @result << { kind: :executable, id: id, source: at.value.source }
108
109
 
109
110
  when MiniHTML::AST::Interpolation
110
111
  assemble_interpolation(at.value)
@@ -135,9 +136,10 @@ module CompEx
135
136
  @literal << v.literal
136
137
  when MiniHTML::AST::Executable
137
138
  push_literal
138
- id = @embedding_id
139
+ id = "__cx_embedding_#{@context_id}_#{@embedding_id}__"
139
140
  @embedding_id += 1
140
- @result << { kind: :executable, id: "__cx_embedding_#{@context_id}_#{id}__", source: v.source }
141
+ @embeddings[id] = v.source
142
+ @result << { kind: :executable, id: id, source: v.source }
141
143
  end
142
144
  end
143
145
 
@@ -3,6 +3,8 @@
3
3
  module CompEx
4
4
  class Template
5
5
  class Renderer
6
+ using CompEx::ClassRefinements
7
+
6
8
  # The following attributes will have their values merged when we
7
9
  # find the same tag already defined by an embedded component
8
10
  MERGEABLE_ATTRIBUTES = Set.new(%w[class])
@@ -61,7 +63,7 @@ module CompEx
61
63
  c_node = CompEx::ComponentRegistry.resolve(inner_name, context: @context)
62
64
  next unless c_node
63
65
 
64
- args = c_node.defined_args.map(&:to_s)
66
+ args = c_node.descriptor.args.map(&:to_s)
65
67
  component_props = node.attributes.slice(*args)
66
68
  component_props.each_key { node.remove_attribute(it) }
67
69
  child = node.children
@@ -121,13 +123,22 @@ module CompEx
121
123
  embed.replace(result)
122
124
  end
123
125
 
126
+ slot = dom.at_css("slot")
127
+
128
+ # WARNING: The safe navigation operator below WILL PREVENT yield when
129
+ # slot is nil.
130
+ slot&.replace((yield :slot) || "")
131
+
124
132
  dom.to_html
125
- .gsub(/__cx_embedding_#{@context_id}_\d+__/) { process_embeddings(it, false, &) }
126
- .gsub(/__cx_embedding_quoted_#{@context_id}_\d+__/) { process_embeddings(it, true, &) }
133
+ .gsub(/__cx_embedding_#{@context_id}_\d+__/) { process_embedding(it, false, &) }
134
+ .gsub(/__cx_embedding_quoted_#{@context_id}_\d+__/) { process_embedding(it, true, &) }
127
135
  end
128
136
 
129
- def process_embeddings(key, quoted)
130
- return key unless @embeddings.key? key
137
+ def process_embedding(key, quoted)
138
+ unless @embeddings.key? key
139
+ warn "[CompEx] BUG: Embedding not found: #{key} @ #{@context}"
140
+ return key
141
+ end
131
142
 
132
143
  ret = yield @embeddings[key]
133
144
  ret = ret.inspect if quoted
@@ -5,6 +5,8 @@ require_relative "template/assembler"
5
5
 
6
6
  module CompEx
7
7
  class Template
8
+ using CompEx::ClassRefinements
9
+
8
10
  def self.parse(value, context)
9
11
  p = MiniHTML::Parser.new(value)
10
12
  ast = p.parse
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CompEx
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/compex.rb CHANGED
@@ -9,8 +9,10 @@ require "minicss"
9
9
  require "minihtml"
10
10
 
11
11
  require_relative "compex/version"
12
+ require_relative "compex/refinements/string_refinements"
13
+ require_relative "compex/refinements/class_refinements"
14
+ require_relative "compex/component_descriptor"
12
15
  require_relative "compex/template"
13
- require_relative "compex/string_refinements"
14
16
  require_relative "compex/cache"
15
17
  require_relative "compex/style"
16
18
  require_relative "compex/bag"
@@ -33,6 +35,7 @@ module CompEx
33
35
  end
34
36
  end
35
37
 
38
+ class MissingTemplateError < Error; end
36
39
  class MultipleRootError < Error; end
37
40
  class NonElementRootError < Error; end
38
41
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vito Sartori
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2025-10-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: minicss
@@ -74,15 +74,13 @@ files:
74
74
  - lib/compex/bag.rb
75
75
  - lib/compex/base.rb
76
76
  - lib/compex/cache.rb
77
- - lib/compex/cache/base.rb
78
- - lib/compex/cache/memcached_backend.rb
79
77
  - lib/compex/cache/memory_backend.rb
80
- - lib/compex/cache/noop_backend.rb
81
- - lib/compex/cache/redis_backend.rb
78
+ - lib/compex/component_descriptor.rb
82
79
  - lib/compex/component_registry.rb
83
80
  - lib/compex/config.rb
84
81
  - lib/compex/js_rewriter.rb
85
- - lib/compex/string_refinements.rb
82
+ - lib/compex/refinements/class_refinements.rb
83
+ - lib/compex/refinements/string_refinements.rb
86
84
  - lib/compex/style.rb
87
85
  - lib/compex/template.rb
88
86
  - lib/compex/template/assembler.rb
@@ -110,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
108
  - !ruby/object:Gem::Version
111
109
  version: '0'
112
110
  requirements: []
113
- rubygems_version: 3.6.7
111
+ rubygems_version: 3.6.2
114
112
  specification_version: 4
115
113
  summary: View Component Extensions for Ruby
116
114
  test_files: []
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CompEx
4
- class Cache
5
- class Base
6
- def prepare!(*, **) = nil
7
- def prefix(key) = "#{CompEx.config.cache_prefix}#{key}"
8
- end
9
- end
10
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CompEx
4
- class Cache
5
- class MemcachedBackend
6
- def prepare!(*, **)
7
- begin
8
- require "memcached"
9
- rescue LoadError
10
- raise "CompEx: Memcached cache mode requires the gem 'memcached' to be installed."
11
- end
12
- @instance = Memcached::Client.new(*, **)
13
- end
14
-
15
- def set(key, value) = @instance.set(prefix(key), value)
16
- def get(key) = @instance.get(prefix(key))
17
- end
18
- end
19
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CompEx
4
- class Cache
5
- class NoopBackend < Base
6
- def get(*) = nil
7
- def set(*) = nil
8
- end
9
- end
10
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CompEx
4
- class Cache
5
- class RedisBackend < Base
6
- def prepare!(*, **)
7
- begin
8
- require "redis"
9
- rescue LoadError
10
- raise "CompEx: Redis cache mode requires the gem 'redis' to be installed."
11
- end
12
- @instance = Redis.new(*, **)
13
- end
14
-
15
- def set(key, value) = @instance.set(prefix(key), value)
16
- def get(key) = @instance.get(prefix(key))
17
- end
18
- end
19
- end