aspera-cli 4.26.1 → 4.26.2

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 (86) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +19 -1
  4. data/CONTRIBUTING.md +2 -5
  5. data/bin/ascli +2 -2
  6. data/lib/aspera/agent/direct.rb +1 -1
  7. data/lib/aspera/agent/factory.rb +4 -0
  8. data/lib/aspera/agent/httpgw.rb +1 -1
  9. data/lib/aspera/agent/node.rb +2 -2
  10. data/lib/aspera/api/aoc.rb +43 -36
  11. data/lib/aspera/api/cos_node.rb +1 -1
  12. data/lib/aspera/api/faspex.rb +7 -6
  13. data/lib/aspera/api/node.rb +7 -7
  14. data/lib/aspera/ascmd.rb +4 -4
  15. data/lib/aspera/ascp/installation.rb +6 -2
  16. data/lib/aspera/cli/context.rb +58 -0
  17. data/lib/aspera/cli/extended_value.rb +3 -2
  18. data/lib/aspera/cli/formatter.rb +83 -184
  19. data/lib/aspera/cli/http.rb +167 -0
  20. data/lib/aspera/cli/manager.rb +3 -2
  21. data/lib/aspera/cli/plugins/alee.rb +2 -2
  22. data/lib/aspera/cli/plugins/aoc.rb +78 -67
  23. data/lib/aspera/cli/plugins/ats.rb +16 -16
  24. data/lib/aspera/cli/plugins/base.rb +18 -12
  25. data/lib/aspera/cli/plugins/config.rb +166 -438
  26. data/lib/aspera/cli/plugins/console.rb +7 -7
  27. data/lib/aspera/cli/plugins/cos.rb +2 -2
  28. data/lib/aspera/cli/plugins/factory.rb +3 -0
  29. data/lib/aspera/cli/plugins/faspex.rb +21 -21
  30. data/lib/aspera/cli/plugins/faspex5.rb +64 -44
  31. data/lib/aspera/cli/plugins/faspio.rb +1 -1
  32. data/lib/aspera/cli/plugins/httpgw.rb +2 -2
  33. data/lib/aspera/cli/plugins/node.rb +79 -79
  34. data/lib/aspera/cli/plugins/orchestrator.rb +21 -26
  35. data/lib/aspera/cli/plugins/preview.rb +9 -9
  36. data/lib/aspera/cli/plugins/server.rb +7 -7
  37. data/lib/aspera/cli/plugins/shares.rb +2 -2
  38. data/lib/aspera/cli/preset_manager.rb +235 -0
  39. data/lib/aspera/cli/result.rb +310 -0
  40. data/lib/aspera/cli/{main.rb → runner.rb} +14 -155
  41. data/lib/aspera/cli/sync_actions.rb +14 -10
  42. data/lib/aspera/cli/terminal_formatter.rb +65 -0
  43. data/lib/aspera/cli/transfer_progress.rb +6 -6
  44. data/lib/aspera/cli/version.rb +1 -1
  45. data/lib/aspera/cli/wizard.rb +5 -6
  46. data/lib/aspera/command_line_builder.rb +1 -1
  47. data/lib/aspera/data_repository.rb +4 -0
  48. data/lib/aspera/dot_container.rb +1 -1
  49. data/lib/aspera/environment.rb +22 -3
  50. data/lib/aspera/faspex_gw.rb +1 -1
  51. data/lib/aspera/json_rpc.rb +6 -5
  52. data/lib/aspera/keychain/base.rb +1 -1
  53. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  54. data/lib/aspera/keychain/factory.rb +1 -1
  55. data/lib/aspera/keychain/macos_security.rb +1 -1
  56. data/lib/aspera/log.rb +6 -2
  57. data/lib/aspera/nagios.rb +2 -2
  58. data/lib/aspera/oauth/base.rb +5 -5
  59. data/lib/aspera/oauth/boot.rb +43 -0
  60. data/lib/aspera/oauth/factory.rb +37 -13
  61. data/lib/aspera/oauth/web.rb +2 -2
  62. data/lib/aspera/oauth.rb +1 -0
  63. data/lib/aspera/persistency_action_once.rb +2 -2
  64. data/lib/aspera/preview/file_types.rb +4 -0
  65. data/lib/aspera/products/connect.rb +3 -0
  66. data/lib/aspera/products/transferd.rb +2 -2
  67. data/lib/aspera/proxy_auto_config.rb +3 -3
  68. data/lib/aspera/rest.rb +8 -4
  69. data/lib/aspera/rest_error_analyzer.rb +4 -0
  70. data/lib/aspera/rest_list.rb +10 -3
  71. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
  72. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +827 -88
  73. data/lib/aspera/schema/async_tables.yaml +361 -0
  74. data/lib/aspera/schema/reader.rb +1 -1
  75. data/lib/aspera/schema/registry.rb +13 -6
  76. data/lib/aspera/secret_hider.rb +4 -0
  77. data/lib/aspera/sync/database.rb +14 -10
  78. data/lib/aspera/sync/operations.rb +5 -5
  79. data/lib/aspera/temp_file_manager.rb +4 -0
  80. data/lib/aspera/transfer/parameters.rb +4 -4
  81. data/lib/aspera/transfer/resumer.rb +1 -1
  82. data/lib/aspera/transfer/spec.schema.yaml +17 -8
  83. data/lib/aspera/transfer/uri.rb +1 -1
  84. data.tar.gz.sig +0 -0
  85. metadata +21 -14
  86. metadata.gz.sig +0 -0
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/cli/context'
3
4
  require 'aspera/cli/manager'
