Class: Aspera::Cli::CommandRegistry
- Inherits:
-
Object
- Object
- Aspera::Cli::CommandRegistry
- Defined in:
- lib/aspera/cli/command_registry.rb
Overview
Stores CommandSpec objects indexed by their full path (Array
Public API:
register(spec) - store a CommandSpec; raises on duplicate full_path
register_option(spec) - store an OptionSpec by name
option_specs - Hash{Symbol => OptionSpec} of all registered options
[](path) - retrieve a CommandSpec by full path
children_of(path) - Hash{Symbol => CommandSpec} of direct children (O(1))
all_paths - Array of all registered full paths
any? - true if at least one spec has been registered
validate! - cross-spec consistency checks; raises on violation
Instance Method Summary collapse
- #[](path) ⇒ CommandSpec?
-
#all_paths ⇒ Array<Array<Symbol>>
All registered full paths.
-
#any? ⇒ Boolean
True if at least one spec is registered.
-
#children_of(path) ⇒ Hash{Symbol => CommandSpec}
Returns a Hash mapping each child id to its CommandSpec for all direct children of
path. -
#none? ⇒ Boolean
True if no specs have been registered.
-
#option_specs ⇒ Hash{Symbol => OptionSpec}
All registered option specs.
-
#register(spec) ⇒ CommandSpec
Register a CommandSpec.
-
#register_option(spec) ⇒ OptionSpec
Register an OptionSpec.
-
#validate!(plugin_class: nil) ⇒ self
Cross-spec consistency checks.
Instance Method Details
#[](path) ⇒ CommandSpec?
22 23 24 |
# File 'lib/aspera/cli/command_registry.rb', line 22 def [](path) @specs[Array(path)] end |
#all_paths ⇒ Array<Array<Symbol>>
Returns all registered full paths.
51 52 53 |
# File 'lib/aspera/cli/command_registry.rb', line 51 def all_paths @specs.keys end |
#any? ⇒ Boolean
Returns true if at least one spec is registered.
70 71 72 |
# File 'lib/aspera/cli/command_registry.rb', line 70 def any? !@specs.empty? end |
#children_of(path) ⇒ Hash{Symbol => CommandSpec}
Returns a Hash mapping each child id to its CommandSpec for all direct
children of path. Empty hash if no children are registered.
O(1) lookup via the children index built in register().
46 47 48 |
# File 'lib/aspera/cli/command_registry.rb', line 46 def children_of(path) @children_index[Array(path)] || {} end |
#none? ⇒ Boolean
Returns true if no specs have been registered.
75 76 77 |
# File 'lib/aspera/cli/command_registry.rb', line 75 def none? @specs.empty? end |
#option_specs ⇒ Hash{Symbol => OptionSpec}
Returns all registered option specs.
65 66 67 |
# File 'lib/aspera/cli/command_registry.rb', line 65 def option_specs @option_specs.dup end |
#register(spec) ⇒ CommandSpec
Register a CommandSpec. Raises if the full_path is already registered. Also updates the children index so children_of remains O(1).
31 32 33 34 35 36 37 38 39 |
# File 'lib/aspera/cli/command_registry.rb', line 31 def register(spec) path = spec.full_path raise ArgumentError, "Duplicate command path: #{path.inspect}" if @specs.key?(path) @specs[path] = spec # Index: parent_path -> { child_id -> spec } parent = path[0..-2] # [] for root-level commands (@children_index[parent] ||= {})[spec.id] = spec spec end |
#register_option(spec) ⇒ OptionSpec
Register an OptionSpec. Raises if the option name is already registered.
59 60 61 62 |
# File 'lib/aspera/cli/command_registry.rb', line 59 def register_option(spec) raise ArgumentError, "Duplicate option: #{spec.name.inspect}" if @option_specs.key?(spec.name) @option_specs[spec.name] = spec end |
#validate!(plugin_class: nil) ⇒ self
Cross-spec consistency checks.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/aspera/cli/command_registry.rb', line 83 def validate!(plugin_class: nil) # Rule: every non-root parent path that appears in the children index must have # a registered CommandSpec. A missing parent means commands_under(:x) was used # without a matching command :x declaration. @children_index.each_key do |parent_path| next if parent_path.empty? # root is never a CommandSpec unless @specs.key?(parent_path) raise ArgumentError, "commands_under(#{parent_path.map(&:inspect).join(', ')}) used but #{parent_path.last.inspect} has no command declaration" end end @specs.each_value do |spec| path = spec.full_path # Rule: delegates_to must point to a known path when present (non-empty array or symbol) if spec.delegates_to dt_path = case spec.delegates_to when Symbol then [spec.delegates_to] when Array then spec.delegates_to end # An empty array [] means re-enter the root - always valid unless dt_path.empty? || @specs.key?(dt_path) raise ArgumentError, "#{path.inspect}: delegates_to #{dt_path.inspect} points to unknown path" end end # Rule: delegate_instance requires delegates_to if spec.delegate_instance && spec.delegates_to.nil? raise ArgumentError, "#{path.inspect}: delegate_instance requires delegates_to to be set" end # Rule: leaf commands with no explicit action must have a matching instance method next if spec.action # explicit action: skip next if @children_index[path]&.any? # intermediate node: skip next if spec.delegates_to || spec.entity_execute # delegated: skip next unless plugin_class implicit_method = CommandSpec.action_method(path) unless plugin_class.method_defined?(implicit_method) || plugin_class.private_method_defined?(implicit_method) raise ArgumentError, "#{path.inspect}: no action: and no method #{implicit_method} on #{plugin_class}" end end self end |