aspera-cli 4.14.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -1,42 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/cli/extended_value'
4
+ require 'aspera/cli/error'
3
5
  require 'aspera/colors'
4
- require 'aspera/log'
5
6
  require 'aspera/secret_hider'
6
- require 'aspera/cli/extended_value'
7
- require 'optparse'
7
+ require 'aspera/log'
8
+ require 'aspera/assert'
8
9
  require 'io/console'
10
+ require 'optparse'
9
11
 
10
12
  module Aspera
11
13
  module Cli
12
- # raised by cli on error conditions
13
- class CliError < StandardError; end
14
-
15
- # raised when an unexpected argument is provided
16
- class CliBadArgument < Aspera::Cli::CliError; end
17
-
18
- class CliNoSuchId < Aspera::Cli::CliError
19
- def initialize(res_type, res_id)
20
- msg = "No such #{res_type} identifier: #{res_id}"
21
- super(msg)
22
- end
23
- end
24
-
25
14
  # option is retrieved from another object using accessor
26
15
  class AttrAccessor
27
16
  # attr_accessor :object
28
- # attr_accessor :attr_symb
29
- def initialize(object, attr_symb)
17
+ # attr_accessor :method_name
18
+ def initialize(object, method_name, option_name)
30
19
  @object = object
31
- @attr_symb = attr_symb
20
+ @method = method_name
21
+ @option_name = option_name
22
+ @has_writer = @object.respond_to?(writer_method)
23
+ Log.log.debug{"AttrAccessor: #{@option_name}: #{@object.class}.#{@method}: writer=#{@has_writer}"}
24
+ assert(@object.respond_to?(@method)) {"#{object} does not respond to #{method_name}"}
32
25
  end
33
26
 
34
27
  def value
35
- return @object.send(@attr_symb)
28
+ return @object.send(@method) if @has_writer
29
+ return @object.send(@method, @option_name, :get)
36
30
  end
37
31
 
38
32
  def value=(val)
39
- @object.send("#{@attr_symb}=", val)
33
+ Log.log.trace1{"AttrAccessor: = #{@method} #{@option_name} :set #{val}, writer=#{@has_writer}"}
34
+ return @object.send(writer_method, val) if @has_writer
35
+ return @object.send(@method, @option_name, :set, val)
36
+ end
37
+
38
+ def writer_method
39
+ return "#{@method}="
40
40
  end
41
41
  end
42
42
 
@@ -53,13 +53,15 @@ module Aspera
53
53
  # option name separator on command line
54
54
  OPTION_SEP_LINE = '-'
55
55
  # option name separator in code (symbol)
56
- OPTION_SEP_SYMB = '_'
56
+ OPTION_SEP_SYMBOL = '_'
57
+ SOURCE_USER = 'cmdline' # cspell:disable-line
58
+ TYPE_INTEGER = [Integer].freeze
57
59
 
58
- private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMB
60
+ private_constant :FALSE_VALUES, :TRUE_VALUES, :BOOLEAN_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :SOURCE_USER, :TYPE_INTEGER
59
61
 
60
62
  class << self
61
63
  def enum_to_bool(enum)
62
- raise "Value not valid for boolean: [#{enum}]/#{enum.class}" unless BOOLEAN_VALUES.include?(enum)
64
+ assert_values(enum, BOOLEAN_VALUES){'boolean'}
63
65
  return TRUE_VALUES.include?(enum)
64
66
  end
65
67
 
@@ -68,28 +70,43 @@ module Aspera
68
70
  end
69
71
 
70
72
  # find shortened string value in allowed symbol list
71
- def get_from_list(shortval, descr, allowed_values)
73
+ def get_from_list(short_value, descr, allowed_values)
72
74
  # we accept shortcuts
73
- matching_exact = allowed_values.select{|i| i.to_s.eql?(shortval)}
75
+ matching_exact = allowed_values.select{|i| i.to_s.eql?(short_value)}
74
76
  return matching_exact.first if matching_exact.length == 1
