Class: Aspera::Cli::Plugins::Base
- Inherits:
-
Object
- Object
- Aspera::Cli::Plugins::Base
- Defined in:
- lib/aspera/cli/plugins/base.rb
Overview
Base class for command plugins
Defined Under Namespace
Modules: Operations
Constant Summary collapse
- FILTER_ARGS =
Shared positional argument for commands that accept an optional file name filter. Accepted types: String (shell glob matched against entry name), Regexp, or Proc. Used by node files find, and preview scan/events/trevents.
[{name: :filter, type: [String, Regexp, Proc], description: 'File name filter: String (glob), Regexp, or Proc', mandatory: false, default: nil}].freeze
Class Attribute Summary collapse
- .root_setup_method ⇒ Symbol? readonly
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
Global objects.
-
#help_path ⇒ Object
readonly
Path reached in the command tree at the moment --help was intercepted.
Class Method Summary collapse
-
.application_name(name = nil) ⇒ String
DSL class method: declare the human-readable application name shown in wizards.
-
.command(id, **kwargs) ⇒ Object
DSL class method: register a command in this plugin's registry.
- .command_registry ⇒ Object
-
.commands_under(parent, description: nil) ⇒ Object
DSL class method: scope block that sets a default parent for nested command() calls.
-
.crud_commands(api:, entity:, operations: nil, name: nil, lookup: nil, **kwargs) ⇒ Object
DSL class method: declare CRUD commands for a REST entity.
-
.declare_options(options, parse: false) ⇒ Object
Declare all options registered on this plugin class onto a Parser instance.
-
.define_action_method(path) {|keyword| ... } ⇒ Object
DSL class method: define an instance method whose name is derived from a path array.
-
.entity_display_name(entity) ⇒ Object
Derive a display name from an entity path: last segment after '/', underscores replaced by spaces, first letter capitalized.
-
.file_matcher(match_expression) ⇒ Proc
Build a filter lambda from a match expression (String glob, Regexp, Proc, or nil).
-
.option(name, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil) ⇒ Object
DSL class method: declare an option in this plugin's registry.
-
.root_setup(method_name) ⇒ Object
DSL class method: declare a setup method to run once before root dispatch.
-
.use_options(source) ⇒ Object
Include options from another plugin or OptionDeclarator module.
-
.used_option_sources ⇒ CommandRegistry
Per-class DSL registry (not inherited: each subclass gets its own instance).
Instance Method Summary collapse
-
#action_for(spec) ⇒ Symbol, Proc
Resolve the action for a leaf CommandSpec.
- #add_manual_header(_has_options = true) ⇒ Object
-
#bulk_result(items, command:, id_result: 'id', fields: :default) {|item| ... } ⇒ Result::ObjectList, Result::SingleObject
Convenience wrapper: reads :bulk and :bfail from options, normalises
itemsto an Array, then delegates to Result.bulk. - #config ⇒ Aspera::Cli::Plugins::Config
-
#dispatch_child(current_path, registry, ctx) ⇒ Object
Phase B, child branch: consume the next command argument, resolve the matching child spec, handle delegation, and recurse or execute.
-
#dispatch_from_registry(current_path, ctx = {}, skip_setup: false) ⇒ Object
Two-phase dispatcher: run setup on the current node (Phase A), then either execute a leaf directly or consume the next argument and recurse (Phase B).
-
#dispatch_leaf(current_path, spec, ctx) ⇒ Object
Phase B, leaf branch: execute a spec that is already a leaf (no children).
-
#entity_create(api:, entity:, display_fields: nil, body_component: nil, input_data: nil, data: nil) ⇒ Object
Create one or more instances of an entity (supports bulk).
-
#entity_delete(api:, entity:, id: nil, id_as_arg: false, delete_style: nil, query_component: nil) ⇒ Object
Delete one or more instances of an entity (supports bulk).
-
#entity_list(api:, entity:, display_fields: nil, items_key: nil, list_query: nil, query_component: nil) ⇒ Object
List all instances of an entity.
-
#entity_modify(api:, entity:, id: nil, is_singleton: false, id_as_arg: false, body_component: nil, input_data: nil, data: nil) ⇒ Object
Modify an existing instance of an entity.
-
#entity_res_path(entity, id, is_singleton: false, id_as_arg: false) ⇒ String
Build the resource path for an instance operation.
-
#entity_show(api:, entity:, id: nil, display_fields: nil, is_singleton: false, id_as_arg: false) ⇒ Object
Show one instance of an entity.
-
#execute_action ⇒ Object
Entry point for all DSL-based plugins.
-
#execute_leaf(spec, ctx) ⇒ Object
Execute a leaf CommandSpec: resolve arguments and call action.
- #formatter ⇒ Aspera::Cli::Formatter
-
#generate_help(path = []) ⇒ Hash
Build a nested Hash tree of the registered command tree for help display.
- #http_config ⇒ Aspera::Cli::Http
-
#initialize(context:) ⇒ Base
constructor
A new instance of Base.
-
#invoke_action(action, args, ctx) ⇒ Object
Invoke an action (Symbol method or Proc block) with the given positional arguments and keyword context.
- #options ⇒ Aspera::Cli::Parser
- #persistency ⇒ Aspera::PersistencyFolder
- #presets ⇒ Aspera::Cli::PresetManager
- #progress_bar ⇒ Aspera::Cli::TransferProgress?
-
#query_read_delete(default: nil, schema: nil) ⇒ Hash?
Query parameters in URL suitable for REST: list/
GETand delete/DELETE. -
#resolve_argument(arg_spec) {|field, value| ... } ⇒ Object
Resolve a single positional argument from the CLI argument stream.
- #transfer ⇒ Aspera::Cli::TransferAgent
Constructor Details
#initialize(context:) ⇒ Base
Returns a new instance of Base.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/aspera/cli/plugins/base.rb', line 284 def initialize(context:) Aspera.assert_type(context, Context) { 'context' } Aspera.assert_type(context.man_header, TrueClass, FalseClass) { 'context.man_header' } @context = context # Switch to the plugin-specific options group so that all options declared # below (DSL-registered and imperative) appear under the plugin section in # --help output, separate from the global options. .group(self.class.name.split('::').last.downcase) if @context.man_header # Auto-declare all options registered via the DSL `option` class method. # Walk the ancestor chain so that options declared on parent plugin classes # (e.g. Oauth, BasicAuth) are also registered for sub-classes (e.g. Aoc). # The options object is shared across all plugins in a run; skip options already # declared by an earlier plugin (Base.option prevents duplicates within one hierarchy). # Each OptionSpec is translated to an options.declare call, resolving the # handler: shorthand: # Symbol handler: {o: self, m: <symbol>} (Category B - plugin instance methods) # Hash handler: used as-is (Category A - singletons / class constants) sources = [] self.class.ancestors.each do |klass| next unless klass.is_a?(Class) && klass <= Base sources << klass if klass.instance_variable_defined?(:@command_registry) sources.concat(klass.used_option_sources) if klass.respond_to?(:used_option_sources) end sources.uniq.each do |src| specs = if src.respond_to?(:command_registry) src.command_registry.option_specs elsif src.respond_to?(:option_specs) src.option_specs else {} end specs.each_value do |spec| next if .option_declared?(spec.name) resolved_handler = case spec.handler when Symbol then {o: self, m: spec.handler} when Hash then spec.handler end .declare( spec.name, description: spec.description, short: spec.short, allowed: spec.allowed, default: spec.default, handler: resolved_handler, deprecation: spec.deprecation, schema: spec.schema ) end end end |
Class Attribute Details
.root_setup_method ⇒ Symbol? (readonly)
249 250 251 |
# File 'lib/aspera/cli/plugins/base.rb', line 249 def root_setup_method @root_setup_method end |
Instance Attribute Details
#context ⇒ Object (readonly)
Global objects
338 339 340 |
# File 'lib/aspera/cli/plugins/base.rb', line 338 def context @context end |
#help_path ⇒ Object (readonly)
Path reached in the command tree at the moment --help was intercepted. Nil until set by dispatch_from_registry.
341 342 343 |
# File 'lib/aspera/cli/plugins/base.rb', line 341 def help_path @help_path end |
Class Method Details
.application_name(name = nil) ⇒ String
DSL class method: declare the human-readable application name shown in wizards. When called with an argument, sets the name. When called with no argument, returns it. Falls back to the last component of the class name if never set.
256 257 258 259 |
# File 'lib/aspera/cli/plugins/base.rb', line 256 def application_name(name = nil) @application_name = name unless name.nil? @application_name || self.name.split('::').last end |
.command(id, **kwargs) ⇒ Object
DSL class method: register a command in this plugin's registry. Inherits parent from the enclosing commands_under block when parent: is omitted.
47 48 49 50 |
# File 'lib/aspera/cli/plugins/base.rb', line 47 def command(id, **kwargs) kwargs[:parent] = @current_parent if kwargs[:parent].nil? && @current_parent command_registry.register(CommandSpec.new(id: id, **kwargs)) end |
.command_registry ⇒ Object
39 40 41 |
# File 'lib/aspera/cli/plugins/base.rb', line 39 def command_registry @command_registry ||= CommandRegistry.new end |
.commands_under(parent, description: nil) ⇒ Object
DSL class method: scope block that sets a default parent for nested command() calls.
Fully re-entrant: blocks may be nested for multi-level parent paths.
If the terminal node of parent has not been declared yet, it is auto-declared
as an intermediate command with description: "Manage
parent is always resolved relative to the current scope:
Array(@current_parent) + Array(parent)
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/aspera/cli/plugins/base.rb', line 137 def commands_under(parent, description: nil) # Always relative: append the given segments to the current scope. path = Array(@current_parent) + Array(parent) unless command_registry[path] id = path.last desc = description || "Manage #{entity_display_name(id)}" parent_path = path[0..-2] saved = @current_parent @current_parent = parent_path.empty? ? nil : parent_path command(id, description: desc) @current_parent = saved end previous = @current_parent @current_parent = path yield ensure @current_parent = previous end |
.crud_commands(api:, entity:, operations: nil, name: nil, lookup: nil, **kwargs) ⇒ Object
DSL class method: declare CRUD commands for a REST entity.
For each verb in operations:, registers one CommandSpec with:
- description: "#{verb.capitalize} #{name}"
- arguments: [{name: :id, type: :identifier, lookup: lookup}] for instance verbs
(:show, :modify, :delete) when not a singleton; none for global verbs
- action: calls entity_<verb>(api:, entity:, **shared_kwargs, **ctx)
api: is resolved at runtime: :@ivar -> instance_variable_get, else -> send. entity: may also be a Symbol — resolved at runtime as a ctx key (e.g. :sf_entity). This covers cases where the entity path is injected by a parent setup: method.
77 78 79 80 81 82 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 |
# File 'lib/aspera/cli/plugins/base.rb', line 77 def crud_commands(api:, entity:, operations: nil, name: nil, lookup: nil, **kwargs) name ||= entity_display_name(entity) unless entity.is_a?(Symbol) operations ||= Operations::ALL operations.each do |verb| id_arg = ({name: :id, type: :identifier, lookup: lookup} if Operations::INSTANCE.include?(verb) && !kwargs[:is_singleton]) schema_val = if kwargs[:body_component] && entity.is_a?(String) case verb when :create then Schema::Registry.req_body(kwargs[:body_component], "#{entity}.post") when :modify then Schema::Registry.req_body(kwargs[:body_component], "#{entity}/{id}.put") end end args = case verb when :create [{name: :data, type: Hash, bulk: true, schema: schema_val}] when :modify [id_arg, {name: :data, type: Hash, schema: schema_val}].compact when :delete id_arg ? [id_arg.merge(bulk: true)] : nil else id_arg ? [id_arg] : nil end action_proc = lambda do |**ctx| resolved_api = if api.is_a?(Symbol) api.to_s.start_with?('@') ? instance_variable_get(api) : send(api) elsif api.is_a?(Proc) instance_exec(&api) else api end resolved_entity = entity.is_a?(Symbol) ? ctx.fetch(entity) : entity send(:"entity_#{verb}", api: resolved_api, entity: resolved_entity, **kwargs, **ctx) end cmd_attrs = {description: "#{verb.capitalize} #{name || entity.inspect}", action: action_proc} cmd_attrs[:arguments] = args if args command(verb, **cmd_attrs) end end |
.declare_options(options, parse: false) ⇒ Object
Declare all options registered on this plugin class onto a Parser instance.
Walks inherited options and any sources added via use_options.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/aspera/cli/plugins/base.rb', line 202 def (, parse: false) sources = [] ancestors.each do |klass| next unless klass.is_a?(Class) && klass <= Base sources << klass if klass.instance_variable_defined?(:@command_registry) sources.concat(klass.used_option_sources) if klass.respond_to?(:used_option_sources) end sources.uniq.each do |src| specs = if src.respond_to?(:command_registry) src.command_registry.option_specs elsif src.respond_to?(:option_specs) src.option_specs else {} end specs.each_value do |spec| next if .option_declared?(spec.name) resolved_handler = case spec.handler when Hash then spec.handler end .declare( spec.name, description: spec.description, short: spec.short, allowed: spec.allowed, default: spec.default, handler: resolved_handler, deprecation: spec.deprecation, schema: spec.schema ) end end . if parse end |
.define_action_method(path) {|keyword| ... } ⇒ Object
DSL class method: define an instance method whose name is derived from a path array. Equivalent to: define_method(CommandSpec.action_method(path), &block)
122 123 124 |
# File 'lib/aspera/cli/plugins/base.rb', line 122 def define_action_method(path, &block) define_method(CommandSpec.action_method(path), &block) end |
.entity_display_name(entity) ⇒ Object
Derive a display name from an entity path: last segment after '/', underscores replaced by spaces, first letter capitalized. e.g. 'data/smtp_server' -> 'Smtp server', 'data/transfer_settings' -> 'Transfer settings'
55 56 57 |
# File 'lib/aspera/cli/plugins/base.rb', line 55 def entity_display_name(entity) entity.to_s.split('/').last.tr('_', ' ').capitalize end |
.file_matcher(match_expression) ⇒ Proc
Build a filter lambda from a match expression (String glob, Regexp, Proc, or nil).
264 265 266 267 268 269 270 271 272 |
# File 'lib/aspera/cli/plugins/base.rb', line 264 def file_matcher(match_expression) case match_expression when Proc then match_expression when Regexp then ->(f) { f['name'].match?(match_expression) } when String then ->(f) { File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH) } when NilClass then ->(_) { true } else Aspera.error_unexpected_value(match_expression.class.name, type: ParameterError) end end |
.option(name, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil) ⇒ Object
DSL class method: declare an option in this plugin's registry. Metadata is stored as an OptionSpec at class-load time; the actual options.declare call happens in Base#initialize once the instance exists.
Raises ArgumentError at class-load time if the same option name is already declared by any ancestor class, preventing silent shadowing.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/aspera/cli/plugins/base.rb', line 175 def option(name, description: nil, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil) ancestor_owner = ancestors.drop(1).find do |klass| klass.is_a?(Class) && klass <= Base && klass.instance_variable_defined?(:@command_registry) && klass.command_registry.option_specs.key?(name) end raise ArgumentError, "#{self}: option :#{name} already declared in ancestor #{ancestor_owner}" if ancestor_owner command_registry.register_option( OptionSpec.new( name: name, description: description, short: short, allowed: allowed, default: default, handler: handler, deprecation: deprecation, schema: schema ) ) end |
.root_setup(method_name) ⇒ Object
DSL class method: declare a setup method to run once before root dispatch. The method is called before any command is consumed, and its return value (a Hash) is merged into the initial ctx. This is useful when conditions on root commands depend on state built during setup (e.g. @connection_type).
244 245 246 |
# File 'lib/aspera/cli/plugins/base.rb', line 244 def root_setup(method_name) @root_setup_method = method_name end |
.use_options(source) ⇒ Object
Include options from another plugin or OptionDeclarator module.
35 36 37 |
# File 'lib/aspera/cli/plugins/base.rb', line 35 def (source) used_option_sources << source unless used_option_sources.include?(source) end |
.used_option_sources ⇒ CommandRegistry
Per-class DSL registry (not inherited: each subclass gets its own instance).
29 30 31 |
# File 'lib/aspera/cli/plugins/base.rb', line 29 def used_option_sources @used_option_sources ||= [] end |
Instance Method Details
#action_for(spec) ⇒ Symbol, Proc
Resolve the action for a leaf CommandSpec. Returns spec.action (Symbol or Proc) if explicitly set; otherwise derives a Symbol from the full path as :action_<path_segment_1><path_segment_2>... (e.g. [:access_key, :list] -> :action_access_key_list).
503 504 505 |
# File 'lib/aspera/cli/plugins/base.rb', line 503 def action_for(spec) spec.action || spec.action_method_name end |
#add_manual_header(_has_options = true) ⇒ Object
360 361 362 363 |
# File 'lib/aspera/cli/plugins/base.rb', line 360 def add_manual_header( = true) # No-op: the group is set at the start of initialize. # Kept for compatibility with Config, which calls add_manual_header(false) from Runner. end |
#bulk_result(items, command:, id_result: 'id', fields: :default) {|item| ... } ⇒ Result::ObjectList, Result::SingleObject
Convenience wrapper: reads :bulk and :bfail from options, normalises items
to an Array, then delegates to Result.bulk.
Use this in action methods instead of the three-line boilerplate:
is_bulk = options.get_option(:bulk)
items = x.is_a?(Array) ? x : [x]
Result.bulk(items, is_bulk: is_bulk, ...)
628 629 630 631 632 633 634 635 636 637 638 639 |
# File 'lib/aspera/cli/plugins/base.rb', line 628 def bulk_result(items, command:, id_result: 'id', fields: :default, &block) items = items.is_a?(Array) ? items : [items] Result.bulk( items, is_bulk: .get_option(:bulk), command: command, id_result: id_result, fields: fields, bfail: .get_option(:bfail), &block ) end |
#config ⇒ Aspera::Cli::Plugins::Config
348 |
# File 'lib/aspera/cli/plugins/base.rb', line 348 def config; @context.config; end |
#dispatch_child(current_path, registry, ctx) ⇒ Object
Phase B, child branch: consume the next command argument, resolve the matching child spec, handle delegation, and recurse or execute. --help is intercepted at two points:
1. Before get_next_command when no positional arg is pending: raises HelpRequest
immediately so the subcommand list with descriptions is shown rather than a
MissingArgument error.
2. After get_next_command when no further args remain: raises HelpRequest scoped
to the consumed command (e.g. `aoc files find -h`).
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/aspera/cli/plugins/base.rb', line 462 def dispatch_child(current_path, registry, ctx) children = registry.children_of(current_path) available = children.reject { |_, c| c.condition && !send(c.condition) } aliases = children.values.each_with_object({}) do |c, h| Array(c.aliases).each { |a| h[a] = c.id } if c.aliases end # Intercept --help before consuming the command token when no arg is pending. # This avoids MissingArgument being raised by get_next_command before HelpRequest. if .help_requested && .command_or_arg_empty? @help_path = current_path raise Cli::HelpRequest, self end command = .get_next_command(available.keys, aliases: aliases.empty? ? nil : aliases) child = available[command] # Intercept --help after a command was consumed but no further args remain. # (e.g. `aoc files find -h`). When further args remain, keep recursing. if .help_requested && .command_or_arg_empty? @help_path = current_path + [command] raise Cli::HelpRequest, self end # Instance delegation: hand off to a different plugin object if child.delegate_instance target = send(child.delegate_instance) return target.dispatch_from_registry(Array(child.delegates_to), {}) end return dispatch_from_registry(Array(child.delegates_to), ctx) if child.delegates_to # Both intermediate and leaf: instance_arg + setup are handled by Phase A of the next call dispatch_from_registry(current_path + [command], ctx) end |
#dispatch_from_registry(current_path, ctx = {}, skip_setup: false) ⇒ Object
Two-phase dispatcher: run setup on the current node (Phase A), then either execute a leaf directly or consume the next argument and recurse (Phase B).
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/aspera/cli/plugins/base.rb', line 390 def dispatch_from_registry(current_path, ctx = {}, skip_setup: false) registry = self.class.command_registry spec = registry[current_path] is_leaf = spec && registry.children_of(current_path).empty? if .help_requested || skip_setup # help_requested on an intermediate node: drain positional args without validation # so that dispatch_child can still consume the correct sub-command token if !is_leaf && !skip_setup && spec&.arguments spec.arguments.each do |arg_spec| next if ctx.key?(arg_spec.name) .get_next_argument(arg_spec.name.to_s, mandatory: false) end end else # Phase A - for intermediate nodes only: resolve all ArgumentSpec declared on this node # before dispatching to children (leaf nodes resolve their arguments inside execute_leaf). if !is_leaf (spec&.arguments || []).each do |arg_spec| next if ctx.key?(arg_spec.name) if arg_spec.type.eql?(:identifier) lookup_cb = arg_spec.lookup res_id = if lookup_cb.nil? .instance_identifier(description: arg_spec.name.to_s) elsif lookup_cb.is_a?(Symbol) .instance_identifier(description: arg_spec.name.to_s) { |f, v| send(lookup_cb, f, v, **ctx) } else .instance_identifier(description: arg_spec.name.to_s) { |f, v| instance_exec(f, v, **ctx, &lookup_cb) } end ctx = ctx.merge(arg_spec.name => res_id) else ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec)) end end end ctx = ctx.merge(send(spec.setup, **ctx)) if spec&.setup end # Phase B - leaf fast-path or child dispatch if is_leaf dispatch_leaf(current_path, spec, ctx) else dispatch_child(current_path, registry, ctx) end end |
#dispatch_leaf(current_path, spec, ctx) ⇒ Object
Phase B, leaf branch: execute a spec that is already a leaf (no children). Intercepts --help before calling execute_leaf.
442 443 444 445 446 447 448 |
# File 'lib/aspera/cli/plugins/base.rb', line 442 def dispatch_leaf(current_path, spec, ctx) if .help_requested @help_path = current_path raise Cli::HelpRequest, self end execute_leaf(spec, ctx) end |
#entity_create(api:, entity:, display_fields: nil, body_component: nil, input_data: nil, data: nil) ⇒ Object
Create one or more instances of an entity (supports bulk).
691 692 693 694 695 696 697 698 699 700 701 702 703 |
# File 'lib/aspera/cli/plugins/base.rb', line 691 def entity_create(api:, entity:, display_fields: nil, body_component: nil, input_data: nil, data: nil, **) schema = body_component ? Schema::Registry.req_body(body_component, "#{entity}.post") : nil input_data ||= data unless input_data is_bulk = .get_option(:bulk) raw = .get_next_argument('data', validation: is_bulk ? Array : Hash, schema: schema) input_data = is_bulk ? raw : [raw] end input_data = [input_data] unless input_data.is_a?(Array) bulk_result(input_data, command: :create, fields: display_fields) do |params| api.create(entity, params) end end |
#entity_delete(api:, entity:, id: nil, id_as_arg: false, delete_style: nil, query_component: nil) ⇒ Object
Delete one or more instances of an entity (supports bulk).
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 |
# File 'lib/aspera/cli/plugins/base.rb', line 728 def entity_delete(api:, entity:, id: nil, id_as_arg: false, delete_style: nil, query_component: nil, **) qs_path = query_component ? Schema::Registry.query_params(query_component, entity) : nil if !delete_style.nil? ids = id.is_a?(Array) ? id : [id] Aspera.assert_type(ids, Array, type: Cli::BadArgument) api.delete(entity, nil, content_type: Mime::JSON, body: {delete_style => ids}) return Result::Status.new('deleted') end bulk_result(id, command: :delete) do |one_id| api.delete( id_as_arg ? "#{entity}?#{id_as_arg}=#{one_id}" : "#{entity}/#{one_id}", query_read_delete(schema: qs_path) ) {'id' => one_id} end end |
#entity_list(api:, entity:, display_fields: nil, items_key: nil, list_query: nil, query_component: nil) ⇒ Object
List all instances of an entity.
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 |
# File 'lib/aspera/cli/plugins/base.rb', line 654 def entity_list(api:, entity:, display_fields: nil, items_key: nil, list_query: nil, query_component: nil, **) qs_path = query_component ? Schema::Registry.query_params(query_component, entity) : nil data, http = api.read(entity, query_read_delete(default: list_query, schema: qs_path), ret: :both) return Result::Empty.new if http.code == '204' # TODO: not generic : which application is this for ? if http['Content-Type'].start_with?('application/vnd.api+json') Log.log.debug('is vnd.api') data = data[entity] end data = data[items_key] if items_key case data when Hash then Result::SingleObject.new(data, fields: display_fields) when Array return Result::ObjectList.new(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash) Result::ValueList.new(data) else Aspera.error_unexpected_value(data.class.name) { 'list type' } end end |
#entity_modify(api:, entity:, id: nil, is_singleton: false, id_as_arg: false, body_component: nil, input_data: nil, data: nil) ⇒ Object
Modify an existing instance of an entity.
713 714 715 716 717 718 719 |
# File 'lib/aspera/cli/plugins/base.rb', line 713 def entity_modify(api:, entity:, id: nil, is_singleton: false, id_as_arg: false, body_component: nil, input_data: nil, data: nil, **) schema = body_component ? Schema::Registry.req_body(body_component, "#{entity}/{id}.put") : nil path = entity_res_path(entity, id, is_singleton: is_singleton, id_as_arg: id_as_arg) parameters = input_data || data || .get_next_argument('data', validation: Hash, schema: schema) api.update(path, parameters) Result::Status.new('modified') end |
#entity_res_path(entity, id, is_singleton: false, id_as_arg: false) ⇒ String
Build the resource path for an instance operation.
751 752 753 754 755 |
# File 'lib/aspera/cli/plugins/base.rb', line 751 def entity_res_path(entity, id, is_singleton: false, id_as_arg: false) return entity if is_singleton return "#{entity}?#{id_as_arg}=#{id}" if id_as_arg "#{entity}/#{id}" end |
#entity_show(api:, entity:, id: nil, display_fields: nil, is_singleton: false, id_as_arg: false) ⇒ Object
Show one instance of an entity.
680 681 682 683 |
# File 'lib/aspera/cli/plugins/base.rb', line 680 def entity_show(api:, entity:, id: nil, display_fields: nil, is_singleton: false, id_as_arg: false, **) path = entity_res_path(entity, id, is_singleton: is_singleton, id_as_arg: id_as_arg) Result::SingleObject.new(api.read(path), fields: display_fields) end |
#execute_action ⇒ Object
Entry point for all DSL-based plugins.
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/aspera/cli/plugins/base.rb', line 366 def execute_action @help_path = nil # Validate the registry once per class (memoised by the ivar check). # Passes the plugin class so implicit handler methods can be verified. unless self.class.instance_variable_defined?(:@registry_validated) self.class.command_registry.validate!(plugin_class: self.class) self.class.instance_variable_set(:@registry_validated, true) end # Run the root setup (if declared) before consuming any argument. # This ensures condition methods on root commands can read instance variables # populated by the setup (e.g. @connection_type in server.rb). init_ctx = {} if (rsm = self.class.root_setup_method) init_ctx = send(rsm) || {} end dispatch_from_registry([], init_ctx) end |
#execute_leaf(spec, ctx) ⇒ Object
Execute a leaf CommandSpec: resolve arguments and call action.
Arguments already present in ctx (pre-resolved by a parent plugin, e.g. aoc.rb forwarding
path: into execute_nodegen4_command) are skipped — the token has already been consumed.
instance_arg (if any) is resolved here as an ArgumentSpec(type: :identifier) and merged
into ctx, exactly like any other keyword argument received by the action.
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/aspera/cli/plugins/base.rb', line 530 def execute_leaf(spec, ctx) a = action_for(spec) # Always resolve declared arguments (even when transfer_paths is set — those arguments # are consumed first; ts_source_paths then reads whatever remains in the queue). (spec.arguments || []).each do |arg_spec| next if ctx.key?(arg_spec.name) if arg_spec.type.eql?(:identifier) lookup_cb = arg_spec.lookup block = if lookup_cb.nil? then nil elsif lookup_cb.is_a?(Symbol) then ->(f, v) { send(lookup_cb, f, v, **ctx) } else ->(f, v) { instance_exec(f, v, **ctx, &lookup_cb) } end ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec, &block)) else ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec)) end end invoke_action(a, [], ctx) end |
#formatter ⇒ Aspera::Cli::Formatter
350 |
# File 'lib/aspera/cli/plugins/base.rb', line 350 def formatter; @context.formatter; end |
#generate_help(path = []) ⇒ Hash
Build a nested Hash tree of the registered command tree for help display. Conditional commands are included with a '[condition_name]' annotation.
605 606 607 608 609 610 611 612 613 614 |
# File 'lib/aspera/cli/plugins/base.rb', line 605 def generate_help(path = []) self.class.command_registry.children_of(path).transform_values do |child_spec| annotation = child_spec.condition ? " [#{child_spec.condition}]" : '' { description: "#{child_spec.description}#{annotation}", condition: child_spec.condition, children: generate_help(child_spec.full_path) } end end |
#http_config ⇒ Aspera::Cli::Http
356 |
# File 'lib/aspera/cli/plugins/base.rb', line 356 def http_config; @context.http_config; end |
#invoke_action(action, args, ctx) ⇒ Object
Invoke an action (Symbol method or Proc block) with the given positional
arguments and keyword context.
Procs are executed via instance_exec so they share the plugin's self.
514 515 516 517 518 519 520 |
# File 'lib/aspera/cli/plugins/base.rb', line 514 def invoke_action(action, args, ctx) if action.is_a?(Proc) instance_exec(*args, **ctx, &action) else send(action, *args, **ctx) end end |
#options ⇒ Aspera::Cli::Parser
344 |
# File 'lib/aspera/cli/plugins/base.rb', line 344 def ; @context.; end |
#persistency ⇒ Aspera::PersistencyFolder
352 |
# File 'lib/aspera/cli/plugins/base.rb', line 352 def persistency; @context.persistency; end |
#presets ⇒ Aspera::Cli::PresetManager
354 |
# File 'lib/aspera/cli/plugins/base.rb', line 354 def presets; @context.presets; end |
#progress_bar ⇒ Aspera::Cli::TransferProgress?
358 |
# File 'lib/aspera/cli/plugins/base.rb', line 358 def ; @context.; end |
#query_read_delete(default: nil, schema: nil) ⇒ Hash?
Query parameters in URL suitable for REST: list/GET and delete/DELETE
761 762 763 764 765 766 767 768 769 770 771 772 |
# File 'lib/aspera/cli/plugins/base.rb', line 761 def query_read_delete(default: nil, schema: nil) # Dup default, as it could be frozen query = .get_option(:query, schema: schema) || default&.dup Log.dump(:query_read_delete, query) begin # Check it is suitable URI.encode_www_form(query) unless query.nil? rescue StandardError => e raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.})" end return query end |
#resolve_argument(arg_spec) {|field, value| ... } ⇒ Object
Resolve a single positional argument from the CLI argument stream. When arg_spec.bulk is true, always returns an Array (normalized to [value] when non-bulk). For type: :identifier, an optional block provides the percent-selector lookup.
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
# File 'lib/aspera/cli/plugins/base.rb', line 559 def resolve_argument(arg_spec, &block) if arg_spec.bulk is_bulk = .get_option(:bulk) if arg_spec.type.eql?(:identifier) val = .instance_identifier(description: arg_spec.name.to_s, &block) else val = .get_next_argument( arg_spec.name.to_s, mandatory: arg_spec.mandatory, validation: is_bulk ? Array : arg_spec.type, default: arg_spec.default, schema: arg_spec.schema ) if is_bulk Aspera.assert_array_all(val, arg_spec.type, type: Cli::BadArgument) { 'type' } unless arg_spec.type.nil? end end # Always return an Array when bulk: true is_bulk ? val : [val] else case arg_spec.type when :identifier .instance_identifier(description: arg_spec.name.to_s, &block) else # Class or Array<Class> -> pass as validation type # When interactive: true, set ask_missing_mandatory so that get_interactive is triggered # when no CLI arguments are provided (mandatory is forced to true for the same reason: # a non-nil default would short-circuit get_interactive before it is ever called). .ask_missing_mandatory = true if arg_spec.interactive .get_next_argument( arg_spec.name.to_s, mandatory: arg_spec.interactive ? true : arg_spec.mandatory, multiple: arg_spec.multiple || false, validation: arg_spec.type, accept_list: arg_spec.allowed, default: arg_spec.interactive ? nil : arg_spec.default, schema: arg_spec.schema ) end end end |
#transfer ⇒ Aspera::Cli::TransferAgent
346 |
# File 'lib/aspera/cli/plugins/base.rb', line 346 def transfer; @context.transfer; end |