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