Module: Vident::Internals::Resolver

Defined in:
lib/vident/internals/resolver.rb

Overview

Resolves raw Declarations and instance state into a Draft of typed Stimulus values. Phase ‘:static` skips proc-bearing entries; `:procs` processes only those; `:all` does both. Procs nested inside a Hash descriptor escape the phase gate — use the fluent builder instead.

Class Method Summary collapse

Class Method Details

.absorb_input(draft, kind, input, instance, implied, phase:, alias_map: {}) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/vident/internals/resolver.rb', line 190

def absorb_input(draft, kind, input, instance, implied, phase:, alias_map: {})
  return if input.nil?

  kind_meta = Registry.fetch(kind)
  case input
  in Hash => h
    h.each do |key, raw|
      is_proc = raw.is_a?(Proc)
      next unless phase_allows?(is_proc, phase)
      absorbed = is_proc ? instance.instance_exec(&raw) : raw
      next if absorbed.nil?
      if kind_meta.keyed?
        parsed = kind_meta.value_class.parse(key, absorbed, implied: implied, component_id: instance_id(instance))
        draft.public_send(:"add_#{kind}", parsed)
      else
        entry = resolve_absorb_alias(kind, [key, absorbed], alias_map)
        parsed = parse_entry(kind_meta, entry, implied: implied, component_id: instance_id(instance))
        draft.public_send(:"add_#{kind}", parsed) if parsed
      end
    end
  in Array => a
    a.each do |entry|
      is_proc = entry.is_a?(Proc)
      next unless phase_allows?(is_proc, phase)
      parsed = absorb_one(kind_meta, entry, instance, implied, kind:, alias_map:)
      draft.public_send(:"add_#{kind}", parsed) if parsed
    end
  else
    is_proc = input.is_a?(Proc)
    return unless phase_allows?(is_proc, phase)
    parsed = absorb_one(kind_meta, input, instance, implied, kind:, alias_map:)
    draft.public_send(:"add_#{kind}", parsed) if parsed
  end
end

.absorb_one(kind_meta, entry, instance, implied, kind: nil, alias_map: {}) ⇒ Object



244
245
246
247
248
249
250
251
# File 'lib/vident/internals/resolver.rb', line 244

def absorb_one(kind_meta, entry, instance, implied, kind: nil, alias_map: {})
  entry = instance.instance_exec(&entry) if entry.is_a?(Proc)
  return nil if entry.nil?
  return entry if entry.is_a?(kind_meta.value_class)

  entry = resolve_absorb_alias(kind, entry, alias_map) if kind
  parse_entry(kind_meta, entry, implied: implied, component_id: instance_id(instance))
end

.absorb_root_element_attributes(draft, instance, implied, phase:, alias_map: {}) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/vident/internals/resolver.rb', line 176

def absorb_root_element_attributes(draft, instance, implied, phase:, alias_map: {})
  return unless instance.respond_to?(:resolved_root_element_attributes, true)
  attrs = instance.send(:resolved_root_element_attributes)
  return unless attrs.is_a?(Hash) && !attrs.empty?

  absorb_input(draft, :controllers, attrs[:stimulus_controllers], instance, implied, phase:, alias_map:)
  absorb_input(draft, :actions, attrs[:stimulus_actions], instance, implied, phase:, alias_map:)
  absorb_input(draft, :targets, attrs[:stimulus_targets], instance, implied, phase:, alias_map:)
  absorb_input(draft, :outlets, attrs[:stimulus_outlets], instance, implied, phase:, alias_map:)
  absorb_input(draft, :values, attrs[:stimulus_values], instance, implied, phase:, alias_map:)
  absorb_input(draft, :params, attrs[:stimulus_params], instance, implied, phase:, alias_map:)
  absorb_input(draft, :class_maps, attrs[:stimulus_classes], instance, implied, phase:, alias_map:)
end

.absorb_stimulus_props(draft, instance, implied, phase:, alias_map: {}) ⇒ Object



166
167
168
169
170
171
172
173
174
# File 'lib/vident/internals/resolver.rb', line 166

def absorb_stimulus_props(draft, instance, implied, phase:, alias_map: {})
  absorb_input(draft, :controllers, instance_ivar(instance, :@stimulus_controllers), instance, implied, phase:, alias_map:)
  absorb_input(draft, :actions, instance_ivar(instance, :@stimulus_actions), instance, implied, phase:, alias_map:)
  absorb_input(draft, :targets, instance_ivar(instance, :@stimulus_targets), instance, implied, phase:, alias_map:)
  absorb_input(draft, :outlets, instance_ivar(instance, :@stimulus_outlets), instance, implied, phase:, alias_map:)
  absorb_input(draft, :values, instance_ivar(instance, :@stimulus_values), instance, implied, phase:, alias_map:)
  absorb_input(draft, :params, instance_ivar(instance, :@stimulus_params), instance, implied, phase:, alias_map:)
  absorb_input(draft, :class_maps, instance_ivar(instance, :@stimulus_classes), instance, implied, phase:, alias_map:)
end

.build_alias_map(declarations) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/vident/internals/resolver.rb', line 38

def build_alias_map(declarations)
  map = {}
  declarations.controllers.each do |decl|
    alias_name = decl.meta[:as]
    next unless alias_name
    raw_path = decl.args.first
    next if raw_path.nil?
    map[alias_name] = raw_path.to_s
  end
  map
end

.build_implied_controller(instance) ⇒ Object



50
51
52
53
54
# File 'lib/vident/internals/resolver.rb', line 50

def build_implied_controller(instance)
  path = instance.class.stimulus_identifier_path
  name = instance.class.stimulus_identifier
  ::Vident::Stimulus::Controller.new(path: path, name: name)
end

.call(declarations, instance, phase: :all) ⇒ Object

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/vident/internals/resolver.rb', line 14

def call(declarations, instance, phase: :all)
  raise ArgumentError, "use resolve_procs_into for phase: :procs" if phase == :procs

  draft = Draft.new
  implied = build_implied_controller(instance)
  alias_map = build_alias_map(declarations)

  seed_implied_controller(draft, instance)
  resolve_declarations(draft, declarations, instance, implied, phase:, alias_map:)
  absorb_stimulus_props(draft, instance, implied, phase:, alias_map:)
  absorb_root_element_attributes(draft, instance, implied, phase:, alias_map:)

  draft
end

.gated_out?(when_proc, instance) ⇒ Boolean

Returns:

  • (Boolean)


285
286
287
288
# File 'lib/vident/internals/resolver.rb', line 285

def gated_out?(when_proc, instance)
  return false unless when_proc
  !instance.instance_exec(&when_proc)
end

.instance_id(instance) ⇒ Object

Must match the mutation-API path (#id, memoised) so DSL outlet auto-selectors and runtime-added outlets scope identically.



305
306
307
# File 'lib/vident/internals/resolver.rb', line 305

def instance_id(instance)
  instance.id
end

.instance_ivar(instance, name) ⇒ Object



298
299
300
301
# File 'lib/vident/internals/resolver.rb', line 298

def instance_ivar(instance, name)
  return nil unless instance.instance_variable_defined?(name)
  instance.instance_variable_get(name)
end

.key_for_parse(key) ⇒ Object



294
# File 'lib/vident/internals/resolver.rb', line 294

def key_for_parse(key) = key

.meta_for_controller(meta) ⇒ Object



296
# File 'lib/vident/internals/resolver.rb', line 296

def meta_for_controller(meta) = meta.slice(:as)

.parse_entry(kind_meta, entry, implied:, component_id:) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/vident/internals/resolver.rb', line 253

def parse_entry(kind_meta, entry, implied:, component_id:)
  case entry
  when Hash
    if kind_meta.keyed?
      first_key, first_val = entry.first
      kind_meta.value_class.parse(first_key, first_val, implied: implied, component_id: component_id)
    else
      kind_meta.value_class.parse(entry, implied: implied, component_id: component_id)
    end
  when Array
    kind_meta.value_class.parse(*entry, implied: implied, component_id: component_id)
  else
    kind_meta.value_class.parse(entry, implied: implied, component_id: component_id)
  end
end

.parse_single(value_class, args, implied:, component_id:) ⇒ Object



290
291
292
# File 'lib/vident/internals/resolver.rb', line 290

def parse_single(value_class, args, implied:, component_id:)
  value_class.parse(*args, implied: implied, component_id: component_id)
end

.phase_allows?(is_proc, phase) ⇒ Boolean

Uses pattern matching so an unknown phase raises NoMatchingPatternError early.

Returns:

  • (Boolean)


236
237
238
239
240
241
242
# File 'lib/vident/internals/resolver.rb', line 236

def phase_allows?(is_proc, phase)
  case phase
  in :all then true
  in :static then !is_proc
  in :procs then is_proc
  end
end

.phase_matches?(decl, phase) ⇒ Boolean

Returns:

  • (Boolean)


160
161
162
163
164
# File 'lib/vident/internals/resolver.rb', line 160

def phase_matches?(decl, phase)
  return true if phase == :all
  has_proc = decl.when_proc || decl.args.any? { |a| a.is_a?(Proc) }
  (phase == :procs) ? has_proc : !has_proc
end

.read_prop(instance, name) ⇒ Object



309
310
311
312
313
# File 'lib/vident/internals/resolver.rb', line 309

def read_prop(instance, name)
  ivar = :"@#{name}"
  return nil unless instance.instance_variable_defined?(ivar)
  instance.instance_variable_get(ivar)
end

.resolve_absorb_alias(kind, entry, alias_map) ⇒ Object



225
226
227
228
229
230
231
232
233
# File 'lib/vident/internals/resolver.rb', line 225

def resolve_absorb_alias(kind, entry, alias_map)
  return entry unless kind == :actions && alias_map.any?
  return entry unless entry.is_a?(Hash) && entry[:controller].is_a?(Symbol)
  sym = entry[:controller]
  unless alias_map.key?(sym)
    raise ::Vident::DeclarationError, "Unknown controller alias :#{sym} in stimulus_actions input. Declared aliases: #{alias_map.keys.inspect}"
  end
  entry.merge(controller: alias_map[sym])
end

.resolve_action_aliases(args, alias_map) ⇒ Object

Unknown aliases raise — a symbolic controller ref is declared intent, not a guess.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/vident/internals/resolver.rb', line 85

def resolve_action_aliases(args, alias_map)
  return args if alias_map.empty?
  args.map do |arg|
    next arg unless arg.is_a?(Hash) && arg[:controller].is_a?(Symbol)
    sym = arg[:controller]
    unless alias_map.key?(sym)
      raise ::Vident::DeclarationError, "Unknown controller alias :#{sym} in action. Declared aliases: #{alias_map.keys.inspect}"
    end
    arg.merge(controller: alias_map[sym])
  end
end

.resolve_args(args, instance) ⇒ Object

Only nil drops — false, blank strings, and empty collections reach the parser.



270
271
272
273
274
# File 'lib/vident/internals/resolver.rb', line 270

def resolve_args(args, instance)
  resolved = args.map { |arg| arg.is_a?(Proc) ? instance.instance_exec(&arg) : arg }
  return nil if resolved.any?(&:nil?)
  resolved
end

.resolve_declarations(draft, declarations, instance, implied, phase:, alias_map: {}) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/vident/internals/resolver.rb', line 61

def resolve_declarations(draft, declarations, instance, implied, phase:, alias_map: {})
  resolve_positional(draft, :controllers, declarations.controllers, instance, phase:) do |args, meta, _inst|
    ::Vident::Stimulus::Controller.parse(*args, implied: implied, **meta_for_controller(meta))
  end

  resolve_positional(draft, :actions, declarations.actions, instance, phase:) do |args, _meta, _inst|
    parse_single(::Vident::Stimulus::Action, resolve_action_aliases(args, alias_map), implied: implied, component_id: instance_id(instance))
  end

  resolve_positional(draft, :targets, declarations.targets, instance, phase:) do |args, _meta, _inst|
    parse_single(::Vident::Stimulus::Target, args, implied: implied, component_id: instance_id(instance))
  end

  resolve_keyed(draft, :outlets, declarations.outlets, instance, phase:) do |key, args, _meta|
    parsed_args = [key_for_parse(key), *args]
    parse_single(::Vident::Stimulus::Outlet, parsed_args, implied: implied, component_id: instance_id(instance))
  end

  resolve_keyed_values(draft, declarations, instance, implied, phase:)
  resolve_keyed_scalars(draft, :params, declarations.params, instance, implied, ::Vident::Stimulus::Param, phase:)
  resolve_keyed_scalars(draft, :class_maps, declarations.class_maps, instance, implied, ::Vident::Stimulus::ClassMap, phase:)
end

.resolve_keyed(draft, kind, entries, instance, phase:) ⇒ Object



113
114
115
116
117
118
119
120
121
122
# File 'lib/vident/internals/resolver.rb', line 113

def resolve_keyed(draft, kind, entries, instance, phase:)
  entries.each do |(key, decl)|
    next unless phase_matches?(decl, phase)
    next if gated_out?(decl.when_proc, instance)
    args = resolve_args(decl.args, instance)
    next if args.nil?
    parsed = yield(key, args, decl.meta)
    draft.public_send(:"add_#{kind}", parsed) if parsed
  end
end

.resolve_keyed_scalars(draft, kind, entries, instance, implied, value_class, phase:) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/vident/internals/resolver.rb', line 150

def resolve_keyed_scalars(draft, kind, entries, instance, implied, value_class, phase:)
  entries.each do |(key, decl)|
    next unless phase_matches?(decl, phase)
    next if gated_out?(decl.when_proc, instance)
    raw = resolve_value_meta(decl, instance)
    next if raw.nil?
    draft.public_send(:"add_#{kind}", value_class.parse(key, raw, implied: implied))
  end
end

.resolve_keyed_values(draft, declarations, instance, implied, phase:) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/vident/internals/resolver.rb', line 124

def resolve_keyed_values(draft, declarations, instance, implied, phase:)
  declarations.values.each do |(key, decl)|
    next unless phase_matches?(decl, phase)
    next if gated_out?(decl.when_proc, instance)

    if decl.meta[:from_prop]
      raw = read_prop(instance, key)
      next if raw.nil?
      draft.add_values(::Vident::Stimulus::Value.parse(key, raw, implied: implied))
      next
    end

    raw = resolve_value_meta(decl, instance)
    next if raw.nil?
    draft.add_values(::Vident::Stimulus::Value.parse(key, raw, implied: implied))
  end

  # values_from_props has no when_proc, so phase_matches? doesn't apply; skip on :procs pass.
  return if phase == :procs
  declarations.values_from_props.each do |name|
    raw = read_prop(instance, name)
    next if raw.nil?
    draft.add_values(::Vident::Stimulus::Value.parse(name, raw, implied: implied))
  end
end

.resolve_positional(draft, kind, entries, instance, phase:) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/vident/internals/resolver.rb', line 97

def resolve_positional(draft, kind, entries, instance, phase:)
  entries.each do |decl|
    next unless phase_matches?(decl, phase)
    next if gated_out?(decl.when_proc, instance)
    args = resolve_args(decl.args, instance)
    next if args.nil?
    args = splat_single_array(args)
    parsed = yield(args, decl.meta, instance)
    draft.public_send(:"add_#{kind}", parsed) if parsed
  end
end

.resolve_procs_into(draft, declarations, instance) ⇒ Object



29
30
31
32
33
34
35
36
# File 'lib/vident/internals/resolver.rb', line 29

def resolve_procs_into(draft, declarations, instance)
  implied = build_implied_controller(instance)
  alias_map = build_alias_map(declarations)
  resolve_declarations(draft, declarations, instance, implied, phase: :procs, alias_map:)
  absorb_stimulus_props(draft, instance, implied, phase: :procs, alias_map:)
  absorb_root_element_attributes(draft, instance, implied, phase: :procs, alias_map:)
  draft
end

.resolve_value_meta(decl, instance) ⇒ Object



276
277
278
279
280
281
282
283
# File 'lib/vident/internals/resolver.rb', line 276

def resolve_value_meta(decl, instance)
  return decl.meta[:static] if decl.meta.key?(:static)
  return nil if decl.args.empty?

  raw = decl.args.first
  raw = instance.instance_exec(&raw) if raw.is_a?(Proc)
  raw
end

.seed_implied_controller(draft, instance) ⇒ Object



56
57
58
59
# File 'lib/vident/internals/resolver.rb', line 56

def seed_implied_controller(draft, instance)
  return unless instance.class.stimulus_controller?
  draft.add_controllers(build_implied_controller(instance))
end

.splat_single_array(args) ⇒ Object



109
110
111
# File 'lib/vident/internals/resolver.rb', line 109

def splat_single_array(args)
  (args.size == 1 && args[0].is_a?(Array)) ? args[0] : args
end