aspera-cli 4.18.0 → 4.19.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 +23 -0
- data/CONTRIBUTING.md +5 -12
- data/README.md +152 -84
- data/examples/build_exec +85 -0
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +4 -4
- data/lib/aspera/agent/base.rb +2 -0
- data/lib/aspera/agent/connect.rb +3 -4
- data/lib/aspera/agent/direct.rb +108 -104
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/api/aoc.rb +2 -2
- data/lib/aspera/api/httpgw.rb +95 -57
- data/lib/aspera/api/node.rb +110 -77
- data/lib/aspera/ascp/installation.rb +47 -32
- data/lib/aspera/ascp/management.rb +4 -1
- data/lib/aspera/ascp/products.rb +2 -8
- data/lib/aspera/cli/extended_value.rb +27 -14
- data/lib/aspera/cli/formatter.rb +35 -28
- data/lib/aspera/cli/main.rb +11 -11
- data/lib/aspera/cli/manager.rb +109 -94
- data/lib/aspera/cli/plugin.rb +4 -7
- data/lib/aspera/cli/plugin_factory.rb +10 -1
- data/lib/aspera/cli/plugins/aoc.rb +15 -14
- data/lib/aspera/cli/plugins/config.rb +35 -29
- data/lib/aspera/cli/plugins/faspex.rb +5 -4
- data/lib/aspera/cli/plugins/faspex5.rb +16 -13
- data/lib/aspera/cli/plugins/node.rb +50 -41
- data/lib/aspera/cli/plugins/orchestrator.rb +3 -2
- data/lib/aspera/cli/plugins/preview.rb +1 -1
- data/lib/aspera/cli/plugins/server.rb +2 -2
- data/lib/aspera/cli/plugins/shares.rb +11 -7
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +73 -32
- data/lib/aspera/cli/transfer_agent.rb +3 -2
- data/lib/aspera/cli/transfer_progress.rb +1 -1
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +100 -7
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -0
- data/lib/aspera/log.rb +1 -0
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -0
- data/lib/aspera/oauth/web.rb +7 -6
- data/lib/aspera/rest.rb +46 -15
- data/lib/aspera/secret_hider.rb +3 -2
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +7 -5
- data/lib/aspera/transfer/parameters.rb +27 -19
- data/lib/aspera/transfer/spec.rb +8 -10
- data/lib/aspera/transfer/sync.rb +52 -47
- data/lib/aspera/web_auth.rb +0 -1
- data/lib/aspera/web_server_simple.rb +24 -13
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
- data/examples/rubyc +0 -24
- data/lib/aspera/open_application.rb +0 -69
|
@@ -41,8 +41,11 @@ module Aspera
|
|
|
41
41
|
</CONF>
|
|
42
42
|
END_OF_CONFIG_FILE
|
|
43
43
|
# all ascp files (in SDK)
|
|
44
|
-
|
|
44
|
+
EXE_FILES = %i[ascp ascp4 async].freeze
|
|
45
|
+
FILES = %i[transferd ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].unshift(*EXE_FILES).freeze
|
|
45
46
|
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_FOLDER, :DEFAULT_ASPERA_CONF, :FILES
|
|
47
|
+
# options for SSH client private key
|
|
48
|
+
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
|
|
46
49
|
# set ascp executable path
|
|
47
50
|
def ascp_path=(v)
|
|
48
51
|
@path_to_ascp = v
|
|
@@ -110,14 +113,14 @@ module Aspera
|
|
|
110
113
|
def path(k)
|
|
111
114
|
file_is_optional = false
|
|
112
115
|
case k
|
|
113
|
-
when
|
|
116
|
+
when *EXE_FILES
|
|
117
|
+
file_is_optional = k.eql?(:async)
|
|
114
118
|
use_ascp_from_product(FIRST_FOUND) if @path_to_ascp.nil?
|
|
115
|
-
file = @path_to_ascp
|
|
116
119
|
# NOTE: that there might be a .exe at the end
|
|
117
|
-
file =
|
|
120
|
+
file = @path_to_ascp.gsub('ascp', k.to_s)
|
|
118
121
|
when :transferd
|
|
119
|
-
file = transferd_filepath
|
|
120
122
|
file_is_optional = true
|
|
123
|
+
file = transferd_filepath
|
|
121
124
|
when :ssh_private_dsa, :ssh_private_rsa
|
|
122
125
|
# assume last 3 letters are type
|
|
123
126
|
type = k.to_s[-3..-1].to_sym
|
|
@@ -151,8 +154,16 @@ module Aspera
|
|
|
151
154
|
return DataRepository.instance.item(:uuid)
|
|
152
155
|
end
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
# get paths of SSH keys to use for ascp client
|
|
158
|
+
# @param types [Symbol] types to use
|
|
159
|
+
def aspera_token_ssh_key_paths(types)
|
|
160
|
+
Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
|
|
161
|
+
return case types
|
|
162
|
+
when :dsa_rsa, :rsa
|
|
163
|
+
types.to_s.split('_').map{|i|Installation.instance.path("ssh_private_#{i}".to_sym)}
|
|
164
|
+
when :per_client
|
|
165
|
+
raise 'Not yet implemented'
|
|
166
|
+
end
|
|
156
167
|
end
|
|
157
168
|
|
|
158
169
|
# use in plugin `config`
|
|
@@ -169,13 +180,14 @@ module Aspera
|
|
|
169
180
|
raise "An error occurred when testing #{ascp_filename}: #{cmd_out}" unless $CHILD_STATUS == 0
|
|
170
181
|
# get version from ascp, only after full extract, as windows requires DLLs (SSL/TLS/etc...)
|
|
171
182
|
m = cmd_out.match(/ version ([0-9.]+)/)
|
|
172
|
-
exe_version = m[1] unless m.nil?
|
|
183
|
+
exe_version = m[1].gsub(/\.$/, '') unless m.nil?
|
|
173
184
|
return exe_version
|
|
174
185
|
end
|
|
175
186
|
|
|
176
|
-
def
|
|
187
|
+
def ascp_pvcl_info
|
|
188
|
+
data = {}
|
|
177
189
|
# read PATHs from ascp directly, and pvcl modules as well
|
|
178
|
-
Open3.popen3(
|
|
190
|
+
Open3.popen3(ascp_path, '-DDL-') do |_stdin, _stdout, stderr, thread|
|
|
179
191
|
last_line = ''
|
|
180
192
|
while (line = stderr.gets)
|
|
181
193
|
line.chomp!
|
|
@@ -194,32 +206,32 @@ module Aspera
|
|
|
194
206
|
data['product_name'] = Regexp.last_match(1)
|
|
195
207
|
data['product_version'] = Regexp.last_match(2)
|
|
196
208
|
when /^LOG Initializing FASP version ([^,]+),/
|
|
197
|
-
data['
|
|
209
|
+
data['sdk_ascp_version'] = Regexp.last_match(1)
|
|
198
210
|
end
|
|
199
211
|
end
|
|
200
212
|
if !thread.value.exitstatus.eql?(1) && !data.key?('root')
|
|
201
213
|
raise last_line
|
|
202
214
|
end
|
|
203
215
|
end
|
|
216
|
+
return data
|
|
204
217
|
end
|
|
205
218
|
|
|
206
219
|
# extract some stings from ascp binary
|
|
207
|
-
def
|
|
208
|
-
|
|
209
|
-
File.binread(
|
|
220
|
+
def ascp_ssl_info
|
|
221
|
+
data = {}
|
|
222
|
+
File.binread(ascp_path).scan(/[\x20-\x7E]{10,}/) do |bin_string|
|
|
210
223
|
if (m = bin_string.match(/OPENSSLDIR.*"(.*)"/))
|
|
211
224
|
data['openssldir'] = m[1]
|
|
212
225
|
elsif (m = bin_string.match(/OpenSSL (\d[^ -]+)/))
|
|
213
226
|
data['openssl_version'] = m[1]
|
|
214
227
|
end
|
|
215
|
-
end if File.file?(
|
|
228
|
+
end if File.file?(ascp_path)
|
|
229
|
+
return data
|
|
216
230
|
end
|
|
217
231
|
|
|
218
232
|
def ascp_info
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
ascp_add_openssl(data)
|
|
222
|
-
return data
|
|
233
|
+
files = file_paths
|
|
234
|
+
return files.merge(ascp_pvcl_info).merge(ascp_ssl_info)
|
|
223
235
|
end
|
|
224
236
|
|
|
225
237
|
# download aspera SDK or use local file
|
|
@@ -263,19 +275,22 @@ module Aspera
|
|
|
263
275
|
# ensure license file are generated so that ascp invocation for version works
|
|
264
276
|
path(:aspera_license)
|
|
265
277
|
path(:aspera_conf)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
raise "No #{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
sdk_ascp_file = Products.ascp_filename
|
|
279
|
+
sdk_ascp_path = File.join(sdk_folder, sdk_ascp_file)
|
|
280
|
+
raise "No #{sdk_ascp_file} found in SDK archive" unless File.exist?(sdk_ascp_path)
|
|
281
|
+
EXE_FILES.each do |exe_sym|
|
|
282
|
+
exe_path = sdk_ascp_path.gsub('ascp', exe_sym.to_s)
|
|
283
|
+
Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
|
|
284
|
+
end
|
|
285
|
+
sdk_ascp_version = get_ascp_version(sdk_ascp_path)
|
|
286
|
+
sdk_daemon_path = transferd_filepath
|
|
287
|
+
Log.log.warn{"No #{sdk_daemon_path} in SDK archive"} unless File.exist?(sdk_daemon_path)
|
|
288
|
+
Environment.restrict_file_access(sdk_daemon_path, mode: 0o755) if File.exist?(sdk_daemon_path)
|
|
289
|
+
transferd_version = get_exe_version(sdk_daemon_path, 'version')
|
|
290
|
+
sdk_name = 'IBM Aspera Transfer SDK'
|
|
291
|
+
sdk_version = transferd_version || sdk_ascp_version
|
|
292
|
+
File.write(File.join(sdk_folder, Products::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
|
293
|
+
return sdk_name, sdk_version
|
|
279
294
|
end
|
|
280
295
|
|
|
281
296
|
private
|
|
@@ -188,7 +188,7 @@ module Aspera
|
|
|
188
188
|
# empty line is separator to end event information
|
|
189
189
|
MGT_FRAME_SEPARATOR = ''
|
|
190
190
|
# fields description for JSON generation
|
|
191
|
-
#
|
|
191
|
+
# cspell: disable
|
|
192
192
|
INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
|
|
193
193
|
DatagramSize XoptFlags VLinkVersion PeerVLinkVersion DSPipelineDepth PeerDSPipelineDepth ReadBlockSize WriteBlockSize
|
|
194
194
|
ClusterNumNodes ClusterNodeId Size Written Loss FileBytes PreTransferBytes TransferBytes PMTU Elapsedusec ArgScansAttempted
|
|
@@ -219,6 +219,9 @@ module Aspera
|
|
|
219
219
|
end
|
|
220
220
|
attr_reader :last_event
|
|
221
221
|
|
|
222
|
+
# process line of mgt port event
|
|
223
|
+
# @param line [String] line of mgt port event
|
|
224
|
+
# @return [Hash] event hash or nil if event is not yet complete
|
|
222
225
|
def process_line(line)
|
|
223
226
|
# Log.log.debug{"line=[#{line}]"}
|
|
224
227
|
case line
|
data/lib/aspera/ascp/products.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Aspera
|
|
|
44
44
|
app_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server'),
|
|
45
45
|
log_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server', 'var', 'log')
|
|
46
46
|
}]
|
|
47
|
-
when Aspera::Environment::
|
|
47
|
+
when Aspera::Environment::OS_MACOS then [{
|
|
48
48
|
expected: CONNECT,
|
|
49
49
|
app_root: File.join(Dir.home, 'Applications', 'Aspera Connect.app'),
|
|
50
50
|
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
|
|
@@ -126,7 +126,7 @@ module Aspera
|
|
|
126
126
|
|
|
127
127
|
# filename for ascp with optional extension (Windows)
|
|
128
128
|
def ascp_filename
|
|
129
|
-
return
|
|
129
|
+
return "ascp#{Environment.exe_extension}"
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
# @return folder paths for specified applications
|
|
@@ -150,12 +150,6 @@ module Aspera
|
|
|
150
150
|
end
|
|
151
151
|
raise "no connect uri file found in #{folder}"
|
|
152
152
|
end
|
|
153
|
-
|
|
154
|
-
# @ return path to configuration file of aspera CLI
|
|
155
|
-
# def cli_conf_file
|
|
156
|
-
# connect = folders(PRODUCT_CLI_V1)
|
|
157
|
-
# return File.join(connect[:app_root], BIN_SUBFOLDER, '.aspera_cli_conf')
|
|
158
|
-
# end
|
|
159
153
|
end
|
|
160
154
|
end
|
|
161
155
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# cspell:ignore csvt jsonpp
|
|
3
|
+
# cspell:ignore csvt jsonpp stdbin
|
|
4
4
|
require 'aspera/uri_reader'
|
|
5
5
|
require 'aspera/environment'
|
|
6
6
|
require 'aspera/log'
|
|
@@ -17,11 +17,6 @@ module Aspera
|
|
|
17
17
|
class ExtendedValue
|
|
18
18
|
include Singleton
|
|
19
19
|
|
|
20
|
-
# special values
|
|
21
|
-
INIT = 'INIT'
|
|
22
|
-
ALL = 'ALL'
|
|
23
|
-
DEF = 'DEF'
|
|
24
|
-
|
|
25
20
|
MARKER_START = '@'
|
|
26
21
|
MARKER_END = ':'
|
|
27
22
|
MARKER_IN_END = '@'
|
|
@@ -74,10 +69,22 @@ module Aspera
|
|
|
74
69
|
zlib: lambda{|v|Zlib::Inflate.inflate(v)},
|
|
75
70
|
extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
|
|
76
71
|
}
|
|
72
|
+
@default_decoder = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Regex to match an extended value
|
|
76
|
+
def handler_regex_string
|
|
77
|
+
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
public
|
|
80
81
|
|
|
82
|
+
def default_decoder=(value)
|
|
83
|
+
Log.log.debug{"setting default decoder to #{value} (#{value.class})"}
|
|
84
|
+
Aspera.assert(value.nil? || @handlers.key?(value))
|
|
85
|
+
@default_decoder = value
|
|
86
|
+
end
|
|
87
|
+
|
|
81
88
|
def modifiers; @handlers.keys; end
|
|
82
89
|
|
|
83
90
|
# add a new handler
|
|
@@ -87,16 +94,13 @@ module Aspera
|
|
|
87
94
|
@handlers[name] = method
|
|
88
95
|
end
|
|
89
96
|
|
|
90
|
-
#
|
|
91
|
-
def ext_re
|
|
92
|
-
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# parse an option value if it is a String using supported extended value modifiers
|
|
97
|
+
# parse an string value to extended value, if it is a String using supported extended value modifiers
|
|
96
98
|
# other value types are returned as is
|
|
99
|
+
# @param value [String] the value to parse
|
|
100
|
+
# @param expect [Class,Array] one or a list of expected types
|
|
97
101
|
def evaluate(value)
|
|
98
102
|
return value unless value.is_a?(String)
|
|
99
|
-
regex = Regexp.new("^#{
|
|
103
|
+
regex = Regexp.new("^#{handler_regex_string}(.*)$", Regexp::MULTILINE)
|
|
100
104
|
# first determine decoders, in reversed order
|
|
101
105
|
handlers_reversed = []
|
|
102
106
|
while (m = value.match(regex))
|
|
@@ -113,9 +117,18 @@ module Aspera
|
|
|
113
117
|
return value
|
|
114
118
|
end
|
|
115
119
|
|
|
120
|
+
# parse string value as extended value
|
|
121
|
+
# use default decoder if none is specified
|
|
122
|
+
def evaluate_with_default(value)
|
|
123
|
+
if value.is_a?(String) && value.match(/^#{handler_regex_string}.*$/).nil? && !@default_decoder.nil?
|
|
124
|
+
value = [MARKER_START, @default_decoder, MARKER_END, value].join
|
|
125
|
+
end
|
|
126
|
+
return evaluate(value)
|
|
127
|
+
end
|
|
128
|
+
|
|
116
129
|
# find inner extended values
|
|
117
130
|
def evaluate_all(value)
|
|
118
|
-
regex = Regexp.new("^(.*)#{
|
|
131
|
+
regex = Regexp.new("^(.*)#{handler_regex_string}([^#{MARKER_IN_END}]*)#{MARKER_IN_END}(.*)$", Regexp::MULTILINE)
|
|
119
132
|
while (m = value.match(regex))
|
|
120
133
|
sub_value = "@#{m[2]}:#{m[3]}"
|
|
121
134
|
Log.log.debug{"evaluating #{sub_value}"}
|
data/lib/aspera/cli/formatter.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# cspell:ignore jsonpp
|
|
4
|
+
require 'aspera/cli/special_values'
|
|
4
5
|
require 'aspera/preview/terminal'
|
|
5
6
|
require 'aspera/secret_hider'
|
|
6
7
|
require 'aspera/environment'
|
|
@@ -97,11 +98,12 @@ module Aspera
|
|
|
97
98
|
CSV_RECORD_SEPARATOR = "\n"
|
|
98
99
|
CSV_FIELD_SEPARATOR = ','
|
|
99
100
|
# supported output formats
|
|
100
|
-
DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv image].freeze
|
|
101
|
+
DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table multi csv image].freeze
|
|
101
102
|
# user output levels
|
|
102
103
|
DISPLAY_LEVELS = %i[info data error].freeze
|
|
104
|
+
FIELD_VALUE_HEADINGS = %i[key value].freeze
|
|
103
105
|
|
|
104
|
-
private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR
|
|
106
|
+
private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR, :FIELD_VALUE_HEADINGS
|
|
105
107
|
# prefix to display error messages in user messages (terminal)
|
|
106
108
|
ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
|
|
107
109
|
WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
|
|
@@ -110,12 +112,12 @@ module Aspera
|
|
|
110
112
|
class << self
|
|
111
113
|
def all_but(list)
|
|
112
114
|
list = [list] unless list.is_a?(Array)
|
|
113
|
-
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(
|
|
115
|
+
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
def tick(yes)
|
|
117
119
|
result =
|
|
118
|
-
if Environment.terminal_supports_unicode?
|
|
120
|
+
if Environment.instance.terminal_supports_unicode?
|
|
119
121
|
if yes
|
|
120
122
|
"\u2713"
|
|
121
123
|
else
|
|
@@ -149,16 +151,9 @@ module Aspera
|
|
|
149
151
|
end
|
|
150
152
|
|
|
151
153
|
# Highlight special values
|
|
152
|
-
def special_format(what
|
|
153
|
-
result =
|
|
154
|
-
|
|
155
|
-
result = if %w[null empty].any?{|s|what.include?(s)}
|
|
156
|
-
result.dim
|
|
157
|
-
else
|
|
158
|
-
result.reverse_color
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
return result
|
|
154
|
+
def special_format(what)
|
|
155
|
+
result = "<#{what}>"
|
|
156
|
+
return %w[null empty].any?{|s|what.include?(s)} ? result.dim : result.reverse_color
|
|
162
157
|
end
|
|
163
158
|
|
|
164
159
|
# call this after REST calls if several api calls are expected
|
|
@@ -197,7 +192,7 @@ module Aspera
|
|
|
197
192
|
end
|
|
198
193
|
|
|
199
194
|
def declare_options(options)
|
|
200
|
-
default_table_style = if Environment.terminal_supports_unicode?
|
|
195
|
+
default_table_style = if Environment.instance.terminal_supports_unicode?
|
|
201
196
|
{border: :unicode_round}
|
|
202
197
|
else
|
|
203
198
|
{}
|
|
@@ -207,9 +202,9 @@ module Aspera
|
|
|
207
202
|
options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
|
|
208
203
|
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
|
209
204
|
options.declare(
|
|
210
|
-
:fields, "Comma separated list of: fields, or #{
|
|
205
|
+
:fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
|
|
211
206
|
types: [String, Array, Regexp, Proc],
|
|
212
|
-
default:
|
|
207
|
+
default: SpecialValues::DEF)
|
|
213
208
|
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
|
214
209
|
options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
|
|
215
210
|
options.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
|
@@ -256,7 +251,7 @@ module Aspera
|
|
|
256
251
|
# the requested list of fields, but if can contain special values
|
|
257
252
|
request =
|
|
258
253
|
case @options[:fields]
|
|
259
|
-
# when NilClass then [
|
|
254
|
+
# when NilClass then [SpecialValues::DEF]
|
|
260
255
|
when String then @options[:fields].split(',')
|
|
261
256
|
when Array then @options[:fields]
|
|
262
257
|
when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
|
|
@@ -272,10 +267,10 @@ module Aspera
|
|
|
272
267
|
item = item[1..-1]
|
|
273
268
|
end
|
|
274
269
|
case item
|
|
275
|
-
when
|
|
270
|
+
when SpecialValues::ALL
|
|
276
271
|
# get the list of all column names used in all lines, not just first one, as all lines may have different columns
|
|
277
272
|
request.unshift(*all_fields(data))
|
|
278
|
-
when
|
|
273
|
+
when SpecialValues::DEF
|
|
279
274
|
default = all_fields(data).select{|i|default.call(i)} if default.is_a?(Proc)
|
|
280
275
|
default = all_fields(data) if default.nil?
|
|
281
276
|
request.unshift(*default)
|
|
@@ -293,11 +288,11 @@ module Aspera
|
|
|
293
288
|
# filter the list of items on the fields option
|
|
294
289
|
def filter_list_on_fields(data)
|
|
295
290
|
# by default, keep all data intact
|
|
296
|
-
return data if @options[:fields].eql?(
|
|
291
|
+
return data if @options[:fields].eql?(SpecialValues::DEF) && @options[:select].nil?
|
|
297
292
|
Aspera.assert_type(data, Array){'Filtering fields or select requires result is an Array of Hash'}
|
|
298
293
|
Aspera.assert(data.all?(Hash)){'Filtering fields or select requires result is an Array of Hash'}
|
|
299
294
|
filter_columns_on_select(data)
|
|
300
|
-
return data if @options[:fields].eql?(
|
|
295
|
+
return data if @options[:fields].eql?(SpecialValues::DEF)
|
|
301
296
|
selected_fields = compute_fields(data, @options[:fields])
|
|
302
297
|
return data.map{|i|i[selected_fields.first]} if selected_fields.length == 1
|
|
303
298
|
return data.map{|i|i.select{|k, _|selected_fields.include?(k)}}
|
|
@@ -325,16 +320,16 @@ module Aspera
|
|
|
325
320
|
display_message(:info, special_format('empty')) if @options[:format].eql?(:table)
|
|
326
321
|
return
|
|
327
322
|
end
|
|
323
|
+
# if table has only one element, and only one field, display the value
|
|
328
324
|
if object_array.length == 1 && fields.length == 1
|
|
329
325
|
display_message(:data, object_array.first[fields.first])
|
|
330
326
|
return
|
|
331
327
|
end
|
|
332
328
|
# Special case if only one row (it could be object_list or single_object)
|
|
333
329
|
if @options[:transpose_single] && object_array.length == 1
|
|
334
|
-
new_columns = %i[key value]
|
|
335
330
|
single = object_array.first
|
|
336
|
-
object_array = fields.map { |i|
|
|
337
|
-
fields =
|
|
331
|
+
object_array = fields.map { |i| FIELD_VALUE_HEADINGS.zip([i, single[i]]).to_h }
|
|
332
|
+
fields = FIELD_VALUE_HEADINGS
|
|
338
333
|
end
|
|
339
334
|
Log.log.debug{Log.dump(:object_array, object_array)}
|
|
340
335
|
# convert data to string, and keep only display fields
|
|
@@ -347,8 +342,18 @@ module Aspera
|
|
|
347
342
|
headings: fields,
|
|
348
343
|
rows: final_table_rows,
|
|
349
344
|
style: @options[:table_style]&.symbolize_keys))
|
|
345
|
+
when :multi
|
|
346
|
+
final_table_rows.each do |row|
|
|
347
|
+
Log.log.debug{Log.dump(:row, row)}
|
|
348
|
+
display_message(:data, Terminal::Table.new(
|
|
349
|
+
headings: FIELD_VALUE_HEADINGS,
|
|
350
|
+
rows: fields.zip(row),
|
|
351
|
+
style: @options[:table_style]&.symbolize_keys))
|
|
352
|
+
end
|
|
350
353
|
when :csv
|
|
351
354
|
display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
|
|
355
|
+
else
|
|
356
|
+
raise "not expected: #{@options[:format]}"
|
|
352
357
|
end
|
|
353
358
|
end
|
|
354
359
|
|
|
@@ -358,8 +363,8 @@ module Aspera
|
|
|
358
363
|
raise URI::InvalidURIError, 'not uri' if !(blob =~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/)
|
|
359
364
|
# it's a url
|
|
360
365
|
url = blob
|
|
361
|
-
unless
|
|
362
|
-
|
|
366
|
+
unless Environment.instance.url_method.eql?(:text)
|
|
367
|
+
Environment.instance.open_uri(url)
|
|
363
368
|
return ''
|
|
364
369
|
end
|
|
365
370
|
# remote_image = Rest.new(base_url: url).read('')
|
|
@@ -419,7 +424,7 @@ module Aspera
|
|
|
419
424
|
end
|
|
420
425
|
raise "not url: #{url.class} #{url}" unless url.is_a?(String)
|
|
421
426
|
display_message(:data, status_image(url))
|
|
422
|
-
when :table, :csv
|
|
427
|
+
when :table, :csv, :multi
|
|
423
428
|
case type
|
|
424
429
|
when :config_over
|
|
425
430
|
display_table(Flattener.new(self).config_over(data), CONF_OVERVIEW_KEYS)
|
|
@@ -451,6 +456,8 @@ module Aspera
|
|
|
451
456
|
else
|
|
452
457
|
raise "unknown data type: #{type}"
|
|
453
458
|
end
|
|
459
|
+
else
|
|
460
|
+
raise "not expected: #{@options[:format]}"
|
|
454
461
|
end
|
|
455
462
|
end
|
|
456
463
|
end
|
data/lib/aspera/cli/main.rb
CHANGED
|
@@ -93,11 +93,11 @@ module Aspera
|
|
|
93
93
|
@plug_init[:only_manual] = false
|
|
94
94
|
# create formatter, in case there is an exception, it is used to display.
|
|
95
95
|
@plug_init[:formatter] = Formatter.new
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
options.parse_command_line(@argv)
|
|
96
|
+
# create command line manager with arguments
|
|
97
|
+
@plug_init[:options] = Manager.new(PROGRAM_NAME, @argv)
|
|
99
98
|
# formatter adds options
|
|
100
|
-
formatter.declare_options(options)
|
|
99
|
+
@plug_init[:formatter].declare_options(options)
|
|
100
|
+
ExtendedValue.instance.default_decoder = options.get_option(:struct_parser)
|
|
101
101
|
# compare $0 with expected name
|
|
102
102
|
current_prog_name = File.basename($PROGRAM_NAME)
|
|
103
103
|
formatter.display_message(
|
|
@@ -162,9 +162,9 @@ module Aspera
|
|
|
162
162
|
options.declare(:version, 'Display version', values: :none, short: 'v') { formatter.display_message(:data, Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon, Layout/LineLength
|
|
163
163
|
options.declare(
|
|
164
164
|
:ui, 'Method to start browser',
|
|
165
|
-
values:
|
|
166
|
-
handler: {o:
|
|
167
|
-
default:
|
|
165
|
+
values: Environment::USER_INTERFACES,
|
|
166
|
+
handler: {o: Environment.instance, m: :url_method},
|
|
167
|
+
default: Environment.default_gui_mode)
|
|
168
168
|
options.declare(:log_level, 'Log level', values: Log.levels, handler: {o: Log.instance, m: :level})
|
|
169
169
|
options.declare(:logger, 'Logging method', values: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
|
|
170
170
|
options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', coerce: Integer, types: Integer)
|
|
@@ -189,7 +189,7 @@ module Aspera
|
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def generate_bash_completion
|
|
192
|
-
if options.get_next_argument('',
|
|
192
|
+
if options.get_next_argument('', multiple: true, mandatory: false).nil?
|
|
193
193
|
PluginFactory.instance.plugin_list.each{|p|puts p}
|
|
194
194
|
else
|
|
195
195
|
Log.log.warn('only first level completion so far')
|
|
@@ -197,11 +197,11 @@ module Aspera
|
|
|
197
197
|
Process.exit(0)
|
|
198
198
|
end
|
|
199
199
|
|
|
200
|
-
def exit_with_usage(
|
|
201
|
-
Log.log.debug(
|
|
200
|
+
def exit_with_usage(include_all_plugins)
|
|
201
|
+
Log.log.debug{"exit_with_usage(#{include_all_plugins})".bg_red}
|
|
202
202
|
# display main plugin options
|
|
203
203
|
formatter.display_message(:error, options.parser)
|
|
204
|
-
if
|
|
204
|
+
if include_all_plugins
|
|
205
205
|
# list plugins that have a "require" field, i.e. all but main plugin
|
|
206
206
|
PluginFactory.instance.plugin_list.each do |plugin_name_sym|
|
|
207
207
|
next if plugin_name_sym.eql?(COMMAND_CONFIG)
|