Class: Hames::Loader
- Inherits:
-
Object
- Object
- Hames::Loader
- Defined in:
- lib/hames/loader.rb
Overview
Mounts config rows into a context in dependency order derived from each plugin's inject list. Layers apply as ordered row lists; a later layer's row with an existing id replaces that row's config wholesale (never a deep merge); unknown ids append.
Instance Attribute Summary collapse
-
#ctx ⇒ Object
readonly
mountedis insertion-ordered by boot!, which mounts in dependency order — so its reverse is exact reverse-dependency order, and that is what a teardown wants: a consumer comes down before what it injects. -
#mounted ⇒ Object
readonly
mountedis insertion-ordered by boot!, which mounts in dependency order — so its reverse is exact reverse-dependency order, and that is what a teardown wants: a consumer comes down before what it injects. -
#rows ⇒ Object
readonly
mountedis insertion-ordered by boot!, which mounts in dependency order — so its reverse is exact reverse-dependency order, and that is what a teardown wants: a consumer comes down before what it injects.
Instance Method Summary collapse
-
#boot! ⇒ Object
Mount all enabled rows.
-
#dump_config ⇒ Object
Resolved tree, layer-agnostic view (for --dump-config).
-
#initialize(ctx = Context.new) ⇒ Loader
constructor
A new instance of Loader.
- #layer(row_hashes) ⇒ Object
-
#reconfigure!(id, config) ⇒ Object
Hot config swap — wholesale, like layering, never a merge.
- #unload!(id) ⇒ Object
Constructor Details
Instance Attribute Details
#ctx ⇒ Object (readonly)
mounted is insertion-ordered by boot!, which mounts in dependency
order — so its reverse is exact reverse-dependency order, and that is
what a teardown wants: a consumer comes down before what it injects.
Row order cannot stand in for it, since an inserted row's position in
the tree need not match where its dependencies put it.
77 78 79 |
# File 'lib/hames/loader.rb', line 77 def ctx @ctx end |
#mounted ⇒ Object (readonly)
mounted is insertion-ordered by boot!, which mounts in dependency
order — so its reverse is exact reverse-dependency order, and that is
what a teardown wants: a consumer comes down before what it injects.
Row order cannot stand in for it, since an inserted row's position in
the tree need not match where its dependencies put it.
77 78 79 |
# File 'lib/hames/loader.rb', line 77 def mounted @mounted end |
#rows ⇒ Object (readonly)
mounted is insertion-ordered by boot!, which mounts in dependency
order — so its reverse is exact reverse-dependency order, and that is
what a teardown wants: a consumer comes down before what it injects.
Row order cannot stand in for it, since an inserted row's position in
the tree need not match where its dependencies put it.
77 78 79 |
# File 'lib/hames/loader.rb', line 77 def rows @rows end |
Instance Method Details
#boot! ⇒ Object
Mount all enabled rows. Plugins whose injected services are not yet present wait; repeated passes mount whatever has become satisfiable. No progress with plugins still pending => cycle / missing provider.
106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/hames/loader.rb', line 106 def boot! pending = @rows.values.reject(&:disabled).map { |r| [r, instantiate(r)] } until pending.empty? ready, pending = pending.partition { |(_r, pl)| satisfied?(pl) } if ready.empty? missing = pending.map { |(r, pl)| "#{r.id} (needs #{unmet(pl).join(', ')})" } raise CycleError, "cannot mount: #{missing.join('; ')}" end ready.each { |(r, pl)| mount(r, pl) } end ctx end |
#dump_config ⇒ Object
Resolved tree, layer-agnostic view (for --dump-config).
160 161 162 |
# File 'lib/hames/loader.rb', line 160 def dump_config @rows.values.map { |r| { id: r.id, plugin: r.plugin.to_s, config: r.config, disabled: r.disabled } } end |
#layer(row_hashes) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/hames/loader.rb', line 85 def layer(row_hashes) row_hashes.each do |h| id = h.fetch(:id).to_s if (existing = @rows[id]) # patch: wholesale config replacement; plugin class may also swap @rows[id] = Hames::Row.new( id: id, plugin: h[:plugin] || existing.plugin, config: h.key?(:config) ? (h[:config] || {}) : existing.config, disabled: h.key?(:disabled) ? h[:disabled] : existing.disabled ) else @rows[id] = Row.build(h) end end self end |
#reconfigure!(id, config) ⇒ Object
Hot config swap — wholesale, like layering, never a merge. Replaces the row's config, swaps the mounted service's, invokes its reconfigure hook under with_owner(id) (so hook-registered effects are owned by the row, not left ownerless), and emits config/updated when the app vocabulary declares it (hames itself declares no events and stays app-agnostic). A raising hook rolls the row and the service's config back to what they were before the call and re-raises — the swap is atomic, never split-brain.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/hames/loader.rb', line 126 def reconfigure!(id, config) id = id.to_s old_row = @rows.fetch(id) { raise KeyError, "no row #{id.inspect}" } service = @mounted.fetch(id) { raise KeyError, "no mounted plugin #{id.inspect}" } old_config = old_row.config @rows[id] = Hames::Row.new(id: old_row.id, plugin: old_row.plugin, config: config, disabled: old_row.disabled) service.replace_config!(config) begin ctx.with_owner(id) { service.reconfigure(config) } rescue StandardError @rows[id] = old_row service.replace_config!(old_config) raise end ctx.emit("config/updated", id, config) if Hames.declared?("config/updated") config end |
#unload!(id) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/hames/loader.rb', line 144 def unload!(id) plugin = @mounted.fetch(id) { raise ArgumentError, "no mounted plugin #{id}" } # Owner effects are disposed even when stop raises, so a wedged stop hook # cannot strand this row's service registration and listeners; and the # @mounted entry is dropped only AFTER a clean teardown, so a stop that # raised keeps the row unloadable (a retry re-runs stop) rather than # vanishing behind a misleading "no mounted plugin". begin plugin.stop(ctx) if plugin.respond_to?(:stop) ensure ctx.dispose_owner!(id) end @mounted.delete(id) end |