4
5
  require 'aspera/cli/formatter'
6
+ require 'aspera/cli/plugins/factory'
5
7
  require 'aspera/cli/plugins/config'
6
8
  require 'aspera/cli/extended_value'
7
- require 'aspera/cli/plugins/factory'
8
9
  require 'aspera/cli/transfer_agent'
9
10
  require 'aspera/cli/version'
10
11
  require 'aspera/cli/info'
11
12
  require 'aspera/cli/hints'
13
+ require 'aspera/cli/result'
12
14
  require 'aspera/secret_hider'
13
15
  require 'aspera/log'
14
16
  require 'aspera/assert'
@@ -19,55 +21,8 @@ require 'openssl'
19
21
 
20
22
  module Aspera
21
23
  module Cli
22
- # Global objects shared with plugins
23
- class Context
24
- # @type [Array<Symbol>]
25
- MEMBERS = %i[options transfer config formatter persistency man_header].freeze
26
- # @!attribute [rw] options
27
- # @return [Manager] the command line options manager
28
- # @!attribute [rw] transfer
29
- # @return [TransferAgent] the transfer agent, used by transfer plugins
30
- # @!attribute [rw] config
31
- # @return [Plugins::Config] the configuration plugin, used by plugins to get configuration values and presets
32
- # @!attribute [rw] formatter
33
- # @return [Formatter] the formatter, used by plugins to display results and messages
34
- # @!attribute [rw] persistency
35
- # @return [Object] # whatever the type is
36
- # @!attribute [rw] man_header
37
- # @return [Boolean] whether to display the manual header in plugin help
38
- attr_accessor(*MEMBERS)
39
-
40
- # Initialize all members to nil, so that they are defined and can be validated later
41
- # @return [nil]
42
- def initialize
43
- MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
44
- end
45
-
46
- # Validate that all members are set, raise exception if not
47
- # @raise [Aspera::AssertionError] if any member is not set
48
- # @return [nil]
49
- def validate
50
- MEMBERS.each do |i|
51
- Aspera.assert(instance_variable_defined?(:"@#{i}"))
52
- Aspera.assert(!instance_variable_get(:"@#{i}").nil?)
53
- end
54
- end
55
-
56
- # Check if the context is in manual-only mode
57
- # @return [Boolean] true if in manual-only mode
58
- def only_manual?
59
- transfer.eql?(:only_manual)
60
- end
61
-
62
- # Set the context to manual-only mode
63
- # @return [Symbol] :only_manual
64
- def only_manual!
65
- @transfer = :only_manual
66
- end
67
- end
68
-
69
24
  # The main CLI class
70
- class Main
25
+ class Runner
71
26
  # Plugins store transfer result using this key and use result_transfer_multiple()
72
27
  STATUS_FIELD = 'status'
73
28
  COMMAND_CONFIG = :config
@@ -79,46 +34,19 @@ module Aspera
79
34
  private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES, :USER_INTERFACES
80
35
 