75
- matching = allowed_values.select{|i| i.to_s.start_with?(shortval)}
76
- raise CliBadArgument, bad_arg_message_multi("unknown value for #{descr}: #{shortval}", allowed_values) if matching.empty?
77
- raise CliBadArgument, bad_arg_message_multi("ambiguous shortcut for #{descr}: #{shortval}", matching) unless matching.length.eql?(1)
77
+ matching = allowed_values.select{|i| i.to_s.start_with?(short_value)}
78
+ multi_choice_assert(!matching.empty?,"unknown value for #{descr}: #{short_value}", allowed_values)
79
+ multi_choice_assert(matching.length.eql?(1),"ambiguous shortcut for #{descr}: #{short_value}", matching)
78
80
  return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
79
81
  return matching.first
80
82
  end
81
83
 
82
- def bad_arg_message_multi(error_msg, choices)
83
- return [error_msg, 'Use:'].concat(choices.map{|c|"- #{c}"}.sort).join("\n")
84
+ # Generates error message with list of allowed values
85
+ # @param error_msg [String] error message
86
+ # @param choices [Array] list of allowed values
87
+ def multi_choice_assert(assertion,error_msg, choices)
88
+ raise Cli::BadArgument, [error_msg, 'Use:'].concat(choices.map{|c|"- #{c}"}.sort).join("\n") unless assertion
84
89
  end
85
90
 
86
91
  # change option name with dash to name with underscore
87
92
  def option_line_to_name(name)
