aspera-cli 4.14.0 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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