aspera-cli 4.25.6 → 4.26.1
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 +89 -48
- data/CONTRIBUTING.md +1 -1
- data/lib/aspera/api/aoc.rb +120 -79
- data/lib/aspera/api/node.rb +103 -51
- data/lib/aspera/ascp/installation.rb +99 -32
- data/lib/aspera/assert.rb +17 -13
- data/lib/aspera/cli/extended_value.rb +7 -2
- data/lib/aspera/cli/formatter.rb +107 -95
- data/lib/aspera/cli/main.rb +69 -10
- data/lib/aspera/cli/manager.rb +158 -78
- data/lib/aspera/cli/options.schema.yaml +82 -0
- data/lib/aspera/cli/plugins/aoc.rb +247 -144
- data/lib/aspera/cli/plugins/ats.rb +3 -3
- data/lib/aspera/cli/plugins/base.rb +60 -76
- data/lib/aspera/cli/plugins/config.rb +14 -12
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +24 -23
- data/lib/aspera/cli/plugins/node.rb +67 -71
- data/lib/aspera/cli/plugins/oauth.rb +5 -12
- data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
- data/lib/aspera/cli/plugins/preview.rb +116 -80
- data/lib/aspera/cli/plugins/server.rb +2 -10
- data/lib/aspera/cli/plugins/shares.rb +7 -7
- data/lib/aspera/cli/sync_actions.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +17 -15
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +22 -18
- data/lib/aspera/dot_container.rb +7 -3
- data/lib/aspera/environment.rb +6 -5
- data/lib/aspera/formatter_interface.rb +14 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/log.rb +5 -4
- data/lib/aspera/markdown.rb +4 -1
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/preview/generator.rb +146 -91
- data/lib/aspera/preview/options.rb +4 -1
- data/lib/aspera/preview/terminal.rb +50 -20
- data/lib/aspera/preview/utils.rb +76 -34
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +3 -0
- data/lib/aspera/rest.rb +2 -1
- data/lib/aspera/rest_list.rb +23 -16
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62801 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +8898 -0
- data/lib/aspera/schema/documentation.rb +107 -0
- data/lib/aspera/schema/reader.rb +75 -0
- data/lib/aspera/schema/registry.rb +63 -0
- data/lib/aspera/secret_hider.rb +3 -1
- data/lib/aspera/sync/conf.schema.yaml +0 -26
- data/lib/aspera/sync/operations.rb +9 -5
- data/lib/aspera/transfer/faux_file.rb +1 -1
- data/lib/aspera/transfer/resumer.rb +1 -1
- data/lib/aspera/transfer/spec.rb +3 -3
- data/lib/aspera/transfer/spec.schema.yaml +1 -1
- data/lib/aspera/uri_reader.rb +17 -2
- data/lib/aspera/yaml.rb +4 -2
- data.tar.gz.sig +0 -0
- metadata +13 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/transfer/spec_doc.rb +0 -76
data/lib/aspera/cli/formatter.rb
CHANGED
|
@@ -9,6 +9,7 @@ require 'aspera/log'
|
|
|
9
9
|
require 'aspera/assert'
|
|
10
10
|
require 'aspera/markdown'
|
|
11
11
|
require 'aspera/dot_container'
|
|
12
|
+
require 'aspera/formatter_interface'
|
|
12
13
|
require 'terminal-table'
|
|
13
14
|
require 'tty-spinner'
|
|
14
15
|
require 'yaml'
|
|
@@ -18,6 +19,60 @@ require 'word_wrap'
|
|
|
18
19
|
|
|
19
20
|
module Aspera
|
|
20
21
|
module Cli
|
|
22
|
+
# Terminal formatter with ANSI colors and Unicode support
|
|
23
|
+
# @see FormatterInterface
|
|
24
|
+
# @see MarkdownFormatter (in build/lib/doc_helper.rb)
|
|
25
|
+
module TerminalFormatter
|
|
26
|
+
include FormatterInterface
|
|
27
|
+
|
|
28
|
+
# Format boolean with colored symbol (✓/✗ or Y/ )
|
|
29
|
+
def tick(yes)
|
|
30
|
+
result =
|
|
31
|
+
if Environment.terminal_supports_unicode?
|
|
32
|
+
yes ? "\u2713" : "\u2717"
|
|
33
|
+
else
|
|
34
|
+
yes ? 'Y' : ' '
|
|
35
|
+
end
|
|
36
|
+
return result.green if yes
|
|
37
|
+
return result.red
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Format special values with colors (dim for empty, reverse for others)
|
|
41
|
+
def special_format(what)
|
|
42
|
+
result = "<#{what}>"
|
|
43
|
+
return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Prepare table row for terminal display (word wrap arrays)
|
|
47
|
+
def check_row(row)
|
|
48
|
+
row.each_key do |k|
|
|
49
|
+
row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Convert Markdown to terminal format (**bold** -> blue, `code` -> bold)
|
|
54
|
+
# @param match [MatchData, String]
|
|
55
|
+
def markdown_text(match)
|
|
56
|
+
if match.is_a?(String)
|
|
57
|
+
match = Markdown::FORMATS.match(match)
|
|
58
|
+
Aspera.assert(match)
|
|
59
|
+
end
|
|
60
|
+
Aspera.assert_type(match, MatchData)
|
|
61
|
+
if match[:entity]
|
|
62
|
+
Aspera.assert_values(match[:entity], %w[bsol])
|
|
63
|
+
'\\'
|
|
64
|
+
elsif match[:bold]
|
|
65
|
+
match[:bold].to_s.blue
|
|
66
|
+
elsif match[:code]
|
|
67
|
+
match[:code].to_s.bold
|
|
68
|
+
else
|
|
69
|
+
Aspera.error_unexpected_value(match.to_s)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module_function :tick, :special_format, :check_row, :markdown_text
|
|
74
|
+
end
|
|
75
|
+
|
|
21
76
|
# Take care of CLI output on terminal
|
|
22
77
|
class Formatter
|
|
23
78
|
# remove a fields from the list
|
|
@@ -28,78 +83,30 @@ module Aspera
|
|
|
28
83
|
DISPLAY_LEVELS = %i[info data error].freeze
|
|
29
84
|
# column names for single object display in table
|
|
30
85
|
SINGLE_OBJECT_COLUMN_NAMES = %i[field value].freeze
|
|
86
|
+
# Terminal: vertical separator for list of strings.
|
|
87
|
+
STR_LST_SEP_VERT = "\n"
|
|
31
88
|
|
|
32
|
-
private_constant :FIELDS_LESS, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES
|
|
89
|
+
private_constant :FIELDS_LESS, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES, :STR_LST_SEP_VERT
|
|
33
90
|
|
|
34
91
|
class << self
|
|
35
|
-
# nicer display for boolean
|
|
36
|
-
# used by spec_doc
|
|
37
|
-
def tick(yes)
|
|
38
|
-
result =
|
|
39
|
-
if Environment.terminal_supports_unicode?
|
|
40
|
-
yes ? "\u2713" : "\u2717"
|
|
41
|
-
else
|
|
42
|
-
yes ? 'Y' : ' '
|
|
43
|
-
end
|
|
44
|
-
return result.green if yes
|
|
45
|
-
return result.red
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Highlight special values on terminal
|
|
49
|
-
# empty values are dim
|
|
50
|
-
# used by spec_doc
|
|
51
|
-
def special_format(what)
|
|
52
|
-
result = "<#{what}>"
|
|
53
|
-
return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# for transfer spec table, build line for display in terminal
|
|
57
|
-
# used by spec_doc
|
|
58
|
-
def check_row(row)
|
|
59
|
-
row.each_key do |k|
|
|
60
|
-
row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Give Markdown String, or matched data, return formatted string for terminal
|
|
65
|
-
# used by spec_doc
|
|
66
|
-
# @param match [MatchData,String]
|
|
67
|
-
def markdown_text(match)
|
|
68
|
-
if match.is_a?(String)
|
|
69
|
-
match = Markdown::FORMATS.match(match)
|
|
70
|
-
Aspera.assert(match)
|
|
71
|
-
end
|
|
72
|
-
Aspera.assert_type(match, MatchData)
|
|
73
|
-
if match[:entity]
|
|
74
|
-
Aspera.assert_values(match[:entity], 'bsol')
|
|
75
|
-
'\\'
|
|
76
|
-
elsif match[:bold]
|
|
77
|
-
match[:bold].to_s.blue
|
|
78
|
-
elsif match[:code]
|
|
79
|
-
match[:code].to_s.bold
|
|
80
|
-
else
|
|
81
|
-
Aspera.error_unexpected_value(match.to_s)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
92
|
# Replace special values with a readable version on terminal
|
|
86
|
-
def replace_specific_for_terminal(input_hash)
|
|
93
|
+
def replace_specific_for_terminal(input_hash, string_list_separator)
|
|
87
94
|
hash_to_process = [input_hash]
|
|
88
95
|
until hash_to_process.empty?
|
|
89
96
|
current = hash_to_process.pop
|
|
90
97
|
current.each do |key, value|
|
|
91
98
|
case value
|
|
92
99
|
when NilClass
|
|
93
|
-
current[key] = special_format('null')
|
|
100
|
+
current[key] = TerminalFormatter.special_format('null')
|
|
94
101
|
when String
|
|
95
|
-
current[key] = special_format('empty string') if value.empty?
|
|
102
|
+
current[key] = TerminalFormatter.special_format('empty string') if value.empty?
|
|
96
103
|
when Proc
|
|
97
|
-
current[key] = special_format('lambda')
|
|
104
|
+
current[key] = TerminalFormatter.special_format('lambda')
|
|
98
105
|
when Array
|
|
99
106
|
if value.empty?
|
|
100
|
-
current[key] = special_format('empty list')
|
|
107
|
+
current[key] = TerminalFormatter.special_format('empty list')
|
|
101
108
|
elsif value.all?(String)
|
|
102
|
-
current[key] = value.join(
|
|
109
|
+
current[key] = value.join(string_list_separator)
|
|
103
110
|
else
|
|
104
111
|
value.each do |item|
|
|
105
112
|
hash_to_process.push(item) if item.is_a?(Hash)
|
|
@@ -107,7 +114,7 @@ module Aspera
|
|
|
107
114
|
end
|
|
108
115
|
when Hash
|
|
109
116
|
if value.empty?
|
|
110
|
-
current[key] = special_format('empty dict')
|
|
117
|
+
current[key] = TerminalFormatter.special_format('empty dict')
|
|
111
118
|
else
|
|
112
119
|
hash_to_process.push(value)
|
|
113
120
|
end
|
|
@@ -117,8 +124,7 @@ module Aspera
|
|
|
117
124
|
end
|
|
118
125
|
|
|
119
126
|
def all_but(list)
|
|
120
|
-
list
|
|
121
|
-
return list.map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
|
|
127
|
+
Array(list).map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
|
|
122
128
|
end
|
|
123
129
|
end
|
|
124
130
|
|
|
@@ -159,11 +165,6 @@ module Aspera
|
|
|
159
165
|
end
|
|
160
166
|
|
|
161
167
|
def declare_options(options)
|
|
162
|
-
default_table_style = if Environment.terminal_supports_unicode?
|
|
163
|
-
{border: :unicode_round}
|
|
164
|
-
else
|
|
165
|
-
{}
|
|
166
|
-
end
|
|
167
168
|
options.declare(:display, 'Output only some information', allowed: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :data)
|
|
168
169
|
options.declare(:format, 'Output format', allowed: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
|
169
170
|
options.declare(:output, 'Destination for results', handler: {o: self, m: :option_handler})
|
|
@@ -173,7 +174,7 @@ module Aspera
|
|
|
173
174
|
default: SpecialValues::DEF
|
|
174
175
|
)
|
|
175
176
|
options.declare(:select, 'Select only some items in lists: column, value', allowed: [Hash, Proc], handler: {o: self, m: :option_handler})
|
|
176
|
-
options.declare(:table_style, '(Table) Display style', allowed: [Hash], handler: {o: self, m: :option_handler}, default:
|
|
177
|
+
options.declare(:table_style, '(Table) Display style', allowed: [Hash], handler: {o: self, m: :option_handler}, default: {})
|
|
177
178
|
options.declare(:flat_hash, '(Table) Display deep values as additional keys', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_handler}, default: true)
|
|
178
179
|
options.declare(
|
|
179
180
|
:multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', allowed: %i[no yes single],
|
|
@@ -244,20 +245,21 @@ module Aspera
|
|
|
244
245
|
!@options[:show_secrets] && !@options[:display].eql?(:data)
|
|
245
246
|
end
|
|
246
247
|
|
|
247
|
-
#
|
|
248
|
+
# Hides secrets in Hash or Array
|
|
248
249
|
def hide_secrets(data)
|
|
249
250
|
SecretHider.instance.deep_remove_secret(data) if hide_secrets?
|
|
250
251
|
end
|
|
251
252
|
|
|
252
|
-
#
|
|
253
|
-
# @param type [Symbol]
|
|
254
|
-
# @param data [Object]
|
|
255
|
-
# @param
|
|
256
|
-
# @param
|
|
257
|
-
# @param name [String]
|
|
258
|
-
def display_results(type:, data: nil,
|
|
259
|
-
Log.log.debug{"display_results: type=#{type}
|
|
260
|
-
Log.
|
|
253
|
+
# Display results, especially the table format
|
|
254
|
+
# @param type [Symbol] Type of data
|
|
255
|
+
# @param data [Object] Data to display
|
|
256
|
+
# @param fields [Array<String>] List of fields to display
|
|
257
|
+
# @param total [Integer] Total number of items
|
|
258
|
+
# @param name [String] Name of the column to display
|
|
259
|
+
def display_results(type:, data: nil, fields: nil, total: nil, name: nil)
|
|
260
|
+
Log.log.debug{"display_results: type=#{type}"}
|
|
261
|
+
Log.dump(:data, data, level: :trace1)
|
|
262
|
+
Log.dump(:fields, fields, level: :trace1)
|
|
261
263
|
Aspera.assert_type(type, Symbol){'result must have type'}
|
|
262
264
|
Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
|
|
263
265
|
display_item_count(data.length, total) unless total.nil?
|
|
@@ -317,9 +319,9 @@ module Aspera
|
|
|
317
319
|
case type
|
|
318
320
|
when :single_object
|
|
319
321
|
# :single_object is a Hash, where key=column name
|
|
320
|
-
Aspera.assert_type(data, Hash)
|
|
322
|
+
Aspera.assert_type(data, Hash){'result'}
|
|
321
323
|
if data.empty?
|
|
322
|
-
display_message(:data,
|
|
324
|
+
display_message(:data, TerminalFormatter.special_format('empty dict'))
|
|
323
325
|
else
|
|
324
326
|
data = DotContainer.new(data).to_dotted if @options[:flat_hash]
|
|
325
327
|
display_table([data], compute_fields([data], fields), single: true)
|
|
@@ -337,7 +339,7 @@ module Aspera
|
|
|
337
339
|
Log.log.debug('no result expected')
|
|
338
340
|
return
|
|
339
341
|
end
|
|
340
|
-
display_message(:info,
|
|
342
|
+
display_message(:info, TerminalFormatter.special_format(data.to_s))
|
|
341
343
|
return
|
|
342
344
|
when :status # no table
|
|
343
345
|
# :status displays a simple message
|
|
@@ -364,6 +366,7 @@ module Aspera
|
|
|
364
366
|
# @param default [Array<String>, Proc] list of fields to display by default (may contain special values)
|
|
365
367
|
def compute_fields(data, default)
|
|
366
368
|
Log.log.debug{"compute_fields: data:#{data.class} default:#{default.class} #{default}"}
|
|
369
|
+
Log.dump(:compute_fields_default, default, level: :trace1)
|
|
367
370
|
# the requested list of fields, but if can contain special values
|
|
368
371
|
request =
|
|
369
372
|
case @options[:fields]
|
|
@@ -374,13 +377,14 @@ module Aspera
|
|
|
374
377
|
when Proc then return all_fields(data).select{ |i| @options[:fields].call(i)}
|
|
375
378
|
else Aspera.error_unexpected_value(@options[:fields])
|
|
376
379
|
end
|
|
380
|
+
Aspera.assert_array_all(request, String)
|
|
377
381
|
result = []
|
|
378
382
|
until request.empty?
|
|
379
383
|
item = request.shift
|
|
380
384
|
removal = false
|
|
381
385
|
if item[0].eql?(FIELDS_LESS)
|
|
382
386
|
removal = true
|
|
383
|
-
item = item
|
|
387
|
+
item = item.delete_prefix(FIELDS_LESS)
|
|
384
388
|
end
|
|
385
389
|
case item
|
|
386
390
|
when SpecialValues::ALL
|
|
@@ -398,11 +402,14 @@ module Aspera
|
|
|
398
402
|
end
|
|
399
403
|
end
|
|
400
404
|
end
|
|
405
|
+
Log.dump(:compute_fields, result, level: :trace1)
|
|
401
406
|
return result
|
|
402
407
|
end
|
|
403
408
|
|
|
404
409
|
# filter the list of items on the fields option
|
|
405
410
|
def filter_list_on_fields(data)
|
|
411
|
+
# no filter for single element
|
|
412
|
+
return data unless data.is_a?(Array)
|
|
406
413
|
# by default, keep all data intact
|
|
407
414
|
return data if @options[:fields].eql?(SpecialValues::DEF) && @options[:select].nil?
|
|
408
415
|
Aspera.assert_array_all(data, Hash){'filter or select'}
|
|
@@ -428,21 +435,26 @@ module Aspera
|
|
|
428
435
|
end
|
|
429
436
|
end
|
|
430
437
|
|
|
431
|
-
#
|
|
432
|
-
# @param object_array
|
|
433
|
-
# @param fields
|
|
438
|
+
# Displays a list of objects
|
|
439
|
+
# @param object_array [Array<Hash>] Array of hash
|
|
440
|
+
# @param fields [Array<String>] List of column names
|
|
441
|
+
# @param single [Boolean] Contains a single object to display
|
|
434
442
|
def display_table(object_array, fields, single: false)
|
|
435
|
-
Aspera.
|
|
443
|
+
Aspera.assert_array_all(object_array, Hash)
|
|
444
|
+
Aspera.assert_array_all(fields, String)
|
|
436
445
|
if object_array.empty?
|
|
437
446
|
# no display for csv
|
|
438
|
-
display_message(:info,
|
|
447
|
+
display_message(:info, TerminalFormatter.special_format('empty')) if @options[:format].eql?(:table)
|
|
439
448
|
return
|
|
440
449
|
end
|
|
441
450
|
filter_columns_on_select(object_array)
|
|
442
|
-
|
|
451
|
+
format_style = @options[:table_style].symbolize_keys
|
|
452
|
+
string_list_separator = format_style.delete(:str_lst_sep) || STR_LST_SEP_VERT
|
|
453
|
+
# convert data to string, and keep only display fields
|
|
454
|
+
object_array.each{ |i| self.class.replace_specific_for_terminal(i, string_list_separator)}
|
|
443
455
|
# if table has only one element, and only one field, display the value
|
|
444
456
|
if object_array.length == 1 && fields.length == 1
|
|
445
|
-
Log.log.debug("
|
|
457
|
+
Log.log.debug("single element, field: #{fields.first}")
|
|
446
458
|
data = object_array.first[fields.first]
|
|
447
459
|
unless data.is_a?(Array) && data.all?(Hash)
|
|
448
460
|
display_message(:data, data)
|
|
@@ -453,6 +465,7 @@ module Aspera
|
|
|
453
465
|
single = false
|
|
454
466
|
end
|
|
455
467
|
Log.dump(:object_array, object_array)
|
|
468
|
+
Log.dump(:fields, fields)
|
|
456
469
|
# convert data to string, and keep only display fields
|
|
457
470
|
final_table_rows = object_array.map{ |r| fields.map{ |c| r[c].to_s}}
|
|
458
471
|
# remove empty rows
|
|
@@ -460,6 +473,7 @@ module Aspera
|
|
|
460
473
|
# here : fields : list of column names
|
|
461
474
|
case @options[:format]
|
|
462
475
|
when :table
|
|
476
|
+
format_style[:border] = :unicode_round if Environment.terminal_supports_unicode?
|
|
463
477
|
if single || @options[:multi_single].eql?(:yes) ||
|
|
464
478
|
(@options[:multi_single].eql?(:single) && final_table_rows.length.eql?(1))
|
|
465
479
|
# display multiple objects as multiple transposed tables
|
|
@@ -467,23 +481,20 @@ module Aspera
|
|
|
467
481
|
display_message(:data, Terminal::Table.new(
|
|
468
482
|
headings: SINGLE_OBJECT_COLUMN_NAMES,
|
|
469
483
|
rows: fields.zip(row),
|
|
470
|
-
style:
|
|
484
|
+
style: format_style
|
|
471
485
|
))
|
|
472
486
|
end
|
|
473
487
|
else
|
|
474
|
-
# display the table
|
|
488
|
+
# display the table as single table
|
|
475
489
|
display_message(:data, Terminal::Table.new(
|
|
476
490
|
headings: fields,
|
|
477
491
|
rows: final_table_rows,
|
|
478
|
-
style:
|
|
492
|
+
style: format_style
|
|
479
493
|
))
|
|
480
494
|
end
|
|
481
495
|
when :csv
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
params.delete(:border)
|
|
485
|
-
add_headers = params.delete(:headers)
|
|
486
|
-
output = CSV.generate(**params) do |csv|
|
|
496
|
+
add_headers = format_style.delete(:headers)
|
|
497
|
+
output = CSV.generate(**format_style) do |csv|
|
|
487
498
|
csv << fields if add_headers
|
|
488
499
|
final_table_rows.each do |row|
|
|
489
500
|
csv << row
|
|
@@ -493,6 +504,7 @@ module Aspera
|
|
|
493
504
|
else
|
|
494
505
|
raise "not expected: #{@options[:format]}"
|
|
495
506
|
end
|
|
507
|
+
nil
|
|
496
508
|
end
|
|
497
509
|
end
|
|
498
510
|
end
|
data/lib/aspera/cli/main.rb
CHANGED
|
@@ -12,6 +12,8 @@ require 'aspera/cli/hints'
|
|
|
12
12
|
require 'aspera/secret_hider'
|
|
13
13
|
require 'aspera/log'
|
|
14
14
|
require 'aspera/assert'
|
|
15
|
+
require 'aspera/schema/documentation'
|
|
16
|
+
require 'aspera/schema/registry'
|
|
15
17
|
require 'net/ssh/errors'
|
|
16
18
|
require 'openssl'
|
|
17
19
|
|
|
@@ -19,6 +21,8 @@ module Aspera
|
|
|
19
21
|
module Cli
|
|
20
22
|
# Global objects shared with plugins
|
|
21
23
|
class Context
|
|
24
|
+
# @type [Array<Symbol>]
|
|
25
|
+
MEMBERS = %i[options transfer config formatter persistency man_header].freeze
|
|
22
26
|
# @!attribute [rw] options
|
|
23
27
|
# @return [Manager] the command line options manager
|
|
24
28
|
# @!attribute [rw] transfer
|
|
@@ -31,15 +35,17 @@ module Aspera
|
|
|
31
35
|
# @return [Object] # whatever the type is
|
|
32
36
|
# @!attribute [rw] man_header
|
|
33
37
|
# @return [Boolean] whether to display the manual header in plugin help
|
|
34
|
-
MEMBERS = %i[options transfer config formatter persistency man_header].freeze
|
|
35
38
|
attr_accessor(*MEMBERS)
|
|
36
39
|
|
|
37
40
|
# Initialize all members to nil, so that they are defined and can be validated later
|
|
41
|
+
# @return [nil]
|
|
38
42
|
def initialize
|
|
39
43
|
MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Validate that all members are set, raise exception if not
|
|
47
|
+
# @raise [Aspera::AssertionError] if any member is not set
|
|
48
|
+
# @return [nil]
|
|
43
49
|
def validate
|
|
44
50
|
MEMBERS.each do |i|
|
|
45
51
|
Aspera.assert(instance_variable_defined?(:"@#{i}"))
|
|
@@ -47,10 +53,14 @@ module Aspera
|
|
|
47
53
|
end
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
# Check if the context is in manual-only mode
|
|
57
|
+
# @return [Boolean] true if in manual-only mode
|
|
50
58
|
def only_manual?
|
|
51
59
|
transfer.eql?(:only_manual)
|
|
52
60
|
end
|
|
53
61
|
|
|
62
|
+
# Set the context to manual-only mode
|
|
63
|
+
# @return [Symbol] :only_manual
|
|
54
64
|
def only_manual!
|
|
55
65
|
@transfer = :only_manual
|
|
56
66
|
end
|
|
@@ -69,26 +79,37 @@ module Aspera
|
|
|
69
79
|
private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES, :USER_INTERFACES
|
|
70
80
|
|
|
71
81
|
class << self
|
|
72
|
-
|
|
82
|
+
# Create a special result type (only used internally here)
|
|
83
|
+
# @param special_sym [Symbol] the special result type
|
|
84
|
+
# @return [Hash] result hash with type :special
|
|
85
|
+
def result_special(special_sym); {type: :special, data: special_sym}; end
|
|
73
86
|
|
|
74
87
|
# Expect some list, but nothing to display
|
|
88
|
+
# @return [Hash] result hash for empty list
|
|
75
89
|
def result_empty; result_special(:empty); end
|
|
76
90
|
|
|
77
91
|
# Nothing expected
|
|
92
|
+
# @return [Hash] result hash for nothing
|
|
78
93
|
def result_nothing; result_special(:nothing); end
|
|
79
94
|
|
|
80
95
|
# Result is some status, such as "complete", "deleted"...
|
|
81
96
|
# @param status [String] The status
|
|
97
|
+
# @return [Hash] result hash with type :status
|
|
82
98
|
def result_status(status); return {type: :status, data: status}; end
|
|
83
99
|
|
|
84
100
|
# Text result coming from command result
|
|
101
|
+
# @param data [String, Integer, Symbol] the text data to display
|
|
102
|
+
# @return [Hash] result hash with type :text
|
|
85
103
|
def result_text(data); return {type: :text, data: data}; end
|
|
86
104
|
|
|
105
|
+
# Create a success result
|
|
106
|
+
# @return [Hash] result hash with status 'complete'
|
|
87
107
|
def result_success; return result_status('complete'); end
|
|
88
108
|
|
|
89
109
|
# Process statuses of finished transfer sessions
|
|
90
|
-
# @
|
|
91
|
-
#
|
|
110
|
+
# @param statuses [Array] array of transfer session statuses
|
|
111
|
+
# @raise [Symbol] exception if there is one error
|
|
112
|
+
# @return [Hash] empty status result if all transfers succeeded
|
|
92
113
|
def result_transfer(statuses)
|
|
93
114
|
worst = TransferAgent.session_status(statuses)
|
|
94
115
|
raise worst unless worst.eql?(:success)
|
|
@@ -112,26 +133,33 @@ module Aspera
|
|
|
112
133
|
end
|
|
113
134
|
|
|
114
135
|
# Display image for that URL or directly blob
|
|
115
|
-
#
|
|
116
136
|
# @param url_or_blob [String] URL or blob to display as image
|
|
137
|
+
# @return [Hash] result hash with type :image
|
|
117
138
|
def result_image(url_or_blob)
|
|
118
139
|
return {type: :image, data: url_or_blob}
|
|
119
140
|
end
|
|
120
141
|
|
|
121
142
|
# A single object, must be Hash
|
|
143
|
+
# @param data [Hash] the object data
|
|
144
|
+
# @param fields [Array<String>, nil] optional list of fields to display
|
|
145
|
+
# @return [Hash] result hash with type :single_object
|
|
122
146
|
def result_single_object(data, fields: nil)
|
|
123
147
|
return {type: :single_object, data: data, fields: fields}
|
|
124
148
|
end
|
|
125
149
|
|
|
126
150
|
# An Array of Hash
|
|
151
|
+
# @param data [Array<Hash>] array of objects
|
|
152
|
+
# @param fields [Array<String>, nil] optional list of fields to display
|
|
153
|
+
# @param total [Integer, nil] optional total count
|
|
154
|
+
# @return [Hash] result hash with type :object_list
|
|
127
155
|
def result_object_list(data, fields: nil, total: nil)
|
|
128
156
|
return {type: :object_list, data: data, fields: fields, total: total}
|
|
129
157
|
end
|
|
130
158
|
|
|
131
159
|
# A list of values
|
|
132
|
-
#
|
|
133
160
|
# @param data [Array] The list of values
|
|
134
161
|
# @param name [String] The name of the list (used for display)
|
|
162
|
+
# @return [Hash] result hash with type :value_list
|
|
135
163
|
def result_value_list(data, name: 'id')
|
|
136
164
|
Aspera.assert_type(data, Array)
|
|
137
165
|
Aspera.assert_type(name, String)
|
|
@@ -139,6 +167,8 @@ module Aspera
|
|
|
139
167
|
end
|
|
140
168
|
|
|
141
169
|
# Determines type of result based on data
|
|
170
|
+
# @param data [Object] the data to analyze and format
|
|
171
|
+
# @return [Hash] result hash with appropriate type based on data
|
|
142
172
|
def result_auto(data)
|
|
143
173
|
case data
|
|
144
174
|
when NilClass
|
|
@@ -159,6 +189,8 @@ module Aspera
|
|
|
159
189
|
end
|
|
160
190
|
|
|
161
191
|
# Minimum initialization, no exception raised
|
|
192
|
+
# @param argv [Array<String>] command line arguments
|
|
193
|
+
# @return [nil]
|
|
162
194
|
def initialize(argv)
|
|
163
195
|
@argv = argv
|
|
164
196
|
Log.dump(:argv, @argv, level: :trace2)
|
|
@@ -169,6 +201,8 @@ module Aspera
|
|
|
169
201
|
end
|
|
170
202
|
|
|
171
203
|
# This is the main function called by initial script just after constructor
|
|
204
|
+
# Processes command line arguments, executes commands, and handles exceptions
|
|
205
|
+
# @return [nil]
|
|
172
206
|
def process_command_line
|
|
173
207
|
# Catch exception information , if any
|
|
174
208
|
exception_info = nil
|
|
@@ -239,6 +273,7 @@ module Aspera
|
|
|
239
273
|
rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
|
|
240
274
|
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing', usage: true}
|
|
241
275
|
rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
|
|
276
|
+
rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
|
|
242
277
|
rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
|
|
243
278
|
rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
|
|
244
279
|
rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
|
|
@@ -251,11 +286,22 @@ module Aspera
|
|
|
251
286
|
# 1- processing of error condition
|
|
252
287
|
unless exception_info.nil?
|
|
253
288
|
Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
|
|
254
|
-
Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"}
|
|
289
|
+
Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"} unless exception_info[:e].is_a?(Cli::SchemaRequest)
|
|
255
290
|
Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
|
|
256
291
|
@context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
|
|
257
292
|
# Is that a known error condition with proposal for remediation ?
|
|
258
293
|
Hints.hint_for(exception_info[:e], @context.formatter)
|
|
294
|
+
# Requested help for a Hash parameter/option ?
|
|
295
|
+
if exception_info[:e].is_a?(Cli::SchemaRequest)
|
|
296
|
+
Log.log.info{"#{exception_info[:t]}: #{exception_info[:e].message}"}
|
|
297
|
+
schema_path = exception_info[:e].path
|
|
298
|
+
if schema_path.nil?
|
|
299
|
+
Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
|
|
300
|
+
else
|
|
301
|
+
builder = Schema::Documentation.new(TerminalFormatter, Schema::Registry.instance.reader(schema_path)).build
|
|
302
|
+
@context.formatter.display_results(**Main.result_object_list(builder.rows, fields: builder.columns))
|
|
303
|
+
end
|
|
304
|
+
end
|
|
259
305
|
end
|
|
260
306
|
# 2- processing of command not processed (due to exception or bad command line)
|
|
261
307
|
if execute_command || @option_show_config
|
|
@@ -276,6 +322,10 @@ module Aspera
|
|
|
276
322
|
return
|
|
277
323
|
end
|
|
278
324
|
|
|
325
|
+
# Display usage information and help
|
|
326
|
+
# @param all [Boolean] if true, show help for all plugins; if false, show only current plugin
|
|
327
|
+
# @param exit [Boolean] if true, exit the process after displaying help
|
|
328
|
+
# @return [nil]
|
|
279
329
|
def show_usage(all: true, exit: true)
|
|
280
330
|
# Display main plugin options (+config)
|
|
281
331
|
@context.formatter.display_message(:error, @context.options.parser)
|
|
@@ -298,7 +348,10 @@ module Aspera
|
|
|
298
348
|
|
|
299
349
|
private
|
|
300
350
|
|
|
351
|
+
# Initialize agents and options
|
|
301
352
|
# This can throw exception if there is a problem with the environment, needs to be caught by execute method
|
|
353
|
+
# @raise [StandardError] if there is a problem with the environment
|
|
354
|
+
# @return [nil]
|
|
302
355
|
def init_agents_and_options
|
|
303
356
|
@context.man_header = true
|
|
304
357
|
# Create formatter, in case there is an exception, it is used to display.
|
|
@@ -328,6 +381,8 @@ module Aspera
|
|
|
328
381
|
@context.options.parser.banner = app_banner
|
|
329
382
|
end
|
|
330
383
|
|
|
384
|
+
# Generate the application banner for help display
|
|
385
|
+
# @return [String] formatted banner text
|
|
331
386
|
def app_banner
|
|
332
387
|
t = ' ' * 8
|
|
333
388
|
return <<~END_OF_BANNER
|
|
@@ -362,7 +417,8 @@ module Aspera
|
|
|
362
417
|
END_OF_BANNER
|
|
363
418
|
end
|
|
364
419
|
|
|
365
|
-
# Define header for manual
|
|
420
|
+
# Define header for manual and declare all global options
|
|
421
|
+
# @return [nil]
|
|
366
422
|
def declare_global_options
|
|
367
423
|
Log.log.debug('declare_global_options')
|
|
368
424
|
@context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
|
|
@@ -397,9 +453,10 @@ module Aspera
|
|
|
397
453
|
@context.options.parse_options!
|
|
398
454
|
end
|
|
399
455
|
|
|
400
|
-
#
|
|
456
|
+
# Get the plugin instance based on name
|
|
401
457
|
# Also loads the plugin options, and default values from conf file
|
|
402
|
-
# @param plugin_name_sym
|
|
458
|
+
# @param plugin_name_sym [Symbol] symbol for plugin name
|
|
459
|
+
# @return [Plugins::Base] the plugin instance
|
|
403
460
|
def get_plugin_instance_with_options(plugin_name_sym)
|
|
404
461
|
Log.log.debug{"get_plugin_instance_with_options(#{plugin_name_sym})"}
|
|
405
462
|
# Load default params only if no param already loaded before plugin instantiation
|
|
@@ -408,6 +465,8 @@ module Aspera
|
|
|
408
465
|
return command_plugin
|
|
409
466
|
end
|
|
410
467
|
|
|
468
|
+
# Generate bash completion suggestions
|
|
469
|
+
# @return [nil]
|
|
411
470
|
def generate_bash_completion
|
|
412
471
|
if @context.options.get_next_argument('', multiple: true, mandatory: false).nil?
|
|
413
472
|
Plugins::Factory.instance.plugin_list.each{ |p| puts p}
|