88
- return name.gsub(OPTION_SEP_LINE, OPTION_SEP_SYMB)
93
+ return name.gsub(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
89
94
  end
90
95
 
91
96
  def option_name_to_line(name)
92
- return "--#{name.to_s.gsub(OPTION_SEP_SYMB, OPTION_SEP_LINE)}"
97
+ return "--#{name.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)}"
98
+ end
99
+
100
+ # @param what [Symbol] :option or :argument
101
+ # @param descr [String] description for help
102
+ # @param value [Object] value to check
103
+ # @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
104
+ def validate_type(what, descr, value, type_list)
105
+ return nil if type_list.nil?
106
+ assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
107
+ raise Cli::BadArgument,
108
+ "#{what.to_s.capitalize} #{descr} is a #{value.class} but must be #{type_list.length > 1 ? 'one of ' : ''}#{type_list.map(&:name).join(',')}" unless \
109
+ type_list.any?{|t|value.is_a?(t)}
93
110
  end
94
111
  end
95
112
 
@@ -97,7 +114,7 @@ module Aspera
97
114
  attr_accessor :ask_missing_mandatory, :ask_missing_optional
98
115
  attr_writer :fail_on_missing_mandatory
99
116
 
100
- def initialize(program_name, argv: nil)
117
+ def initialize(program_name)
101
118
  # command line values not starting with '-'
102
119
  @unprocessed_cmd_line_arguments = []
103
120
  # command line values starting with '-'
@@ -118,7 +135,7 @@ module Aspera
118
135
  @parser = OptionParser.new
119
136
  @parser.program_name = program_name
120
137
  # options can also be provided by env vars : --param-name -> ASCLI_PARAM_NAME
121
- env_prefix = program_name.upcase + OPTION_SEP_SYMB
138
+ env_prefix = program_name.upcase + OPTION_SEP_SYMBOL
122
139
  ENV.each do |k, v|
123
140
  if k.start_with?(env_prefix)
124
141
  @unprocessed_env.push([k[env_prefix.length..-1].downcase.to_sym, v])
@@ -127,71 +144,83 @@ module Aspera
127
144
  Log.log.debug{"env=#{@unprocessed_env}".red}
128
145
  @unprocessed_cmd_line_options = []
129
146
  @unprocessed_cmd_line_arguments = []
130
- # argv is nil when help is generated for every plugin
131
- unless argv.nil?
132
- @parser.separator('')
133
- @parser.separator('OPTIONS: global')
134
- declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
135
- declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
136
- parse_options!
137
- process_options = true
138
- until argv.empty?
139
- value = argv.shift
140
- if process_options && value.start_with?('-')
141
- if value.eql?('--')
142
- process_options = false
143
- else
144
- @unprocessed_cmd_line_options.push(value)
145
- end
147
+ end
148
+
149
+ def parse_command_line(argv)
150
+ @parser.separator('')
151
+ @parser.separator('OPTIONS: global')
152
+ declare(:interactive, 'Use interactive input of missing params', values: :bool, handler: {o: self, m: :ask_missing_mandatory})
153
+ declare(:ask_options, 'Ask even optional options', values: :bool, handler: {o: self, m: :ask_missing_optional})
154
+ parse_options!
155
+ process_options = true
156
+ until argv.empty?
157
+ value = argv.shift
158
+ if process_options && value.start_with?('-')
159
+ if value.eql?('--')
160
+ process_options = false
146
161
  else
147
- @unprocessed_cmd_line_arguments.push(value)
162
+ @unprocessed_cmd_line_options.push(value)
148
163
  end
164
+ else
165
+ @unprocessed_cmd_line_arguments.push(value)
149
166
  end
150
167
  end
151
168
  @initial_cli_options = @unprocessed_cmd_line_options.dup
152
- Log.log.debug{"add_cmd_line_options:commands/args=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red}
169
+ Log.log.debug{"add_cmd_line_options:commands/arguments=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red}
153
170
  end
154
171
 
172
+ # @param descr [String] description for help
155
173
  # @param expected is
156
- # - Array of allowed value (single value)
157
- # - :multiple for remaining values
158
- # - :single for a single unconstrained value
159
- # @param mandatory true/false
160
- # @param type expected class for result
161
- # @param aliases list of aliases for the value
174
+ # - Array of allowed value (single value)
175
+ # - :multiple for remaining values
176
+ # - :single for a single unconstrained value
177
+ # - :integer for a single integer value
178
+ # @param mandatory [Boolean] if true, raise error if option not set
179
+ # @param type [Class, Array] accepted value type(s)
180
+ # @param aliases [Hash] map of aliases: key = alias, value = real value
181
+ # @param default [Object] default value
162
182
  # @return value, list or nil
163
183
  def get_next_argument(descr, expected: :single, mandatory: true, type: nil, aliases: nil, default: nil)
164
- unless type.nil?
165
- raise 'internal: type must be a Class' unless type.is_a?(Class)
166
- descr = "#{descr} (#{type})"
184
+ assert(%i[single multiple].include?(expected) || (expected.is_a?(Array) && expected.all?(Symbol))){'expected must be single, multiple, or array of symbol'}
185
+ assert(type.nil? || type.is_a?(Class) || (type.is_a?(Array) && type.all?(Class))){'type must be Class or Array of Class'}
186
+ assert(aliases.nil? || (aliases.is_a?(Hash) && aliases.keys.all?(Symbol) && aliases.values.all?(Symbol))){'aliases must be Hash'}
187
+ allowed_types = type
188
+ unless allowed_types.nil?
189
+ allowed_types = [allowed_types] unless allowed_types.is_a?(Array)
190
+ descr = "#{descr} (#{allowed_types.join(', ')})"
191
+ Log.log.debug{">>>> #{descr}=#{allowed_types}"}
167
192
  end
168
- result = default
169
- if !@unprocessed_cmd_line_arguments.empty?
170
- # there are values
171
- case expected
172
- when :single
173
- result = ExtendedValue.instance.evaluate(@unprocessed_cmd_line_arguments.shift)
174
- when :multiple
175
- result = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length).map{|v|ExtendedValue.instance.evaluate(v)}
176
- # if expecting list and only one arg of type array : it is the list
177
- if result.length.eql?(1) && result.first.is_a?(Array)
178
- result = result.first
193
+ result =
194
+ if !@unprocessed_cmd_line_arguments.empty?
195
+ # there are values
196
+ case expected
197
+ when :single
198
+ ExtendedValue.instance.evaluate(@unprocessed_cmd_line_arguments.shift)
199
+ when :multiple
200
+ value = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length).map{|v|ExtendedValue.instance.evaluate(v)}
201
+ # if expecting list and only one arg of type array : it is the list
202
+ if value.length.eql?(1) && value.first.is_a?(Array)
203
+ value = value.first
204
+ end
205
+ value
206
+ when Array
207
+ allowed_values = [].concat(expected)
208
+ allowed_values.concat(aliases.keys) unless aliases.nil?
209
+ self.class.get_from_list(@unprocessed_cmd_line_arguments.shift, descr, allowed_values)
210
+ else error_unexpected_value(expected)
179
211
  end
180
- when Array
181
- allowed_values = [].concat(expected)
182
- allowed_values.concat(aliases.keys) unless aliases.nil?
183
- raise "internal error: only symbols allowed: #{allowed_values}" unless allowed_values.all?(Symbol)
184
- result = self.class.get_from_list(@unprocessed_cmd_line_arguments.shift, descr, allowed_values)
185
- else
186
- raise 'internal error'
212
+ elsif !default.nil? then default
213
+ # no value provided, either get value interactively, or exception
214
+ elsif mandatory then get_interactive(:argument, descr, expected: expected)
187
215
  end
188
- elsif mandatory
189
- # no value provided
190
- result = get_interactive(:argument, descr, expected: expected)
216
+ if result.is_a?(String) && allowed_types.eql?(TYPE_INTEGER)
217
+ int_result = Integer(result, exception: false)
218
+ raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
219
+ result = int_result
191
220
  end
192
221
  Log.log.debug{"#{descr}=#{result}"}
193
- result = aliases[result] if !aliases.nil? && aliases.key?(result)
194
- raise "argument shall be #{type.name}" unless type.nil? || result.is_a?(type)
222
+ result = aliases[result] if aliases&.key?(result)
223
+ self.class.validate_type(:argument, descr, result, allowed_types) unless result.nil? && !mandatory
195
224
  return result
196
225
  end
197
226
 
@@ -201,52 +230,50 @@ module Aspera
201
230
  # either return value or calls handler, can return nil
202
231
  # ask interactively if requested/required
203
232
  # @param mandatory [Boolean] if true, raise error if option not set
204
- # @param allowed_types [Array] list of allowed types
205
- def get_option(option_symbol, mandatory: false, allowed_types: nil)
206
- allowed_types = [allowed_types] if allowed_types.is_a?(Class)
207
- raise 'Internal Error: allowed_types must be an Array of Class or a Class' unless allowed_types.nil? || allowed_types.is_a?(Array)
233
+ def get_option(option_symbol, mandatory: false, default: nil)
234
+ attributes = @declared_options[option_symbol]
235
+ assert(attributes){"option not declared: #{option_symbol}"}
208
236
  result = nil
209
- if @declared_options.key?(option_symbol)
210
- case @declared_options[option_symbol][:read_write]
211
- when :accessor
212
- result = @declared_options[option_symbol][:accessor].value
213
- when :value
214
- result = @declared_options[option_symbol][:value]
215
- else
216
- raise 'unknown type'
217
- end
218
- Log.log.debug{"(#{@declared_options[option_symbol][:read_write]}) get #{option_symbol}=#{result}"}
237
+ case attributes[:read_write]
238
+ when :accessor
239
+ result = attributes[:accessor].value
240
+ when :value
241
+ result = attributes[:value]
242
+ else
243
+ raise 'unknown type'
219
244
  end
245
+ Log.log.debug{"(#{attributes[:read_write]}) get #{option_symbol}=#{result}"}
246
+ result = default if result.nil?
220
247
  # do not fail for manual generation if option mandatory but not set
221
248
  result = '' if result.nil? && mandatory && !@fail_on_missing_mandatory
222
249
  # Log.log.debug{"interactive=#{@ask_missing_mandatory}"}
223
250
  if result.nil?
224
251
  if !@ask_missing_mandatory
225
- raise CliBadArgument, "Missing mandatory option: #{option_symbol}" if mandatory
252
+ raise Cli::BadArgument, "Missing mandatory option: #{option_symbol}" if mandatory
226
253
  elsif @ask_missing_optional || mandatory
227
254
  # ask_missing_mandatory
228
255
  expected = :single
229
256
  # print "please enter: #{option_symbol.to_s}"
230
- if @declared_options.key?(option_symbol) && @declared_options[option_symbol].key?(:values)
231
- expected = @declared_options[option_symbol][:values]
257
+ if @declared_options.key?(option_symbol) && attributes.key?(:values)
258
+ expected = attributes[:values]
232
259
  end
233
260
  result = get_interactive(:option, option_symbol.to_s, expected: expected)
234
261
  set_option(option_symbol, result, 'interactive')
235
262
  end
236
263
  end
237
- raise "option #{option_symbol} is #{result.class} but must be one of #{allowed_types}" unless \
238
- !mandatory || allowed_types.nil? || allowed_types.any? { |t|result.is_a?(t)}
264
+ self.class.validate_type(:option, option_symbol, result, attributes[:types]) unless result.nil? && !mandatory
239
265
  return result
240
266
  end
241
267
 
242
268
  # set an option value by name, either store value or call handler
243
269
  def set_option(option_symbol, value, where='code override')
244
- raise CliBadArgument, "Unknown option: #{option_symbol}" unless @declared_options.key?(option_symbol)
270
+ raise Cli::BadArgument, "Unknown option: #{option_symbol}" unless @declared_options.key?(option_symbol)
245
271
  attributes = @declared_options[option_symbol]
246
272
  Log.log.warn("#{option_symbol}: Option is deprecated: #{attributes[:deprecation]}") if attributes[:deprecation]
247
273
  value = ExtendedValue.instance.evaluate(value)
248
274
  value = Manager.enum_to_bool(value) if attributes[:values].eql?(BOOLEAN_VALUES)
249
275
  Log.log.debug{"(#{attributes[:read_write]}/#{where}) set #{option_symbol}=#{value}"}
276
+ self.class.validate_type(:option, option_symbol, value, attributes[:types])
250
277
  case attributes[:read_write]
251
278
  when :accessor
252
279
  attributes[:accessor].value = value
@@ -268,10 +295,10 @@ module Aspera
268
295
  # @param types [Class, Array] accepted value type(s)
269
296
  # @param block [Proc] block to execute when option is found
270
297
  def declare(option_symbol, description, handler: nil, default: nil, values: nil, short: nil, coerce: nil, types: nil, deprecation: nil, &block)
271
- raise "INTERNAL ERROR: #{option_symbol} already declared" if @declared_options.key?(option_symbol)
272
- raise "INTERNAL ERROR: #{option_symbol} ends with dot" unless description[-1] != '.'
273
- raise "INTERNAL ERROR: #{option_symbol} does not start with capital" unless description[0] == description[0].upcase
274
- raise "INTERNAL ERROR: #{option_symbol} shall use :types" if description.downcase.include?('hash') || description.downcase.include?('extended value')
298
+ assert(!@declared_options.key?(option_symbol)){"#{option_symbol} already declared"}
299
+ assert(description[-1] != '.'){"#{option_symbol} ends with dot"}
300
+ assert(description[0] == description[0].upcase){"#{option_symbol} description does not start with capital"}
301
+ assert(!['hash', 'extended value'].any?{|s|description.downcase.include?(s) }){"#{option_symbol} shall use :types"}
275
302
  opt = @declared_options[option_symbol] = {
276
303
  read_write: handler.nil? ? :value : :accessor,
277
304
  # by default passwords and secrets are sensitive, else specify when declaring the option
@@ -279,7 +306,7 @@ module Aspera
279
306
  }
280
307
  if !types.nil?
281
308
  types = [types] unless types.is_a?(Array)
282
- raise "INTERNAL ERROR: types must be classes: #{types}" unless types.all?(Class)
309
+ assert(types.all?(Class)){"types must be Array of Class: #{types}"}
283
310
  opt[:types] = types
284
311
  description = "#{description} (#{types.map(&:name).join(', ')})"
285
312
  end
@@ -289,10 +316,10 @@ module Aspera
289
316
  end
290
317
  Log.log.debug{"declare: #{option_symbol}: #{opt[:read_write]}".green}
291
318
  if opt[:read_write].eql?(:accessor)
292
- raise 'internal error' unless handler.is_a?(Hash)
293
- raise 'internal error' unless handler.keys.sort.eql?(%i[m o])
319
+ assert_type(handler, Hash)
320
+ assert(handler.keys.sort.eql?(%i[m o]))
294
321
  Log.log.debug{"set attr obj #{option_symbol} (#{handler[:o]},#{handler[:m]})"}
295
- opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m])
322
+ opt[:accessor] = AttrAccessor.new(handler[:o], handler[:m], option_symbol)
296
323
  end