81
36
  class << self
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
86
-
87
- # Expect some list, but nothing to display
88
- # @return [Hash] result hash for empty list
89
- def result_empty; result_special(:empty); end
90
-
91
- # Nothing expected
92
- # @return [Hash] result hash for nothing
93
- def result_nothing; result_special(:nothing); end
94
-
95
- # Result is some status, such as "complete", "deleted"...
96
- # @param status [String] The status
97
- # @return [Hash] result hash with type :status
98
- def result_status(status); return {type: :status, data: status}; end
99
-
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
103
- def result_text(data); return {type: :text, data: data}; end
104
-
105
- # Create a success result
106
- # @return [Hash] result hash with status 'complete'
107
- def result_success; return result_status('complete'); end
108
-
109
37
  # Process statuses of finished transfer sessions
110
38
  # @param statuses [Array] array of transfer session statuses
111
39
  # @raise [Symbol] exception if there is one error
112
- # @return [Hash] empty status result if all transfers succeeded
40
+ # @return [Result] empty status result if all transfers succeeded
113
41
  def result_transfer(statuses)
114
42
  worst = TransferAgent.session_status(statuses)
115
43
  raise worst unless worst.eql?(:success)
116
- return Main.result_nothing
44
+ return Result::Nothing.new
117
45
  end
118
46
 
119
47
  # Used when one command executes several transfer jobs (each job being possibly multi session)
120
48
  # @param status_table [Array] [{STATUS_FIELD=>[status array],...},...]
121
- # @return a status object suitable as command result
49
+ # @return [Result] a status object suitable as command result
122
50
  # Each element has a key STATUS_FIELD which contains the result of possibly multiple sessions
123
51
  def result_transfer_multiple(status_table)
124
52
  global_status = :success
@@ -129,62 +57,7 @@ module Aspera
129
57
  item[STATUS_FIELD] = item[STATUS_FIELD].join(',')
130
58
  end
131
59
  raise global_status unless global_status.eql?(:success)
132
- return result_object_list(status_table)
133
- end
134
-
135
- # Display image for that URL or directly blob
136
- # @param url_or_blob [String] URL or blob to display as image
137
- # @return [Hash] result hash with type :image
138
- def result_image(url_or_blob)
139
- return {type: :image, data: url_or_blob}
140
- end
141
-
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
146
- def result_single_object(data, fields: nil)
147
- return {type: :single_object, data: data, fields: fields}
148
- end
149
-
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
155
- def result_object_list(data, fields: nil, total: nil)
156
- return {type: :object_list, data: data, fields: fields, total: total}
157
- end
158
-
159
- # A list of values
160
- # @param data [Array] The list of values
161
- # @param name [String] The name of the list (used for display)
162
- # @return [Hash] result hash with type :value_list
163
- def result_value_list(data, name: 'id')
164
- Aspera.assert_type(data, Array)
165
- Aspera.assert_type(name, String)
166
- return {type: :value_list, data: data, name: name}
167
- end
168
-
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
172
- def result_auto(data)
173
- case data
174
- when NilClass
175
- return result_special(:null)
176
- when Hash
177
- return result_single_object(data)
178
- when Array
179
- all_types = data.map(&:class).uniq
180
- return result_object_list(data) if all_types.eql?([Hash])
181
- unsupported_types = all_types - SCALAR_TYPES
182
- return result_value_list(data, name: 'list') if unsupported_types.empty?
183
- Aspera.error_unexpected_value(unsupported_types){'list item types'}
184
- when *SCALAR_TYPES
185
- return result_text(data)
186
- else Aspera.error_unexpected_value(data.class.name){'result type'}
187
- end
60
+ return Result::ObjectList.new(status_table)
188
61
  end
189
62
  end
190
63
 
@@ -196,14 +69,13 @@ module Aspera
196
69
  Log.dump(:argv, @argv, level: :trace2)
197
70
  @option_help = false
198
71
  @option_show_config = false
199
- @bash_completion = false
200
72
  @context = Context.new
201
73
  end
202
74
 
203
75
  # This is the main function called by initial script just after constructor
204
76
  # Processes command line arguments, executes commands, and handles exceptions
205
77
  # @return [nil]
206
- def process_command_line
78
+ def run
207
79
  # Catch exception information , if any
