aspera-cli 4.27.0 → 4.27.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 (142) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +46 -1
  4. data/CONTRIBUTING.md +1 -7
  5. data/TODO.md +50 -0
  6. data/docs/README.md +709 -489
  7. data/lib/aspera/agent/base.rb +10 -2
  8. data/lib/aspera/agent/connect.rb +7 -7
  9. data/lib/aspera/agent/desktop.rb +3 -3
  10. data/lib/aspera/agent/direct.rb +48 -19
  11. data/lib/aspera/agent/factory.rb +3 -3
  12. data/lib/aspera/agent/httpgw.rb +55 -14
  13. data/lib/aspera/agent/node.rb +2 -2
  14. data/lib/aspera/agent/transferd.rb +12 -12
  15. data/lib/aspera/api/aoc.rb +28 -28
  16. data/lib/aspera/api/cos_node.rb +10 -9
  17. data/lib/aspera/api/faspex.rb +2 -2
  18. data/lib/aspera/api/httpgw.rb +20 -20
  19. data/lib/aspera/api/node.rb +31 -53
  20. data/lib/aspera/ascmd.rb +13 -13
  21. data/lib/aspera/ascp/installation.rb +43 -36
  22. data/lib/aspera/ascp/management.rb +6 -6
  23. data/lib/aspera/assert.rb +12 -13
  24. data/lib/aspera/cli/ascp_actions.rb +7 -7
  25. data/lib/aspera/cli/async_transfer_store.rb +49 -5
  26. data/lib/aspera/cli/bootstrapper.rb +9 -9
  27. data/lib/aspera/cli/command_registry.rb +1 -1
  28. data/lib/aspera/cli/command_spec.rb +6 -3
  29. data/lib/aspera/cli/context.rb +3 -3
  30. data/lib/aspera/cli/extended_value.rb +26 -26
  31. data/lib/aspera/cli/formatter.rb +76 -54
  32. data/lib/aspera/cli/gem_checker.rb +1 -1
  33. data/lib/aspera/cli/hints.rb +2 -2
  34. data/lib/aspera/cli/http.rb +22 -29
  35. data/lib/aspera/cli/mailer.rb +7 -7
  36. data/lib/aspera/cli/mcp_tool.rb +31 -4
  37. data/lib/aspera/cli/option_declarator.rb +77 -0
  38. data/lib/aspera/cli/options.schema.yaml +273 -3
  39. data/lib/aspera/cli/parser.rb +539 -280
  40. data/lib/aspera/cli/plugins/alee.rb +1 -1
  41. data/lib/aspera/cli/plugins/aoc.rb +303 -314
  42. data/lib/aspera/cli/plugins/ats.rb +25 -21
  43. data/lib/aspera/cli/plugins/base.rb +357 -186
  44. data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
  45. data/lib/aspera/cli/plugins/config.rb +76 -73
  46. data/lib/aspera/cli/plugins/console.rb +8 -8
  47. data/lib/aspera/cli/plugins/cos.rb +4 -3
  48. data/lib/aspera/cli/plugins/factory.rb +2 -2
  49. data/lib/aspera/cli/plugins/faspex5.rb +259 -248
  50. data/lib/aspera/cli/plugins/faspio.rb +7 -2
  51. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  52. data/lib/aspera/cli/plugins/mcp.rb +11 -11
  53. data/lib/aspera/cli/plugins/node.rb +227 -283
  54. data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
  55. data/lib/aspera/cli/plugins/preview.rb +61 -64
  56. data/lib/aspera/cli/plugins/server.rb +36 -31
  57. data/lib/aspera/cli/plugins/shares.rb +174 -135
  58. data/lib/aspera/cli/preset_actions.rb +39 -19
  59. data/lib/aspera/cli/preset_manager.rb +74 -32
  60. data/lib/aspera/cli/result.rb +18 -16
  61. data/lib/aspera/cli/runner.rb +75 -39
  62. data/lib/aspera/cli/secret_finder.rb +1 -1
  63. data/lib/aspera/cli/sync_actions.rb +36 -35
  64. data/lib/aspera/cli/terminal_formatter.rb +2 -2
  65. data/lib/aspera/cli/transfer_actions.rb +17 -10
  66. data/lib/aspera/cli/transfer_agent.rb +44 -44
  67. data/lib/aspera/cli/transfer_progress.rb +7 -7
  68. data/lib/aspera/cli/vault_manager.rb +34 -12
  69. data/lib/aspera/cli/version.rb +1 -1
  70. data/lib/aspera/cli/wizard.rb +17 -20
  71. data/lib/aspera/colors.rb +3 -3
  72. data/lib/aspera/command_line_builder.rb +17 -16
  73. data/lib/aspera/command_line_converter.rb +1 -1
  74. data/lib/aspera/coverage.rb +1 -2
  75. data/lib/aspera/data_repository.rb +1 -1
  76. data/lib/aspera/dot_container.rb +8 -8
  77. data/lib/aspera/environment.rb +13 -13
  78. data/lib/aspera/faspex_gw.rb +4 -4
  79. data/lib/aspera/faspex_postproc.rb +14 -9
  80. data/lib/aspera/graphql.rb +4 -2
  81. data/lib/aspera/hash_ext.rb +2 -2
  82. data/lib/aspera/json_rpc/client.rb +1 -1
  83. data/lib/aspera/keychain/base.rb +10 -3
  84. data/lib/aspera/keychain/encrypted_hash.rb +92 -17
  85. data/lib/aspera/keychain/factory.rb +17 -5
  86. data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
  87. data/lib/aspera/keychain/macos_security.rb +23 -25
  88. data/lib/aspera/keychain/one_password_api.rb +86 -0
  89. data/lib/aspera/keychain/one_password_base.rb +34 -0
  90. data/lib/aspera/keychain/one_password_cli.rb +98 -0
  91. data/lib/aspera/link_header.rb +2 -2
  92. data/lib/aspera/log.rb +13 -13
  93. data/lib/aspera/markdown.rb +5 -5
  94. data/lib/aspera/nagios.rb +8 -8
  95. data/lib/aspera/node_simulator.rb +33 -27
  96. data/lib/aspera/oauth/base.rb +4 -5
  97. data/lib/aspera/oauth/boot.rb +3 -3
  98. data/lib/aspera/oauth/factory.rb +5 -5
  99. data/lib/aspera/oauth/jwt.rb +5 -7
  100. data/lib/aspera/oauth/web.rb +4 -4
  101. data/lib/aspera/persistency_action_once.rb +4 -4
  102. data/lib/aspera/persistency_folder.rb +8 -8
  103. data/lib/aspera/preview/file_types.rb +4 -4
  104. data/lib/aspera/preview/generator.rb +49 -46
  105. data/lib/aspera/preview/options.rb +3 -2
  106. data/lib/aspera/preview/terminal.rb +5 -5
  107. data/lib/aspera/preview/utils.rb +65 -18
  108. data/lib/aspera/products/connect.rb +2 -2
  109. data/lib/aspera/products/desktop.rb +1 -1
  110. data/lib/aspera/products/other.rb +2 -2
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -10
  113. data/lib/aspera/rest.rb +149 -115
  114. data/lib/aspera/rest_error_analyzer.rb +3 -3
  115. data/lib/aspera/rest_list.rb +10 -9
  116. data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
  117. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
  118. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
  119. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
  120. data/lib/aspera/schema/documentation.rb +17 -11
  121. data/lib/aspera/schema/reader.rb +78 -4
  122. data/lib/aspera/schema/registry.rb +26 -7
  123. data/lib/aspera/secret_hider.rb +5 -5
  124. data/lib/aspera/ssh.rb +64 -31
  125. data/lib/aspera/ssl.rb +3 -3
  126. data/lib/aspera/sync/database.rb +2 -2
  127. data/lib/aspera/sync/operations.rb +13 -13
  128. data/lib/aspera/temp_file_manager.rb +24 -4
  129. data/lib/aspera/transfer/faux_file.rb +2 -2
  130. data/lib/aspera/transfer/parameters.rb +15 -15
  131. data/lib/aspera/transfer/resumer.rb +9 -9
  132. data/lib/aspera/transfer/spec.rb +1 -1
  133. data/lib/aspera/transfer/spec.schema.yaml +1 -0
  134. data/lib/aspera/uri_reader.rb +52 -15
  135. data/lib/aspera/web_auth.rb +6 -5
  136. data/lib/aspera/web_server_simple.rb +9 -9
  137. data/lib/aspera/yaml.rb +4 -4
  138. data.tar.gz.sig +0 -0
  139. metadata +10 -4
  140. metadata.gz.sig +0 -0
  141. data/lib/aspera/cli/plugins/faspex.rb +0 -576
  142. data/lib/aspera/transfer/uri.rb +0 -56