297
324
  set_option(option_symbol, default, 'default') unless default.nil?
298
325
  on_args = [description]
@@ -301,7 +328,7 @@ module Aspera
301
328
  on_args.push(symbol_to_option(option_symbol, 'VALUE'))
302
329
  on_args.push("-#{short}VALUE") unless short.nil?
303
330
  on_args.push(coerce) unless coerce.nil?
304
- @parser.on(*on_args) { |v| set_option(option_symbol, v, 'cmdline') }
331
+ @parser.on(*on_args) { |v| set_option(option_symbol, v, SOURCE_USER) }
305
332
  when Array, :bool
306
333
  if values.eql?(:bool)
307
334
  values = BOOLEAN_VALUES
@@ -317,7 +344,7 @@ module Aspera
317
344
  on_args[0] = "#{description}: #{help_values}"
318
345
  on_args.push(symbol_to_option(option_symbol, 'ENUM'))
319
346
  on_args.push(values)
320
- @parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), 'cmdline')}
347
+ @parser.on(*on_args){|v|set_option(option_symbol, self.class.get_from_list(v.to_s, description, values), SOURCE_USER)}
321
348
  when :date
322
349
  on_args.push(symbol_to_option(option_symbol, 'DATE'))
323
350
  @parser.on(*on_args) do |v|