208
80
  exception_info = nil
209
81
  # False if command shall not be executed (e.g. --show-config)
@@ -215,7 +87,6 @@ module Aspera
215
87
  Plugins::Factory.instance.add_plugins_from_lookup_folders
216
88
  # Help requested without command ? (plugins must be known here)
217
89
  show_usage if @option_help && @context.options.command_or_arg_empty?
218
- generate_bash_completion if @bash_completion
219
90
  @context.config.periodic_check_newer_gem_version
220
91
  command_sym =
221
92
  if @option_show_config && @context.options.command_or_arg_empty?
@@ -240,7 +111,7 @@ module Aspera
240
111
  # Help requested for current plugin
241
112
  show_usage(all: false) if @option_help
242
113
  if @option_show_config
243
- @context.formatter.display_results(type: :single_object, data: @context.options.known_options(only_defined: true).stringify_keys)
114
+ @context.formatter.display_results(Result::SingleObject.new(@context.options.known_options(only_defined: true).stringify_keys))
244
115
  execute_command = false
245
116
  end
246
117
  # Locking for single execution (only after "per plugin" option, in case lock port is there)
@@ -263,7 +134,7 @@ module Aspera
263
134
  at_exit{File.delete(pid_file)}
264
135
  end
265
136
  # Execute and display (if not exclusive execution)
266
- @context.formatter.display_results(**command_plugin.execute_action) if execute_command
137
+ @context.formatter.display_results(command_plugin.execute_action) if execute_command
267
138
  # Save config file if command modified it
268
139
  @context.config.save_config_file_if_needed
269
140
  # Finish
@@ -271,7 +142,7 @@ module Aspera
271
142
  rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
272
143
  rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
273
144
  rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
274
- rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing', usage: true}
145
+ rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing'}
275
146
  rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
276
147
  rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
277
148
  rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
@@ -299,7 +170,7 @@ module Aspera
299
170
  Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
300
171
  else
301
172
  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))
173
+ @context.formatter.display_results(Result::ObjectList.new(builder.rows, fields: builder.columns))
303
174
  end
304
175
  end
305
176
  end
@@ -371,7 +242,7 @@ module Aspera
371
242
  @context.config = Plugins::Config.new(context: @context)
372
243
  @context.man_header = true
373
244
  # Data persistency is set in config
374
- Aspera.assert(@context.persistency){'missing persistency object'}
245
+ Aspera.assert(@context.persistency, 'missing persistency object')
375
246
  # The TransferAgent plugin may use the @preset parser
376
247
  @context.transfer = TransferAgent.new(@context.options, @context.config)
377
248
  # Add commands for config plugin after all options have been added
@@ -422,7 +293,6 @@ module Aspera
422
293
  def declare_global_options
423
294
  Log.log.debug('declare_global_options')
424
295
  @context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
425
- @context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
426
296
  @context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