data/lib/aspera/assert.rb CHANGED
@@ -34,9 +34,9 @@ module Aspera
34
34
  end
35
35
 
36
36
  # Assert that a condition is true, else raise exception
37
- # @param assertion [TrueClass, FalseClass] Must be true
38
- # @param info [String,nil] Fixed message in case assert fails, else use block
39
- # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
37
+ # @param assertion [Boolean] Must be true
38
+ # @param info [String,nil] Fixed message in case assert fails, else use block
39
+ # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log level
40
40
  # @yieldreturn [String] A string that describes the problem for complex messages
41
41
  # The block is executed in the context of the Aspera module
42
42
  def assert(assertion, info = nil, type: AssertError)
@@ -45,7 +45,6 @@ module Aspera
45
45
  message = 'assertion failed'
46
46
  info = yield if block_given?
47
47
  message = type.eql?(AssertError) ? "#{message}: #{info}" : info if info
48
- # message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
49
48
  report_error(type, message)
50
49
  end
51
50
 
@@ -55,7 +54,7 @@ module Aspera
55
54
  # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
56
55
  # @yieldreturn [String] Additional description to prepend to the error message
57
56
  def assert_type(value, *classes, type: AssertError)
58
- assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting type #{classes.join(', ')}, but have (#{value.class})#{value.inspect}"}
57
+ assert(classes.any? { |k| value.is_a?(k) }, type: type) { "#{"#{yield}: " if block_given?}expecting type #{classes.join(', ')}, but have (#{value.class})#{value.inspect}" }
59
58
  end
60
59
 
61
60
  # Assert that all value of array are of the same specified type.
@@ -64,20 +63,20 @@ module Aspera
64
63
  # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
65
64
  # @yieldreturn [String] Additional description to prepend to the error message
66
65
  def assert_array_all(array, klass, type: AssertError)
67
- assert_type(array, Array, type: AssertError){'array'}
68
- assert(array.all?(klass), type: type){"#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}"}
66
+ assert_type(array, Array, type: AssertError) { 'array' }
67
+ assert(array.all?(klass), type: type) { "#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}" }
69
68
  end
70
69
 
71
70
  # Assert value is Hash, keys have type, and Values have type
72
71
  # @param hash [Hash] The hash to check
73
- # @param key_class [Class] The expected type of keys (or nil)
74
- # @param value_class [Class] The expected type of values (or nil)
72
+ # @param key_class [Class] The expected type of keys (or nil for no check)
73
+ # @param value_class [Class] The expected type of values (or nil for no check)
75
74
  # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
76
75
  # @yieldreturn [String] Additional description to prepend to the error message
77
76
  def assert_hash_all(hash, key_class, value_class, type: AssertError)
78
- assert_type(hash, Hash, type: AssertError){'hash'}
79
- assert_array_all(hash.keys, key_class, type: type){"#{"#{yield}: " if block_given?}keys"} unless key_class.nil?
80
- assert_array_all(hash.values, value_class, type: type){"#{"#{yield}: " if block_given?}values"} unless value_class.nil?
77
+ assert_type(hash, Hash, type: AssertError) { 'hash' }
78
+ assert_array_all(hash.keys, key_class, type: type) { "#{"#{yield}: " if block_given?}keys" } unless key_class.nil?
79
+ assert_array_all(hash.values, value_class, type: type) { "#{"#{yield}: " if block_given?}values" } unless value_class.nil?
81
80
  end
82
81
 
83
82
  # Assert that value is one of the given values
@@ -86,7 +85,7 @@ module Aspera
86
85
  # @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
87
86
  # @yieldreturn [String] Additional description to prepend to the error message
88
87
  def assert_values(value, values, type: AssertError)
89
- assert_type(values, Array, type: AssertError){'values'}
88
+ assert_type(values, Array, type: AssertError) { 'values' }
90
89
  assert(values.include?(value), type: type) do
91
90
  val_list = values.inspect
92
91
  val_list = "one of #{val_list}" if values.is_a?(Array)
@@ -27,14 +27,14 @@ module Aspera
27
27
  @sdk_default_location = true
28
28
  Log.log.debug('SDK folder is not set, checking default')
29
29
  sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
30
- Log.log.debug{"Checking: #{sdk_dir}"}
30
+ Log.log.debug { "Checking: #{sdk_dir}" }
31
31
  if !Dir.exist?(sdk_dir)
32
- Log.log.debug{"No such folder: #{sdk_dir}"}
32
+ Log.log.debug { "No such folder: #{sdk_dir}" }
33
33
  former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
34
- Log.log.debug{"Checking: #{former_sdk_folder}"}
34
+ Log.log.debug { "Checking: #{former_sdk_folder}" }
35
35
  sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
36
36
  end
37
- Log.log.debug{"Using: #{sdk_dir}"}
37
+ Log.log.debug { "Using: #{sdk_dir}" }
38
38
  Products::Transferd.sdk_directory = sdk_dir
39
39
  end
40
40
  end
@@ -59,7 +59,7 @@ module Aspera
59
59
  def action_ascp_info(**)
60
60
  data = Ascp::Installation.instance.ascp_info
61
61
  data['ts'] = transfer.user_transfer_spec
62
- DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
62
+ DataRepository::ELEMENTS.each_with_object(data) { |i, h| h[i.to_s] = DataRepository.instance.item(i) }
63
63
  SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
64
64
  Result::SingleObject.new(data)
65
65
  end
@@ -75,7 +75,7 @@ module Aspera
75
75
 
76
76
  def action_ascp_schema(agent_name: nil, **)
77
77
  schema = Transfer::Spec::SCHEMA.current.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
78
- schema['properties'] = schema['properties'].select{ |_k, v| CommandLineBuilder.supported_by_agent(agent_name, v)} unless agent_name.nil?
78
+ schema['properties'] = schema['properties'].select { |_k, v| CommandLineBuilder.supported_by_agent(agent_name, v) } unless agent_name.nil?
79
79
  schema['properties'] = schema['properties'].sort.to_h
80
80
  Result::SingleObject.new(schema)
81
81
  end
@@ -106,7 +106,7 @@ module Aspera
106
106
  'short' => names[:short].to_s,
107
107
  'parameters' => param_names
108
108
  }
109
- end.sort_by{ |r| r['name']}
109
+ end.sort_by { |r| r['name'] }
110
110
  Result::ObjectList.new(rows, fields: %w[name short parameters])
111
111
  end
112
112
 
@@ -27,18 +27,46 @@ module Aspera
27
27
  CATEGORY = 'async_transfer_'
28
28
  private_constant :CATEGORY
29
29
 
30
+ # Class-level registry of in-process agent references, keyed by job_id.
31
+ # Shared across all AsyncTransferStore instances within the same Ruby process,
32
+ # so that a reference registered in one Runner call is visible in the next
33
+ # (e.g. MCP server mode where each tool call creates a fresh Runner/Context).
34
+ # Never persisted to disk.
35
+ @agent_refs = {}
36
+ class << self
37
+ attr_reader :agent_refs
38
+ end
39
+
30
40
  # @param persistency [PersistencyFolder]
31
41
  def initialize(persistency)
32
42
  @persistency = persistency
33
43
  end
34
44
 
45
+ # Register an in-process agent reference for a job.
46
+ # Called by TransferAgent immediately after start_transfer (async mode).
47
+ # The reference is only available while the same Ruby process is alive.
48
+ # @param job_id [String] the ascli-generated UUID
49
+ # @param agent_ref [Object] live agent instance (responds to sessions_by_job)
50
+ def register_agent_ref(job_id, agent_ref)
51
+ self.class.agent_refs[job_id] = agent_ref
52
+ end
53
+
54
+ # Retrieve a previously registered in-process agent reference.
55
+ # @param job_id [String]
56
+ # @return [Object, nil] the live agent instance, or nil if not registered / process restarted
57
+ def agent_ref(job_id)
58
+ self.class.agent_refs[job_id]
59
+ end
60
+
35
61
  # Persist (create or update) an async transfer entry.
62
+ # Keys whose name starts with '_' are in-process references (e.g. '_agent_ref')
63
+ # and are intentionally excluded from the JSON serialization — at every nesting level.
36
64
  # @param job_id [String] the ascli-generated UUID
37
65
  # @param data [Hash] fields to store (will be JSON-serialised)
38
66
  def write(job_id, data)
39
- Aspera.assert_type(job_id, String){'job_id'}
40
- Aspera.assert_type(data, Hash){'data'}
41
- @persistency.put(store_key(job_id), JSON.generate(data))
67
+ Aspera.assert_type(job_id, String) { 'job_id' }
68
+ Aspera.assert_type(data, Hash) { 'data' }
69
+ @persistency.put(store_key(job_id), JSON.generate(strip_internal_keys(data)))
42
70
  nil
43
71
  end
44
72
 
@@ -46,7 +74,7 @@ module Aspera
46
74
  # @param job_id [String]
47
75
  # @return [Hash, nil] the stored data, or nil if not found
48
76
  def read(job_id)
49
- Aspera.assert_type(job_id, String){'job_id'}
77
+ Aspera.assert_type(job_id, String) { 'job_id' }
50
78
  raw = @persistency.get(store_key(job_id))
51
79
  return if raw.nil?
52
80
  JSON.parse(raw)
@@ -65,7 +93,7 @@ module Aspera
65
93
  # Delete one entry.
66
94
  # @param job_id [String]
67
95
  def delete(job_id)
68
- Aspera.assert_type(job_id, String){'job_id'}
96
+ Aspera.assert_type(job_id, String) { 'job_id' }
69
97
  @persistency.delete(store_key(job_id))
70
98
  nil
71
99
  end
@@ -76,6 +104,22 @@ module Aspera
76
104
  def store_key(job_id)
77
105
  "#{CATEGORY}#{job_id}"
78
106
  end
107
+
108
+ # Recursively remove keys whose name starts with '_' from a Hash.
109
+ # Such keys hold in-process Ruby object references (e.g. '_agent_ref') that
110
+ # cannot be serialized to JSON and must never reach the file system.
111
+ def strip_internal_keys(value)
112
+ case value
113
+ when Hash
114
+ value.each_with_object({}) do |(k, v), h|
115
+ h[k] = strip_internal_keys(v) unless k.to_s.start_with?('_')
116
+ end
117
+ when Array
118
+ value.map { |v| strip_internal_keys(v) }
119
+ else
120
+ value
121
+ end
122
+ end
79
123
  end
80
124
  end
81
125
  end
@@ -66,7 +66,7 @@ module Aspera
66
66
 
67
67
  # @param context [Context] the shared context to populate
68
68
  def initialize(context)
69
- Aspera.assert_type(context, Context){'context'}
69
+ Aspera.assert_type(context, Context) { 'context' }
70
70
  @context = context
71
71
  @pac_exec = nil
72
72
  end
@@ -101,7 +101,7 @@ module Aspera
101
101
  default: default_app_main_folder(app_name: Info::CMD_NAME)
102
102
  )
103
103
  @context.options.parse_options!
104
- Log.log.debug{"#{Info::CMD_NAME} folder: #{@context.main_folder}"}
104
+ Log.log.debug { "#{Info::CMD_NAME} folder: #{@context.main_folder}" }
105
105
  end
106
106
 
107
107
  # context.persistency + plugin lookup folders
@@ -130,20 +130,20 @@ module Aspera
130
130
  @context.options.declare(:vault_password, description: 'Vault password')
131
131
  # Register @preset and @vault handlers BEFORE parse_options! so that
132
132
  # values like --secret=@preset:name are correctly evaluated at parse time.
133
- ExtendedValue.instance.on(EXTEND_PRESET){ |v| @context.presets.by_name(v)}
133
+ ExtendedValue.instance.on(EXTEND_PRESET) { |v| @context.presets.by_name(v) }
134
134
  ExtendedValue.instance.on(EXTEND_VAULT, &vault_value_cb)
135
135
  @context.options.parse_options!
136
136
  # Load global config default preset (equivalent of add_plugin_default_preset(:config))
137
137
  default_config_name = @context.presets.plugin_default_name(CONF_GLOBAL_SYM)
138
138
  unless default_config_name.nil?
139
- Log.log.debug{"add_plugin_default_preset:#{CONF_GLOBAL_SYM}:#{default_config_name}"}
139
+ Log.log.debug { "add_plugin_default_preset:#{CONF_GLOBAL_SYM}:#{default_config_name}" }
140
140
  @context.options.add_option_preset(@context.presets.by_name(default_config_name), 'default_plugin', override: false)
141
141
  end
142
142
  end
143
143
 
144
144
  # Declare + parse :progress_bar: sets context.progress_bar
145
145
  def setup_progress_bar
146
- @context.options.declare(:progress_bar, description: 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
146
+ @context.options.declare(:progress_bar, description: 'Display progress bar', allowed: Type::BOOLEAN, default: Environment.terminal?)
147
147
  @context.options.parse_options!
148
148
  @context.progress_bar = TransferProgress.new if @context.options.get_option(:progress_bar)
149
149
  end
@@ -159,7 +159,7 @@ module Aspera
159
159
  @context.pac_executor = ProxyAutoConfig.new(pac_script).register_uri_generic
160
160
  proxy_user_pass = @context.options.get_option(:proxy_credentials)
161
161
  if proxy_user_pass
162
- Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
162
+ Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument) { "proxy_credentials shall have two elements (#{proxy_user_pass.length})" }
163
163
  @context.pac_executor.proxy_user = proxy_user_pass[0]
164
164
  @context.pac_executor.proxy_pass = proxy_user_pass[1]
165
165
  end
@@ -171,8 +171,8 @@ module Aspera
171
171
  def setup_rest_and_transfer_runtime
172
172
  RestParameters.instance.user_agent = Info::CMD_NAME
173
173
  RestParameters.instance.progress_bar = @context.progress_bar
174
- RestParameters.instance.session_cb = ->(http_session){@context.http_config.update_session(http_session)}
175
- RestParameters.instance.spinner_cb = ->(title = nil, action: :spin){@context.formatter.long_operation(title, action: action)}
174
+ RestParameters.instance.session_cb = ->(http_session) { @context.http_config.update_session(http_session) }
175
+ RestParameters.instance.spinner_cb = ->(title = nil, action: :spin) { @context.formatter.long_operation(title, action: action) }
176
176
  OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
177
177
  Transfer::Parameters.file_list_folder = File.join(@context.main_folder, FILE_LIST_FOLDER_NAME)
178
178
  RestErrorAnalyzer.instance.log_file = File.join(@context.main_folder, REST_EXCEPTIONS_LOG_FILENAME)
@@ -182,7 +182,7 @@ module Aspera
182
182
  # @return [String] ~/.aspera
183
183
  def module_family_folder
184
184
  user_home_folder = Dir.home
185
- Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
185
+ Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error) { "Home folder does not exist: #{user_home_folder}. Check your user environment." }
186
186
  File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
187
187
  end
188
188
 
@@ -118,7 +118,7 @@ module Aspera
118
118
  # Rule: leaf commands with no explicit action must have a matching instance method
119
119
  next if spec.action # explicit action: skip
120
120
  next if @children_index[path]&.any? # intermediate node: skip
121
- next if spec.delegates_to || spec.entity_execute # delegated: skip
121
+ next if spec.delegates_to # delegated: skip
122
122
  next unless plugin_class
123
123
  implicit_method = CommandSpec.action_method(path)
124
124
  unless plugin_class.method_defined?(implicit_method) || plugin_class.private_method_defined?(implicit_method)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/schema/registry'
4
+
3
5
  module Aspera
4
6
  module Cli
5
7
  # Declares a positional argument consumed by a command.
@@ -14,7 +16,10 @@ module Aspera
14
16
  # @!attribute default [Object, nil] Default value when mandatory: false and no argument provided
15
17
  # @!attribute schema [String, nil] JSON schema name for validation and --help introspection
16
18
  # @!attribute bulk [Boolean] When true, wraps read+loop for bulk mode (Array if --bulk yes)
17
- # @!attribute lookup [Symbol, nil] Instance method name for percent-selector resolution (only used when type: :identifier)
19
+ # @!attribute lookup [Symbol, Proc, nil] Percent-selector resolver (only used when type: :identifier).
20
+ # Symbol → resolved via send(lookup, field, value, **ctx).
21
+ # Proc/lambda → called via instance_exec(field, value, **ctx, &lookup).
22
+ # Style: use Symbol for named methods; ->(){} for 1-liners; lambda do…end for 2–3 statements.
18
23
  # @!attribute allowed [Array<Symbol>, nil] Allowed Symbol values; when set, type is forced to Symbol and accept_list is applied
19
24
  # @!attribute interactive [Boolean] When true, sets ask_missing_mandatory before resolving so interactive prompting is triggered when no CLI args are provided
20
25
  ArgumentSpec = Struct.new(
@@ -81,7 +86,6 @@ module Aspera
81
86
  # @!attribute delegates_to [Symbol, Array<Symbol>, nil] Re-enter the command tree at this path
82
87
  # @!attribute delegate_instance [Symbol, nil] Instance method returning a different plugin object
83
88
  # @!attribute aliases [Array<Symbol>, nil] Alternative names accepted for this command (each resolves to this command's id)
84
- # @!attribute entity_execute [Hash, nil] Shorthand: expand to Base#entity_execute with these parameters
85
89
  # @!attribute transfer_paths [:send, :receive, nil] File-list resolution delegated to TransferAgent; mutually exclusive with arguments
86
90
  # @!attribute condition [Symbol, nil] Instance method returning Boolean; if false command is hidden from dispatch
87
91
  # @!attribute query_schema [String, nil] Schema path for --query help; when set, the runner hints `--query=help`
@@ -96,7 +100,6 @@ module Aspera
96
100
  :delegates_to,
97
101
  :delegate_instance,
98
102
  :aliases,
99
- :entity_execute,
100
103
  :transfer_paths,
101
104
  :condition,
102
105
  :query_schema,
@@ -40,7 +40,7 @@ module Aspera
40
40
  # Initialize all members to nil, so that they are defined and can be validated later
41
41
  # @return [nil]
42
42
  def initialize
43
- MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
43
+ MEMBERS.each { |i| instance_variable_set(:"@#{i}", nil) }
44
44
  @progress_bar = nil
45
45
  @pac_executor = nil
46
46
  end
@@ -50,8 +50,8 @@ module Aspera
50
50
  # @return [nil]
51
51
  def validate
52
52
  MEMBERS.each do |i|
53
- Aspera.assert(instance_variable_defined?(:"@#{i}")){"context member @#{i} is not defined"}
54
- Aspera.assert(!instance_variable_get(:"@#{i}").nil?){"context member @#{i} is nil"}
53
+ Aspera.assert(instance_variable_defined?(:"@#{i}")) { "context member @#{i} is not defined" }
54
+ Aspera.assert(!instance_variable_get(:"@#{i}").nil?) { "context member @#{i} is nil" }
55
55
  end
56
56
  end
57
57
 
@@ -68,7 +68,7 @@ module Aspera
68
68
  # @param value [String] The value as parameter
69
69
  # @param ext_type [Symbol] The method of extended value
70
70
  def assert_no_value(value, ext_type)
71
- Aspera.assert(value.empty?, type: BadArgument){"no value allowed for extended value type: #{ext_type}"}
71
+ Aspera.assert(value.empty?, type: BadArgument) { "no value allowed for extended value type: #{ext_type}" }
72
72
  end
73
73
 
74
74
  def read_stdin(mode)
@@ -89,25 +89,25 @@ module Aspera
89
89
  # e.g. `preset` is reader in config plugin
90
90
  # @type [Hash{Symbol => Proc}]
91
91
  @handlers = {
92
- val: lambda{ |i| i},
93
- base64: lambda{ |i| Base64.decode64(i)},
94
- csvt: lambda{ |i| ExtendedValue.decode_csvt(i)},
95
- env: lambda{ |i| ENV.fetch(i, nil)},
96
- file: lambda{ |i| File.read(File.expand_path(i))},
97
- uri: lambda{ |i| UriReader.read(i)},
98
- json: lambda{ |i| ExtendedValue.JSON_parse(i)},
99
- lines: lambda{ |i| i.split("\n")},
100
- list: lambda{ |i| i[1..].split(i[0])},
101
- none: lambda{ |i| ExtendedValue.assert_no_value(i, :none); nil}, # rubocop:disable Style/Semicolon
102
- path: lambda{ |i| File.expand_path(i)},
103
- re: lambda{ |i| Regexp.new(i, Regexp::MULTILINE)},
104
- ruby: lambda{ |i| Environment.secure_eval(i, __FILE__, __LINE__)},
92
+ val: lambda { |i| i },
93
+ base64: lambda { |i| Base64.decode64(i) },
94
+ csvt: lambda { |i| ExtendedValue.decode_csvt(i) },
95
+ env: lambda { |i| ENV.fetch(i, nil) },
96
+ file: lambda { |i| File.read(File.expand_path(i)) },
97
+ uri: lambda { |i| UriReader.read(i) },
98
+ json: lambda { |i| ExtendedValue.JSON_parse(i) },
99
+ lines: lambda { |i| i.split("\n") },
100
+ list: lambda { |i| i[1..].split(i[0]) },
101
+ none: lambda { |i| ExtendedValue.assert_no_value(i, :none); nil }, # rubocop:disable Style/Semicolon
102
+ path: lambda { |i| File.expand_path(i) },
103
+ re: lambda { |i| Regexp.new(i, Regexp::MULTILINE) },
104
+ ruby: lambda { |i| Environment.secure_eval(i, __FILE__, __LINE__) },
105
105
  s: lambda(&:to_s),
106
- secret: lambda{ |i| prompt = i.empty? ? 'secret' : i; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
107
- stdin: lambda{ |i| ExtendedValue.read_stdin(i)},
108
- yaml: lambda{ |i| YAML.safe_load(i)},
109
- zlib: lambda{ |i| Zlib::Inflate.inflate(i)},
110
- extend: lambda{ |i| ExtendedValue.instance.evaluate_extend(i)}
106
+ secret: lambda { |i| prompt = i.empty? ? 'secret' : i; $stdin.getpass("#{prompt}> ") }, # rubocop:disable Style/Semicolon
107
+ stdin: lambda { |i| ExtendedValue.read_stdin(i) },
108
+ yaml: lambda { |i| YAML.safe_load(i) },
109
+ zlib: lambda { |i| Zlib::Inflate.inflate(i) },
110
+ extend: lambda { |i| ExtendedValue.instance.evaluate_extend(i) }
111
111
  }
112
112
  @regex_single = nil
113
113
  @regex_extend = nil
@@ -127,7 +127,7 @@ module Aspera
127
127
  attr_reader :default_decoder
128
128
 
129
129
  def default_decoder=(value)
130
- Log.log.debug{"Setting default decoder to (#{value.class}) #{value}"}
130
+ Log.log.debug { "Setting default decoder to (#{value.class}) #{value}" }
131
131
  Aspera.assert_values(value, DEFAULT_DECODERS)
132
132
  value = nil if value.eql?(:none)
133
133
  @default_decoder = value
@@ -138,9 +138,9 @@ module Aspera
138
138
 
139
139
  # Add a new handler
140
140
  def on(name, &block)
141
- Aspera.assert_type(name, Symbol){'name'}
142
- Aspera.assert(block){"block required when registering handler for #{name}"}
143
- Log.log.debug{"Setting handler for #{name}"}
141
+ Aspera.assert_type(name, Symbol) { 'name' }
142
+ Aspera.assert(block) { "block required when registering handler for #{name}" }
143
+ Log.log.debug { "Setting handler for #{name}" }
144
144
  @handlers[name] = block
145
145
  update_regex
146
146
  end
@@ -156,7 +156,7 @@ module Aspera
156
156
  return value unless value.is_a?(String)
157
157
  Aspera.assert_array_all(allowed, Class) unless allowed.nil?
158
158
  # use default decoder if not an extended value and expect complex types
159
- using_default_decoder = allowed&.all?{ |t| DEFAULT_PARSER_TYPES.include?(t)} && !@regex_single.match?(value) && !@default_decoder.nil?
159
+ using_default_decoder = allowed&.all? { |t| DEFAULT_PARSER_TYPES.include?(t) } && !@regex_single.match?(value) && !@default_decoder.nil?
160
160
  value = [MARKER_START, @default_decoder, MARKER_END, value].join if using_default_decoder
161
161
  # First determine decoders, in reversed order
162
162
  handlers_reversed = []
@@ -166,7 +166,7 @@ module Aspera
166
166
  value = m[2]
167
167
  break if SPECIAL_HANDLERS.include?(handler)
168
168
  end
169
- Log.log.trace1{"evaluating: #{handlers_reversed}, value: #{value}"}
169
+ Log.log.trace1 { "evaluating: #{handlers_reversed}, value: #{value.class}:#{value}" }
170
170
  handlers_reversed.each do |handler|
171
171
  value = @handlers[handler].call(value)
172
172
  rescue => e
@@ -180,7 +180,7 @@ module Aspera
180
180
  def evaluate_extend(value)
181
181
  while (m = value.match(@regex_extend))
182
182
  sub_value = "@#{m[2]}:#{m[3]}"
183
- Log.log.debug{"evaluating #{sub_value}"}
183
+ Log.log.debug { "evaluating #{sub_value}" }
184
184
  value = "#{m[1]}#{evaluate(sub_value, context: 'composite extended value')}#{m[4]}"
185
185
  end
186
186
  return value