aspera-cli 4.26.0 → 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 +17 -3
- data/lib/aspera/api/aoc.rb +2 -1
- data/lib/aspera/api/node.rb +2 -2
- data/lib/aspera/ascp/installation.rb +7 -4
- data/lib/aspera/assert.rb +17 -13
- data/lib/aspera/cli/extended_value.rb +6 -2
- data/lib/aspera/cli/formatter.rb +65 -60
- data/lib/aspera/cli/main.rb +69 -10
- data/lib/aspera/cli/manager.rb +130 -76
- data/lib/aspera/cli/options.schema.yaml +82 -0
- data/lib/aspera/cli/plugins/aoc.rb +36 -11
- data/lib/aspera/cli/plugins/base.rb +46 -37
- data/lib/aspera/cli/plugins/config.rb +9 -9
- data/lib/aspera/cli/plugins/faspex.rb +1 -1
- data/lib/aspera/cli/plugins/faspex5.rb +4 -5
- data/lib/aspera/cli/plugins/node.rb +1 -1
- 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/environment.rb +3 -3
- data/lib/aspera/formatter_interface.rb +14 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/log.rb +4 -3
- data/lib/aspera/markdown.rb +4 -1
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +3 -0
- data/lib/aspera/rest.rb +1 -1
- 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/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 +1 -1
- data/lib/aspera/yaml.rb +4 -2
- data.tar.gz.sig +0 -0
- metadata +9 -3
- 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)
|
|
@@ -150,7 +171,6 @@ module Aspera
|
|
|
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,25 @@ 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)}"
|
|
206
227
|
end
|
|
207
228
|
|
|
208
229
|
# @return [Hash{Symbol => String}, nil] `{field:,value:}` if identifier is a percent selector, else `nil`
|
|
@@ -219,6 +240,8 @@ module Aspera
|
|
|
219
240
|
attr_accessor :ask_missing_mandatory, :ask_missing_optional
|
|
220
241
|
attr_writer :fail_on_missing_mandatory
|
|
221
242
|
|
|
243
|
+
# @param program_name [String] Name of the program
|
|
244
|
+
# @param argv [Array<String>, nil] Command line arguments to parse
|
|
222
245
|
def initialize(program_name, argv = nil)
|
|
223
246
|
# command line values *not* starting with '-'
|
|
224
247
|
@unprocessed_cmd_line_arguments = []
|
|
@@ -226,7 +249,8 @@ module Aspera
|
|
|
226
249
|
@unprocessed_cmd_line_options = []
|
|
227
250
|
# a copy of all initial options
|
|
228
251
|
@initial_cli_options = []
|
|
229
|
-
#
|
|
252
|
+
# Option descriptions: maps option symbol to its OptionValue descriptor
|
|
253
|
+
# @type [Hash{Symbol => OptionValue}]
|
|
230
254
|
@declared_options = {}
|
|
231
255
|
# do we ask missing options and arguments to user ?
|
|
232
256
|
@ask_missing_mandatory = false # STDIN.isatty
|
|
@@ -245,7 +269,7 @@ module Aspera
|
|
|
245
269
|
# options can also be provided by env vars : --param-name -> ASCLI_PARAM_NAME
|
|
246
270
|
env_prefix = program_name.upcase + OPTION_SEP_SYMBOL
|
|
247
271
|
ENV.each do |k, v|
|
|
248
|
-
@option_pairs_env[k
|
|
272
|
+
@option_pairs_env[k.delete_prefix(env_prefix).downcase.to_sym] = v if k.start_with?(env_prefix)
|
|
249
273
|
end
|
|
250
274
|
Log.log.debug{"env=#{@option_pairs_env}".red}
|
|
251
275
|
@unprocessed_cmd_line_options = []
|
|
@@ -276,6 +300,14 @@ module Aspera
|
|
|
276
300
|
# do not parse options yet, let's wait for option `-h` to be overridden
|
|
277
301
|
end
|
|
278
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
|
+
|
|
279
311
|
# Declare an option
|
|
280
312
|
# @param option_symbol [Symbol] option name
|
|
281
313
|
# @param description [String] description for help
|
|
@@ -284,8 +316,9 @@ module Aspera
|
|
|
284
316
|
# @param default [Object] default value
|
|
285
317
|
# @param handler [Hash] handler for option value: keys: :o(object) and :m(method)
|
|
286
318
|
# @param deprecation [String] deprecation
|
|
319
|
+
# @param schema [String] Definition of schema for Hash parameters
|
|
287
320
|
# @param block [Proc] Block to execute when option is found
|
|
288
|
-
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)
|
|
289
322
|
Aspera.assert_type(option_symbol, Symbol)
|
|
290
323
|
Aspera.assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
|
|
291
324
|
Aspera.assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
|
|
@@ -298,10 +331,11 @@ module Aspera
|
|
|
298
331
|
description: description,
|
|
299
332
|
allowed: allowed,
|
|
300
333
|
handler: handler,
|
|
301
|
-
deprecation: deprecation
|
|
334
|
+
deprecation: deprecation,
|
|
335
|
+
schema: schema
|
|
302
336
|
)
|
|
303
337
|
real_types = option_attrs.types&.reject{ |i| [NilClass, String, Symbol].include?(i)}
|
|
304
|
-
description
|
|
338
|
+
description += add_types_info(real_types)
|
|
305
339
|
description = "#{description} (#{'deprecated'.blue}: #{deprecation})" if deprecation
|
|
306
340
|
set_option(option_symbol, default, where: 'default') unless default.nil?
|
|
307
341
|
on_args = [description]
|
|
@@ -340,32 +374,36 @@ module Aspera
|
|
|
340
374
|
end
|
|
341
375
|
|
|
342
376
|
# @param descr [String] description for help
|
|
343
|
-
# @param mandatory [Boolean]
|
|
344
|
-
# @param multiple [Boolean]
|
|
345
|
-
# @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
|
|
346
380
|
# @param validation [Class, Array, NilClass] Accepted value type(s) or list of Symbols
|
|
347
381
|
# @param aliases [Hash] map of aliases: key = alias, value = real value
|
|
348
382
|
# @param default [Object] default value
|
|
349
383
|
# @return one value, list or nil (if optional and no default)
|
|
350
|
-
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)
|
|
351
385
|
Aspera.assert_array_all(accept_list, Symbol) unless accept_list.nil?
|
|
352
386
|
Aspera.assert_hash_all(aliases, Symbol, Symbol) unless aliases.nil?
|
|
353
387
|
validation = Symbol unless accept_list.nil?
|
|
354
388
|
validation = [validation] unless validation.is_a?(Array) || validation.nil?
|
|
355
389
|
Aspera.assert_array_all(validation, Class){'validation'} unless validation.nil?
|
|
356
|
-
descr = "#{descr}
|
|
390
|
+
descr = "#{descr}#{add_types_info(validation)}"
|
|
357
391
|
result =
|
|
358
392
|
if !@unprocessed_cmd_line_arguments.empty?
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
|
364
401
|
values = @unprocessed_cmd_line_arguments.shift(index)
|
|
365
|
-
@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)
|
|
366
405
|
end
|
|
367
|
-
else
|
|
368
|
-
values = [@unprocessed_cmd_line_arguments.shift]
|
|
406
|
+
else Aspera.error_unexpected_value(multiple){'multiple'}
|
|
369
407
|
end
|
|
370
408
|
values = values.map{ |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation)}
|
|
371
409
|
# If expecting list and only one arg of type array : it is the list
|
|
@@ -378,7 +416,7 @@ module Aspera
|
|
|
378
416
|
multiple ? values : values.first
|
|
379
417
|
elsif !default.nil? then default
|
|
380
418
|
# no value provided, either get value interactively, or exception
|
|
381
|
-
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)
|
|
382
420
|
end
|
|
383
421
|
if result.is_a?(String) && validation&.eql?(Allowed::TYPES_INTEGER)
|
|
384
422
|
int_result = Integer(result, exception: false)
|
|
@@ -392,18 +430,22 @@ module Aspera
|
|
|
392
430
|
if validation && (mandatory || !result.nil?)
|
|
393
431
|
value_list = multiple ? result : [result]
|
|
394
432
|
value_list.each do |value|
|
|
433
|
+
raise SchemaRequest.new(:argument, descr, schema) if validation.include?(Hash) && value.eql?(HELP)
|
|
395
434
|
raise Cli::BadArgument,
|
|
396
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)}
|
|
397
436
|
end
|
|
398
437
|
end
|
|
399
|
-
|
|
438
|
+
result
|
|
400
439
|
end
|
|
401
440
|
|
|
402
441
|
# Resource identifier as positional parameter
|
|
403
442
|
#
|
|
404
443
|
# @param description [String] description of the identifier
|
|
405
444
|
# @param block [Proc] block to search for identifier based on attribute value
|
|
406
|
-
# @return [String, Array] identifier or list of
|
|
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
|
|
407
449
|
def instance_identifier(description: 'identifier', &block)
|
|
408
450
|
res_id = get_next_argument(description, multiple: get_option(:bulk)) if res_id.nil?
|
|
409
451
|
# Can be an Array
|
|
@@ -411,19 +453,28 @@ module Aspera
|
|
|
411
453
|
Aspera.assert(block_given?, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
|
|
412
454
|
res_id = yield(m[:field], m[:value])
|
|
413
455
|
end
|
|
414
|
-
|
|
456
|
+
res_id
|
|
415
457
|
end
|
|
416
458
|
|
|
417
|
-
def get_next_command(command_list, aliases: nil);
|
|
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
|
|
418
469
|
|
|
419
470
|
# Get an option value by name
|
|
420
471
|
# either return value or calls handler, can return nil
|
|
421
472
|
# ask interactively if requested/required
|
|
473
|
+
# @param option_symbol [Symbol]
|
|
422
474
|
# @param mandatory [Boolean] if true, raise error if option not set
|
|
423
475
|
def get_option(option_symbol, mandatory: false)
|
|
424
476
|
Aspera.assert_type(option_symbol, Symbol)
|
|
425
|
-
|
|
426
|
-
option_attrs = @declared_options[option_symbol]
|
|
477
|
+
option_attrs = option_def(option_symbol)
|
|
427
478
|
result = option_attrs.value
|
|
428
479
|
# Do not fail for manual generation if option mandatory but not set
|
|
429
480
|
return :skip_missing_mandatory if result.nil? && mandatory && !@fail_on_missing_mandatory
|
|
@@ -432,11 +483,11 @@ module Aspera
|
|
|
432
483
|
Aspera.assert(!mandatory, type: Cli::BadArgument){"Missing mandatory option: #{option_symbol}"}
|
|
433
484
|
elsif @ask_missing_optional || mandatory
|
|
434
485
|
# ask_missing_mandatory
|
|
435
|
-
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)
|
|
436
487
|
set_option(option_symbol, result, where: 'interactive')
|
|
437
488
|
end
|
|
438
489
|
end
|
|
439
|
-
|
|
490
|
+
result
|
|
440
491
|
end
|
|
441
492
|
|
|
442
493
|
# Set an option value by name, either store value or call handler
|
|
@@ -446,15 +497,15 @@ module Aspera
|
|
|
446
497
|
# @param where [String] Where the value comes from
|
|
447
498
|
def set_option(option_symbol, value, where: 'code override')
|
|
448
499
|
Aspera.assert_type(option_symbol, Symbol)
|
|
449
|
-
|
|
450
|
-
|
|
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)
|
|
451
503
|
end
|
|
452
504
|
|
|
453
505
|
# Set option to `nil`
|
|
454
506
|
def clear_option(option_symbol)
|
|
455
507
|
Aspera.assert_type(option_symbol, Symbol)
|
|
456
|
-
|
|
457
|
-
@declared_options[option_symbol].clear
|
|
508
|
+
option_def(option_symbol).clear
|
|
458
509
|
end
|
|
459
510
|
|
|
460
511
|
# Adds each of the keys of specified hash as an option
|
|
@@ -477,7 +528,7 @@ module Aspera
|
|
|
477
528
|
|
|
478
529
|
# Check if there were unprocessed values to generate error
|
|
479
530
|
def command_or_arg_empty?
|
|
480
|
-
|
|
531
|
+
@unprocessed_cmd_line_arguments.empty?
|
|
481
532
|
end
|
|
482
533
|
|
|
483
534
|
# Unprocessed options or arguments ?
|
|
@@ -485,7 +536,7 @@ module Aspera
|
|
|
485
536
|
result = []
|
|
486
537
|
result.push("unprocessed options: #{@unprocessed_cmd_line_options}") unless @unprocessed_cmd_line_options.empty?
|
|
487
538
|
result.push("unprocessed values: #{@unprocessed_cmd_line_arguments}") unless @unprocessed_cmd_line_arguments.empty?
|
|
488
|
-
|
|
539
|
+
result
|
|
489
540
|
end
|
|
490
541
|
|
|
491
542
|
# Get all original options on command line used to generate a config in config file
|
|
@@ -495,7 +546,7 @@ module Aspera
|
|
|
495
546
|
@initial_cli_options.each do |option_argument|
|
|
496
547
|
# ignore short options
|
|
497
548
|
next unless option_argument.start_with?(OPTION_PREFIX)
|
|
498
|
-
name, value = option_argument
|
|
549
|
+
name, value = option_argument.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2)
|
|
499
550
|
# ignore options without value
|
|
500
551
|
next if value.nil?
|
|
501
552
|
Log.log.debug{"option #{name}=#{value}"}
|
|
@@ -504,7 +555,7 @@ module Aspera
|
|
|
504
555
|
DotContainer.dotted_to_container(path, smart_convert(value), result)
|
|
505
556
|
@unprocessed_cmd_line_options.delete(option_argument)
|
|
506
557
|
end
|
|
507
|
-
|
|
558
|
+
result
|
|
508
559
|
end
|
|
509
560
|
|
|
510
561
|
# @param only_defined [Boolean] if true, only return options that were defined
|
|
@@ -517,7 +568,7 @@ module Aspera
|
|
|
517
568
|
rescue => e
|
|
518
569
|
result[option_symbol] = e.to_s
|
|
519
570
|
end
|
|
520
|
-
|
|
571
|
+
result
|
|
521
572
|
end
|
|
522
573
|
|
|
523
574
|
# Removes already known options from the list
|
|
@@ -539,7 +590,7 @@ module Aspera
|
|
|
539
590
|
Log.log.trace1{"InvalidOption #{e}".red}
|
|
540
591
|
# An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
541
592
|
if e.args.first.start_with?(OPTION_PREFIX)
|
|
542
|
-
name, value = e.args.first
|
|
593
|
+
name, value = e.args.first.delete_prefix(OPTION_PREFIX).split(OPTION_VALUE_SEPARATOR, 2)
|
|
543
594
|
if !value.nil?
|
|
544
595
|
path = name.split(DotContainer::SEPARATOR)
|
|
545
596
|
option_sym = self.class.option_line_to_name(path.shift).to_sym
|
|
@@ -565,7 +616,7 @@ module Aspera
|
|
|
565
616
|
print("#{prompt}> ")
|
|
566
617
|
line = $stdin.gets
|
|
567
618
|
Aspera.assert_type(line, String){'Unexpected end of standard input'}
|
|
568
|
-
|
|
619
|
+
line.chomp
|
|
569
620
|
end
|
|
570
621
|
|
|
571
622
|
# prompt user for input in a list of symbols
|
|
@@ -586,20 +637,19 @@ module Aspera
|
|
|
586
637
|
# Prompt user for input in a list of symbols
|
|
587
638
|
# @param descr [String] description for help
|
|
588
639
|
# @param check_option [Boolean] Check attributes of option with name=descr
|
|
589
|
-
# @param multiple [Boolean] true if multiple values expected
|
|
590
|
-
# @param accept_list [Array]
|
|
591
|
-
|
|
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)
|
|
592
644
|
option_attrs = @declared_options[descr.to_sym]
|
|
593
645
|
what = option_attrs ? 'option' : 'argument'
|
|
646
|
+
default_prompt = "#{what}: #{descr}"
|
|
594
647
|
if !@ask_missing_mandatory
|
|
595
|
-
message = "
|
|
596
|
-
if accept_list
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Aspera.assert(false, self.class.multi_choice_assert_msg(message, accept_list), type: Cli::MissingArgument)
|
|
600
|
-
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
|
|
601
652
|
end
|
|
602
|
-
default_prompt = "#{what}: #{descr}"
|
|
603
653
|
# ask interactively
|
|
604
654
|
result = []
|
|
605
655
|
puts(' (one per line, end with empty line)') if multiple
|
|
@@ -613,18 +663,20 @@ module Aspera
|
|
|
613
663
|
return entry unless multiple
|
|
614
664
|
result.push(entry)
|
|
615
665
|
end
|
|
616
|
-
|
|
666
|
+
result
|
|
617
667
|
end
|
|
618
668
|
|
|
619
|
-
# Read remaining args and build an Array or Hash
|
|
620
|
-
# @param value [
|
|
621
|
-
|
|
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)
|
|
622
673
|
# This extended value does not take args (`@:`)
|
|
623
|
-
ExtendedValue.assert_no_value(
|
|
674
|
+
# ExtendedValue.assert_no_value(end_marker, :p)
|
|
675
|
+
end_marker = SpecialValues::EOA if end_marker.empty?
|
|
624
676
|
result = nil
|
|
625
|
-
get_next_argument(
|
|
626
|
-
Aspera.assert(
|
|
627
|
-
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)
|
|
628
680
|
result = DotContainer.dotted_to_container(path.split(DotContainer::SEPARATOR), smart_convert(value), result)
|
|
629
681
|
end
|
|
630
682
|
result
|
|
@@ -651,7 +703,7 @@ module Aspera
|
|
|
651
703
|
def symbol_to_option(symbol, opt_val = nil)
|
|
652
704
|
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
|
653
705
|
result = [result, OPTION_VALUE_SEPARATOR, opt_val].join unless opt_val.nil?
|
|
654
|
-
|
|
706
|
+
result
|
|
655
707
|
end
|
|
656
708
|
|
|
657
709
|
# TODO: use formatter
|
|
@@ -704,6 +756,8 @@ module Aspera
|
|
|
704
756
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
|
705
757
|
# Percent selector: select by this field for this value
|
|
706
758
|
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
759
|
+
# Ask for schema of Extended value
|
|
760
|
+
HELP = 'help'
|
|
707
761
|
|
|
708
762
|
private_constant :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :OPTION_PREFIX, :OPTIONS_STOP, :SOURCE_USER, :REGEX_LOOKUP_ID_BY_FIELD
|
|
709
763
|
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
|