aspera-cli 4.27.2 → 4.27.4
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 +133 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +3 -1
- data/docs/README.md +1006 -766
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +6 -8
- data/lib/aspera/agent/desktop.rb +2 -6
- data/lib/aspera/agent/direct.rb +52 -22
- data/lib/aspera/agent/node.rb +9 -8
- data/lib/aspera/agent/transferd.rb +2 -2
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +11 -7
- data/lib/aspera/api/httpgw.rb +38 -35
- data/lib/aspera/api/node.rb +39 -34
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +63 -28
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +12 -11
- data/lib/aspera/cli/bootstrapper.rb +14 -16
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +215 -37
- data/lib/aspera/cli/command_spec.rb +104 -15
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +6 -3
- data/lib/aspera/cli/formatter.rb +94 -80
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +22 -34
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +105 -0
- data/lib/aspera/cli/option_value.rb +278 -0
- data/lib/aspera/cli/options.schema.yaml +124 -15
- data/lib/aspera/cli/parser.rb +333 -862
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +545 -518
- data/lib/aspera/cli/plugins/ats.rb +59 -80
- data/lib/aspera/cli/plugins/base.rb +221 -265
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +263 -184
- data/lib/aspera/cli/plugins/console.rb +103 -39
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +204 -182
- data/lib/aspera/cli/plugins/faspio.rb +6 -11
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +300 -327
- data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
- data/lib/aspera/cli/plugins/preview.rb +96 -105
- data/lib/aspera/cli/plugins/server.rb +78 -53
- data/lib/aspera/cli/plugins/shares.rb +80 -131
- data/lib/aspera/cli/preset_actions.rb +44 -27
- data/lib/aspera/cli/preset_manager.rb +44 -19
- data/lib/aspera/cli/prompt.rb +36 -0
- data/lib/aspera/cli/result.rb +42 -36
- data/lib/aspera/cli/runner.rb +32 -59
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +51 -46
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +14 -9
- data/lib/aspera/cli/transfer_agent.rb +34 -38
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -3
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/environment.rb +35 -5
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +47 -27
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +37 -26
- data/lib/aspera/oauth/factory.rb +7 -3
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +4 -5
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +11 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +72 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +523 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +149 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +23 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +43 -9
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/node_simulator.rb +0 -344
- data/lib/aspera/preview/options.rb +0 -45
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -6,6 +6,7 @@ require 'aspera/assert'
|
|
|
6
6
|
require 'aspera/cli/result'
|
|
7
7
|
require 'aspera/cli/command_registry'
|
|
8
8
|
require 'aspera/cli/option_declarator'
|
|
9
|
+
require 'aspera/cli/special_values'
|
|
9
10
|
require 'aspera/schema/registry'
|
|
10
11
|
|
|
11
12
|
module Aspera
|
|
@@ -24,18 +25,24 @@ module Aspera
|
|
|
24
25
|
ALL = (GLOBAL + INSTANCE).freeze
|
|
25
26
|
end
|
|
26
27
|
class << self
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
include OptionDeclarator
|
|
29
|
+
|
|
30
|
+
# Option sources added with `use_options`, with their `on_set` target.
|
|
31
|
+
# @return [Hash{Class, Module => Proc, nil}]
|
|
29
32
|
def used_option_sources
|
|
30
|
-
@used_option_sources ||=
|
|
33
|
+
@used_option_sources ||= {}
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
# Include options from another plugin or OptionDeclarator module.
|
|
34
37
|
# @param source [Class, Module]
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
# @param target [Proc, nil] Executed on the plugin instance, returns the object for Symbol and Proc `on_set` callbacks
|
|
39
|
+
# of `source` (e.g. `-> { http_config }`); nil: the plugin instance
|
|
40
|
+
def use_options(source, target: nil)
|
|
41
|
+
used_option_sources[source] = target unless used_option_sources.key?(source)
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
# Per-class DSL registry (not inherited: each subclass gets its own instance).
|
|
45
|
+
# @return [CommandRegistry]
|
|
39
46
|
def command_registry
|
|
40
47
|
@command_registry ||= CommandRegistry.new
|
|
41
48
|
end
|
|
@@ -43,25 +50,50 @@ module Aspera
|
|
|
43
50
|
# DSL class method: register a command in this plugin's registry.
|
|
44
51
|
# Inherits parent from the enclosing commands_under block when parent: is omitted.
|
|
45
52
|
# @param id [Symbol]
|
|
46
|
-
# @param kwargs [Hash] forwarded to CommandSpec
|
|
53
|
+
# @param kwargs [Hash] forwarded to [CommandSpec]
|
|
47
54
|
def command(id, **kwargs)
|
|
48
55
|
kwargs[:parent] = @current_parent if kwargs[:parent].nil? && @current_parent
|
|
49
56
|
command_registry.register(CommandSpec.new(id: id, **kwargs))
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
# Words displayed with specific case in descriptions
|
|
60
|
+
NOUN_WORDS = {'smtp' => 'SMTP', 'ldap' => 'LDAP', 'saml' => 'SAML', 'oauth' => 'OAuth', 'kms' => 'KMS', 'api' => 'API'}.freeze
|
|
61
|
+
private_constant :NOUN_WORDS
|
|
62
|
+
|
|
63
|
+
# Derive a lowercase noun from an entity path, singular unless told otherwise.
|
|
64
|
+
# e.g. 'access_keys' -> 'access key', 'data/smtp_server' -> 'SMTP server'
|
|
65
|
+
# @param entity [String, Symbol] REST path or entity name
|
|
66
|
+
# @param singular [Boolean] Singularize the last word
|
|
67
|
+
# @return [String]
|
|
68
|
+
def entity_noun(entity, singular: true)
|
|
69
|
+
words = entity.to_s.split('/').last.split('_').map { |w| NOUN_WORDS.fetch(w, w) }
|
|
70
|
+
words[-1] = words[-1].sub(/ies\z/, 'y').sub(/(ss|x|sh|ch)es\z/, '\1').sub(/(?<!s)s\z/, '') if singular
|
|
71
|
+
words.join(' ')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Standard description of a CRUD operation on an entity.
|
|
75
|
+
# e.g. (:list, 'access key') -> 'List access keys', (:show, 'access key') -> 'Show access key'
|
|
76
|
+
# @param verb [Symbol] Operation
|
|
77
|
+
# @param noun [String] Singular noun of entity
|
|
78
|
+
# @return [String]
|
|
79
|
+
def operation_description(verb, noun)
|
|
80
|
+
return "#{verb.capitalize} #{noun}" unless verb.eql?(:list)
|
|
81
|
+
plural =
|
|
82
|
+
case noun
|
|
83
|
+
when /[^aeiou]y\z/ then noun.sub(/y\z/, 'ies')
|
|
84
|
+
when /(s|x|sh|ch)\z/ then "#{noun}es"
|
|
85
|
+
else "#{noun}s"
|
|
86
|
+
end
|
|
87
|
+
"List #{plural}"
|
|
57
88
|
end
|
|
58
89
|
|
|
59
90
|
# DSL class method: declare CRUD commands for a REST entity.
|
|
60
91
|
#
|
|
61
92
|
# For each verb in operations:, registers one CommandSpec with:
|
|
62
|
-
# - description:
|
|
63
|
-
# - arguments: [{name:
|
|
93
|
+
# - description: operation_description(verb, name)
|
|
94
|
+
# - arguments: [{name: id_name, type: :identifier, lookup: lookup}] for instance verbs
|
|
64
95
|
# (:show, :modify, :delete) when not a singleton; none for global verbs
|
|
96
|
+
# body of :create and :modify is named after the entity (e.g. <access_key>), passed as data:
|
|
65
97
|
# - action: calls entity_<verb>(api:, entity:, **shared_kwargs, **ctx)
|
|
66
98
|
#
|
|
67
99
|
# api: is resolved at runtime: :@ivar -> instance_variable_get, else -> send.
|
|
@@ -71,14 +103,23 @@ module Aspera
|
|
|
71
103
|
# @param api [Symbol, String] Runtime API ref (:@ivar or method name) or literal string
|
|
72
104
|
# @param entity [String, Symbol] REST sub-path, or ctx key Symbol resolved at runtime
|
|
73
105
|
# @param operations [Array<Symbol>] Verbs to expose; defaults to Operations::ALL
|
|
74
|
-
# @param name [String, nil]
|
|
106
|
+
# @param name [String, nil] Singular display name; defaults to last segment of entity (static only)
|
|
75
107
|
# @param lookup [Symbol, nil] Instance method for percent-selector resolution
|
|
108
|
+
# @param id_name [Symbol, nil] Name of identifier argument; defaults to <name>_id, or id_as_arg field, or :id
|
|
76
109
|
# @param kwargs [Hash] Shared params forwarded to every per-verb method
|
|
77
|
-
def crud_commands(api:, entity:, operations: nil, name: nil, lookup: nil, **kwargs)
|
|
78
|
-
name ||=
|
|
110
|
+
def crud_commands(api:, entity:, operations: nil, name: nil, lookup: nil, id_name: nil, **kwargs)
|
|
111
|
+
name ||= entity_noun(entity, singular: !kwargs[:is_singleton]) unless entity.is_a?(Symbol)
|
|
79
112
|
operations ||= Operations::ALL
|
|
113
|
+
# Body argument is named after the entity, e.g. <access_key>, and passed as data: to entity_<verb>
|
|
114
|
+
data_name = name ? name.downcase.tr(' ', '_').to_sym : :data
|
|
115
|
+
# Identifier argument is named after the entity, e.g. <access_key_id>, and passed as id: to entity_<verb>
|
|
116
|
+
id_name ||=
|
|
117
|
+
if kwargs[:id_as_arg].is_a?(String) then kwargs[:id_as_arg].to_sym
|
|
118
|
+
elsif name then :"#{data_name}_id"
|
|
119
|
+
else :id
|
|
120
|
+
end
|
|
80
121
|
operations.each do |verb|
|
|
81
|
-
id_arg = ({name:
|
|
122
|
+
id_arg = ({name: id_name, type: :identifier, lookup: lookup} if Operations::INSTANCE.include?(verb) && !kwargs[:is_singleton])
|
|
82
123
|
schema_val =
|
|
83
124
|
if kwargs[:body_component] && entity.is_a?(String)
|
|
84
125
|
case verb
|
|
@@ -89,9 +130,9 @@ module Aspera
|
|
|
89
130
|
args =
|
|
90
131
|
case verb
|
|
91
132
|
when :create
|
|
92
|
-
[{name:
|
|
133
|
+
[{name: data_name, type: Hash, bulk: true, schema: schema_val}]
|
|
93
134
|
when :modify
|
|
94
|
-
[id_arg, {name:
|
|
135
|
+
[id_arg, {name: data_name, type: Hash, schema: schema_val}].compact
|
|
95
136
|
when :delete
|
|
96
137
|
id_arg ? [id_arg.merge(bulk: true)] : nil
|
|
97
138
|
else
|
|
@@ -107,10 +148,13 @@ module Aspera
|
|
|
107
148
|
api
|
|
108
149
|
end
|
|
109
150
|
resolved_entity = entity.is_a?(Symbol) ? ctx.fetch(entity) : entity
|
|
151
|
+
ctx = ctx.merge(data: ctx[data_name]) if ctx.key?(data_name)
|
|
152
|
+
ctx = ctx.merge(id: ctx[id_name]) if ctx.key?(id_name)
|
|
110
153
|
send(:"entity_#{verb}", api: resolved_api, entity: resolved_entity, **kwargs, **ctx)
|
|
111
154
|
end
|
|
112
|
-
cmd_attrs = {description:
|
|
155
|
+
cmd_attrs = {description: operation_description(verb, name || entity.inspect), action: action_proc}
|
|
113
156
|
cmd_attrs[:arguments] = args if args
|
|
157
|
+
cmd_attrs[:query_schema] = Schema::Registry.query_params(kwargs[:query_component], entity) if verb.eql?(:list) && kwargs[:query_component] && entity.is_a?(String)
|
|
114
158
|
command(verb, **cmd_attrs)
|
|
115
159
|
end
|
|
116
160
|
end
|
|
@@ -139,7 +183,7 @@ module Aspera
|
|
|
139
183
|
path = Array(@current_parent) + Array(parent)
|
|
140
184
|
unless command_registry[path]
|
|
141
185
|
id = path.last
|
|
142
|
-
desc = description || "Manage #{
|
|
186
|
+
desc = description || "Manage #{entity_noun(id, singular: false)}"
|
|
143
187
|
parent_path = path[0..-2]
|
|
144
188
|
saved = @current_parent
|
|
145
189
|
@current_parent = parent_path.empty? ? nil : parent_path
|
|
@@ -153,101 +197,56 @@ module Aspera
|
|
|
153
197
|
@current_parent = previous
|
|
154
198
|
end
|
|
155
199
|
|
|
156
|
-
#
|
|
200
|
+
# Options of this plugin class (`option` DSL, see OptionDeclarator).
|
|
157
201
|
# Metadata is stored as an OptionSpec at class-load time; the actual
|
|
158
202
|
# options.declare call happens in Base#initialize once the instance exists.
|
|
159
|
-
#
|
|
203
|
+
# @return [Hash{Symbol => OptionSpec}]
|
|
204
|
+
def option_specs
|
|
205
|
+
command_registry.option_specs
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Store an OptionSpec in the command registry.
|
|
160
209
|
# Raises ArgumentError at class-load time if the same option name is already
|
|
161
210
|
# declared by any ancestor class, preventing silent shadowing.
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
# @param description [String, nil] User-facing description; if nil, derived from schema: title/description
|
|
165
|
-
# @param short [String, nil] Single-character short form (without leading '-')
|
|
166
|
-
# @param allowed [Object, nil] Allowed values (see OptionValue)
|
|
167
|
-
# @param default [Object, nil] Default value
|
|
168
|
-
# @param handler [Symbol, Hash, nil]
|
|
169
|
-
# - Symbol: resolved to {o: <plugin instance>, m: <symbol>} at runtime (Category B)
|
|
170
|
-
# - Hash: {o: <object>, m: <method>} used as-is (Category A: singletons / constants)
|
|
171
|
-
# - nil: option stores its value locally (no delegation)
|
|
172
|
-
# @param deprecation [String, nil] Deprecation message forwarded to options.declare
|
|
173
|
-
# @param schema [String, nil] Schema reference (e.g. "opts:components.schemas.Foo");
|
|
174
|
-
# when description: is nil, the schema title or first description line is used
|
|
175
|
-
def option(name, description: nil,
|
|
176
|
-
short: nil, allowed: nil, default: nil,
|
|
177
|
-
handler: nil, deprecation: nil, schema: nil)
|
|
211
|
+
# @param spec [OptionSpec]
|
|
212
|
+
def register_option_spec(spec)
|
|
178
213
|
ancestor_owner = ancestors.drop(1).find do |klass|
|
|
179
214
|
klass.is_a?(Class) && klass <= Base &&
|
|
180
215
|
klass.instance_variable_defined?(:@command_registry) &&
|
|
181
|
-
klass.command_registry.option_specs.key?(name)
|
|
216
|
+
klass.command_registry.option_specs.key?(spec.name)
|
|
182
217
|
end
|
|
183
|
-
raise ArgumentError, "#{self}: option :#{name} already declared in ancestor #{ancestor_owner}" if ancestor_owner
|
|
184
|
-
command_registry.register_option(
|
|
185
|
-
OptionSpec.new(
|
|
186
|
-
name: name,
|
|
187
|
-
description: description,
|
|
188
|
-
short: short,
|
|
189
|
-
allowed: allowed,
|
|
190
|
-
default: default,
|
|
191
|
-
handler: handler,
|
|
192
|
-
deprecation: deprecation,
|
|
193
|
-
schema: schema
|
|
194
|
-
)
|
|
195
|
-
)
|
|
218
|
+
raise ArgumentError, "#{self}: option :#{spec.name} already declared in ancestor #{ancestor_owner}" if ancestor_owner
|
|
219
|
+
command_registry.register_option(spec)
|
|
196
220
|
end
|
|
197
221
|
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# @
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
sources = []
|
|
222
|
+
# Classes and modules whose options apply to this plugin:
|
|
223
|
+
# this class, its plugin ancestors and sources added via `use_options`.
|
|
224
|
+
# @return [Hash{Class, Module => Proc, nil}] key responds to `option_specs`, value: `on_set` target (see `use_options`)
|
|
225
|
+
def option_sources
|
|
226
|
+
sources = {}
|
|
204
227
|
ancestors.each do |klass|
|
|
205
228
|
next unless klass.is_a?(Class) && klass <= Base
|
|
206
|
-
sources
|
|
207
|
-
|
|
229
|
+
sources[klass] = nil if klass.instance_variable_defined?(:@command_registry) && !sources.key?(klass)
|
|
230
|
+
klass.used_option_sources.each { |src, src_target| sources[src] = src_target unless sources.key?(src) }
|
|
208
231
|
end
|
|
209
|
-
sources
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
when Hash then spec.handler
|
|
223
|
-
end
|
|
224
|
-
options.declare(
|
|
225
|
-
spec.name,
|
|
226
|
-
description: spec.description,
|
|
227
|
-
short: spec.short,
|
|
228
|
-
allowed: spec.allowed,
|
|
229
|
-
default: spec.default,
|
|
230
|
-
handler: resolved_handler,
|
|
231
|
-
deprecation: spec.deprecation,
|
|
232
|
-
schema: spec.schema
|
|
233
|
-
)
|
|
232
|
+
sources
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Declare all options of `option_sources` onto a Parser instance.
|
|
236
|
+
# Skips options already declared on the parser: it is shared across all plugins in a run.
|
|
237
|
+
# @param options [Aspera::Cli::Parser]
|
|
238
|
+
# @param target [Base, nil] plugin instance for Symbol and Proc `on_set` callbacks; nil: such callbacks are not bound
|
|
239
|
+
# @param parse [Boolean] whether to call parse_options! after declaring
|
|
240
|
+
def declare_options(options, target: nil, parse: false)
|
|
241
|
+
option_sources.each do |src, src_target|
|
|
242
|
+
src_target = src_target.nil? ? target : target&.instance_exec(&src_target)
|
|
243
|
+
src.option_specs.each_value do |spec|
|
|
244
|
+
spec.declare_on(options, target: src_target) unless options.option_declared?(spec.name)
|
|
234
245
|
end
|
|
235
246
|
end
|
|
236
247
|
options.parse_options! if parse
|
|
237
248
|
end
|
|
238
249
|
|
|
239
|
-
# DSL class method: declare a setup method to run once before root dispatch.
|
|
240
|
-
# The method is called before any command is consumed, and its return value
|
|
241
|
-
# (a Hash) is merged into the initial ctx. This is useful when conditions
|
|
242
|
-
# on root commands depend on state built during setup (e.g. @connection_type).
|
|
243
|
-
# @param method_name [Symbol]
|
|
244
|
-
def root_setup(method_name)
|
|
245
|
-
@root_setup_method = method_name
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# @return [Symbol, nil]
|
|
249
|
-
attr_reader :root_setup_method
|
|
250
|
-
|
|
251
250
|
# DSL class method: declare the human-readable application name shown in wizards.
|
|
252
251
|
# When called with an argument, sets the name. When called with no argument, returns it.
|
|
253
252
|
# Falls back to the last component of the class name if never set.
|
|
@@ -289,49 +288,9 @@ module Aspera
|
|
|
289
288
|
# below (DSL-registered and imperative) appear under the plugin section in
|
|
290
289
|
# --help output, separate from the global options.
|
|
291
290
|
options.group(self.class.name.split('::').last.downcase) if @context.man_header
|
|
292
|
-
# Auto-declare all options registered via the DSL `option` class method
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
# The options object is shared across all plugins in a run; skip options already
|
|
296
|
-
# declared by an earlier plugin (Base.option prevents duplicates within one hierarchy).
|
|
297
|
-
# Each OptionSpec is translated to an options.declare call, resolving the
|
|
298
|
-
# handler: shorthand:
|
|
299
|
-
# Symbol handler: {o: self, m: <symbol>} (Category B - plugin instance methods)
|
|
300
|
-
# Hash handler: used as-is (Category A - singletons / class constants)
|
|
301
|
-
sources = []
|
|
302
|
-
self.class.ancestors.each do |klass|
|
|
303
|
-
next unless klass.is_a?(Class) && klass <= Base
|
|
304
|
-
sources << klass if klass.instance_variable_defined?(:@command_registry)
|
|
305
|
-
sources.concat(klass.used_option_sources) if klass.respond_to?(:used_option_sources)
|
|
306
|
-
end
|
|
307
|
-
sources.uniq.each do |src|
|
|
308
|
-
specs =
|
|
309
|
-
if src.respond_to?(:command_registry)
|
|
310
|
-
src.command_registry.option_specs
|
|
311
|
-
elsif src.respond_to?(:option_specs)
|
|
312
|
-
src.option_specs
|
|
313
|
-
else
|
|
314
|
-
{}
|
|
315
|
-
end
|
|
316
|
-
specs.each_value do |spec|
|
|
317
|
-
next if options.option_declared?(spec.name)
|
|
318
|
-
resolved_handler =
|
|
319
|
-
case spec.handler
|
|
320
|
-
when Symbol then {o: self, m: spec.handler}
|
|
321
|
-
when Hash then spec.handler
|
|
322
|
-
end
|
|
323
|
-
options.declare(
|
|
324
|
-
spec.name,
|
|
325
|
-
description: spec.description,
|
|
326
|
-
short: spec.short,
|
|
327
|
-
allowed: spec.allowed,
|
|
328
|
-
default: spec.default,
|
|
329
|
-
handler: resolved_handler,
|
|
330
|
-
deprecation: spec.deprecation,
|
|
331
|
-
schema: spec.schema
|
|
332
|
-
)
|
|
333
|
-
end
|
|
334
|
-
end
|
|
291
|
+
# Auto-declare all options registered via the DSL `option` class method,
|
|
292
|
+
# including those of parent plugin classes (e.g. Oauth, BasicAuth) and `use_options` sources.
|
|
293
|
+
self.class.declare_options(options, target: self)
|
|
335
294
|
end
|
|
336
295
|
|
|
337
296
|
# Global objects
|
|
@@ -357,46 +316,28 @@ module Aspera
|
|
|
357
316
|
# @return [Aspera::Cli::TransferProgress, nil]
|
|
358
317
|
def progress_bar; @context.progress_bar; end
|
|
359
318
|
|
|
360
|
-
def add_manual_header(_has_options = true)
|
|
361
|
-
# No-op: the group is set at the start of initialize.
|
|
362
|
-
# Kept for compatibility with Config, which calls add_manual_header(false) from Runner.
|
|
363
|
-
end
|
|
364
|
-
|
|
365
319
|
# Entry point for all DSL-based plugins.
|
|
366
320
|
def execute_action
|
|
367
321
|
@help_path = nil
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
unless self.class.instance_variable_defined?(:@registry_validated)
|
|
371
|
-
self.class.command_registry.validate!(plugin_class: self.class)
|
|
372
|
-
self.class.instance_variable_set(:@registry_validated, true)
|
|
373
|
-
end
|
|
374
|
-
# Run the root setup (if declared) before consuming any argument.
|
|
375
|
-
# This ensures condition methods on root commands can read instance variables
|
|
376
|
-
# populated by the setup (e.g. @connection_type in server.rb).
|
|
377
|
-
init_ctx = {}
|
|
378
|
-
if (rsm = self.class.root_setup_method)
|
|
379
|
-
init_ctx = send(rsm) || {}
|
|
380
|
-
end
|
|
381
|
-
dispatch_from_registry([], init_ctx)
|
|
322
|
+
validate_registry
|
|
323
|
+
dispatch_from_registry([])
|
|
382
324
|
end
|
|
383
325
|
|
|
384
326
|
# Two-phase dispatcher: run setup on the current node (Phase A), then either
|
|
385
327
|
# execute a leaf directly or consume the next argument and recurse (Phase B).
|
|
386
328
|
# @param current_path [Array<Symbol>] path of the node currently being dispatched
|
|
387
329
|
# @param ctx [Hash] accumulated context passed down from parent nodes
|
|
388
|
-
# @param skip_setup [Boolean] when true, skip Phase A (setup already done by caller)
|
|
389
330
|
# @return [Object] result suitable for CLI output
|
|
390
|
-
def dispatch_from_registry(current_path, ctx = {}
|
|
331
|
+
def dispatch_from_registry(current_path, ctx = {})
|
|
391
332
|
registry = self.class.command_registry
|
|
392
333
|
spec = registry[current_path]
|
|
393
334
|
is_leaf = spec && registry.children_of(current_path).empty?
|
|
394
335
|
|
|
395
|
-
if
|
|
336
|
+
if @context.help_requested
|
|
396
337
|
# help_requested on an intermediate node: drain positional args without validation
|
|
397
338
|
# so that dispatch_child can still consume the correct sub-command token
|
|
398
|
-
if !is_leaf
|
|
399
|
-
|
|
339
|
+
if !is_leaf
|
|
340
|
+
registry.arguments_at(current_path).each do |arg_spec|
|
|
400
341
|
next if ctx.key?(arg_spec.name)
|
|
401
342
|
options.get_next_argument(arg_spec.name.to_s, mandatory: false)
|
|
402
343
|
end
|
|
@@ -404,24 +345,7 @@ module Aspera
|
|
|
404
345
|
else
|
|
405
346
|
# Phase A - for intermediate nodes only: resolve all ArgumentSpec declared on this node
|
|
406
347
|
# before dispatching to children (leaf nodes resolve their arguments inside execute_leaf).
|
|
407
|
-
if !is_leaf
|
|
408
|
-
(spec&.arguments || []).each do |arg_spec|
|
|
409
|
-
next if ctx.key?(arg_spec.name)
|
|
410
|
-
if arg_spec.type.eql?(:identifier)
|
|
411
|
-
lookup_cb = arg_spec.lookup
|
|
412
|
-
res_id = if lookup_cb.nil?
|
|
413
|
-
options.instance_identifier(description: arg_spec.name.to_s)
|
|
414
|
-
elsif lookup_cb.is_a?(Symbol)
|
|
415
|
-
options.instance_identifier(description: arg_spec.name.to_s) { |f, v| send(lookup_cb, f, v, **ctx) }
|
|
416
|
-
else
|
|
417
|
-
options.instance_identifier(description: arg_spec.name.to_s) { |f, v| instance_exec(f, v, **ctx, &lookup_cb) }
|
|
418
|
-
end
|
|
419
|
-
ctx = ctx.merge(arg_spec.name => res_id)
|
|
420
|
-
else
|
|
421
|
-
ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec))
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
end
|
|
348
|
+
ctx = resolve_arguments(spec.arguments, ctx) if !is_leaf && spec&.arguments
|
|
425
349
|
ctx = ctx.merge(send(spec.setup, **ctx)) if spec&.setup
|
|
426
350
|
end
|
|
427
351
|
|
|
@@ -440,15 +364,15 @@ module Aspera
|
|
|
440
364
|
# @param ctx [Hash]
|
|
441
365
|
# @return [Object]
|
|
442
366
|
def dispatch_leaf(current_path, spec, ctx)
|
|
443
|
-
if
|
|
367
|
+
if @context.help_requested
|
|
444
368
|
@help_path = current_path
|
|
445
369
|
raise Cli::HelpRequest, self
|
|
446
370
|
end
|
|
447
371
|
execute_leaf(spec, ctx)
|
|
448
372
|
end
|
|
449
373
|
|
|
450
|
-
# Phase B, child branch: consume the next command argument,
|
|
451
|
-
#
|
|
374
|
+
# Phase B, child branch: consume the next command argument, then either continue
|
|
375
|
+
# on a mounted plugin instance, or recurse into the child.
|
|
452
376
|
# --help is intercepted at two points:
|
|
453
377
|
# 1. Before get_next_command when no positional arg is pending: raises HelpRequest
|
|
454
378
|
# immediately so the subcommand list with descriptions is shown rather than a
|
|
@@ -461,37 +385,64 @@ module Aspera
|
|
|
461
385
|
# @return [Object]
|
|
462
386
|
def dispatch_child(current_path, registry, ctx)
|
|
463
387
|
children = registry.children_of(current_path)
|
|
464
|
-
|
|
465
|
-
|
|
388
|
+
# condition: methods belong to the class declaring the spec: only evaluate local ones
|
|
389
|
+
# (mounted children are only walked here for --help, see below).
|
|
390
|
+
# With --help, conditions are not evaluated: they may need the API, which is not built for help.
|
|
391
|
+
available = children.reject { |id, c| c.condition && !@context.help_requested && registry.local?(current_path + [id]) && !send(c.condition) }
|
|
392
|
+
# Aliases of available commands only: an alias must not bypass the condition of its command
|
|
393
|
+
aliases = available.values.each_with_object({}) do |c, h|
|
|
466
394
|
Array(c.aliases).each { |a| h[a] = c.id } if c.aliases
|
|
467
395
|
end
|
|
468
396
|
|
|
469
397
|
# Intercept --help before consuming the command token when no arg is pending.
|
|
470
398
|
# This avoids MissingArgument being raised by get_next_command before HelpRequest.
|
|
471
|
-
if
|
|
399
|
+
if @context.help_requested && options.command_or_arg_empty?
|
|
472
400
|
@help_path = current_path
|
|
473
401
|
raise Cli::HelpRequest, self
|
|
474
402
|
end
|
|
475
403
|
|
|
476
404
|
command = options.get_next_command(available.keys, aliases: aliases.empty? ? nil : aliases)
|
|
477
|
-
child = available[command]
|
|
478
405
|
|
|
479
406
|
# Intercept --help after a command was consumed but no further args remain.
|
|
480
407
|
# (e.g. `aoc files find -h`). When further args remain, keep recursing.
|
|
481
|
-
if
|
|
408
|
+
if @context.help_requested && options.command_or_arg_empty?
|
|
482
409
|
@help_path = current_path + [command]
|
|
483
410
|
raise Cli::HelpRequest, self
|
|
484
411
|
end
|
|
485
412
|
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
413
|
+
# Mounted child: continue on the target plugin instance, in its own namespace.
|
|
414
|
+
# For --help, keep walking the (mount-aware) registry of this class instead, so that
|
|
415
|
+
# no target instance (and thus no API connection) is needed.
|
|
416
|
+
child_path = current_path + [command]
|
|
417
|
+
return dispatch_mount(registry.mount_of(current_path), command, ctx) unless @context.help_requested || registry.local?(child_path)
|
|
418
|
+
|
|
419
|
+
# Both intermediate and leaf: arguments + setup are handled by Phase A of the next call
|
|
420
|
+
dispatch_from_registry(child_path, ctx)
|
|
421
|
+
end
|
|
492
422
|
|
|
493
|
-
|
|
494
|
-
|
|
423
|
+
# Hand over dispatch of a mounted child to the target plugin instance.
|
|
424
|
+
# Setups of the mount point `at` and of its ancestors in the target are not executed:
|
|
425
|
+
# the seed ctx returned by the host's `instance` method replaces them.
|
|
426
|
+
# The mount's own arguments (if any) are read first, and passed to `instance` in ctx.
|
|
427
|
+
# @param mount [MountSpec]
|
|
428
|
+
# @param command [Symbol] mounted child id, already consumed
|
|
429
|
+
# @param ctx [Hash] host context, passed to the `instance` method
|
|
430
|
+
# @return [Object]
|
|
431
|
+
def dispatch_mount(mount, command, ctx)
|
|
432
|
+
ctx = resolve_arguments(mount.arguments, ctx)
|
|
433
|
+
target = send(mount.instance, **ctx)
|
|
434
|
+
target, seed = target if target.is_a?(Array)
|
|
435
|
+
Aspera.assert_type(target, mount.plugin)
|
|
436
|
+
target.validate_registry
|
|
437
|
+
target.dispatch_from_registry(mount.at + [command], seed || {})
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Validate the registry once per class (memoised by the ivar check).
|
|
441
|
+
# Passes the plugin class so implicit action methods can be verified.
|
|
442
|
+
def validate_registry
|
|
443
|
+
return if self.class.instance_variable_defined?(:@registry_validated)
|
|
444
|
+
self.class.command_registry.validate!(plugin_class: self.class)
|
|
445
|
+
self.class.instance_variable_set(:@registry_validated, true)
|
|
495
446
|
end
|
|
496
447
|
|
|
497
448
|
# Resolve the action for a leaf CommandSpec.
|
|
@@ -520,10 +471,8 @@ module Aspera
|
|
|
520
471
|
end
|
|
521
472
|
|
|
522
473
|
# Execute a leaf CommandSpec: resolve arguments and call action.
|
|
523
|
-
# Arguments already present in
|
|
524
|
-
#
|
|
525
|
-
# instance_arg (if any) is resolved here as an ArgumentSpec(type: :identifier) and merged
|
|
526
|
-
# into ctx, exactly like any other keyword argument received by the action.
|
|
474
|
+
# Arguments already present in `ctx` (e.g. provided by a caller or a mount seed) are skipped:
|
|
475
|
+
# they are not read again from the command line.
|
|
527
476
|
# @param spec [CommandSpec] a leaf node (no children)
|
|
528
477
|
# @param ctx [Hash] accumulated context (pre-resolved keys are not re-consumed)
|
|
529
478
|
# @return [Object]
|
|
@@ -531,25 +480,40 @@ module Aspera
|
|
|
531
480
|
a = action_for(spec)
|
|
532
481
|
# Always resolve declared arguments (even when transfer_paths is set — those arguments
|
|
533
482
|
# are consumed first; ts_source_paths then reads whatever remains in the queue).
|
|
534
|
-
(spec.arguments
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
483
|
+
ctx = resolve_arguments(spec.arguments, ctx) if spec.arguments
|
|
484
|
+
invoke_action(a, [], ctx)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Resolve positional arguments from the CLI argument stream, in order.
|
|
488
|
+
# Arguments already present in `ctx` (e.g. injected by a setup method) are not read from the command line.
|
|
489
|
+
# If the next CLI argument is that same value, or `ALL`, it is skipped, so that it is not mistaken for a later argument
|
|
490
|
+
# (e.g. `faspex5 packages receive ALL` with a public link to a package).
|
|
491
|
+
# For type: :identifier, the percent-selector lookup receives the ctx accumulated so far.
|
|
492
|
+
# @param arg_specs [Array<ArgumentSpec>]
|
|
493
|
+
# @param ctx [Hash] accumulated context
|
|
494
|
+
# @return [Hash] ctx merged with the resolved arguments
|
|
495
|
+
def resolve_arguments(arg_specs, ctx)
|
|
496
|
+
arg_specs.each do |arg_spec|
|
|
497
|
+
if ctx.key?(arg_spec.name)
|
|
498
|
+
options.get_next_argument(arg_spec.name.to_s, mandatory: false) if options.next_argument_in?([ctx[arg_spec.name].to_s, SpecialValues::ALL])
|
|
499
|
+
next
|
|
546
500
|
end
|
|
501
|
+
lookup_cb = arg_spec.lookup if arg_spec.type.eql?(:identifier)
|
|
502
|
+
current = ctx
|
|
503
|
+
block =
|
|
504
|
+
case lookup_cb
|
|
505
|
+
when nil then nil
|
|
506
|
+
when Symbol then ->(f, v) { send(lookup_cb, f, v, **current) }
|
|
507
|
+
else ->(f, v) { instance_exec(f, v, **current, &lookup_cb) }
|
|
508
|
+
end
|
|
509
|
+
ctx = ctx.merge(arg_spec.name => resolve_argument(arg_spec, &block))
|
|
547
510
|
end
|
|
548
|
-
|
|
511
|
+
ctx
|
|
549
512
|
end
|
|
550
513
|
|
|
551
514
|
# Resolve a single positional argument from the CLI argument stream.
|
|
552
515
|
# When arg_spec.bulk is true, always returns an Array (normalized to [value] when non-bulk).
|
|
516
|
+
# Only such arguments are read as a list with `--bulk=yes`: others (e.g. the id of a parent node) are single values.
|
|
553
517
|
# For type: :identifier, an optional block provides the percent-selector lookup.
|
|
554
518
|
# @param arg_spec [ArgumentSpec]
|
|
555
519
|
# @yieldparam field [String] field name from a percent-selector (%field:value)
|
|
@@ -560,7 +524,7 @@ module Aspera
|
|
|
560
524
|
if arg_spec.bulk
|
|
561
525
|
is_bulk = options.get_option(:bulk)
|
|
562
526
|
if arg_spec.type.eql?(:identifier)
|
|
563
|
-
val = options.instance_identifier(description: arg_spec.name.to_s, &block)
|
|
527
|
+
val = options.instance_identifier(description: arg_spec.name.to_s, multiple: is_bulk, &block)
|
|
564
528
|
else
|
|
565
529
|
val = options.get_next_argument(
|
|
566
530
|
arg_spec.name.to_s,
|
|
@@ -581,19 +545,19 @@ module Aspera
|
|
|
581
545
|
options.instance_identifier(description: arg_spec.name.to_s, &block)
|
|
582
546
|
else
|
|
583
547
|
# Class or Array<Class> -> pass as validation type
|
|
584
|
-
# When interactive: true,
|
|
585
|
-
#
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
548
|
+
# When interactive: true, prompt for this argument only when no CLI argument is provided
|
|
549
|
+
# (mandatory is forced to true and default ignored: a non-nil default would short-circuit the prompt).
|
|
550
|
+
options.with_interactive(enabled: arg_spec.interactive) do
|
|
551
|
+
options.get_next_argument(
|
|
552
|
+
arg_spec.name.to_s,
|
|
553
|
+
mandatory: arg_spec.interactive ? true : arg_spec.mandatory,
|
|
554
|
+
multiple: arg_spec.multiple || false,
|
|
555
|
+
validation: arg_spec.type,
|
|
556
|
+
accept_list: arg_spec.allowed,
|
|
557
|
+
default: arg_spec.interactive ? nil : arg_spec.default,
|
|
558
|
+
schema: arg_spec.schema
|
|
559
|
+
)
|
|
560
|
+
end
|
|
597
561
|
end
|
|
598
562
|
end
|
|
599
563
|
end
|
|
@@ -603,17 +567,18 @@ module Aspera
|
|
|
603
567
|
# @param path [Array<Symbol>] starting path ([] for the full tree)
|
|
604
568
|
# @return [Hash] { command_id => { description:, condition:, children: } }
|
|
605
569
|
def generate_help(path = [])
|
|
606
|
-
self.class.command_registry.children_of(path).
|
|
570
|
+
self.class.command_registry.children_of(path).to_h do |id, child_spec|
|
|
607
571
|
annotation = child_spec.condition ? " [#{child_spec.condition}]" : ''
|
|
608
|
-
|
|
572
|
+
# path + [id], not child_spec.full_path: a mounted spec's full_path is in the target namespace
|
|
573
|
+
[id, {
|
|
609
574
|
description: "#{child_spec.description}#{annotation}",
|
|
610
575
|
condition: child_spec.condition,
|
|
611
|
-
children: generate_help(
|
|
612
|
-
}
|
|
576
|
+
children: generate_help(path + [id])
|
|
577
|
+
}]
|
|
613
578
|
end
|
|
614
579
|
end
|
|
615
580
|
|
|
616
|
-
# Convenience wrapper: reads :bulk and :bfail from options,
|
|
581
|
+
# Convenience wrapper: reads :bulk and :bfail from options, normalizes `items`
|
|
617
582
|
# to an Array, then delegates to Result.bulk.
|
|
618
583
|
# Use this in action methods instead of the three-line boilerplate:
|
|
619
584
|
# is_bulk = options.get_option(:bulk)
|
|
@@ -623,7 +588,7 @@ module Aspera
|
|
|
623
588
|
# @param command [Symbol] Operation name (:create, :delete, ...)
|
|
624
589
|
# @param id_result [String] Key used as item identifier in the result row
|
|
625
590
|
# @param fields [Object] Fields hint passed to Result constructor (non-bulk only)
|
|
626
|
-
# @yieldparam item [Object] Each item in
|
|
591
|
+
# @yieldparam item [Object] Each item in `items`
|
|
627
592
|
# @return [Result::ObjectList, Result::SingleObject]
|
|
628
593
|
def bulk_result(items, command:, id_result: 'id', fields: :default, &block)
|
|
629
594
|
items = items.is_a?(Array) ? items : [items]
|
|
@@ -645,7 +610,7 @@ module Aspera
|
|
|
645
610
|
# NOT read from the CLI queue inside the method.
|
|
646
611
|
|
|
647
612
|
# List all instances of an entity.
|
|
648
|
-
# @param api [Aspera::Rest] REST API object
|
|
613
|
+
# @param api [Aspera::Rest::Client] REST API object
|
|
649
614
|
# @param entity [String] API sub-path
|
|
650
615
|
# @param display_fields [Array, nil] Fields to display
|
|
651
616
|
# @param items_key [String, nil] Sub-key in response containing the array
|
|
@@ -655,12 +620,14 @@ module Aspera
|
|
|
655
620
|
qs_path = query_component ? Schema::Registry.query_params(query_component, entity) : nil
|
|
656
621
|
data, http = api.read(entity, query_read_delete(default: list_query, schema: qs_path), ret: :both)
|
|
657
622
|
return Result::Empty.new if http.code == '204'
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
623
|
+
if !data.is_a?(Hash)
|
|
624
|
+
# already the list
|
|
625
|
+
elsif items_key
|
|
626
|
+
data = data[items_key]
|
|
627
|
+
elsif http['Content-Type'].start_with?(Mime::JSON_API)
|
|
628
|
+
# JSON:API: list is under the entity name
|
|
661
629
|
data = data[entity]
|
|
662
630
|
end
|
|
663
|
-
data = data[items_key] if items_key
|
|
664
631
|
case data
|
|
665
632
|
when Hash then Result::SingleObject.new(data, fields: display_fields)
|
|
666
633
|
when Array
|
|
@@ -671,7 +638,7 @@ module Aspera
|
|
|
671
638
|
end
|
|
672
639
|
|
|
673
640
|
# Show one instance of an entity.
|
|
674
|
-
# @param api [Aspera::Rest] REST API object
|
|
641
|
+
# @param api [Aspera::Rest::Client] REST API object
|
|
675
642
|
# @param entity [String] API sub-path
|
|
676
643
|
# @param id [String, nil] Resource identifier; nil when is_singleton: true
|
|
677
644
|
# @param display_fields [Array, nil] Fields to display
|
|
@@ -683,43 +650,32 @@ module Aspera
|
|
|
683
650
|
end
|
|
684
651
|
|
|
685
652
|
# Create one or more instances of an entity (supports bulk).
|
|
686
|
-
# @param api [Aspera::Rest]
|
|
687
|
-
# @param entity [String]
|
|
688
|
-
# @param
|
|
689
|
-
# @param
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
input_data ||= data
|
|
694
|
-
unless input_data
|
|
695
|
-
is_bulk = options.get_option(:bulk)
|
|
696
|
-
raw = options.get_next_argument('data', validation: is_bulk ? Array : Hash, schema: schema)
|
|
697
|
-
input_data = is_bulk ? raw : [raw]
|
|
698
|
-
end
|
|
699
|
-
input_data = [input_data] unless input_data.is_a?(Array)
|
|
700
|
-
bulk_result(input_data, command: :create, fields: display_fields) do |params|
|
|
653
|
+
# @param api [Aspera::Rest::Client] REST API object
|
|
654
|
+
# @param entity [String] API sub-path
|
|
655
|
+
# @param data [Hash, Array<Hash>] Entity data (Array with bulk), from the command's declared `data` argument
|
|
656
|
+
# @param display_fields [Array, nil] Fields to display
|
|
657
|
+
def entity_create(api:, entity:, data:, display_fields: nil, **)
|
|
658
|
+
data = [data] unless data.is_a?(Array)
|
|
659
|
+
bulk_result(data, command: :create, fields: display_fields) do |params|
|
|
701
660
|
api.create(entity, params)
|
|
702
661
|
end
|
|
703
662
|
end
|
|
704
663
|
|
|
705
664
|
# Modify an existing instance of an entity.
|
|
706
|
-
# @param api [Aspera::Rest] REST API object
|
|
665
|
+
# @param api [Aspera::Rest::Client] REST API object
|
|
707
666
|
# @param entity [String] API sub-path
|
|
708
667
|
# @param id [String, nil] Resource identifier; nil when is_singleton: true
|
|
709
668
|
# @param is_singleton [Boolean] When true, entity is the full path (no id appended)
|
|
710
669
|
# @param id_as_arg [Boolean, String] When set, id is appended as ?<id_as_arg>=<id>
|
|
711
|
-
# @param
|
|
712
|
-
|
|
713
|
-
def entity_modify(api:, entity:, id: nil, is_singleton: false, id_as_arg: false, body_component: nil, input_data: nil, data: nil, **)
|
|
714
|
-
schema = body_component ? Schema::Registry.req_body(body_component, "#{entity}/{id}.put") : nil
|
|
670
|
+
# @param data [Hash] Modified fields, from the command's declared `data` argument
|
|
671
|
+
def entity_modify(api:, entity:, data:, id: nil, is_singleton: false, id_as_arg: false, **)
|
|
715
672
|
path = entity_res_path(entity, id, is_singleton: is_singleton, id_as_arg: id_as_arg)
|
|
716
|
-
|
|
717
|
-
api.update(path, parameters)
|
|
673
|
+
api.update(path, data)
|
|
718
674
|
Result::Status.new('modified')
|
|
719
675
|
end
|
|
720
676
|
|
|
721
677
|
# Delete one or more instances of an entity (supports bulk).
|
|
722
|
-
# @param api [Aspera::Rest] REST API object
|
|
678
|
+
# @param api [Aspera::Rest::Client] REST API object
|
|
723
679
|
# @param entity [String] API sub-path
|
|
724
680
|
# @param id [String, Array, nil] Resource identifier(s)
|
|
725
681
|
# @param id_as_arg [Boolean, String] When set, id is appended as ?<id_as_arg>=<id>
|