@@ -326,14 +353,14 @@ module Aspera
326
353
  when /^-([0-9]+)h/ then Manager.time_to_string(Time.now - (Regexp.last_match(1).to_i * 3600))
327
354
  else v
328
355
  end
329
- set_option(option_symbol, time_string, 'cmdline')
356
+ set_option(option_symbol, time_string, SOURCE_USER)
330
357
  end
331
358
  when :none
332
- raise "internal error: missing block for #{option_symbol}" if block.nil?
359
+ assert(!block.nil?){"missing block for #{option_symbol}"}
333
360
  on_args.push(symbol_to_option(option_symbol, nil))
334
361
  on_args.push("-#{short}") if short.is_a?(String)
335
362
  @parser.on(*on_args, &block)
336
- else raise 'internal error'
363
+ else error_unexpected_value(values)
337
364
  end
338
365
  Log.log.debug{"on_args=#{on_args}"}
339
366
  end
@@ -341,8 +368,8 @@ module Aspera
341
368
  # Adds each of the keys of specified hash as an option
342
369
  # @param preset_hash [Hash] hash of options to add
343
370
  def add_option_preset(preset_hash, op: :push)
371
+ assert_type(preset_hash, Hash)
344
372
  Log.log.debug{"add_option_preset=#{preset_hash}"}