427
297
  @context.options.declare(:version, 'Display version', allowed: Allowed::TYPES_NONE, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
428
298
  @context.options.declare(
@@ -464,17 +334,6 @@ module Aspera
464
334
  command_plugin = Plugins::Factory.instance.create(plugin_name_sym, context: @context)
465
335
  return command_plugin
466
336
  end
467
-
468
- # Generate bash completion suggestions
469
- # @return [nil]
470
- def generate_bash_completion
471
- if @context.options.get_next_argument('', multiple: true, mandatory: false).nil?
472
- Plugins::Factory.instance.plugin_list.each{ |p| puts p}
473
- else
474
- Log.log.warn('only first level completion so far')
475
- end
476
- Process.exit(0)
477
- end
478
337
  end
479
338
  end
480
339
  end
@@ -15,7 +15,8 @@ module Aspera
15
15
  %w[Syncd Error Confl Pconf] +
16
16
  (23..24).map{ |i| "P(#{i})"}).freeze
17
17
  class << self
18
- def declare_options(_options)
18
+ def declare_options(options)
19
+ options.declare(:sql, 'SQL suffix appended to sqlite3 queries for admin subcommands (e.g. WHERE clause)')
19
20
  end
20
21
  end
21
22
 
@@ -33,7 +34,7 @@ module Aspera
33
34
  if sync_info.key?('sessions') || sync_info.key?('instance')
34
35
  # `args`
35
36
  sync_info['sessions'] ||= [{}]
36
- Aspera.assert(sync_info['sessions'].length == 1){'Only one session is supported'}
37
+ Aspera.assert(sync_info['sessions'].length == 1, 'Only one session is supported')
37
38
  session = sync_info['sessions'].first
38
39
  dir_key = path_is_remote ? 'remote_dir' : 'local_dir'
39
40
  raise "Parameter #{dir_key} shall not be in sync_info" if session.key?(dir_key)
@@ -94,31 +95,34 @@ module Aspera
94
95
  end
95
96
 
96
97
  def execute_sync_admin
97
- command2 = options.get_next_command(%i[status find meta counters file_info overview])
98
+ command2 = options.get_next_command(%i[status find meta counters file_info overview query])
98
99
  require 'aspera/sync/database' unless command2.eql?(:status)
100
+ sql_suffix = options.get_option(:sql)
99
101
  case command2
100
102
  when :status
101
- return Main.result_single_object(Sync::Operations.admin_status(async_info_from_args))
103
+ return Result::SingleObject.new(Sync::Operations.admin_status(async_info_from_args))
102
104
  when :find
103
105
  folder = options.get_next_argument('path')
104
106
  dbs = Sync::Operations.list_db_files(folder)
105
- return Main.result_object_list(dbs.keys.map{ |n| {name: n, path: dbs[n]}})
107
+ return Result::ObjectList.new(dbs.keys.map{ |n| {name: n, path: dbs[n]}})
106
108
  when :meta, :counters
107
- return Main.result_single_object(db_from_args.send(command2))
109
+ return Result::SingleObject.new(db_from_args.send(command2, sql_suffix))
108
110
  when :file_info
109
- result = db_from_args.send(command2)
111
+ result = db_from_args.file_info(sql_suffix)
110
112
  result.each do |r|
111
113
  r['sstate'] = SyncActions::STATE_STR[r['state']] if r['state']
112
114
  end
113
- return Main.result_object_list(
115
+ return Result::ObjectList.new(
114
116
  result,
115
117
  fields: %w[sstate record_id f_meta_path message]
116
118
  )
117
119
  when :overview
118
- return Main.result_object_list(
120
+ return Result::ObjectList.new(
119
121
  db_from_args.overview,
120
122
  fields: %w[table name type]
121
123
  )
124
+ when :query
125
+ return Result.auto(db_from_args.execute(options.get_option(:sql, mandatory: true)))
122
126
  else Aspera.error_unexpected_value(command2)
123
127
  end
124
128
  end
@@ -131,7 +135,7 @@ module Aspera
131
135
  case command
132
136
  when *Sync::Operations::DIRECTIONS
133
137
  Sync::Operations.start(async_info_from_args(direction: command), transfer.user_transfer_spec, &block)
134
- return Main.result_success
138
+ return Result::Success.new
135
139
  when :admin
136
140
  return execute_sync_admin
137
141
  else Aspera.error_unexpected_value(command)
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/environment'
4
+ require 'aspera/formatter_interface'
5
+ require 'aspera/markdown'
6
+ require 'aspera/assert'
7
+
8
+ module Aspera
9
+ module Cli
10
+ # Terminal formatter with ANSI colors and Unicode support
11
+ # @see FormatterInterface
12
+ # @see MarkdownFormatter (in build/lib/doc_helper.rb)
13
+ module TerminalFormatter
14
+ HINT = 'HINT:'.bg_green.gray.blink.freeze
15
+ include FormatterInterface
16
+
17
+ # Format boolean with colored symbol (✓/✗ or Y/ )
18
+ def tick(yes)
19
+ result =
20
+ if Environment.terminal_supports_unicode?
21
+ yes ? "\u2713" : "\u2717"
22
+ else
23
+ yes ? 'Y' : ' '
24
+ end
25
+ return result.green if yes
26
+ return result.red
27
+ end
28
+
29
+ # Format special values with colors (dim for empty, reverse for others)
30
+ def special_format(what)
31
+ result = "<#{what}>"
32
+ return %w[null empty].any?{ |s| what.include?(s)} ? result.dim : result.reverse_color
33
+ end
34
+
35
+ # Prepare table row for terminal display (word wrap arrays)
36
+ def check_row(row)
37
+ row.each_key do |k|
38
+ row[k] = row[k].map{ |i| WordWrap.ww(i.to_s, 120).chomp}.join("\n") if row[k].is_a?(Array)
39
+ end
40
+ end
41
+
42
+ # Convert Markdown to terminal format (**bold** -> blue, `code` -> bold)
43
+ # @param match [MatchData, String]
44
+ def markdown_text(match)
45
+ if match.is_a?(String)
46
+ match = Markdown::FORMATS.match(match)
47
+ Aspera.assert(match, 'markdown text does not match any known format')
48
+ end
49
+ Aspera.assert_type(match, MatchData)
50
+ if match[:entity]
51
+ Aspera.assert_values(match[:entity], %w[bsol])
52
+ '\\'
53
+ elsif match[:bold]
54
+ match[:bold].to_s.blue
55
+ elsif match[:code]
56
+ match[:code].to_s.bold
57
+ else
58
+ Aspera.error_unexpected_value(match.to_s)
59
+ end
60
+ end
61
+
62
+ module_function :tick, :special_format, :check_row, :markdown_text
63
+ end
64
+ end
65
+ end
@@ -43,13 +43,13 @@ module Aspera
43
43
  case type
44
44
  when :sessions_init
45
45
  # Give opportunity to show progress of initialization with multiple status
46
- Aspera.assert(session_id.nil?)
46
+ Aspera.assert(session_id.nil?, 'session_id must be nil for :sessions_init event')
47
47
  Aspera.assert_type(info, String)
48
48
  # Initialization of progress bar
49
49
  @title = info
50
50
  when :session_start
51
51
  Aspera.assert_type(session_id, String)
52
- Aspera.assert(info.nil?)
52
+ Aspera.assert(info.nil?, 'info must be nil for :session_start event')
53
53
  raise "Session #{session_id} already started" if @sessions[session_id]
54
54
  @sessions[session_id] = {
55
55
  job_size: 0, # Total size of transfer (pre-calc)
@@ -60,7 +60,7 @@ module Aspera
60
60
  @title = nil
61
61
  when :session_size
62
62
  Aspera.assert_type(session_id, String)
63
- Aspera.assert(!info.nil?)
63
+ Aspera.assert(!info.nil?, 'info must not be nil for :session_size event')
64
64
  Aspera.assert_type(@sessions[session_id], Hash)
65
65
  @sessions[session_id][:job_size] = info.to_i
66
66
  sessions_total = total(:job_size)
@@ -76,12 +76,12 @@ module Aspera
76
76
  end
77
77
  when :session_end
78
78
  Aspera.assert_type(session_id, String)
79
- Aspera.assert(info.nil?)
79
+ Aspera.assert(info.nil?, 'info must be nil for :session_end event')
80
80
  # A session may be too short and finish before it has been started
81
81
  @sessions[session_id][:running] = false if @sessions[session_id].is_a?(Hash)
82
82
  when :end
83
- Aspera.assert(session_id.nil?)
84
- Aspera.assert(info.nil?)
83
+ Aspera.assert(session_id.nil?, 'session_id must be nil for :end event')
84
+ Aspera.assert(info.nil?, 'info must be nil for :end event')
85
85
  @progress_bar.finish
86
86
  else Aspera.error_unexpected_value(type){'event type'}
87
87
  end
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # For beta add extension : .beta1
6
6
  # For dev version add extension : .pre
7
- VERSION = '4.26.1'
7
+ VERSION = '4.26.2'
8
8
  end
9
9
  end
@@ -80,7 +80,7 @@ module Aspera
80
80
  end
81
81
  RestParameters.instance.spinner_cb.call(action: :success)
82
82
  raise "No known application found at #{app_url}" if found_apps.empty?
83
- Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)})
83
+ Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)}, 'all app info keys must be symbols')
84
84
  return found_apps
