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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +89 -48
  4. data/CONTRIBUTING.md +1 -1
  5. data/lib/aspera/api/aoc.rb +120 -79
  6. data/lib/aspera/api/node.rb +103 -51
  7. data/lib/aspera/ascp/installation.rb +99 -32
  8. data/lib/aspera/assert.rb +17 -13
  9. data/lib/aspera/cli/extended_value.rb +7 -2
  10. data/lib/aspera/cli/formatter.rb +107 -95
  11. data/lib/aspera/cli/main.rb +69 -10
  12. data/lib/aspera/cli/manager.rb +158 -78
  13. data/lib/aspera/cli/options.schema.yaml +82 -0
  14. data/lib/aspera/cli/plugins/aoc.rb +247 -144
  15. data/lib/aspera/cli/plugins/ats.rb +3 -3
  16. data/lib/aspera/cli/plugins/base.rb +60 -76
  17. data/lib/aspera/cli/plugins/config.rb +14 -12
  18. data/lib/aspera/cli/plugins/console.rb +3 -3
  19. data/lib/aspera/cli/plugins/faspex.rb +6 -6
  20. data/lib/aspera/cli/plugins/faspex5.rb +24 -23
  21. data/lib/aspera/cli/plugins/node.rb +67 -71
  22. data/lib/aspera/cli/plugins/oauth.rb +5 -12
  23. data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
  24. data/lib/aspera/cli/plugins/preview.rb +116 -80
  25. data/lib/aspera/cli/plugins/server.rb +2 -10
  26. data/lib/aspera/cli/plugins/shares.rb +7 -7
  27. data/lib/aspera/cli/sync_actions.rb +1 -1
  28. data/lib/aspera/cli/transfer_agent.rb +17 -15
  29. data/lib/aspera/cli/version.rb +1 -1
  30. data/lib/aspera/command_line_builder.rb +22 -18
  31. data/lib/aspera/dot_container.rb +7 -3
  32. data/lib/aspera/environment.rb +6 -5
  33. data/lib/aspera/formatter_interface.rb +14 -0
  34. data/lib/aspera/hash_ext.rb +6 -0
  35. data/lib/aspera/log.rb +5 -4
  36. data/lib/aspera/markdown.rb +4 -1
  37. data/lib/aspera/oauth/factory.rb +1 -1
  38. data/lib/aspera/preview/file_types.rb +1 -1
  39. data/lib/aspera/preview/generator.rb +146 -91
  40. data/lib/aspera/preview/options.rb +4 -1
  41. data/lib/aspera/preview/terminal.rb +50 -20
  42. data/lib/aspera/preview/utils.rb +76 -34
  43. data/lib/aspera/products/transferd.rb +1 -1
  44. data/lib/aspera/proxy_auto_config.rb +3 -0
  45. data/lib/aspera/rest.rb +2 -1
  46. data/lib/aspera/rest_list.rb +23 -16
  47. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62801 -0
  48. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +8898 -0
  49. data/lib/aspera/schema/documentation.rb +107 -0
  50. data/lib/aspera/schema/reader.rb +75 -0
  51. data/lib/aspera/schema/registry.rb +63 -0
  52. data/lib/aspera/secret_hider.rb +3 -1
  53. data/lib/aspera/sync/conf.schema.yaml +0 -26
  54. data/lib/aspera/sync/operations.rb +9 -5
  55. data/lib/aspera/transfer/faux_file.rb +1 -1
  56. data/lib/aspera/transfer/resumer.rb +1 -1
  57. data/lib/aspera/transfer/spec.rb +3 -3
  58. data/lib/aspera/transfer/spec.schema.yaml +1 -1
  59. data/lib/aspera/uri_reader.rb +17 -2
  60. data/lib/aspera/yaml.rb +4 -2
  61. data.tar.gz.sig +0 -0
  62. metadata +13 -7
  63. metadata.gz.sig +0 -0
  64. data/lib/aspera/transfer/spec_doc.rb +0 -76
@@ -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 = [list] unless list.is_a?(Array)
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: default_table_style)
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
- # hides secrets in Hash or Array
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
- # this method displays the results, especially the table format
253
- # @param type [Symbol] type of data
254
- # @param data [Object] data to display
255
- # @param total [Integer] total number of items
256
- # @param fields [Array<String>] list of fields to display
257
- # @param name [String] name of the column to display
258
- def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
259
- Log.log.debug{"display_results: type=#{type} class=#{data.class}"}
260
- Log.log.trace1{"display_results: data=#{data}"}
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, self.class.special_format('empty dict'))
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, self.class.special_format(data.to_s))
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[1..-1]
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
- # displays a list of objects
432
- # @param object_array [Array] array of hash
433
- # @param fields [Array] list of column names
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.assert(!fields.nil?){'missing fields parameter'}
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, self.class.special_format('empty')) if @options[:format].eql?(:table)
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
- object_array.each{ |i| self.class.replace_specific_for_terminal(i)}
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("display_table: single element, field: #{fields.first}")
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: @options[:table_style].symbolize_keys
484
+ style: format_style
471
485
  ))
472
486
  end
473
487
  else
474
- # display the table ! as single 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: @options[:table_style].symbolize_keys
492
+ style: format_style
479
493
  ))
480
494
  end
481
495
  when :csv
482
- params = @options[:table_style].symbolize_keys
483
- # delete default
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
@@ -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
- def result_special(how); {type: :special, data: how}; end
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
- # @raise exception if there is one error
91
- # else returns an empty status
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
- # @return the plugin instance, based on name
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 : symbol for plugin name
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}