aspera-cli 4.25.6 → 4.26.1
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 +89 -48
- data/CONTRIBUTING.md +1 -1
- data/lib/aspera/api/aoc.rb +120 -79
- data/lib/aspera/api/node.rb +103 -51
- data/lib/aspera/ascp/installation.rb +99 -32
- data/lib/aspera/assert.rb +17 -13
- data/lib/aspera/cli/extended_value.rb +7 -2
- data/lib/aspera/cli/formatter.rb +107 -95
- data/lib/aspera/cli/main.rb +69 -10
- data/lib/aspera/cli/manager.rb +158 -78
- data/lib/aspera/cli/options.schema.yaml +82 -0
- data/lib/aspera/cli/plugins/aoc.rb +247 -144
- data/lib/aspera/cli/plugins/ats.rb +3 -3
- data/lib/aspera/cli/plugins/base.rb +60 -76
- data/lib/aspera/cli/plugins/config.rb +14 -12
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +24 -23
- data/lib/aspera/cli/plugins/node.rb +67 -71
- data/lib/aspera/cli/plugins/oauth.rb +5 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
- data/lib/aspera/cli/plugins/preview.rb +116 -80
- data/lib/aspera/cli/plugins/server.rb +2 -10
- data/lib/aspera/cli/plugins/shares.rb +7 -7
- data/lib/aspera/cli/sync_actions.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +17 -15
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +22 -18
- data/lib/aspera/dot_container.rb +7 -3
- data/lib/aspera/environment.rb +6 -5
- data/lib/aspera/formatter_interface.rb +14 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/markdown.rb +4 -1
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/preview/generator.rb +146 -91
- data/lib/aspera/preview/options.rb +4 -1
- data/lib/aspera/preview/terminal.rb +50 -20
- data/lib/aspera/preview/utils.rb +76 -34
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +3 -0
- data/lib/aspera/rest.rb +2 -1
- data/lib/aspera/rest_list.rb +23 -16
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62801 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +8898 -0
- data/lib/aspera/schema/documentation.rb +107 -0
- data/lib/aspera/schema/reader.rb +75 -0
- data/lib/aspera/schema/registry.rb +63 -0
- data/lib/aspera/secret_hider.rb +3 -1
- data/lib/aspera/sync/conf.schema.yaml +0 -26
- data/lib/aspera/sync/operations.rb +9 -5
- data/lib/aspera/transfer/faux_file.rb +1 -1
- data/lib/aspera/transfer/resumer.rb +1 -1
- data/lib/aspera/transfer/spec.rb +3 -3
- data/lib/aspera/transfer/spec.schema.yaml +1 -1
- data/lib/aspera/uri_reader.rb +17 -2
- data/lib/aspera/yaml.rb +4 -2
- data.tar.gz.sig +0 -0
- metadata +13 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/transfer/spec_doc.rb +0 -76
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -12,6 +12,20 @@ require 'optparse'
|
|
|
12
12
|
|
|
13
13
|
module Aspera
|
|
14
14
|
module Cli
|
|
15
|
+
# Exception raised when schema is asked (`help`)
|
|
16
|
+
class SchemaRequest < Error
|
|
17
|
+
# @return [String, nil] path to schema file
|
|
18
|
+
attr_reader :path
|
|
19
|
+
|
|
20
|
+
# @param type [Symbol] :argument or :option
|
|
21
|
+
# @param name [String] name of the option/argument
|
|
22
|
+
# @param schema_path [String, nil] path to schema file, or `nil` if not available
|
|
23
|
+
def initialize(type, name, schema_path)
|
|
24
|
+
super("#{type}: #{name}")
|
|
25
|
+
@path = schema_path
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
15
29
|
module BoolValue
|
|
16
30
|
# boolean options are set to true/false from the following values
|
|
17
31
|
YES_SYM = :yes
|
|
@@ -22,23 +36,24 @@ module Aspera
|
|
|
22
36
|
# Boolean values
|
|
23
37
|
# @return [Array<true, false, :yes, :no>]
|
|
24
38
|
ALL = (TRUE_VALUES + FALSE_VALUES).freeze
|
|
39
|
+
# `false` and `true`
|
|
25
40
|
TYPES = [FalseClass, TrueClass].freeze
|
|
26
41
|
SYMBOLS = [NO_SYM, YES_SYM].freeze
|
|
27
42
|
# @return `true` if value is a value for `true` in ALL
|
|
28
43
|
def true?(enum)
|
|
29
44
|
Aspera.assert_values(enum, ALL){'boolean'}
|
|
30
|
-
|
|
45
|
+
TRUE_VALUES.include?(enum)
|
|
31
46
|
end
|
|
32
47
|
|
|
33
48
|
# @return [:yes, :no]
|
|
34
49
|
def to_sym(enum)
|
|
35
50
|
Aspera.assert_values(enum, ALL){'boolean'}
|
|
36
|
-
|
|
51
|
+
TRUE_VALUES.include?(enum) ? YES_SYM : NO_SYM
|
|
37
52
|
end
|
|
38
53
|
|
|
39
54
|
# @return `true` if value is a value for `true` or `false` in ALL
|
|
40
55
|
def symbol?(sym)
|
|
41
|
-
|
|
56
|
+
ALL.include?(sym)
|
|
42
57
|
end
|
|
43
58
|
module_function :true?, :to_sym, :symbol?
|
|
44
59
|
end
|
|
@@ -52,30 +67,33 @@ module Aspera
|
|
|
52
67
|
# Value will be coerced to int
|
|
53
68
|
TYPES_INTEGER = [Integer].freeze
|
|
54
69
|
TYPES_BOOLEAN = BoolValue::TYPES
|
|
55
|
-
#
|
|
70
|
+
# No value at all for the option, it's a switch, like `-N`
|
|
56
71
|
TYPES_NONE = [].freeze
|
|
72
|
+
# Symbol
|
|
57
73
|
TYPES_ENUM = [Symbol].freeze
|
|
74
|
+
# String
|
|
58
75
|
TYPES_STRING = [String].freeze
|
|
59
76
|
end
|
|
60
77
|
|
|
61
78
|
# Description of option, how to manage
|
|
62
79
|
class OptionValue
|
|
63
80
|
# [Array(Class)] List of allowed types
|
|
64
|
-
attr_reader :types, :sensitive
|
|
81
|
+
attr_reader :types, :sensitive, :schema, :option
|
|
65
82
|
# [Array] List of allowed values (Symbols and specific values)
|
|
66
83
|
attr_accessor :values
|
|
67
84
|
|
|
68
|
-
# @param option
|
|
85
|
+
# @param option [Symbol] Name of option
|
|
69
86
|
# @param description [String] Description for help
|
|
70
|
-
# @param allowed
|
|
71
|
-
# @param handler
|
|
87
|
+
# @param allowed [nil,Class,Array<Class>,Array<Symbol>] Allowed values
|
|
88
|
+
# @param handler [Hash] Accessor: keys: :o(object) and :m(method)
|
|
72
89
|
# @param deprecation [String] Deprecation message
|
|
90
|
+
# @param schema [String] Declaration of schema
|
|
73
91
|
# `allowed`:
|
|
74
92
|
# - `nil` No validation, so just a string
|
|
75
93
|
# - `Class` The single allowed Class
|
|
76
94
|
# - `Array<Class>` Multiple allowed classes
|
|
77
95
|
# - `Array<Symbol>` List of allowed values
|
|
78
|
-
def initialize(option:, description:, allowed: Allowed::TYPES_STRING, handler: nil, deprecation: nil)
|
|
96
|
+
def initialize(option:, description:, allowed: Allowed::TYPES_STRING, handler: nil, deprecation: nil, schema: nil)
|
|
79
97
|
Log.log.trace1{"option: #{option}, allowed: #{allowed}"}
|
|
80
98
|
@option = option
|
|
81
99
|
@description = description
|
|
@@ -86,6 +104,7 @@ module Aspera
|
|
|
86
104
|
@read_method = handler&.[](:m)
|
|
87
105
|
@write_method = @read_method ? "#{@read_method}=".to_sym : nil
|
|
88
106
|
@deprecation = deprecation
|
|
107
|
+
@schema = schema
|
|
89
108
|
@access = if @object.nil?
|
|
90
109
|
:local
|
|
91
110
|
elsif @object.respond_to?(@write_method)
|
|
@@ -102,7 +121,7 @@ module Aspera
|
|
|
102
121
|
if allowed.take(Allowed::TYPES_SYMBOL_ARRAY.length) == Allowed::TYPES_SYMBOL_ARRAY
|
|
103
122
|
# Special case: array of defined symbol values
|
|
104
123
|
@types = Allowed::TYPES_SYMBOL_ARRAY
|
|
105
|
-
@values = allowed[Allowed::TYPES_SYMBOL_ARRAY.length
|
|
124
|
+
@values = allowed[Allowed::TYPES_SYMBOL_ARRAY.length..]
|
|
106
125
|
elsif allowed.all?(Class)
|
|
107
126
|
@types = allowed
|
|
108
127
|
@values = BoolValue::ALL if allowed.eql?(Allowed::TYPES_BOOLEAN)
|
|
@@ -131,12 +150,14 @@ module Aspera
|
|
|
131
150
|
when :setter then @object.send(@read_method, @option, :get)
|
|
132
151
|
end
|
|
133
152
|
Log.log.trace1{"#{@option} -> (#{current_value.class})#{current_value}"} if log
|
|
134
|
-
|
|
153
|
+
current_value
|
|
135
154
|
end
|
|
136
155
|
|
|
137
156
|
# Assign value to option.
|
|
138
|
-
# Value can be a String
|
|
157
|
+
# Value can be a `String`, then evaluated with `ExtendedValue`, or directly a value.
|
|
139
158
|
# @param value [String, Object] Value to assign to option
|
|
159
|
+
# @param where [String] Where the value is assigned from
|
|
160
|
+
# @return [nil]
|
|
140
161
|
def assign_value(value, where:)
|
|
141
162
|
Aspera.assert(!@deprecation, type: warn){"Option #{@option} is deprecated: #{@deprecation}"}
|
|
142
163
|
new_value = ExtendedValue.instance.evaluate(value, context: "option: #{@option}", allowed: @types)
|
|
@@ -146,11 +167,10 @@ module Aspera
|
|
|
146
167
|
new_value = [new_value] if @types.eql?(Allowed::TYPES_STRING_ARRAY) && new_value.is_a?(String)
|
|
147
168
|
# Setting a Hash to null set an empty hash
|
|
148
169
|
new_value = {} if new_value.eql?(nil) && @types&.first.eql?(Hash)
|
|
149
|
-
# Setting a Array to null set an empty
|
|
170
|
+
# Setting a Array to null set an empty array
|
|
150
171
|
new_value = [] if new_value.eql?(nil) && @types&.first.eql?(Array)
|
|
151
172
|
if @types.eql?(Aspera::Cli::Allowed::TYPES_SYMBOL_ARRAY)
|
|
152
173
|
new_value = [new_value] if new_value.is_a?(String)
|
|
153
|
-
Aspera.assert_type(new_value, Array, type: BadArgument)
|
|
154
174
|
Aspera.assert_array_all(new_value, String, type: BadArgument)
|
|
155
175
|
new_value = new_value.map{ |v| Manager.get_from_list(v, @option, @values)}
|
|
156
176
|
end
|
|
@@ -166,6 +186,7 @@ module Aspera
|
|
|
166
186
|
when :setter then @object.send(@read_method, @option, :set, new_value)
|
|
167
187
|
end
|
|
168
188
|
Log.log.trace1{v = value(log: false); "#{@option} <- (#{v.class})#{v}"} # rubocop:disable Style/Semicolon
|
|
189
|
+
nil
|
|
169
190
|
end
|
|
170
191
|
end
|
|
171
192
|
|
|
@@ -184,25 +205,34 @@ module Aspera
|
|
|
184
205
|
Aspera.assert(!matching.empty?, multi_choice_assert_msg("unknown value for #{descr}: #{short_value}", allowed_values), type: BadArgument)
|
|
185
206
|
Aspera.assert(matching.length.eql?(1), multi_choice_assert_msg("ambiguous shortcut for #{descr}: #{short_value}", matching), type: BadArgument)
|
|
186
207
|
return BoolValue.true?(matching.first) if allowed_values.eql?(BoolValue::ALL)
|
|
187
|
-
|
|
208
|
+
matching.first
|
|
188
209
|
end
|
|
189
210
|
|
|
190
211
|
# Generates error message with list of allowed values
|
|
191
|
-
# @param error_msg [String]
|
|
192
|
-
# @param accept_list [Array]
|
|
212
|
+
# @param error_msg [String] Error message
|
|
213
|
+
# @param accept_list [Array<Symbol>] List of allowed values
|
|
193
214
|
def multi_choice_assert_msg(error_msg, accept_list)
|
|
194
|
-
[error_msg, 'Use:'
|
|
215
|
+
[error_msg, 'Use:', *accept_list.map{ |choice| "- #{choice}"}.sort].join("\n")
|
|
195
216
|
end
|
|
196
217
|
|
|
197
218
|
# Change option name with dash to name with underscore
|
|
198
219
|
# @param name [String] option name
|
|
199
220
|
# @return [String]
|
|
200
221
|
def option_line_to_name(name)
|
|
201
|
-
|
|
222
|
+
name.gsub(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
|
|
202
223
|
end
|
|
203
224
|
|
|
204
225
|
def option_name_to_line(name)
|
|
205
|
-
|
|
226
|
+
"#{OPTION_PREFIX}#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @return [Hash{Symbol => String}, nil] `{field:,value:}` if identifier is a percent selector, else `nil`
|
|
230
|
+
def percent_selector(identifier)
|
|
231
|
+
Aspera.assert_type(identifier, String)
|
|
232
|
+
if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
233
|
+
return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")}
|
|
234
|
+
end
|
|
235
|
+
nil
|
|
206
236
|
end
|
|
207
237
|
end
|
|
208
238
|
|
|
@@ -210,6 +240,8 @@ module Aspera
|
|
|
210
240
|
attr_accessor :ask_missing_mandatory, :ask_missing_optional
|
|
211
241
|
attr_writer :fail_on_missing_mandatory
|
|
212
242
|
|
|
243
|
+
# @param program_name [String] Name of the program
|
|
244
|
+
# @param argv [Array<String>, nil] Command line arguments to parse
|
|
213
245
|
def initialize(program_name, argv = nil)
|
|
214
246
|
# command line values *not* starting with '-'
|
|
215
247
|
@unprocessed_cmd_line_arguments = []
|
|
@@ -217,7 +249,8 @@ module Aspera
|
|
|
217
249
|
@unprocessed_cmd_line_options = []
|
|
218
250
|
# a copy of all initial options
|
|
219
251
|
@initial_cli_options = []
|
|
220
|
-
#
|
|
252
|
+
# Option descriptions: maps option symbol to its OptionValue descriptor
|
|
253
|
+
# @type [Hash{Symbol => OptionValue}]
|
|
221
254
|
@declared_options = {}
|
|
222
255
|
# do we ask missing options and arguments to user ?
|
|
223
256
|
@ask_missing_mandatory = false # STDIN.isatty
|
|
@@ -236,7 +269,7 @@ module Aspera
|
|
|
236
269
|
# options can also be provided by env vars : --param-name -> ASCLI_PARAM_NAME
|
|
237
270
|
env_prefix = program_name.upcase + OPTION_SEP_SYMBOL
|
|
238
271
|
ENV.each do |k, v|
|
|
239
|
-
@option_pairs_env[k
|
|
272
|
+
@option_pairs_env[k.delete_prefix(env_prefix).downcase.to_sym] = v if k.start_with?(env_prefix)
|
|
240
273
|
end
|
|
241
274
|
Log.log.debug{"env=#{@option_pairs_env}".red}
|
|
242
275
|
@unprocessed_cmd_line_options = []
|
|
@@ -267,6 +300,14 @@ module Aspera
|
|
|
267
300
|
# do not parse options yet, let's wait for option `-h` to be overridden
|
|
268
301
|
end
|
|
269
302
|
|
|
303
|
+
# Add a type to the message if not special types
|
|
304
|
+
# @param types [Array<Class>] types to add
|
|
305
|
+
# @return [String] Types if relevant
|
|
306
|
+
def add_types_info(types)
|
|
307
|
+
return '' if !types || types.empty? || types.eql?(Allowed::TYPES_ENUM) || types.eql?(Allowed::TYPES_BOOLEAN) || types.eql?(Allowed::TYPES_STRING)
|
|
308
|
+
" (#{types.map(&:name).join(', ')})"
|
|
309
|
+
end
|
|
310
|
+
|
|
270
311
|
# Declare an option
|
|
271
312
|
# @param option_symbol [Symbol] option name
|
|
272
313
|
# @param description [String] description for help
|
|
@@ -275,13 +316,14 @@ module Aspera
|
|
|
275
316
|
# @param default [Object] default value
|
|
276
317
|
# @param handler [Hash] handler for option value: keys: :o(object) and :m(method)
|
|
277
318
|
# @param deprecation [String] deprecation
|
|
319
|
+
# @param schema [String] Definition of schema for Hash parameters
|
|
278
320
|
# @param block [Proc] Block to execute when option is found
|
|
279
|
-
def declare(option_symbol, description, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, &block)
|
|
321
|
+
def declare(option_symbol, description, short: nil, allowed: nil, default: nil, handler: nil, deprecation: nil, schema: nil, &block)
|
|
280
322
|
Aspera.assert_type(option_symbol, Symbol)
|
|
281
323
|
Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
|
|
282
324
|
Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
|
|
283
325
|
Aspera.assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with an uppercase"}
|
|
284
|
-
Aspera.assert(!['hash', 'extended value'].any?{ |s| description.downcase.include?(s)}){"#{option_symbol} shall use :allowed"}
|
|
326
|
+
Aspera.assert(!['hash', 'extended value'].any?{ |s| description.downcase.include?(s)}){"#{option_symbol} shall use :allowed instead of hash/extended value in option description"}
|
|
285
327
|
Aspera.assert_type(handler, Hash) if handler
|
|
286
328
|
Aspera.assert(handler.keys.sort.eql?(%i[m o])) if handler
|
|
287
329
|
option_attrs = @declared_options[option_symbol] = OptionValue.new(
|
|
@@ -289,10 +331,11 @@ module Aspera
|
|
|
289
331
|
description: description,
|
|
290
332
|
allowed: allowed,
|
|
291
333
|
handler: handler,
|
|
292
|
-
deprecation: deprecation
|
|
334
|
+
deprecation: deprecation,
|
|
335
|
+
schema: schema
|
|
293
336
|
)
|
|
294
337
|
real_types = option_attrs.types&.reject{ |i| [NilClass, String, Symbol].include?(i)}
|
|
295
|
-
description
|
|
338
|
+
description += add_types_info(real_types)
|
|
296
339
|
description = "#{description} (#{'deprecated'.blue}: #{deprecation})" if deprecation
|
|
297
340
|
set_option(option_symbol, default, where: 'default') unless default.nil?
|
|
298
341
|
on_args = [description]
|
|
@@ -331,32 +374,36 @@ module Aspera
|
|
|
331
374
|
end
|
|
332
375
|
|
|
333
376
|
# @param descr [String] description for help
|
|
334
|
-
# @param mandatory [Boolean]
|
|
335
|
-
# @param multiple [Boolean]
|
|
336
|
-
# @param accept_list [Array
|
|
377
|
+
# @param mandatory [Boolean] `true`: raise error no more argument
|
|
378
|
+
# @param multiple [Boolean] `true`: return all remaining arguments (Array). String: until marker
|
|
379
|
+
# @param accept_list [Array<Symbol>, NilClass] list of allowed values
|
|
337
380
|
# @param validation [Class, Array, NilClass] Accepted value type(s) or list of Symbols
|
|
338
381
|
# @param aliases [Hash] map of aliases: key = alias, value = real value
|
|
339
382
|
# @param default [Object] default value
|
|
340
383
|
# @return one value, list or nil (if optional and no default)
|
|
341
|
-
def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Allowed::TYPES_STRING, aliases: nil, default: nil)
|
|
384
|
+
def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Allowed::TYPES_STRING, aliases: nil, default: nil, schema: nil)
|
|
342
385
|
Aspera.assert_array_all(accept_list, Symbol) unless accept_list.nil?
|
|
343
386
|
Aspera.assert_hash_all(aliases, Symbol, Symbol) unless aliases.nil?
|
|
344
387
|
validation = Symbol unless accept_list.nil?
|
|
345
388
|
validation = [validation] unless validation.is_a?(Array) || validation.nil?
|
|
346
389
|
Aspera.assert_array_all(validation, Class){'validation'} unless validation.nil?
|
|
347
|
-
descr = "#{descr}
|
|
390
|
+
descr = "#{descr}#{add_types_info(validation)}"
|
|
348
391
|
result =
|
|
349
392
|
if !@unprocessed_cmd_line_arguments.empty?
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
393
|
+
case multiple
|
|
394
|
+
when true
|
|
395
|
+
values = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length)
|
|
396
|
+
when false
|
|
397
|
+
values = [@unprocessed_cmd_line_arguments.shift]
|
|
398
|
+
when String
|
|
399
|
+
index = @unprocessed_cmd_line_arguments.index(multiple)
|
|
400
|
+
if index
|
|
355
401
|
values = @unprocessed_cmd_line_arguments.shift(index)
|
|
356
|
-
@unprocessed_cmd_line_arguments.shift # remove
|
|
402
|
+
@unprocessed_cmd_line_arguments.shift # remove end marker
|
|
403
|
+
else
|
|
404
|
+
values = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length)
|
|
357
405
|
end
|
|
358
|
-
else
|
|
359
|
-
values = [@unprocessed_cmd_line_arguments.shift]
|
|
406
|
+
else Aspera.error_unexpected_value(multiple){'multiple'}
|
|
360
407
|
end
|
|
361
408
|
values = values.map{ |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation)}
|
|
362
409
|
# If expecting list and only one arg of type array : it is the list
|
|
@@ -369,7 +416,7 @@ module Aspera
|
|
|
369
416
|
multiple ? values : values.first
|
|
370
417
|
elsif !default.nil? then default
|
|
371
418
|
# no value provided, either get value interactively, or exception
|
|
372
|
-
elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list)
|
|
419
|
+
elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list, schema: schema)
|
|
373
420
|
end
|
|
374
421
|
if result.is_a?(String) && validation&.eql?(Allowed::TYPES_INTEGER)
|
|
375
422
|
int_result = Integer(result, exception: false)
|
|
@@ -383,23 +430,51 @@ module Aspera
|
|
|
383
430
|
if validation && (mandatory || !result.nil?)
|
|
384
431
|
value_list = multiple ? result : [result]
|
|
385
432
|
value_list.each do |value|
|
|
433
|
+
raise SchemaRequest.new(:argument, descr, schema) if validation.include?(Hash) && value.eql?(HELP)
|
|
386
434
|
raise Cli::BadArgument,
|
|
387
435
|
"Argument #{descr} is a #{value.class} but must be #{'one of: ' if validation.length > 1}#{validation.map(&:name).join(', ')}" unless validation.any?{ |t| value.is_a?(t)}
|
|
388
436
|
end
|
|
389
437
|
end
|
|
390
|
-
|
|
438
|
+
result
|
|
391
439
|
end
|
|
392
440
|
|
|
393
|
-
|
|
441
|
+
# Resource identifier as positional parameter
|
|
442
|
+
#
|
|
443
|
+
# @param description [String] description of the identifier
|
|
444
|
+
# @param block [Proc] block to search for identifier based on attribute value
|
|
445
|
+
# @return [String, Array<String>] identifier or list of IDs (if `bulk` option is set)
|
|
446
|
+
# @yieldparam field [String] The field name from percent selector
|
|
447
|
+
# @yieldparam value [String] The value from percent selector
|
|
448
|
+
# @yieldreturn [String] Resolved identifier
|
|
449
|
+
def instance_identifier(description: 'identifier', &block)
|
|
450
|
+
res_id = get_next_argument(description, multiple: get_option(:bulk)) if res_id.nil?
|
|
451
|
+
# Can be an Array
|
|
452
|
+
if res_id.is_a?(String) && (m = Manager.percent_selector(res_id))
|
|
453
|
+
Aspera.assert(block_given?, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
|
|
454
|
+
res_id = yield(m[:field], m[:value])
|
|
455
|
+
end
|
|
456
|
+
res_id
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def get_next_command(command_list, aliases: nil); get_next_argument('command', accept_list: command_list, aliases: aliases); end
|
|
460
|
+
|
|
461
|
+
# Get an option definition by name
|
|
462
|
+
# @param option_symbol [Symbol]
|
|
463
|
+
# @return [OptionValue] Option definition
|
|
464
|
+
# @raise [Cli::BadArgument] if option not found
|
|
465
|
+
def option_def(option_symbol)
|
|
466
|
+
Aspera.assert(@declared_options.key?(option_symbol), type: Cli::BadArgument){"Unknown option: #{option_symbol}"}
|
|
467
|
+
@declared_options[option_symbol]
|
|
468
|
+
end
|
|
394
469
|
|
|
395
470
|
# Get an option value by name
|
|
396
471
|
# either return value or calls handler, can return nil
|
|
397
472
|
# ask interactively if requested/required
|
|
473
|
+
# @param option_symbol [Symbol]
|
|
398
474
|
# @param mandatory [Boolean] if true, raise error if option not set
|
|
399
475
|
def get_option(option_symbol, mandatory: false)
|
|
400
476
|
Aspera.assert_type(option_symbol, Symbol)
|
|
401
|
-
|
|
402
|
-
option_attrs = @declared_options[option_symbol]
|
|
477
|
+
option_attrs = option_def(option_symbol)
|
|
403
478
|
result = option_attrs.value
|
|
404
479
|
# Do not fail for manual generation if option mandatory but not set
|
|
405
480
|
return :skip_missing_mandatory if result.nil? && mandatory && !@fail_on_missing_mandatory
|
|
@@ -408,11 +483,11 @@ module Aspera
|
|
|
408
483
|
Aspera.assert(!mandatory, type: Cli::BadArgument){"Missing mandatory option: #{option_symbol}"}
|
|
409
484
|
elsif @ask_missing_optional || mandatory
|
|
410
485
|
# ask_missing_mandatory
|
|
411
|
-
result = get_interactive(option_symbol.to_s, check_option: true, accept_list: option_attrs.values)
|
|
486
|
+
result = get_interactive(option_symbol.to_s, check_option: true, accept_list: option_attrs.values, schema: option_attrs.schema)
|
|
412
487
|
set_option(option_symbol, result, where: 'interactive')
|
|
413
488
|
end
|
|
414
489
|
end
|
|
415
|
-
|
|
490
|
+
result
|
|
416
491
|
end
|
|
417
492
|
|
|
418
493
|
# Set an option value by name, either store value or call handler
|
|
@@ -422,15 +497,15 @@ module Aspera
|
|
|
422
497
|
# @param where [String] Where the value comes from
|
|
423
498
|
def set_option(option_symbol, value, where: 'code override')
|
|
424
499
|
Aspera.assert_type(option_symbol, Symbol)
|
|
425
|
-
|
|
426
|
-
|
|
500
|
+
option = option_def(option_symbol)
|
|
501
|
+
raise SchemaRequest.new(:option, option.option, option.schema) if option.types&.include?(Hash) && value.eql?(HELP)
|
|
502
|
+
option.assign_value(value, where: where)
|
|
427
503
|
end
|
|
428
504
|
|
|
429
505
|
# Set option to `nil`
|
|
430
506
|
def clear_option(option_symbol)
|
|
431
507
|
Aspera.assert_type(option_symbol, Symbol)
|
|
432
|
-
|
|
433
|
-
@declared_options[option_symbol].clear
|
|
508
|
+
option_def(option_symbol).clear
|
|
434
509
|
end
|
|
435
510
|
|
|
436
511
|
# Adds each of the keys of specified hash as an option
|
|
@@ -453,7 +528,7 @@ module Aspera
|
|
|
453
528
|
|
|
454
529
|
# Check if there were unprocessed values to generate error
|
|
455
530
|
def command_or_arg_empty?
|
|
456
|
-
|
|
531
|
+
@unprocessed_cmd_line_arguments.empty?
|
|
457
532
|
end
|
|
458
533
|
|
|
459
534
|
# Unprocessed options or arguments ?
|
|
@@ -461,7 +536,7 @@ module Aspera
|
|
|
461
536
|
result = []
|
|
462
537
|
result.push("unprocessed options: #{@unprocessed_cmd_line_options}") unless @unprocessed_cmd_line_options.empty?
|
|
463
538
|
result.push("unprocessed values: #{@unprocessed_cmd_line_arguments}") unless @unprocessed_cmd_line_arguments.empty?
|
|
464
|
-
|
|
539
|
+
result
|
|
465
540
|
end
|
|
466
541
|
|
|
467
542
|
# Get all original options on command line used to generate a config in config file
|
|
@@ -471,7 +546,7 @@ module Aspera
|
|
|
471
546
|
@initial_cli_options.each do |option_argument|
|
|
472
547
|
# ignore short options
|
|
473
548
|
next unless option_argument.start_with?(OPTION_PREFIX)
|
|
474
|
-
name, value = option_argument
|
|
549
|
+
name, value = option_argument.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2)
|
|
475
550
|
# ignore options without value
|
|
476
551
|
next if value.nil?
|
|
477
552
|
Log.log.debug{"option #{name}=#{value}"}
|
|
@@ -480,7 +555,7 @@ module Aspera
|
|
|
480
555
|
DotContainer.dotted_to_container(path, smart_convert(value), result)
|
|
481
556
|
@unprocessed_cmd_line_options.delete(option_argument)
|
|
482
557
|
end
|
|
483
|
-
|
|
558
|
+
result
|
|
484
559
|
end
|
|
485
560
|
|
|
486
561
|
# @param only_defined [Boolean] if true, only return options that were defined
|
|
@@ -493,7 +568,7 @@ module Aspera
|
|
|
493
568
|
rescue => e
|
|
494
569
|
result[option_symbol] = e.to_s
|
|
495
570
|
end
|
|
496
|
-
|
|
571
|
+
result
|
|
497
572
|
end
|
|
498
573
|
|
|
499
574
|
# Removes already known options from the list
|
|
@@ -508,14 +583,14 @@ module Aspera
|
|
|
508
583
|
begin
|
|
509
584
|
# remove known options one by one, exception if unknown
|
|
510
585
|
Log.log.trace1('Before parse')
|
|
511
|
-
Log.dump(:unprocessed_cmd_line_options, @unprocessed_cmd_line_options)
|
|
586
|
+
Log.dump(:unprocessed_cmd_line_options, @unprocessed_cmd_line_options, level: :trace1)
|
|
512
587
|
@parser.parse!(@unprocessed_cmd_line_options)
|
|
513
588
|
Log.log.trace1('After parse')
|
|
514
589
|
rescue OptionParser::InvalidOption => e
|
|
515
590
|
Log.log.trace1{"InvalidOption #{e}".red}
|
|
516
591
|
# An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
517
592
|
if e.args.first.start_with?(OPTION_PREFIX)
|
|
518
|
-
name, value = e.args.first
|
|
593
|
+
name, value = e.args.first.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2)
|
|
519
594
|
if !value.nil?
|
|
520
595
|
path = name.split(DotContainer::SEPARATOR)
|
|
521
596
|
option_sym = self.class.option_line_to_name(path.shift).to_sym
|
|
@@ -541,7 +616,7 @@ module Aspera
|
|
|
541
616
|
print("#{prompt}> ")
|
|
542
617
|
line = $stdin.gets
|
|
543
618
|
Aspera.assert_type(line, String){'Unexpected end of standard input'}
|
|
544
|
-
|
|
619
|
+
line.chomp
|
|
545
620
|
end
|
|
546
621
|
|
|
547
622
|
# prompt user for input in a list of symbols
|
|
@@ -562,20 +637,19 @@ module Aspera
|
|
|
562
637
|
# Prompt user for input in a list of symbols
|
|
563
638
|
# @param descr [String] description for help
|
|
564
639
|
# @param check_option [Boolean] Check attributes of option with name=descr
|
|
565
|
-
# @param multiple [Boolean] true if multiple values expected
|
|
566
|
-
# @param accept_list [Array]
|
|
567
|
-
|
|
640
|
+
# @param multiple [Boolean, String] `true` if multiple values expected
|
|
641
|
+
# @param accept_list [Array<Symbol>,NilClass] List of expected values
|
|
642
|
+
# @return [String] user input
|
|
643
|
+
def get_interactive(descr, check_option: false, multiple: false, accept_list: nil, schema: nil)
|
|
568
644
|
option_attrs = @declared_options[descr.to_sym]
|
|
569
645
|
what = option_attrs ? 'option' : 'argument'
|
|
646
|
+
default_prompt = "#{what}: #{descr}"
|
|
570
647
|
if !@ask_missing_mandatory
|
|
571
|
-
message = "
|
|
572
|
-
if accept_list
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
Aspera.assert(false, self.class.multi_choice_assert_msg(message, accept_list), type: Cli::MissingArgument)
|
|
576
|
-
end
|
|
648
|
+
message = "Missing #{default_prompt}"
|
|
649
|
+
message = self.class.multi_choice_assert_msg(message, accept_list) if accept_list
|
|
650
|
+
message += "\nGive `#{HELP}` as argument to retrieve the schema of the missing argument." if schema
|
|
651
|
+
raise Cli::MissingArgument, message
|
|
577
652
|
end
|
|
578
|
-
default_prompt = "#{what}: #{descr}"
|
|
579
653
|
# ask interactively
|
|
580
654
|
result = []
|
|
581
655
|
puts(' (one per line, end with empty line)') if multiple
|
|
@@ -589,18 +663,20 @@ module Aspera
|
|
|
589
663
|
return entry unless multiple
|
|
590
664
|
result.push(entry)
|
|
591
665
|
end
|
|
592
|
-
|
|
666
|
+
result
|
|
593
667
|
end
|
|
594
668
|
|
|
595
|
-
# Read remaining args and build an Array or Hash
|
|
596
|
-
# @param value [
|
|
597
|
-
|
|
669
|
+
# Read remaining args and build an `Array` or `Hash`
|
|
670
|
+
# @param value [String] Argument to `@:` extended value
|
|
671
|
+
# @return [Hash, Array] Object representing dot-path values
|
|
672
|
+
def args_as_extended(end_marker)
|
|
598
673
|
# This extended value does not take args (`@:`)
|
|
599
|
-
ExtendedValue.assert_no_value(
|
|
674
|
+
# ExtendedValue.assert_no_value(end_marker, :p)
|
|
675
|
+
end_marker = SpecialValues::EOA if end_marker.empty?
|
|
600
676
|
result = nil
|
|
601
|
-
get_next_argument(
|
|
602
|
-
Aspera.assert(
|
|
603
|
-
path, value =
|
|
677
|
+
get_next_argument('args', multiple: end_marker).each do |argument|
|
|
678
|
+
Aspera.assert(argument.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{argument} does not include #{OPTION_VALUE_SEPARATOR}"}
|
|
679
|
+
path, value = argument.split(OPTION_VALUE_SEPARATOR, 2)
|
|
604
680
|
result = DotContainer.dotted_to_container(path.split(DotContainer::SEPARATOR), smart_convert(value), result)
|
|
605
681
|
end
|
|
606
682
|
result
|
|
@@ -627,7 +703,7 @@ module Aspera
|
|
|
627
703
|
def symbol_to_option(symbol, opt_val = nil)
|
|
628
704
|
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
|
629
705
|
result = [result, OPTION_VALUE_SEPARATOR, opt_val].join unless opt_val.nil?
|
|
630
|
-
|
|
706
|
+
result
|
|
631
707
|
end
|
|
632
708
|
|
|
633
709
|
# TODO: use formatter
|
|
@@ -678,8 +754,12 @@ module Aspera
|
|
|
678
754
|
# when this is alone, this stops option processing
|
|
679
755
|
OPTIONS_STOP = '--'
|
|
680
756
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
|
757
|
+
# Percent selector: select by this field for this value
|
|
758
|
+
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
759
|
+
# Ask for schema of Extended value
|
|
760
|
+
HELP = 'help'
|
|
681
761
|
|
|
682
|
-
private_constant :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :OPTION_PREFIX, :OPTIONS_STOP, :SOURCE_USER
|
|
762
|
+
private_constant :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :OPTION_PREFIX, :OPTIONS_STOP, :SOURCE_USER, :REGEX_LOOKUP_ID_BY_FIELD
|
|
683
763
|
end
|
|
684
764
|
end
|
|
685
765
|
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: Aspera CLI Options Schema
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Schema definitions for Aspera CLI options
|
|
6
|
+
paths: {}
|
|
7
|
+
components:
|
|
8
|
+
schemas:
|
|
9
|
+
TransferInfo:
|
|
10
|
+
type: object
|
|
11
|
+
description: Optional parameters to control multi-session transfers, Web Socket Session, resume policy, and additional arguments for `ascp`.
|
|
12
|
+
properties:
|
|
13
|
+
wss:
|
|
14
|
+
type: boolean
|
|
15
|
+
description: Enable Web Socket Session when available.
|
|
16
|
+
default: true
|
|
17
|
+
quiet:
|
|
18
|
+
type: boolean
|
|
19
|
+
description: Suppress the `ascp` progress bar display.
|
|
20
|
+
default: false
|
|
21
|
+
trusted_certs:
|
|
22
|
+
type: array
|
|
23
|
+
description: List of trusted certificate repositories.
|
|
24
|
+
items:
|
|
25
|
+
type: string
|
|
26
|
+
client_ssh_key:
|
|
27
|
+
type: string
|
|
28
|
+
description: SSH key type to use for token-based transfers.
|
|
29
|
+
enum:
|
|
30
|
+
- rsa
|
|
31
|
+
- dsa_rsa
|
|
32
|
+
- per_client
|
|
33
|
+
default: rsa
|
|
34
|
+
ascp_args:
|
|
35
|
+
type: array
|
|
36
|
+
description: List of native `ascp` command-line arguments.
|
|
37
|
+
items:
|
|
38
|
+
type: string
|
|
39
|
+
default: []
|
|
40
|
+
spawn_timeout_sec:
|
|
41
|
+
type: number
|
|
42
|
+
format: float
|
|
43
|
+
description: Multi session - Maximum time (in seconds) to verify that `ascp` is running.
|
|
44
|
+
default: 3
|
|
45
|
+
spawn_delay_sec:
|
|
46
|
+
type: number
|
|
47
|
+
format: float
|
|
48
|
+
description: Multi session - Delay (in seconds) between starting each `ascp` session.
|
|
49
|
+
default: 2
|
|
50
|
+
multi_incr_udp:
|
|
51
|
+
type: boolean
|
|
52
|
+
description: >-
|
|
53
|
+
Multi session - Increment UDP port for each session.
|
|
54
|
+
|
|
55
|
+
If `true`, each session uses a different UDP port starting at `fasp_port` (default: 33001).
|
|
56
|
+
|
|
57
|
+
If `false`, all sessions use the same `fasp_port` (or `ascp` default).
|
|
58
|
+
default: true
|
|
59
|
+
resume:
|
|
60
|
+
type: object
|
|
61
|
+
description: Configuration for automatic transfer resume on interruption.
|
|
62
|
+
properties:
|
|
63
|
+
iter_max:
|
|
64
|
+
type: integer
|
|
65
|
+
description: Maximum number of retry attempts on error.
|
|
66
|
+
default: 7
|
|
67
|
+
sleep_initial:
|
|
68
|
+
type: integer
|
|
69
|
+
description: Initial sleep duration (in seconds) before first retry.
|
|
70
|
+
default: 2
|
|
71
|
+
sleep_factor:
|
|
72
|
+
type: integer
|
|
73
|
+
description: Multiplier applied to sleep duration between consecutive retry attempts.
|
|
74
|
+
default: 2
|
|
75
|
+
sleep_max:
|
|
76
|
+
type: integer
|
|
77
|
+
description: Maximum sleep duration (in seconds) between retry attempts.
|
|
78
|
+
default: 60
|
|
79
|
+
monitor:
|
|
80
|
+
type: boolean
|
|
81
|
+
description: Enable use of the `ascp` management port for transfer monitoring.
|
|
82
|
+
default: true
|