85
85
  end
86
86
 
@@ -128,7 +128,8 @@ module Aspera
128
128
  apps.first
129
129
  else
130
130
  formatter.display_status('Multiple applications detected, please select from:')
131
- formatter.display_results(type: :object_list, data: apps, fields: %w[product url version])
131
+ require 'aspera/cli/result'
132
+ formatter.display_results(Result::ObjectList.new(apps, fields: %w[product url version]))
132
133
  answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
133
134
  apps.find{ |a| a[:product].eql?(answer)}
134
135
  end
@@ -140,9 +141,7 @@ module Aspera
140
141
  options.add_option_preset({url: wiz_url}, 'wizard')
141
142
  # Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
142
143
  plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
143
- Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument) do
144
- "Detected: #{identification[:product]}, but this application has no wizard"
145
- end
144
+ Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
146
145
  # Call the wizard
147
146
  wizard_result = plugin_instance.wizard(self, wiz_url)
148
147
  Log.log.debug{"wizard result: #{wizard_result}"}
@@ -166,7 +165,7 @@ module Aspera
166
165
  test_args = "-P#{wiz_preset_name} #{test_args}" unless option_default
167
166
  # TODO: actually test the command
168
167
  test_cmd = "#{Info::CMD_NAME} #{identification[:product]} #{test_args}"