345
- raise "internal error: default expects Hash: #{preset_hash.class}" unless preset_hash.is_a?(Hash)
346
373
  # incremental override
347
374
  preset_hash.each{|k, v|@unprocessed_defaults.send(op, [k.to_sym, v])}
348
375
  end
@@ -363,31 +390,32 @@ module Aspera
363
390
  # get all original options on command line used to generate a config in config file
364
391
  def get_options_table(remove_from_remaining: true)
365
392
  result = {}
366
- @initial_cli_options.each do |optionval|
367
- case optionval
393
+ @initial_cli_options.each do |option_value|
394
+ case option_value
368
395
  when /^--([^=]+)$/
369
396
  # ignore
370
397
  when /^--([^=]+)=(.*)$/
371
398
  name = Regexp.last_match(1)
372
399
  value = Regexp.last_match(2)
373
- name.gsub!(OPTION_SEP_LINE, OPTION_SEP_SYMB)
400
+ name.gsub!(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
374
401
  value = ExtendedValue.instance.evaluate(value)
375
402
  Log.log.debug{"option #{name}=#{value}"}
376
403
  result[name] = value
377
- @unprocessed_cmd_line_options.delete(optionval) if remove_from_remaining
404
+ @unprocessed_cmd_line_options.delete(option_value) if remove_from_remaining
378
405
  else
379
- raise CliBadArgument, "wrong option format: #{optionval}"
406
+ raise Cli::BadArgument, "wrong option format: #{option_value}"
380
407
  end
381
408
  end
382
409
  return result
383
410
  end
384
411
 
385
- # return options as taken from config file and command line just before command execution
386
- def declared_options(only_defined: false)
412
+ # @param only_defined [Boolean] if true, only return options that were defined
413
+ # @return [Hash] options as taken from config file and command line just before command execution
414
+ def known_options(only_defined: false)
387
415
  result = {}
388
- @declared_options.each_key do |option_symb|
389
- v = get_option(option_symb)
390
- result[option_symb.to_s] = v unless only_defined && v.nil?
416
+ @declared_options.each_key do |option_symbol|
417
+ v = get_option(option_symbol)
418
+ result[option_symbol] = v unless only_defined && v.nil?
391
419
  end
392
420
  return result
393
421
  end
@@ -416,21 +444,36 @@ module Aspera
416
444
  @unprocessed_cmd_line_options = unknown_options
417
445
  end
418
446
 
419
- private
420
-
421
447
  def prompt_user_input(prompt, sensitive)
422
448
  return $stdin.getpass("#{prompt}> ") if sensitive
423
449
  print("#{prompt}> ")
424
- return $stdin.gets.chomp
450
+ line = $stdin.gets
451
+ raise 'Unexpected end of standard input' if line.nil?
452
+ return line.chomp
453
+ end
454
+
455
+ # prompt user for input in a list of symbols
456
+ # @param prompt [String] prompt to display
457
+ # @param sym_list [Array] list of symbols to select from
458
+ # @return [Symbol] selected symbol
459
+ def prompt_user_input_in_list(prompt, sym_list)
460
+ loop do
461
+ input = prompt_user_input(prompt, false).to_sym
462
+ if sym_list.any?{|a|a.eql?(input)}
463
+ return input
464
+ else
465
+ $stderr.puts("No such #{prompt}: #{input}, select one of: #{sym_list.join(', ')}")
466
+ end
467
+ end
425
468
  end
426
469
 
427
470
  def get_interactive(type, descr, expected: :single)
428
471
  if !@ask_missing_mandatory
429
- raise CliBadArgument, self.class.bad_arg_message_multi("missing: #{descr}", expected) if expected.is_a?(Array)
430
- raise CliBadArgument, "missing argument (#{expected}): #{descr}"
472
+ raise Cli::BadArgument, "missing argument (#{expected}): #{descr}" unless expected.is_a?(Array)
473
+ self.class.multi_choice_assert(false,"missing: #{descr}", expected)
431
474
  end
432
475
  result = nil
433
- sensitive = type.eql?(:option) && @declared_options[descr.to_sym][:sensitive]
476
+ sensitive = type.eql?(:option) && @declared_options[descr.to_sym].is_a?(Hash) && @declared_options[descr.to_sym][:sensitive]
434
477
  default_prompt = "#{type}: #{descr}"
435
478
  # ask interactively
436
479
  case expected
@@ -450,9 +493,11 @@ module Aspera
450
493
  return result
451
494
  end
452
495
 
496
+ private
497
+
453
498
  # generate command line option from option symbol
454
499
  def symbol_to_option(symbol, opt_val)
455
- result = '--' + symbol.to_s.gsub(OPTION_SEP_SYMB, OPTION_SEP_LINE)
500
+ result = '--' + symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)
456
501
  result = result + '=' + opt_val unless opt_val.nil?
457
502
  return result
458
503
  end