aspera-cli 4.26.1 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +300 -22
- data/CONTRIBUTING.md +100 -12
- data/bin/ascli +5 -5
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +27 -25
- data/lib/aspera/agent/factory.rb +4 -0
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +56 -51
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +22 -20
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +33 -30
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +25 -23
- data/lib/aspera/ascp/installation.rb +43 -40
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +71 -0
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +5 -3
- data/lib/aspera/cli/formatter.rb +122 -209
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +218 -0
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1173 -849
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +470 -107
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +434 -1032
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +7 -4
- data/lib/aspera/cli/plugins/faspex.rb +296 -259
- data/lib/aspera/cli/plugins/faspex5.rb +592 -344
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +1001 -797
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
- data/lib/aspera/cli/plugins/preview.rb +183 -142
- data/lib/aspera/cli/plugins/server.rb +125 -80
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +236 -0
- data/lib/aspera/cli/result.rb +360 -0
- data/lib/aspera/cli/runner.rb +473 -0
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +84 -56
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +26 -26
- data/lib/aspera/command_line_builder.rb +23 -23
- data/lib/aspera/data_repository.rb +6 -1
- data/lib/aspera/dot_container.rb +5 -5
- data/lib/aspera/environment.rb +39 -13
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +4 -4
- data/lib/aspera/keychain/macos_security.rb +4 -4
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +23 -5
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/nagios.rb +2 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +10 -10
- data/lib/aspera/oauth/boot.rb +43 -0
- data/lib/aspera/oauth/factory.rb +41 -15
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_action_once.rb +10 -10
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +7 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +4 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -9
- data/lib/aspera/rest.rb +18 -40
- data/lib/aspera/rest_error_analyzer.rb +7 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +13 -6
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +60 -11
- data/lib/aspera/schema/registry.rb +50 -10
- data/lib/aspera/secret_hider.rb +11 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +15 -18
- data/lib/aspera/temp_file_manager.rb +5 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +27 -25
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +7 -7
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +26 -8
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +45 -16
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/main.rb +0 -480
- data/lib/aspera/json_rpc.rb +0 -51
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/cli/extended_value'
|
|
4
|
+
require 'aspera/cli/parser'
|
|
4
5
|
require 'aspera/assert'
|
|
6
|
+
require 'aspera/cli/result'
|
|
7
|
+
require 'aspera/cli/command_registry'
|
|
8
|
+
require 'aspera/schema/registry'
|
|
5
9
|
|
|
6
10
|
module Aspera
|
|
7
11
|
module Cli
|
|
@@ -19,25 +23,168 @@ module Aspera
|
|
|
19
23
|
ALL = (GLOBAL + INSTANCE).freeze
|
|
20
24
|
end
|
|
21
25
|
class << self
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
# Per-class DSL registry (not inherited: each subclass gets its own instance).
|
|
27
|
+
# @return [CommandRegistry]
|
|
28
|
+
def command_registry
|
|
29
|
+
@command_registry ||= CommandRegistry.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# DSL class method: register a command in this plugin's registry.
|
|
33
|
+
# Inherits parent from the enclosing commands_under block when parent: is omitted.
|
|
34
|
+
# @param id [Symbol]
|
|
35
|
+
# @param kwargs [Hash] forwarded to CommandSpec
|
|
36
|
+
def command(id, **kwargs)
|
|
37
|
+
kwargs[:parent] = @current_parent if kwargs[:parent].nil? && @current_parent
|
|
38
|
+
command_registry.register(CommandSpec.new(id: id, **kwargs))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# DSL class method: shorthand for a command whose sole action is Base#entity_execute.
|
|
42
|
+
# The api: value is a Symbol resolved at runtime: if it starts with '@' it is treated
|
|
43
|
+
# as an instance variable name; otherwise it is sent as a method call.
|
|
44
|
+
# description: defaults to "Manage <last segment of entity path>" when omitted.
|
|
45
|
+
# @param id [Symbol] Command identifier
|
|
46
|
+
# @param api [Symbol] Method name or :@ivar returning the REST API at runtime
|
|
47
|
+
# @param entity [String] API sub-path (e.g. 'admin/dropboxes')
|
|
48
|
+
# @param description [String, nil] User-facing help text; derived from entity when nil
|
|
49
|
+
# @param kwargs [Hash] Any other entity_execute params (display_fields:, command:, is_singleton:, etc.)
|
|
50
|
+
def entity_command(id, api:, entity:, description: nil, **kwargs)
|
|
51
|
+
description ||= "Manage #{entity.split('/').last}"
|
|
52
|
+
command(id, description: description, entity_execute: {api: api, entity: entity, **kwargs})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# DSL class method: define an instance method whose name is derived from a path array.
|
|
56
|
+
# Equivalent to: define_method(CommandSpec.action_method(path), &block)
|
|
57
|
+
# @param path [Array<Symbol>] command path segments, e.g. [:admin, :user, :list]
|
|
58
|
+
# @yieldparam [Hash] keyword context forwarded from dispatch
|
|
59
|
+
def define_action_method(path, &block)
|
|
60
|
+
define_method(CommandSpec.action_method(path), &block)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# DSL class method: scope block that sets a default parent for nested command() calls.
|
|
64
|
+
# Fully re-entrant: blocks may be nested for multi-level parent paths.
|
|
65
|
+
# @param parent [Symbol, Array<Symbol>] parent path applied to every command() inside
|
|
66
|
+
# @yieldreturn [void]
|
|
67
|
+
def commands_under(parent)
|
|
68
|
+
previous = @current_parent
|
|
69
|
+
@current_parent = parent
|
|
70
|
+
yield
|
|
71
|
+
ensure
|
|
72
|
+
@current_parent = previous
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# DSL class method: declare an option in this plugin's registry.
|
|
76
|
+
# Metadata is stored as an OptionSpec at class-load time; the actual
|
|
77
|
+
# options.declare call happens in Base#initialize once the instance exists.
|
|
78
|
+
#
|
|
79
|
+
# Raises ArgumentError at class-load time if the same option name is already
|
|
80
|
+
# declared by any ancestor class, preventing silent shadowing.
|
|
81
|
+
#
|
|
82
|
+
# @param name [Symbol] Option name
|
|
83
|
+
# @param description [String, nil] User-facing description; if nil, derived from schema: title/description
|
|
84
|
+
# @param short [String, nil] Single-character short form (without leading '-')
|
|
85
|
+
# @param allowed [Object, nil] Allowed values (see OptionValue)
|
|
86
|
+
# @param default [Object, nil] Default value
|
|
87
|
+
# @param handler [Symbol, Hash, nil]
|
|
88
|
+
# - Symbol: resolved to {o: <plugin instance>, m: <symbol>} at runtime (Category B)
|
|
89
|
+
# - Hash: {o: <object>, m: <method>} used as-is (Category A: singletons / constants)
|
|
90
|
+
# - nil: option stores its value locally (no delegation)
|
|
91
|
+
# @param deprecation [String, nil] Deprecation message forwarded to options.declare
|
|
92
|
+
# @param schema [String, nil] Schema reference (e.g. "opts:components.schemas.Foo");
|
|
93
|
+
# when description: is nil, the schema title or first description line is used
|
|
94
|
+
def option(name, description: nil,
|
|
95
|
+
short: nil, allowed: nil, default: nil,
|
|
96
|
+
handler: nil, deprecation: nil, schema: nil)
|
|
97
|
+
ancestor_owner = ancestors.drop(1).find do |klass|
|
|
98
|
+
klass.is_a?(Class) && klass <= Base &&
|
|
99
|
+
klass.instance_variable_defined?(:@command_registry) &&
|
|
100
|
+
klass.command_registry.option_specs.key?(name)
|
|
101
|
+
end
|
|
102
|
+
raise ArgumentError, "#{self}: option :#{name} already declared in ancestor #{ancestor_owner}" if ancestor_owner
|
|
103
|
+
command_registry.register_option(
|
|
104
|
+
OptionSpec.new(
|
|
105
|
+
name: name,
|
|
106
|
+
description: description,
|
|
107
|
+
short: short,
|
|
108
|
+
allowed: allowed,
|
|
109
|
+
default: default,
|
|
110
|
+
handler: handler,
|
|
111
|
+
deprecation: deprecation,
|
|
112
|
+
schema: schema
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# DSL class method: declare a setup method to run once before root dispatch.
|
|
118
|
+
# The method is called before any command is consumed, and its return value
|
|
119
|
+
# (a Hash) is merged into the initial ctx. This is useful when conditions
|
|
120
|
+
# on root commands depend on state built during setup (e.g. @connection_type).
|
|
121
|
+
# @param method_name [Symbol]
|
|
122
|
+
def root_setup(method_name)
|
|
123
|
+
@root_setup_method = method_name
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [Symbol, nil]
|
|
127
|
+
attr_reader :root_setup_method
|
|
128
|
+
|
|
129
|
+
# DSL class method: declare the human-readable application name shown in wizards.
|
|
130
|
+
# When called with an argument, sets the name. When called with no argument, returns it.
|
|
131
|
+
# Falls back to the last component of the class name if never set.
|
|
132
|
+
# @param name [String, nil]
|
|
133
|
+
# @return [String]
|
|
134
|
+
def application_name(name = nil)
|
|
135
|
+
@application_name = name unless name.nil?
|
|
136
|
+
@application_name || self.name.split('::').last
|
|
26
137
|
end
|
|
27
138
|
end
|
|
28
139
|
|
|
140
|
+
option :query, description: 'Additional filter for for some commands (list/delete)', allowed: [Hash, Array, NilClass]
|
|
141
|
+
option :bulk, description: 'Bulk operation (only some)', allowed: Allowed::TYPES_BOOLEAN, default: false
|
|
142
|
+
option :bfail, description: 'Bulk operation error handling', allowed: Allowed::TYPES_BOOLEAN, default: true
|
|
143
|
+
|
|
29
144
|
def initialize(context:)
|
|
30
|
-
|
|
31
|
-
Aspera.
|
|
32
|
-
Aspera.assert(self.class.const_defined?(:ACTIONS), type: InternalError){"Missing constant 'ACTIONS' in #{self.class}"}
|
|
145
|
+
Aspera.assert_type(context, Context){'context'}
|
|
146
|
+
Aspera.assert_type(context.man_header, TrueClass, FalseClass){'context.man_header'}
|
|
33
147
|
@context = context
|
|
148
|
+
# Auto-declare all options registered via the DSL `option` class method.
|
|
149
|
+
# Walk the ancestor chain so that options declared on parent plugin classes
|
|
150
|
+
# (e.g. Oauth, BasicAuth) are also registered for sub-classes (e.g. Aoc).
|
|
151
|
+
# The options object is shared across all plugins in a run; skip options already
|
|
152
|
+
# declared by an earlier plugin (Base.option prevents duplicates within one hierarchy).
|
|
153
|
+
# Each OptionSpec is translated to an options.declare call, resolving the
|
|
154
|
+
# handler: shorthand:
|
|
155
|
+
# Symbol handler: {o: self, m: <symbol>} (Category B - plugin instance methods)
|
|
156
|
+
# Hash handler: used as-is (Category A - singletons / class constants)
|
|
157
|
+
self.class.ancestors.each do |klass|
|
|
158
|
+
next unless klass.is_a?(Class) && klass <= Base && klass.instance_variable_defined?(:@command_registry)
|
|
159
|
+
klass.command_registry.option_specs.each_value do |spec|
|
|
160
|
+
next if options.option_declared?(spec.name)
|
|
161
|
+
resolved_handler =
|
|
162
|
+
case spec.handler
|
|
163
|
+
when Symbol then {o: self, m: spec.handler}
|
|
164
|
+
when Hash then spec.handler
|
|
165
|
+
end
|
|
166
|
+
options.declare(
|
|
167
|
+
spec.name,
|
|
168
|
+
description: spec.description,
|
|
169
|
+
short: spec.short,
|
|
170
|
+
allowed: spec.allowed,
|
|
171
|
+
default: spec.default,
|
|
172
|
+
handler: resolved_handler,
|
|
173
|
+
deprecation: spec.deprecation,
|
|
174
|
+
schema: spec.schema
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
34
178
|
add_manual_header if @context.man_header
|
|
35
179
|
end
|
|
36
180
|
|
|
37
181
|
# Global objects
|
|
38
182
|
attr_reader :context
|
|
183
|
+
# Path reached in the command tree at the moment --help was intercepted.
|
|
184
|
+
# Nil until set by dispatch_from_registry.
|
|
185
|
+
attr_reader :help_path
|
|
39
186
|
|
|
40
|
-
# @return [Aspera::Cli::
|
|
187
|
+
# @return [Aspera::Cli::Parser]
|
|
41
188
|
def options; @context.options; end
|
|
42
189
|
# @return [Aspera::Cli::TransferAgent]
|
|
43
190
|
def transfer; @context.transfer; end
|
|
@@ -47,63 +194,274 @@ module Aspera
|
|
|
47
194
|
def formatter; @context.formatter; end
|
|
48
195
|
# @return [Aspera::PersistencyFolder]
|
|
49
196
|
def persistency; @context.persistency; end
|
|
197
|
+
# @return [Aspera::Cli::PresetManager]
|
|
198
|
+
def presets; @context.presets; end
|
|
199
|
+
# @return [Aspera::Cli::Http]
|
|
200
|
+
def http_config; @context.http_config; end
|
|
201
|
+
# @return [Aspera::Cli::TransferProgress, nil]
|
|
202
|
+
def progress_bar; @context.progress_bar; end
|
|
50
203
|
|
|
51
|
-
def add_manual_header(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# Create -> created
|
|
94
|
-
result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
|
|
95
|
-
rescue StandardError => e
|
|
96
|
-
raise e if options.get_option(:bfail)
|
|
97
|
-
result['status'] = e.to_s
|
|
204
|
+
def add_manual_header(_has_options = true)
|
|
205
|
+
options.rename_current_group(self.class.name.split('::').last.downcase)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Entry point for all DSL-based plugins.
|
|
209
|
+
def execute_action
|
|
210
|
+
@help_path = nil
|
|
211
|
+
# Validate the registry once per class (memoised by the ivar check).
|
|
212
|
+
# Passes the plugin class so implicit handler methods can be verified.
|
|
213
|
+
unless self.class.instance_variable_defined?(:@registry_validated)
|
|
214
|
+
self.class.command_registry.validate!(plugin_class: self.class)
|
|
215
|
+
self.class.instance_variable_set(:@registry_validated, true)
|
|
216
|
+
end
|
|
217
|
+
# Run the root setup (if declared) before consuming any argument.
|
|
218
|
+
# This ensures condition methods on root commands can read instance variables
|
|
219
|
+
# populated by the setup (e.g. @connection_type in server.rb).
|
|
220
|
+
init_ctx = {}
|
|
221
|
+
if (rsm = self.class.root_setup_method)
|
|
222
|
+
init_ctx = send(rsm) || {}
|
|
223
|
+
end
|
|
224
|
+
dispatch_from_registry([], init_ctx)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Two-phase dispatcher: run setup on the current node (Phase A), then either
|
|
228
|
+
# execute a leaf directly or consume the next argument and recurse (Phase B).
|
|
229
|
+
# @param current_path [Array<Symbol>] path of the node currently being dispatched
|
|
230
|
+
# @param ctx [Hash] accumulated context passed down from parent nodes
|
|
231
|
+
# @param skip_setup [Boolean] when true, skip Phase A (setup already done by caller)
|
|
232
|
+
# @return [Object] result suitable for CLI output
|
|
233
|
+
def dispatch_from_registry(current_path, ctx = {}, skip_setup: false)
|
|
234
|
+
registry = self.class.command_registry
|
|
235
|
+
spec = registry[current_path]
|
|
236
|
+
is_leaf = spec && registry.children_of(current_path).empty?
|
|
237
|
+
|
|
238
|
+
if options.help_requested || skip_setup
|
|
239
|
+
# help_requested on an intermediate node: drain positional args without validation
|
|
240
|
+
# so that dispatch_child can still consume the correct sub-command token
|
|
241
|
+
if !is_leaf && !skip_setup && spec&.arguments
|
|
242
|
+
spec.arguments.each do |arg_spec|
|
|
243
|
+
next if ctx.key?(arg_spec.name)
|
|
244
|
+
options.get_next_argument(arg_spec.name.to_s, mandatory: false)
|
|
245
|
+
end
|
|
98
246
|
end
|
|
99
|
-
|
|
247
|
+
else
|
|
248
|
+
# Phase A - for intermediate nodes only: resolve all ArgumentSpec declared on this node
|
|
249
|
+
# before dispatching to children (leaf nodes resolve their arguments inside execute_leaf).
|
|
250
|
+
if !is_leaf
|
|
251
|
+
(spec&.arguments || []).each do |arg_spec|
|
|
252
|
+
next if ctx.key?(arg_spec.name)
|
|
253
|
+
if arg_spec.type.eql?(:identifier)
|
|
254
|
+
lookup_method = arg_spec.lookup
|
|
255
|
+
res_id = if lookup_method
|
|
256
|
+
options.instance_identifier(description: arg_spec.name.to_s){ |f, v| send(lookup_method, f, v, **ctx)}
|
|
257
|
+
else
|
|
258
|
+
options.instance_identifier(description: arg_spec.name.to_s)
|
|
259
|
+
end
|
|
260
|
+
ctx = ctx.merge(arg_spec.name => res_id)
|
|
261
|
+
else
|
|
262
|
+
ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec))
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
ctx = ctx.merge(send(spec.setup, **ctx)) if spec&.setup
|
|
100
267
|
end
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
268
|
+
|
|
269
|
+
# Phase B - leaf fast-path or child dispatch
|
|
270
|
+
if is_leaf
|
|
271
|
+
dispatch_leaf(current_path, spec, ctx)
|
|
104
272
|
else
|
|
105
|
-
|
|
106
|
-
|
|
273
|
+
dispatch_child(current_path, registry, ctx)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Phase B, leaf branch: execute a spec that is already a leaf (no children).
|
|
278
|
+
# Intercepts --help before calling execute_leaf.
|
|
279
|
+
# @param current_path [Array<Symbol>]
|
|
280
|
+
# @param spec [CommandSpec]
|
|
281
|
+
# @param ctx [Hash]
|
|
282
|
+
# @return [Object]
|
|
283
|
+
def dispatch_leaf(current_path, spec, ctx)
|
|
284
|
+
if options.help_requested
|
|
285
|
+
@help_path = current_path
|
|
286
|
+
raise Cli::HelpRequest, self
|
|
287
|
+
end
|
|
288
|
+
execute_leaf(spec, ctx)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Phase B, child branch: consume the next command argument, resolve the matching
|
|
292
|
+
# child spec, handle delegation / entity_execute shorthands, and recurse or execute.
|
|
293
|
+
# @param current_path [Array<Symbol>]
|
|
294
|
+
# @param registry [CommandRegistry]
|
|
295
|
+
# @param ctx [Hash]
|
|
296
|
+
# @return [Object]
|
|
297
|
+
def dispatch_child(current_path, registry, ctx)
|
|
298
|
+
children = registry.children_of(current_path)
|
|
299
|
+
available = children.reject{ |_, c| c.condition && !send(c.condition)}
|
|
300
|
+
aliases = children.values.each_with_object({}) do |c, h|
|
|
301
|
+
Array(c.aliases).each{ |a| h[a] = c.id} if c.aliases
|
|
302
|
+
end
|
|
303
|
+
command = options.get_next_command(available.keys, aliases: aliases.empty? ? nil : aliases)
|
|
304
|
+
child = available[command]
|
|
305
|
+
|
|
306
|
+
# Intercept --help when no more positional args remain after consuming this command.
|
|
307
|
+
# (e.g. `aoc files -h` or `aoc files find -h`). When args remain, keep recursing.
|
|
308
|
+
if options.help_requested && options.command_or_arg_empty?
|
|
309
|
+
@help_path = current_path + [command]
|
|
310
|
+
raise Cli::HelpRequest, self
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Instance delegation: hand off to a different plugin object
|
|
314
|
+
if child.delegate_instance
|
|
315
|
+
target = send(child.delegate_instance)
|
|
316
|
+
return target.dispatch_from_registry(Array(child.delegates_to), {})
|
|
317
|
+
end
|
|
318
|
+
return dispatch_from_registry(Array(child.delegates_to), ctx) if child.delegates_to
|
|
319
|
+
|
|
320
|
+
# entity_execute shorthand
|
|
321
|
+
return run_entity_execute(child, ctx) if child.entity_execute
|
|
322
|
+
|
|
323
|
+
# Both intermediate and leaf: instance_arg + setup are handled by Phase A of the next call
|
|
324
|
+
dispatch_from_registry(current_path + [command], ctx)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Resolve the action for a leaf CommandSpec.
|
|
328
|
+
# Returns spec.action (Symbol or Proc) if explicitly set; otherwise derives a Symbol
|
|
329
|
+
# from the full path as :action_<path_segment_1>_<path_segment_2>_...
|
|
330
|
+
# (e.g. [:access_key, :list] -> :action_access_key_list).
|
|
331
|
+
# @param spec [CommandSpec]
|
|
332
|
+
# @return [Symbol, Proc]
|
|
333
|
+
def action_for(spec)
|
|
334
|
+
spec.action || spec.action_method_name
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Invoke an action (Symbol method or Proc block) with the given positional
|
|
338
|
+
# arguments and keyword context.
|
|
339
|
+
# Procs are executed via instance_exec so they share the plugin's `self`.
|
|
340
|
+
# @param action [Symbol, Proc]
|
|
341
|
+
# @param args [Array] positional arguments
|
|
342
|
+
# @param ctx [Hash] keyword context
|
|
343
|
+
# @return [Object]
|
|
344
|
+
def invoke_action(action, args, ctx)
|
|
345
|
+
if action.is_a?(Proc)
|
|
346
|
+
instance_exec(*args, **ctx, &action)
|
|
347
|
+
else
|
|
348
|
+
send(action, *args, **ctx)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Execute a leaf CommandSpec: resolve arguments (or skip for transfer_paths) and call action.
|
|
353
|
+
# instance_arg (if any) is resolved here as an ArgumentSpec(type: :identifier) and merged
|
|
354
|
+
# into ctx, exactly like any other keyword argument received by the action.
|
|
355
|
+
# @param spec [CommandSpec] a leaf node (no children)
|
|
356
|
+
# @param ctx [Hash] accumulated context
|
|
357
|
+
# @return [Object]
|
|
358
|
+
def execute_leaf(spec, ctx)
|
|
359
|
+
a = action_for(spec)
|
|
360
|
+
# Always resolve declared arguments (even when transfer_paths is set — those arguments
|
|
361
|
+
# are consumed first; ts_source_paths then reads whatever remains in the queue).
|
|
362
|
+
(spec.arguments || []).each do |arg_spec|
|
|
363
|
+
if arg_spec.type.eql?(:identifier)
|
|
364
|
+
unless ctx.key?(arg_spec.name)
|
|
365
|
+
lookup_method = arg_spec.lookup
|
|
366
|
+
block = lookup_method ? ->(f, v){send(lookup_method, f, v, **ctx)} : nil
|
|
367
|
+
ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec, &block))
|
|
368
|
+
end
|
|
369
|
+
else
|
|
370
|
+
ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec))
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
invoke_action(a, [], ctx)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Expand an entity_execute shorthand from a CommandSpec.
|
|
377
|
+
# Calls Base#entity_execute with the parameters from spec.entity_execute merged
|
|
378
|
+
# with the context hash (context entries are low-priority: spec params win).
|
|
379
|
+
# @param spec [CommandSpec] the command spec carrying entity_execute: Hash
|
|
380
|
+
# @param ctx [Hash] accumulated context (e.g. api:, lookup block)
|
|
381
|
+
# @return [Object]
|
|
382
|
+
def run_entity_execute(spec, ctx)
|
|
383
|
+
ee_params = spec.entity_execute.dup
|
|
384
|
+
# Resolve api: Symbol at runtime: :@ivar -> instance_variable_get, :method -> send
|
|
385
|
+
if (api_ref = ee_params[:api]).is_a?(Symbol)
|
|
386
|
+
ee_params[:api] = if api_ref.to_s.start_with?('@')
|
|
387
|
+
instance_variable_get(api_ref)
|
|
388
|
+
else
|
|
389
|
+
send(api_ref)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
# Merge context into params (spec wins on key collision)
|
|
393
|
+
merged = ctx.merge(ee_params)
|
|
394
|
+
# Extract lookup_block before passing to entity_execute (it is not a kwarg of entity_execute)
|
|
395
|
+
block = merged.delete(:lookup_block)
|
|
396
|
+
if block
|
|
397
|
+
entity_execute(**merged, &block)
|
|
398
|
+
else
|
|
399
|
+
entity_execute(**merged)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Resolve a single positional argument from the CLI argument stream.
|
|
404
|
+
# When arg_spec.bulk is true, always returns an Array (normalized to [value] when non-bulk).
|
|
405
|
+
# For type: :identifier, an optional block provides the percent-selector lookup.
|
|
406
|
+
# @param arg_spec [ArgumentSpec]
|
|
407
|
+
# @yieldparam field [String] field name from a percent-selector (%field:value)
|
|
408
|
+
# @yieldparam value [String] value from a percent-selector
|
|
409
|
+
# @yieldreturn [String] resolved identifier
|
|
410
|
+
# @return [Object] the resolved value, or Array when arg_spec.bulk is true
|
|
411
|
+
def resolve_argument(arg_spec, &block)
|
|
412
|
+
if arg_spec.bulk
|
|
413
|
+
is_bulk = options.get_option(:bulk)
|
|
414
|
+
if arg_spec.type.eql?(:identifier)
|
|
415
|
+
val = options.instance_identifier(description: arg_spec.name.to_s, &block)
|
|
416
|
+
else
|
|
417
|
+
val = options.get_next_argument(
|
|
418
|
+
arg_spec.name.to_s,
|
|
419
|
+
mandatory: arg_spec.mandatory,
|
|
420
|
+
validation: is_bulk ? Array : arg_spec.type,
|
|
421
|
+
default: arg_spec.default,
|
|
422
|
+
schema: arg_spec.schema
|
|
423
|
+
)
|
|
424
|
+
if is_bulk
|
|
425
|
+
Aspera.assert_array_all(val, arg_spec.type, type: Cli::BadArgument){'type'} unless arg_spec.type.nil?
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
# Always return an Array when bulk: true
|
|
429
|
+
is_bulk ? val : [val]
|
|
430
|
+
else
|
|
431
|
+
case arg_spec.type
|
|
432
|
+
when :identifier
|
|
433
|
+
options.instance_identifier(description: arg_spec.name.to_s, &block)
|
|
434
|
+
else
|
|
435
|
+
# Class or Array<Class> -> pass as validation type
|
|
436
|
+
# When interactive: true, set ask_missing_mandatory so that get_interactive is triggered
|
|
437
|
+
# when no CLI arguments are provided (mandatory is forced to true for the same reason:
|
|
438
|
+
# a non-nil default would short-circuit get_interactive before it is ever called).
|
|
439
|
+
options.ask_missing_mandatory = true if arg_spec.interactive
|
|
440
|
+
options.get_next_argument(
|
|
441
|
+
arg_spec.name.to_s,
|
|
442
|
+
mandatory: arg_spec.interactive ? true : arg_spec.mandatory,
|
|
443
|
+
multiple: arg_spec.multiple || false,
|
|
444
|
+
validation: arg_spec.type,
|
|
445
|
+
accept_list: arg_spec.allowed,
|
|
446
|
+
default: arg_spec.interactive ? nil : arg_spec.default,
|
|
447
|
+
schema: arg_spec.schema
|
|
448
|
+
)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Build a nested Hash tree of the registered command tree for help display.
|
|
454
|
+
# Conditional commands are included with a '[condition_name]' annotation.
|
|
455
|
+
# @param path [Array<Symbol>] starting path ([] for the full tree)
|
|
456
|
+
# @return [Hash] { command_id => { description:, condition:, children: } }
|
|
457
|
+
def generate_help(path = [])
|
|
458
|
+
self.class.command_registry.children_of(path).transform_values do |child_spec|
|
|
459
|
+
annotation = child_spec.condition ? " [#{child_spec.condition}]" : ''
|
|
460
|
+
{
|
|
461
|
+
description: "#{child_spec.description}#{annotation}",
|
|
462
|
+
condition: child_spec.condition,
|
|
463
|
+
children: generate_help(child_spec.full_path)
|
|
464
|
+
}
|
|
107
465
|
end
|
|
108
466
|
end
|
|
109
467
|
|
|
@@ -111,13 +469,24 @@ module Aspera
|
|
|
111
469
|
# @param api [Aspera::Rest] API to use
|
|
112
470
|
# @param entity [String] Sub path in URL to resource relative to base url
|
|
113
471
|
# @param command [Symbol, nil] Command to execute: :create, :show, :list, :modify, :delete
|
|
472
|
+
# When nil, reads the next command token from the CLI (fallback, deprecated).
|
|
114
473
|
# @param display_fields [Array, nil] Fields to display by default
|
|
115
474
|
# @param items_key [String, nil] Result is in a sub key of the JSON
|
|
116
475
|
# @param delete_style [String, nil] If set, the delete operation by array in payload
|
|
117
476
|
# @param id_as_arg [Boolean, String] If set, the id is provided as url argument ?<id_as_arg>=<id>
|
|
118
477
|
# @param is_singleton [Boolean] If `true`, entity is the full path to the resource
|
|
119
478
|
# @param list_query [Hash, nil] Query parameters for list operation
|
|
120
|
-
# @
|
|
479
|
+
# @param schema [String, nil] JSON schema name for create/modify validation
|
|
480
|
+
# @param query_component [String, nil] Registry component key (e.g. 'faspex', 'aoc') used to derive
|
|
481
|
+
# the query parameters schema path from the entity name. When set, `--query=help` on list/delete
|
|
482
|
+
# commands displays the available filter parameters from the OpenAPI spec.
|
|
483
|
+
# @param input_data [Array, Hash, nil] Pre-resolved data for :create (Array) or :modify (Hash).
|
|
484
|
+
# When nil, data is read from the CLI (fallback, deprecated).
|
|
485
|
+
# @param res_id [String, Array, nil] Pre-resolved resource identifier(s) for instance commands.
|
|
486
|
+
# When nil, identifier is read from the CLI (fallback, deprecated).
|
|
487
|
+
# @param is_bulk [Boolean] Whether operation runs in bulk mode (used when data: is provided)
|
|
488
|
+
# @param bfail [Boolean] When false, errors are captured as status strings instead of re-raised
|
|
489
|
+
# @yieldparam value [String] Value to search for identifier (lookup block)
|
|
121
490
|
# @yieldreturn [String] The identifier
|
|
122
491
|
# @return [Hash] Result suitable for CLI result
|
|
123
492
|
def entity_execute(
|
|
@@ -130,25 +499,44 @@ module Aspera
|
|
|
130
499
|
id_as_arg: false,
|
|
131
500
|
is_singleton: false,
|
|
132
501
|
list_query: nil,
|
|
502
|
+
schema: nil,
|
|
503
|
+
query_component: nil,
|
|
504
|
+
input_data: nil,
|
|
505
|
+
res_id: nil,
|
|
506
|
+
is_bulk: false,
|
|
507
|
+
bfail: true,
|
|
133
508
|
&block
|
|
134
509
|
)
|
|
510
|
+
# Fallback: read command from CLI when not provided (entity_command shorthand without command:)
|
|
135
511
|
command = options.get_next_command(Operations::ALL) if command.nil?
|
|
512
|
+
# Derive query schema path from component + entity when component is given
|
|
513
|
+
qs_path = query_component ? Schema::Registry.query_params(query_component, entity) : nil
|
|
514
|
+
|
|
136
515
|
if is_singleton
|
|
137
516
|
one_res_path = entity
|
|
138
517
|
elsif Operations::INSTANCE.include?(command)
|
|
139
|
-
|
|
518
|
+
# Use pre-resolved identifier if provided; otherwise fall back to CLI read (entity_command shorthand)
|
|
519
|
+
one_res_id = res_id || options.instance_identifier(&block)
|
|
140
520
|
one_res_path = "#{entity}/#{one_res_id}"
|
|
141
521
|
one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
|
|
142
522
|
end
|
|
143
523
|
|
|
144
524
|
case command
|
|
145
525
|
when :create
|
|
146
|
-
|
|
147
|
-
|
|
526
|
+
Aspera.assert(!is_singleton, type: BadArgument){'cannot create singleton'}
|
|
527
|
+
unless input_data
|
|
528
|
+
# No pre-resolved data: read from CLI
|
|
529
|
+
cli_is_bulk = options.get_option(:bulk)
|
|
530
|
+
raw = options.get_next_argument('data', validation: cli_is_bulk ? Array : Hash, schema: schema)
|
|
531
|
+
input_data = cli_is_bulk ? raw : [raw]
|
|
532
|
+
is_bulk = cli_is_bulk
|
|
533
|
+
bfail = options.get_option(:bfail)
|
|
534
|
+
end
|
|
535
|
+
return Result.bulk(input_data, is_bulk: is_bulk, command: command, fields: display_fields, bfail: bfail) do |params|
|
|
148
536
|
api.create(entity, params)
|
|
149
537
|
end
|
|
150
538
|
when :delete
|
|
151
|
-
|
|
539
|
+
Aspera.assert(!is_singleton, type: BadArgument){'cannot delete singleton'}
|
|
152
540
|
if !delete_style.nil?
|
|
153
541
|
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
|
154
542
|
Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
|
|
@@ -158,17 +546,18 @@ module Aspera
|
|
|
158
546
|
content_type: Mime::JSON,
|
|
159
547
|
body: {delete_style => one_res_id}
|
|
160
548
|
)
|
|
161
|
-
return
|
|
549
|
+
return Result::Status.new('deleted')
|
|
162
550
|
end
|
|
163
|
-
|
|
164
|
-
|
|
551
|
+
items = one_res_id.is_a?(Array) ? one_res_id : [one_res_id]
|
|
552
|
+
return Result.bulk(items, is_bulk: is_bulk, command: command, bfail: bfail) do |one_id|
|
|
553
|
+
api.delete("#{entity}/#{one_id}", query_read_delete(schema: qs_path))
|
|
165
554
|
{'id' => one_id}
|
|
166
555
|
end
|
|
167
556
|
when :show
|
|
168
|
-
return
|
|
557
|
+
return Result::SingleObject.new(api.read(one_res_path), fields: display_fields)
|
|
169
558
|
when :list
|
|
170
|
-
data, http = api.read(entity, query_read_delete, ret: :both)
|
|
171
|
-
return
|
|
559
|
+
data, http = api.read(entity, query_read_delete(default: list_query, schema: qs_path), ret: :both)
|
|
560
|
+
return Result::Empty.new if http.code == '204'
|
|
172
561
|
# TODO: not generic : which application is this for ?
|
|
173
562
|
if http['Content-Type'].start_with?('application/vnd.api+json')
|
|
174
563
|
Log.log.debug('is vnd.api')
|
|
@@ -177,17 +566,18 @@ module Aspera
|
|
|
177
566
|
data = data[items_key] if items_key
|
|
178
567
|
case data
|
|
179
568
|
when Hash
|
|
180
|
-
return
|
|
569
|
+
return Result::SingleObject.new(data, fields: display_fields)
|
|
181
570
|
when Array
|
|
182
|
-
return
|
|
183
|
-
return
|
|
571
|
+
return Result::ObjectList.new(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash)
|
|
572
|
+
return Result::ValueList.new(data)
|
|
184
573
|
else
|
|
185
574
|
Aspera.error_unexpected_value(data.class.name){'list type'}
|
|
186
575
|
end
|
|
187
576
|
when :modify
|
|
188
|
-
|
|
577
|
+
# Use pre-resolved data if provided; otherwise read from CLI
|
|
578
|
+
parameters = input_data || options.get_next_argument('data', validation: Hash, schema: schema)
|
|
189
579
|
api.update(one_res_path, parameters)
|
|
190
|
-
return
|
|
580
|
+
return Result::Status.new('modified')
|
|
191
581
|
else
|
|
192
582
|
Aspera.error_unexpected_value(command){'command'}
|
|
193
583
|
end
|
|
@@ -195,10 +585,11 @@ module Aspera
|
|
|
195
585
|
|
|
196
586
|
# Query parameters in URL suitable for REST: list/`GET` and delete/`DELETE`
|
|
197
587
|
# @param default [Hash, nil] Default query parameters
|
|
588
|
+
# @param schema [String, nil] Contextual schema path for --query help display
|
|
198
589
|
# @return [Hash, nil] Query parameters
|
|
199
|
-
def query_read_delete(default: nil)
|
|
590
|
+
def query_read_delete(default: nil, schema: nil)
|
|
200
591
|
# Dup default, as it could be frozen
|
|
201
|
-
query = options.get_option(:query) || default
|
|
592
|
+
query = options.get_option(:query, schema: schema) || default&.dup
|
|
202
593
|
Log.log.debug{"query_read_delete=#{query}".bg_red}
|
|
203
594
|
begin
|
|
204
595
|
# Check it is suitable
|
|
@@ -208,34 +599,6 @@ module Aspera
|
|
|
208
599
|
end
|
|
209
600
|
return query
|
|
210
601
|
end
|
|
211
|
-
|
|
212
|
-
# Retrieves an extended value from command line.
|
|
213
|
-
# Used for creation or modification of entities.
|
|
214
|
-
# @param command [Symbol] Command name for error message
|
|
215
|
-
# @param description [String, nil] Description of the value
|
|
216
|
-
# @param type [Class, nil] Expected type of value
|
|
217
|
-
# @param bulk [Boolean] If `true`, value must be an Array of `type`
|
|
218
|
-
# @param default [Object, nil] Default value if not provided
|
|
219
|
-
# @param schema [Hash, nil] JSON schema for validation
|
|
220
|
-
# @return [Hash, Array<Hash>] The value(s) to create object(s)
|
|
221
|
-
def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil, schema: nil)
|
|
222
|
-
value = options.get_next_argument(
|
|
223
|
-
"parameters for #{command}#{" (#{description})" unless description.nil?}",
|
|
224
|
-
mandatory: default.nil?,
|
|
225
|
-
validation: bulk ? Array : type,
|
|
226
|
-
schema: schema
|
|
227
|
-
)
|
|
228
|
-
value = default if value.nil?
|
|
229
|
-
unless type.nil?
|
|
230
|
-
Aspera.assert_type(type, Class){'type'}
|
|
231
|
-
if bulk
|
|
232
|
-
Aspera.assert_array_all(value, type, type: Cli::BadArgument){'type'}
|
|
233
|
-
else
|
|
234
|
-
Aspera.assert_type(value, type, type: Cli::BadArgument){'type'}
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
return value
|
|
238
|
-
end
|
|
239
602
|
end
|
|
240
603
|
end
|
|
241
604
|
end
|