Class: Kward::PluginRegistry
- Inherits:
-
Object
- Object
- Kward::PluginRegistry
show all
- Defined in:
- lib/kward/plugin_registry.rb
Overview
Loads trusted user plugin files and provides the plugin DSL.
Plugins live in the user plugin directory, run as local Ruby code, and can
register slash commands, one footer renderer, prompt context, and live
transcript-event observers for CLI and RPC frontends.
This registry is intentionally trust-based, not a sandbox. Keep plugin loading
restricted to ConfigFiles.plugin_paths, keep workspace-local code out of the
load path, and expose immutable transcript views so plugins can observe state
without corrupting active conversations.
Defined Under Namespace
Classes: Command, Context, DSL, InteractiveCommand, Transcript, TranscriptEvent
Constant Summary
collapse
- COMMAND_NAME_PATTERN =
/\A[A-Za-z0-9][A-Za-z0-9_-]*\z/.freeze
Class Attribute Summary collapse
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
-
#command_for(name) ⇒ Object
-
#commands ⇒ Object
-
#evaluate(path: nil, &block) ⇒ Object
-
#footer_renderer ⇒ Object
-
#initialize(reserved_commands: []) ⇒ PluginRegistry
constructor
Creates an object for trusted plugin loading and dispatch.
-
#interactive_command_for(name) ⇒ Object
-
#interactive_commands ⇒ Object
-
#load_file(path) ⇒ Object
-
#notify_transcript_event(event, context) ⇒ Object
-
#prompt_context(context) ⇒ Object
-
#prompt_context_renderers ⇒ Object
-
#register_command(name, description: "", argument_hint: "", path: nil, &handler) ⇒ Object
-
#register_footer(path: nil, &renderer) ⇒ Object
-
#register_interactive_command(name, rows:, fps: 30, description: "", argument_hint: "", path: nil, &handler) ⇒ Object
-
#register_prompt_context(path: nil, &renderer) ⇒ Object
-
#register_transcript_event(path: nil, &handler) ⇒ Object
-
#transcript_event_handlers ⇒ Object
Constructor Details
#initialize(reserved_commands: []) ⇒ PluginRegistry
Creates an object for trusted plugin loading and dispatch.
234
235
236
237
238
239
240
241
242
243
|
# File 'lib/kward/plugin_registry.rb', line 234
def initialize(reserved_commands: [])
@reserved_commands = reserved_commands.map(&:to_s)
@commands = {}
@interactive_commands = {}
@footer = nil
@footer_path = nil
@transcript_event_handlers = []
@prompt_context_renderers = []
@paths = []
end
|
Class Attribute Details
.loading_path ⇒ Object
Returns the value of attribute loading_path.
201
202
203
|
# File 'lib/kward/plugin_registry.rb', line 201
def loading_path
@loading_path
end
|
.loading_registry ⇒ Object
Returns the value of attribute loading_registry.
201
202
203
|
# File 'lib/kward/plugin_registry.rb', line 201
def loading_registry
@loading_registry
end
|
Instance Attribute Details
Returns plugin file currently responsible for footer output.
246
247
248
|
# File 'lib/kward/plugin_registry.rb', line 246
def
@footer_path
end
|
#paths ⇒ Array<String>
Returns plugin files successfully loaded by this registry.
249
250
251
|
# File 'lib/kward/plugin_registry.rb', line 249
def paths
@paths
end
|
Class Method Details
.deep_dup(value) ⇒ Object
209
210
211
212
213
214
215
216
217
218
219
220
|
# File 'lib/kward/plugin_registry.rb', line 209
def deep_dup(value)
case value
when Hash
value.each_with_object({}) { |(key, item), result| result[key] = deep_dup(item) }
when Array
value.map { |item| deep_dup(item) }
else
value.dup
end
rescue TypeError
value
end
|
.deep_freeze(value) ⇒ Object
222
223
224
225
226
227
228
229
230
|
# File 'lib/kward/plugin_registry.rb', line 222
def deep_freeze(value)
case value
when Hash
value.each_value { |item| deep_freeze(item) }
when Array
value.each { |item| deep_freeze(item) }
end
value.freeze
end
|
.load(paths: ConfigFiles.plugin_paths, reserved_commands: []) ⇒ Object
203
204
205
206
207
|
# File 'lib/kward/plugin_registry.rb', line 203
def load(paths: ConfigFiles.plugin_paths, reserved_commands: [])
registry = new(reserved_commands: reserved_commands)
paths.each { |path| registry.load_file(path) }
registry
end
|
Instance Method Details
#command_for(name) ⇒ Object
255
256
257
|
# File 'lib/kward/plugin_registry.rb', line 255
def command_for(name)
@commands[name.to_s]
end
|
#commands ⇒ Object
251
252
253
|
# File 'lib/kward/plugin_registry.rb', line 251
def commands
@commands.values
end
|
#evaluate(path: nil, &block) ⇒ Object
316
317
318
319
320
|
# File 'lib/kward/plugin_registry.rb', line 316
def evaluate(path: nil, &block)
dsl = DSL.new(self, path)
block.arity == 1 ? block.call(dsl) : dsl.instance_eval(&block)
self
end
|
267
268
269
|
# File 'lib/kward/plugin_registry.rb', line 267
def
@footer
end
|
#interactive_command_for(name) ⇒ Object
263
264
265
|
# File 'lib/kward/plugin_registry.rb', line 263
def interactive_command_for(name)
@interactive_commands[name.to_s]
end
|
#interactive_commands ⇒ Object
259
260
261
|
# File 'lib/kward/plugin_registry.rb', line 259
def interactive_commands
@interactive_commands.values
end
|
#load_file(path) ⇒ Object
302
303
304
305
306
307
308
309
310
311
312
313
314
|
# File 'lib/kward/plugin_registry.rb', line 302
def load_file(path)
previous_registry = self.class.loading_registry
previous_path = self.class.loading_path
self.class.loading_registry = self
self.class.loading_path = path
Kernel.load(path)
@paths << path
rescue StandardError => e
warn "Warning: skipping Kward plugin #{path}: #{e.message}"
ensure
self.class.loading_registry = previous_registry
self.class.loading_path = previous_path
end
|
#notify_transcript_event(event, context) ⇒ Object
290
291
292
293
294
295
296
297
298
299
300
|
# File 'lib/kward/plugin_registry.rb', line 290
def notify_transcript_event(event, context)
transcript_event = transcript_event_for(event)
return unless transcript_event
@transcript_event_handlers.each do |entry|
entry[:handler].call(transcript_event, context)
rescue StandardError => e
warn "Warning: Kward plugin transcript event error in #{entry[:path]}: #{e.message}"
end
nil
end
|
#prompt_context(context) ⇒ Object
279
280
281
282
283
284
285
286
287
288
|
# File 'lib/kward/plugin_registry.rb', line 279
def prompt_context(context)
parts = []
@prompt_context_renderers.each do |entry|
rendered = entry[:renderer].call(context)
parts << rendered.to_s unless rendered.to_s.empty?
rescue StandardError => e
warn "Warning: Kward plugin prompt context error in #{entry[:path]}: #{e.message}"
end
parts.empty? ? nil : parts.join("\n\n")
end
|
#prompt_context_renderers ⇒ Object
275
276
277
|
# File 'lib/kward/plugin_registry.rb', line 275
def prompt_context_renderers
@prompt_context_renderers.map { |entry| entry[:renderer] }
end
|
#register_command(name, description: "", argument_hint: "", path: nil, &handler) ⇒ Object
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
# File 'lib/kward/plugin_registry.rb', line 322
def register_command(name, description: "", argument_hint: "", path: nil, &handler)
name = name.to_s
raise "Plugin command name is invalid: #{name}" unless name.match?(COMMAND_NAME_PATTERN)
raise "Plugin command /#{name} requires a handler" unless handler
if @reserved_commands.include?(name)
warn "Warning: skipping Kward plugin command /#{name}: reserved command"
return nil
end
if @commands.key?(name)
warn "Warning: skipping duplicate Kward plugin command /#{name}: #{path}"
return nil
end
@commands[name] = Command.new(
name: name,
description: description.to_s,
argument_hint: argument_hint.to_s,
path: path,
handler: handler
)
end
|
370
371
372
373
374
375
376
|
# File 'lib/kward/plugin_registry.rb', line 370
def (path: nil, &renderer)
raise "Plugin footer requires a renderer" unless renderer
warn "Warning: replacing Kward plugin footer from #{@footer_path}: #{path}" if @footer
@footer = renderer
@footer_path = path
end
|
#register_interactive_command(name, rows:, fps: 30, description: "", argument_hint: "", path: nil, &handler) ⇒ Object
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
# File 'lib/kward/plugin_registry.rb', line 345
def register_interactive_command(name, rows:, fps: 30, description: "", argument_hint: "", path: nil, &handler)
name = name.to_s
raise "Interactive command name is invalid: #{name}" unless name.match?(COMMAND_NAME_PATTERN)
raise "Interactive command /#{name} requires a handler" unless handler
if @reserved_commands.include?(name) || @commands.key?(name)
warn "Warning: skipping Kward interactive command /#{name}: reserved command"
return nil
end
if @interactive_commands.key?(name)
warn "Warning: skipping duplicate Kward interactive command /#{name}: #{path}"
return nil
end
@interactive_commands[name] = InteractiveCommand.new(
name: name,
description: description.to_s,
argument_hint: argument_hint.to_s,
rows: [[rows.to_i, 1].max, 1].max,
fps: [[fps.to_f, 1].max, 120].min,
path: path,
handler: handler
)
end
|
#register_prompt_context(path: nil, &renderer) ⇒ Object
384
385
386
387
388
|
# File 'lib/kward/plugin_registry.rb', line 384
def register_prompt_context(path: nil, &renderer)
raise "Plugin prompt context requires a renderer" unless renderer
@prompt_context_renderers << { path: path, renderer: renderer }
end
|
#register_transcript_event(path: nil, &handler) ⇒ Object
378
379
380
381
382
|
# File 'lib/kward/plugin_registry.rb', line 378
def register_transcript_event(path: nil, &handler)
raise "Plugin transcript event requires a handler" unless handler
@transcript_event_handlers << { path: path, handler: handler }
end
|
#transcript_event_handlers ⇒ Object
271
272
273
|
# File 'lib/kward/plugin_registry.rb', line 271
def transcript_event_handlers
@transcript_event_handlers.map { |entry| entry[:handler] }
end
|