169
- return Main.result_status("You can test with:\n#{test_cmd.red}")
168
+ return Result::Status.new("You can test with:\n#{test_cmd.red}")
170
169
  end
171
170
  end
172
171
  end
@@ -66,7 +66,7 @@ module Aspera
66
66
  Aspera.assert(node.key?('type') || node.key?('enum')){"Missing type for #{name} in #{schema.current.dig('description').current}"}
67
67
  Aspera.assert(node['type'].eql?('boolean')){"switch must be bool: #{name}"} if node['x-cli-switch'] && !node['x-cli-special']
68
68
  node['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if node['x-cli-option'].eql?(true) || (node['x-cli-switch'].eql?(true) && !node.key?('x-cli-option'))
69
- Aspera.assert(DIRECT_PROPERTIES.any?{ |i| node.key?(i)}, type: :warn){name} if ascp && supported_by_agent(:direct, node)
69
+ Aspera.assert(DIRECT_PROPERTIES.any?{ |i| node.key?(i)}, name, type: :warn) if ascp && supported_by_agent(:direct, node)
70
70
  node.freeze
71
71
  end
72
72
  schema
@@ -6,6 +6,10 @@ require 'openssl'
6
6
 
7
7
  module Aspera
8
8
  # a simple binary data repository
9
+ #
10
+ # @!method self.instance
11
+ # Returns the singleton instance of DataRepository
12
+ # @return [DataRepository] the singleton instance
9
13
  class DataRepository
10
14
  include Singleton
11
15
 
@@ -39,7 +39,7 @@ module Aspera
39
39
  # @param container [Hash, Array]
40
40
  # @param index [String, Integer]
41
41
  def array_requires_integer_index!(container, index)
42
- Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)){'Using String index when Integer index used previously'}
42
+ Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer), 'Using String index when Integer index used previously')
43
43
  end
44
44
 
45
45
  # Create a new `Hash` or `Array` depending on type of `key`
@@ -13,6 +13,10 @@ require 'shellwords'
13
13
 
14
14
  module Aspera
15
15
  # detect OS, architecture, and specific stuff
16
+ #
17
+ # @!method self.instance
18
+ # Returns the singleton instance of Environment
19
+ # @return [Environment] the singleton instance
16
20
  class Environment
17
21
  include Singleton
18
22
 
@@ -111,7 +115,7 @@ module Aspera
111
115
  # @raise [RuntimeError] If `:exception` is `true` and the process fails in `:capture` mode.
112
116
  def secure_execute(*cmd, mode: :execute, **kwargs)
113
117
  cmd = cmd.map(&:to_s)
114
- Aspera.assert(cmd.size.positive?, type: ArgumentError){'executable must be present'}
118
+ Aspera.assert(cmd.size.positive?, 'executable must be present', type: ArgumentError)
115
119
  Aspera.assert_values(mode, PROCESS_MODES, type: ArgumentError){'mode'}
116
120
  Log.log.debug do
117
121
  parts = [mode.to_s, 'command:']
@@ -153,7 +157,7 @@ module Aspera
153
157
  # @param mode [Integer] the file mode (permissions)
154
158
  # @yieldreturn [String] The content to write to the file
155
159
  def write_file_restricted(path, force: false, mode: nil)
156
- Aspera.assert(block_given?, type: Aspera::InternalError)
160
+ Aspera.assert(block_given?, 'block required for write_file_restricted', type: Aspera::InternalError)
157
161
  if force || !File.exist?(path)
