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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -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/
|
|
7
|
-
require '
|
|
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 :
|
|
29
|
-
def initialize(object,
|
|
17
|
+
# attr_accessor :method_name
|
|
18
|
+
def initialize(object, method_name, option_name)
|
|
30
19
|
@object = object
|
|
31
|
-
@
|
|
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(@
|
|
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
|
-
|
|
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
|
-
|
|
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, :
|
|
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
|
-
|
|
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(
|
|
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?(
|
|
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?(
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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 +
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
@
|
|
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/
|
|
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
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# @param
|
|
161
|
-
# @param
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
194
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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) &&
|
|
231
|
-
expected =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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,
|
|
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),
|
|
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,
|
|
356
|
+
set_option(option_symbol, time_string, SOURCE_USER)
|
|
330
357
|
end
|
|
331
358
|
when :none
|
|
332
|
-
|
|
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
|
|
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 |
|
|
367
|
-
case
|
|
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,
|
|
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(
|
|
404
|
+
@unprocessed_cmd_line_options.delete(option_value) if remove_from_remaining
|
|
378
405
|
else
|
|
379
|
-
raise
|
|
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
|
-
#
|
|
386
|
-
|
|
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 |
|
|
389
|
-
v = get_option(
|
|
390
|
-
result[
|
|
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
|
-
|
|
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
|
|
430
|
-
|
|
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(
|
|
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
|