158
162
  # Windows may give error
159
163
  File.unlink(path) rescue nil
@@ -192,10 +196,24 @@ module Aspera
192
196
  end
193
197
 
194
198
  # @return true if we can display Unicode characters
199
+ # Uses Encoding.locale_charmap for OS-independent detection.
200
+ # Falls back to locale env vars for systems where charmap is not available.
195
201
  # https://www.gnu.org/software/libc/manual/html_node/Locale-Categories.html
196
202
  # https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
197
203
  def terminal_supports_unicode?
198
- terminal? && I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')}
204
+ return false unless terminal?
205
+ locale_charmap_utf8? || I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')}
206
+ end
207
+
208
+ private
209
+
210
+ # @return true if the locale charmap resolves to UTF-8
211
+ # Unix: nl_langinfo(CODESET) returns "UTF-8"
212
+ # Windows: GetACP returns "CP65001" which Encoding.find resolves as UTF-8
213
+ def locale_charmap_utf8?
214
+ Encoding.find(Encoding.locale_charmap) == Encoding::UTF_8
215
+ rescue ArgumentError
216
+ false
199
217
  end
200
218
  end
201
219
  attr_accessor :url_method, :file_illegal_characters
@@ -266,6 +284,7 @@ module Aspera
266
284
  return unless @os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
267
285
  ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
268
286
  Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
287
+ nil
269
288
  end
270
289
 
271
290
  def graphical?
@@ -68,7 +68,7 @@ module Aspera
68
68
  case request.path
69
69
  when '/aspera/faspex/send'
70
70
  begin
71
- Aspera.assert(!request.body.nil?){'payload missing'}
71
+ Aspera.assert(!request.body.nil?, 'payload missing')
72
72
  faspex_pkg_parameters = JSON.parse(request.body)
73
73
  Log.log.debug{"faspex pkg create parameters=#{faspex_pkg_parameters}"}
74
74
  # compare string, as class is not yet known here
@@ -36,15 +36,16 @@ module Aspera
36
36
  id: @request_id += 1
37
37
  })
38
38
  Aspera.assert_type(data, Hash){'response'}
39
- Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
- Aspera.assert(data.key?('id')){'missing id in response'}
41
- Aspera.assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
39
+ Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION, 'bad version in response')
40
+ Aspera.assert(data.key?('id'), 'missing id in response')
41
+ Aspera.assert(!(data.key?('error') && data.key?('result')), 'both error and response')
42
42
  Aspera.assert(
43
43
  !data.key?('error') ||
44
44
  data['error'].is_a?(Hash) &&
45
45
  data['error']['code'].is_a?(Integer) &&
46
- data['error']['message'].is_a?(String)
47
- ){'bad error response'}
46
+ data['error']['message'].is_a?(String),
47
+ 'bad error response'
48
+ )
48
49
  return data['result']
49
50
  end
50
51
  end
@@ -11,7 +11,7 @@ module Aspera
11
11
  options.each_pair do |k, v|
12
12
  Aspera.assert_type(v, String){k.to_s}
13
13
  end
14
- Aspera.assert(options.key?(:label)){'label is required'}
14
+ Aspera.assert(options.key?(:label), 'label is required')
15
15
  end
16
16
  end
17
17
  end
@@ -28,7 +28,7 @@ module Aspera
28
28
  vault_file = File.read(@path)
29
29
  if vault_file.start_with?('---')
30
30
  vault_info = YAML.parse(vault_file).to_ruby
31
- Aspera.assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
31
+ Aspera.assert(vault_info.keys.sort == FILE_KEYS, 'Invalid vault file')
32
32
  @cipher_name = vault_info['cipher']
33
33
  vault_encrypted_data = vault_info['data']
34
34
  else
@@ -20,7 +20,7 @@ module Aspera
20
20
  when 'file'
21
21
  info[:file] = name || 'vault.bin'
22
22
  info[:file] = File.join(folder, info[:file]) unless File.absolute_path?(info[:file])
23
- Aspera.assert(!password.nil?){'please provide password'}
23
+ Aspera.assert(!password.nil?, 'please provide password')
24
24
  info[:password] = password
25
25
  # this module requires compilation, so it is optional
26
26
  require 'aspera/keychain/encrypted_hash'