aspera-cli 4.26.2 → 4.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +310 -21
- data/CONTRIBUTING.md +99 -14
- data/TODO.md +50 -0
- data/bin/ascli +3 -3
- data/docs/README.md +10977 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +29 -7
- data/lib/aspera/agent/connect.rb +58 -21
- data/lib/aspera/agent/desktop.rb +53 -18
- data/lib/aspera/agent/direct.rb +71 -40
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +48 -12
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +42 -44
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +16 -15
- data/lib/aspera/api/httpgw.rb +23 -23
- data/lib/aspera/api/node.rb +51 -70
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +32 -30
- data/lib/aspera/ascp/installation.rb +75 -69
- data/lib/aspera/ascp/management.rb +7 -8
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +125 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +141 -0
- data/lib/aspera/cli/context.rb +19 -6
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +27 -26
- data/lib/aspera/cli/formatter.rb +95 -59
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +3 -3
- data/lib/aspera/cli/http.rb +74 -30
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +225 -0
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +634 -7
- data/lib/aspera/cli/parser.rb +1217 -0
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1178 -881
- data/lib/aspera/cli/plugins/ats.rb +205 -162
- data/lib/aspera/cli/plugins/base.rb +703 -175
- data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
- data/lib/aspera/cli/plugins/config.rb +434 -757
- data/lib/aspera/cli/plugins/console.rb +107 -65
- data/lib/aspera/cli/plugins/cos.rb +46 -33
- data/lib/aspera/cli/plugins/factory.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +627 -388
- data/lib/aspera/cli/plugins/faspio.rb +54 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +988 -840
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
- data/lib/aspera/cli/plugins/preview.rb +195 -157
- data/lib/aspera/cli/plugins/server.rb +141 -91
- data/lib/aspera/cli/plugins/shares.rb +343 -110
- data/lib/aspera/cli/preset_actions.rb +159 -0
- data/lib/aspera/cli/preset_manager.rb +82 -39
- data/lib/aspera/cli/result.rb +78 -26
- data/lib/aspera/cli/runner.rb +323 -153
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +89 -64
- data/lib/aspera/cli/terminal_formatter.rb +3 -3
- data/lib/aspera/cli/transfer_actions.rb +90 -0
- data/lib/aspera/cli/transfer_agent.rb +129 -64
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +79 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +28 -30
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +34 -34
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +12 -12
- data/lib/aspera/environment.rb +30 -23
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +16 -10
- data/lib/aspera/graphql.rb +37 -0
- data/lib/aspera/hash_ext.rb +8 -2
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +20 -8
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +29 -15
- data/lib/aspera/markdown.rb +90 -7
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +35 -27
- data/lib/aspera/oauth/base.rb +9 -10
- data/lib/aspera/oauth/boot.rb +5 -5
- data/lib/aspera/oauth/factory.rb +12 -10
- data/lib/aspera/oauth/jwt.rb +9 -11
- data/lib/aspera/oauth/web.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +12 -12
- data/lib/aspera/persistency_folder.rb +18 -11
- data/lib/aspera/preview/file_types.rb +5 -5
- data/lib/aspera/preview/generator.rb +52 -49
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +66 -19
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +3 -3
- data/lib/aspera/products/transferd.rb +4 -4
- data/lib/aspera/proxy_auto_config.rb +15 -14
- data/lib/aspera/rest.rb +159 -151
- data/lib/aspera/rest_error_analyzer.rb +6 -6
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +11 -10
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +65 -28
- data/lib/aspera/schema/reader.rb +136 -13
- data/lib/aspera/schema/registry.rb +60 -8
- data/lib/aspera/secret_hider.rb +12 -7
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +5 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +22 -25
- data/lib/aspera/temp_file_manager.rb +25 -5
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +36 -34
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +10 -10
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +8 -7
- data/lib/aspera/web_server_simple.rb +15 -12
- data/lib/aspera/yaml.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/manager.rb +0 -766
- data/lib/aspera/cli/plugins/faspex.rb +0 -539
- data/lib/aspera/json_rpc.rb +0 -52
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/agent/factory'
|
|
4
|
+
require 'aspera/schema/registry'
|
|
5
|
+
require 'aspera/cli/option_declarator'
|
|
6
|
+
require 'aspera/transfer/result'
|
|
4
7
|
require 'aspera/transfer/spec'
|
|
5
8
|
require 'aspera/cli/info'
|
|
9
|
+
require 'aspera/cli/async_transfer_store'
|
|
6
10
|
require 'aspera/log'
|
|
7
11
|
require 'aspera/assert'
|
|
8
|
-
require '
|
|
12
|
+
require 'securerandom'
|
|
9
13
|
|
|
10
14
|
module Aspera
|
|
11
15
|
module Cli
|
|
@@ -28,53 +32,46 @@ module Aspera
|
|
|
28
32
|
<%=ts.to_yaml%>
|
|
29
33
|
END_OF_TEMPLATE
|
|
30
34
|
CP4I_REMOTE_HOST_LB = 'N/A'
|
|
31
|
-
private_constant :
|
|
32
|
-
:FILE_LIST_FROM_TRANSFER_SPEC,
|
|
35
|
+
private_constant :FILE_LIST_FROM_TRANSFER_SPEC,
|
|
33
36
|
:FILE_LIST_OPTIONS,
|
|
34
37
|
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return :success if error_statuses.empty?
|
|
45
|
-
return error_statuses.first
|
|
46
|
-
end
|
|
47
|
-
end
|
|
39
|
+
extend OptionDeclarator
|
|
40
|
+
|
|
41
|
+
option :ts, description: 'Override transfer spec values', schema: Schema::Registry::TRANSFER_SPEC
|
|
42
|
+
option :to_folder, description: 'Destination folder for transferred files'
|
|
43
|
+
option :sources, description: "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})", default: FILE_LIST_FROM_ARGS
|
|
44
|
+
option :src_type, description: 'Type of file list', allowed: %i[list pair], default: :list
|
|
45
|
+
option :transfer, description: 'Transfer agent type, or agent parameters with optional agent key', allowed: [Hash, String], schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
46
|
+
option :transfer_info, description: 'Parameters for transfer agent', deprecation: 'use --transfer instead', schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
48
47
|
|
|
49
|
-
# @param
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@
|
|
48
|
+
# @param context [Context] Application context
|
|
49
|
+
def initialize(context)
|
|
50
|
+
Aspera.assert_type(context, Context) { 'context' }
|
|
51
|
+
Aspera.assert_type(context.options, Parser) { 'context.options' }
|
|
52
|
+
@context = context
|
|
54
53
|
# Command line can override transfer spec
|
|
55
54
|
@user_transfer_spec = {
|
|
56
55
|
'create_dir' => true,
|
|
57
56
|
'resume_policy' => 'sparse_csum'
|
|
58
57
|
}
|
|
59
|
-
# options for transfer agent
|
|
60
|
-
@
|
|
58
|
+
# options for transfer agent (agent type + agent-specific parameters)
|
|
59
|
+
@transfer_options = {}
|
|
61
60
|
# the currently selected transfer agent
|
|
62
61
|
@agent = nil
|
|
63
62
|
# source/destination pair, like "paths" of transfer spec
|
|
64
63
|
@transfer_paths = nil
|
|
65
64
|
# HTTPGW URL provided by webapp
|
|
66
65
|
@httpgw_url_lambda = nil
|
|
67
|
-
@
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
@
|
|
71
|
-
@
|
|
72
|
-
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', allowed: Hash, handler: {o: self, m: :transfer_info}, schema: Schema::Registry::TRANSFER_INFO)
|
|
73
|
-
@opt_mgr.parse_options!
|
|
66
|
+
self.class.declare_options(@context.options)
|
|
67
|
+
@context.options.set_handler(:ts, object: self, method: :user_transfer_spec)
|
|
68
|
+
@context.options.set_handler(:transfer, object: self, method: :option_transfer)
|
|
69
|
+
@context.options.set_handler(:transfer_info, object: self, method: :transfer_options)
|
|
70
|
+
@context.options.parse_options!
|
|
74
71
|
@notification_cb = nil
|
|
75
|
-
if !@
|
|
72
|
+
if !@context.options.get_option(:notify_to).nil?
|
|
76
73
|
@notification_cb = ->(transfer_spec, global_status) do
|
|
77
|
-
@
|
|
74
|
+
@context.mailer.send_email_template(email_template_default: DEFAULT_TRANSFER_NOTIFY_TEMPLATE, values: {
|
|
78
75
|
subject: "#{Info::CMD_NAME} transfer: #{global_status}",
|
|
79
76
|
status: global_status,
|
|
80
77
|
ts: transfer_spec
|
|
@@ -83,7 +80,23 @@ module Aspera
|
|
|
83
80
|
end
|
|
84
81
|
end
|
|
85
82
|
|
|
86
|
-
attr_accessor :user_transfer_spec, :
|
|
83
|
+
attr_accessor :user_transfer_spec, :transfer_options
|
|
84
|
+
|
|
85
|
+
# Composite option handler for :transfer
|
|
86
|
+
# String value: shorthand for agent type, stored as {'agent' => value}
|
|
87
|
+
# Hash value: merged into @transfer_options (may include 'agent' key)
|
|
88
|
+
def option_transfer(_option_sym, operation, value = nil)
|
|
89
|
+
Aspera.assert_values(operation, %i[set get])
|
|
90
|
+
case operation
|
|
91
|
+
when :set
|
|
92
|
+
value = {'agent' => value} if value.is_a?(String)
|
|
93
|
+
Aspera.assert_type(value, Hash)
|
|
94
|
+
@transfer_options = @transfer_options.deep_merge(value)
|
|
95
|
+
when :get
|
|
96
|
+
return @transfer_options
|
|
97
|
+
end
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
87
100
|
|
|
88
101
|
def agent_instance=(instance)
|
|
89
102
|
@agent = instance
|
|
@@ -92,25 +105,27 @@ module Aspera
|
|
|
92
105
|
# analyze options and create new agent if not already created or set
|
|
93
106
|
def agent_instance
|
|
94
107
|
return @agent unless @agent.nil?
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
agent_options
|
|
108
|
+
# agent type: from composite option 'agent' key, default :direct
|
|
109
|
+
raw_type = @transfer_options['agent'] || :direct
|
|
110
|
+
agent_type = Parser.get_from_list(raw_type.to_s, 'transfer agent', Agent::Factory::ALL.keys)
|
|
111
|
+
# set keys as symbols, strip internal keys not forwarded to the agent constructor
|
|
112
|
+
agent_options = @transfer_options.except('agent', 'asynchronous').symbolize_keys
|
|
113
|
+
agent_options[:progress] = @context.progress_bar
|
|
114
|
+
agent_options[:config_dir] = @context.main_folder
|
|
100
115
|
# special cases
|
|
101
116
|
case agent_type
|
|
102
117
|
when :node
|
|
103
118
|
if !agent_options.key?(:url)
|
|
104
|
-
param_set_name = @
|
|
105
|
-
|
|
106
|
-
agent_options.merge!(@
|
|
119
|
+
param_set_name = @context.presets.plugin_default_name(:node)
|
|
120
|
+
Aspera.assert(!param_set_name.nil?, type: Cli::BadArgument) { "No default node configured. Please specify #{Options.option_name_to_line(:transfer)}.url or #{Options.option_name_to_line(:transfer)}" }
|
|
121
|
+
agent_options.merge!(@context.presets.by_name(param_set_name).symbolize_keys)
|
|
107
122
|
end
|
|
108
123
|
when :direct
|
|
109
124
|
# by default do not display ascp native progress bar
|
|
110
125
|
agent_options[:quiet] = true unless agent_options.key?(:quiet)
|
|
111
|
-
agent_options[:check_ignore_cb] = ->(host, port){@
|
|
126
|
+
agent_options[:check_ignore_cb] = ->(host, port) { @context.http_config.ignore_cert?(host, port) }
|
|
112
127
|
# JRuby
|
|
113
|
-
agent_options[:trusted_certs] = @
|
|
128
|
+
agent_options[:trusted_certs] = @context.http_config.trusted_cert_locations unless agent_options.key?(:trusted_certs)
|
|
114
129
|
when :httpgw
|
|
115
130
|
unless agent_options.key?(:url) || @httpgw_url_lambda.nil?
|
|
116
131
|
Log.log.debug('retrieving HTTPGW URL from webapp')
|
|
@@ -119,7 +134,7 @@ module Aspera
|
|
|
119
134
|
end
|
|
120
135
|
# get agent instance
|
|
121
136
|
self.agent_instance = Agent::Factory.instance.create(agent_type, agent_options)
|
|
122
|
-
Log.log.debug{"transfer agent is a #{@agent.class}"}
|
|
137
|
+
Log.log.debug { "transfer agent is a #{@agent.class}" }
|
|
123
138
|
return @agent
|
|
124
139
|
end
|
|
125
140
|
|
|
@@ -127,7 +142,7 @@ module Aspera
|
|
|
127
142
|
# @param direction [String] `send`` or `receive``
|
|
128
143
|
# @return [String] Destination folder for transfers (with default based on direction)
|
|
129
144
|
def destination_folder(direction)
|
|
130
|
-
dest_folder = @
|
|
145
|
+
dest_folder = @context.options.get_option(:to_folder)
|
|
131
146
|
# do not expand path, if user wants to expand path: user @path:
|
|
132
147
|
return dest_folder unless dest_folder.nil?
|
|
133
148
|
dest_folder = @user_transfer_spec['destination_root']
|
|
@@ -150,22 +165,22 @@ module Aspera
|
|
|
150
165
|
|
|
151
166
|
# @param httpgw_url_proc [Proc]
|
|
152
167
|
def httpgw_url_cb=(httpgw_url_proc)
|
|
153
|
-
Aspera.assert_type(httpgw_url_proc, Proc){'httpgw_url_cb'}
|
|
168
|
+
Aspera.assert_type(httpgw_url_proc, Proc) { 'httpgw_url_cb' }
|
|
154
169
|
@httpgw_url_lambda = httpgw_url_proc
|
|
155
170
|
end
|
|
156
171
|
|
|
157
172
|
# Transform the list of paths to a list of hash with source/dest
|
|
158
173
|
# @param file_list [Array<Hash>]
|
|
159
174
|
def list_to_paths(file_list)
|
|
160
|
-
source_type = @
|
|
175
|
+
source_type = @context.options.get_option(:src_type, mandatory: true)
|
|
161
176
|
@transfer_paths =
|
|
162
177
|
case source_type
|
|
163
178
|
when :list
|
|
164
179
|
# when providing a list, just specify source
|
|
165
|
-
file_list.map{ |i| {'source' => i}}
|
|
180
|
+
file_list.map { |i| {'source' => i} }
|
|
166
181
|
when :pair
|
|
167
|
-
Aspera.assert(file_list.length.even?, type: Cli::BadArgument){"When using pair, provide an even number of paths: #{file_list.length}"}
|
|
168
|
-
file_list.each_slice(2).map{ |s, d| {'source' => s, 'destination' => d}}
|
|
182
|
+
Aspera.assert(file_list.length.even?, type: Cli::BadArgument) { "When using pair, provide an even number of paths: #{file_list.length}" }
|
|
183
|
+
file_list.each_slice(2).map { |s, d| {'source' => s, 'destination' => d} }
|
|
169
184
|
else Aspera.error_unexpected_value(source_type)
|
|
170
185
|
end
|
|
171
186
|
end
|
|
@@ -181,41 +196,41 @@ module Aspera
|
|
|
181
196
|
# start with lower priority : get paths from transfer spec on command line
|
|
182
197
|
@transfer_paths = @user_transfer_spec['paths'] if @user_transfer_spec.key?('paths')
|
|
183
198
|
# is there a source list option ?
|
|
184
|
-
sources = @
|
|
199
|
+
sources = @context.options.get_option(:sources)
|
|
185
200
|
@transfer_paths =
|
|
186
201
|
case sources
|
|
187
202
|
when FILE_LIST_FROM_ARGS
|
|
188
203
|
Log.log.debug('getting file list as parameters')
|
|
189
204
|
Aspera.assert_type(default, Array, NilClass)
|
|
190
205
|
# get remaining arguments
|
|
191
|
-
list = @
|
|
206
|
+
list = @context.options.get_next_argument('source file list', multiple: true, default: default)
|
|
192
207
|
raise Cli::BadArgument, 'specify at least one file on command line or use ' \
|
|
193
208
|
"--sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !list.is_a?(Array) || list.empty?
|
|
194
209
|
list_to_paths(list)
|
|
195
210
|
when FILE_LIST_FROM_TRANSFER_SPEC
|
|
196
211
|
Log.log.debug('assume list provided in transfer spec')
|
|
197
212
|
special_case_direct_with_list =
|
|
198
|
-
@
|
|
199
|
-
Transfer::Parameters.ascp_args_file_list?(@
|
|
200
|
-
|
|
213
|
+
(@transfer_options['agent'] || :direct).to_sym.eql?(:direct) &&
|
|
214
|
+
Transfer::Parameters.ascp_args_file_list?(@transfer_options['ascp_args'])
|
|
215
|
+
Aspera.assert(!@transfer_paths.nil? || special_case_direct_with_list, type: Cli::BadArgument) { 'transfer spec on command line must have sources' }
|
|
201
216
|
# can be nil
|
|
202
217
|
@transfer_paths
|
|
203
218
|
when Array
|
|
204
219
|
Log.log.debug('getting file list as extended value')
|
|
205
|
-
Aspera.assert_array_all(sources, String, type: Cli::BadArgument){'sources must be a Array of String'}
|
|
220
|
+
Aspera.assert_array_all(sources, String, type: Cli::BadArgument) { 'sources must be a Array of String' }
|
|
206
221
|
list_to_paths(sources)
|
|
207
|
-
else Aspera.error_unexpected_value(sources){'sources'}
|
|
222
|
+
else Aspera.error_unexpected_value(sources) { 'sources' }
|
|
208
223
|
end
|
|
209
|
-
Log.
|
|
224
|
+
Log.dump(:paths, @transfer_paths)
|
|
210
225
|
return @transfer_paths
|
|
211
226
|
end
|
|
212
227
|
|
|
213
228
|
# Start a transfer and wait for completion, plugins shall use this method
|
|
214
|
-
# @param transfer_spec [Hash]
|
|
215
|
-
# @param rest_token [Rest] if oauth token regeneration supported
|
|
229
|
+
# @param transfer_spec [Hash] transfer specification
|
|
230
|
+
# @param rest_token [Rest, nil] if oauth token regeneration supported
|
|
216
231
|
def start(transfer_spec, rest_token: nil)
|
|
217
232
|
# check parameters
|
|
218
|
-
Aspera.assert_type(transfer_spec, Hash){'transfer_spec'}
|
|
233
|
+
Aspera.assert_type(transfer_spec, Hash) { 'transfer_spec' }
|
|
219
234
|
raise "Wrong remote host: #{CP4I_REMOTE_HOST_LB}" if transfer_spec['remote_host'].eql?(CP4I_REMOTE_HOST_LB)
|
|
220
235
|
# process :src option
|
|
221
236
|
case transfer_spec['direction']
|
|
@@ -240,15 +255,60 @@ module Aspera
|
|
|
240
255
|
@user_transfer_spec['paths'] = transfer_spec['paths'] || ts_source_paths
|
|
241
256
|
# updated transfer spec with command line
|
|
242
257
|
transfer_spec.deep_merge!(@user_transfer_spec)
|
|
258
|
+
# resolve pseudo-parameter: target_rate -> target_rate_kbps (overrides target_rate_kbps if both are present)
|
|
259
|
+
transfer_spec['target_rate_kbps'] = Transfer::Spec.rate_string_to_kbps(transfer_spec.delete('target_rate')) if transfer_spec.key?('target_rate')
|
|
243
260
|
# recursively remove values that are nil (user wants to delete)
|
|
244
|
-
transfer_spec.deep_do{ |hash, key, value, _unused| hash.delete(key) if value.nil?}
|
|
261
|
+
transfer_spec.deep_do { |hash, key, value, _unused| hash.delete(key) if value.nil? }
|
|
245
262
|
# if TS from app has content_protection (e.g. F5), that means content is protected: ask password if not provided
|
|
246
|
-
transfer_spec['content_protection_password'] = @
|
|
263
|
+
transfer_spec['content_protection_password'] = @context.options.prompt_user_input('content protection password', sensitive: true) if transfer_spec['content_protection'].eql?('decrypt') && !transfer_spec.key?('content_protection_password')
|
|
247
264
|
# create transfer agent
|
|
248
265
|
agent_instance.start_transfer(transfer_spec, token_regenerator: rest_token)
|
|
249
|
-
#
|
|
266
|
+
# --- async mode ---
|
|
267
|
+
if @transfer_options['asynchronous']
|
|
268
|
+
agent_type = (@transfer_options['agent'] || 'direct').to_s
|
|
269
|
+
# In-process agents (direct, httpgw) expose last_job_id directly.
|
|
270
|
+
# Remote-daemon agents do not override last_job_id (returns nil): generate a UUID.
|
|
271
|
+
job_id = agent_instance.last_job_id || SecureRandom.uuid
|
|
272
|
+
# Base agent_params from transfer options (strip internal keys)
|
|
273
|
+
agent_params = @transfer_options.except('agent', 'asynchronous')
|
|
274
|
+
# For daemon agents: capture any runtime-resolved connection detail
|
|
275
|
+
case agent_type
|
|
276
|
+
when 'desktop'
|
|
277
|
+
agent_params['application_id'] = agent_instance.application_id
|
|
278
|
+
when 'connect'
|
|
279
|
+
agent_params['app_id'] = agent_instance.app_id
|
|
280
|
+
when 'transferd'
|
|
281
|
+
agent_params['url'] = agent_instance.daemon_endpoint
|
|
282
|
+
end
|
|
283
|
+
# Register an in-process agent reference for direct/httpgw agents so that
|
|
284
|
+
# config transfer status can re-query them while the same process is alive
|
|
285
|
+
# (e.g. MCP server mode). The ref is stored in AsyncTransferStore's memory map,
|
|
286
|
+
# never on disk.
|
|
287
|
+
async_store.register_agent_ref(job_id, agent_instance) if %w[direct httpgw].include?(agent_type)
|
|
288
|
+
# transfer_id: in-process agents use job_id; daemon agents expose @transfer_id
|
|
289
|
+
transfer_id =
|
|
290
|
+
if %w[direct httpgw].include?(agent_type)
|
|
291
|
+
job_id
|
|
292
|
+
else
|
|
293
|
+
agent_instance.instance_variable_get(:@transfer_id).to_s
|
|
294
|
+
end
|
|
295
|
+
async_store.write(job_id, {
|
|
296
|
+
'job_id' => job_id,
|
|
297
|
+
'agent_type' => agent_type,
|
|
298
|
+
'transfer_id' => transfer_id,
|
|
299
|
+
'agent_params' => agent_params,
|
|
300
|
+
'status' => 'running',
|
|
301
|
+
'bytes_transferred' => 0,
|
|
302
|
+
'started_at' => Time.now.utc.iso8601,
|
|
303
|
+
'ended_at' => nil,
|
|
304
|
+
'error' => nil
|
|
305
|
+
})
|
|
306
|
+
Log.log.info { "Async transfer started: job_id=#{job_id}, agent=#{agent_type}" }
|
|
307
|
+
return Transfer::Result.async(job_id: job_id)
|
|
308
|
+
end
|
|
309
|
+
# --- synchronous mode (default) ---
|
|
250
310
|
result = agent_instance.wait_for_completion
|
|
251
|
-
@notification_cb&.call(transfer_spec,
|
|
311
|
+
@notification_cb&.call(transfer_spec, result)
|
|
252
312
|
return result
|
|
253
313
|
end
|
|
254
314
|
|
|
@@ -256,6 +316,11 @@ module Aspera
|
|
|
256
316
|
def shutdown
|
|
257
317
|
@agent.shutdown if @agent.respond_to?(:shutdown)
|
|
258
318
|
end
|
|
319
|
+
|
|
320
|
+
# Lazy accessor for the async transfer store (shared with TransferActions via context.transfer).
|
|
321
|
+
def async_store
|
|
322
|
+
@async_store ||= AsyncTransferStore.new(@context.persistency)
|
|
323
|
+
end
|
|
259
324
|
end
|
|
260
325
|
end
|
|
261
326
|
end
|
|
@@ -25,16 +25,16 @@ module Aspera
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Called by user of progress bar with a status on a transfer session
|
|
28
|
-
# @param session_id the unique identifier of a transfer session
|
|
28
|
+
# @param session_id [String] the unique identifier of a transfer session
|
|
29
29
|
# @param type [Symbol] one of: sessions_init, session_start, session_size, transfer, session_end and end
|
|
30
|
-
# @param info optional specific additional info for the given event type
|
|
30
|
+
# @param info [Object, nil] optional specific additional info for the given event type
|
|
31
31
|
def event(type, session_id: nil, info: nil)
|
|
32
|
-
Log.log.trace1{"progress: #{type} #{session_id} #{info}"}
|
|
32
|
+
Log.log.trace1 { "progress: #{type} #{session_id} #{info}" }
|
|
33
33
|
return if @completed
|
|
34
34
|
if @progress_bar.nil?
|
|
35
35
|
@progress_bar = ProgressBar.create(
|
|
36
36
|
format: '%t %a %B %p%% %r Mbps %E',
|
|
37
|
-
rate_scale: lambda{ |rate| rate / Environment::BYTES_PER_MEBIBIT},
|
|
37
|
+
rate_scale: lambda { |rate| rate / Environment::BYTES_PER_MEBIBIT },
|
|
38
38
|
title: '',
|
|
39
39
|
total: nil
|
|
40
40
|
)
|
|
@@ -50,7 +50,7 @@ module Aspera
|
|
|
50
50
|
when :session_start
|
|
51
51
|
Aspera.assert_type(session_id, String)
|
|
52
52
|
Aspera.assert(info.nil?, 'info must be nil for :session_start event')
|
|
53
|
-
|
|
53
|
+
Aspera.assert(!@sessions[session_id]) { "Session #{session_id} already started" }
|
|
54
54
|
@sessions[session_id] = {
|
|
55
55
|
job_size: 0, # Total size of transfer (pre-calc)
|
|
56
56
|
current: 0,
|
|
@@ -83,19 +83,19 @@ module Aspera
|
|
|
83
83
|
Aspera.assert(session_id.nil?, 'session_id must be nil for :end event')
|
|
84
84
|
Aspera.assert(info.nil?, 'info must be nil for :end event')
|
|
85
85
|
@progress_bar.finish
|
|
86
|
-
else Aspera.error_unexpected_value(type){'event type'}
|
|
86
|
+
else Aspera.error_unexpected_value(type) { 'event type' }
|
|
87
87
|
end
|
|
88
|
-
new_title = @sessions.length < 2 ? @title.to_s : "[#{@sessions.count{ |_i, d| d[:running]}}] #{@title}"
|
|
88
|
+
new_title = @sessions.length < 2 ? @title.to_s : "[#{@sessions.count { |_i, d| d[:running] }}] #{@title}"
|
|
89
89
|
@progress_bar&.title = new_title unless @progress_bar&.title.eql?(new_title)
|
|
90
90
|
@progress_bar&.increment if !progress_provided && @progress_bar.progress.nil?
|
|
91
91
|
rescue ProgressBar::InvalidProgressError => e
|
|
92
|
-
Log.log.error{"Progress error: #{e}"}
|
|
92
|
+
Log.log.error { "Progress error: #{e}" }
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
private
|
|
96
96
|
|
|
97
97
|
def total(key)
|
|
98
|
-
@sessions.values.inject(0){ |m, s| m + s[key]}
|
|
98
|
+
@sessions.values.inject(0) { |m, s| m + s[key] }
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/cli/error'
|
|
4
|
+
require 'aspera/log'
|
|
5
|
+
require 'aspera/assert'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Aspera
|
|
9
|
+
module Cli
|
|
10
|
+
# Mixin providing vault/keychain functionality to Plugin::Config.
|
|
11
|
+
# Depends on `options` and `context.main_folder` being available in the including class.
|
|
12
|
+
module VaultManager
|
|
13
|
+
def action_vault_show(label:, id: nil, **)
|
|
14
|
+
v = vault_required
|
|
15
|
+
kwargs = id && v.method(:get).parameters.any? { |_t, n| n == :id } ? {id: id} : {}
|
|
16
|
+
Result::SingleObject.new(v.get(label: label, **kwargs))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def action_vault_create(info:, **)
|
|
20
|
+
vault_required.set(info.symbolize_keys)
|
|
21
|
+
Result::Status.new('Secret added')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Import secrets from a JSON array; skips entries missing :label
|
|
25
|
+
def action_vault_import(secrets:, **)
|
|
26
|
+
bulk_result(secrets, command: :import, id_result: 'label') do |entry|
|
|
27
|
+
vault_required.set(entry.symbolize_keys)
|
|
28
|
+
{'label' => entry['label'] || entry[:label]}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def action_vault_delete(label:, id: nil, **)
|
|
33
|
+
v = vault_required
|
|
34
|
+
kwargs = id && v.method(:delete).parameters.any? { |_t, n| n == :id } ? {id: id} : {}
|
|
35
|
+
v.delete(label: label, **kwargs)
|
|
36
|
+
Result::Status.new("Secret deleted: #{label}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def action_vault_password(new_password:, **)
|
|
40
|
+
Aspera.assert(vault_required.respond_to?(:change_password), 'Vault does not support password change')
|
|
41
|
+
vault_required.change_password(new_password)
|
|
42
|
+
Result::Status.new('Vault password updated')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Keychain::Base] vault instance, raises if not configured
|
|
46
|
+
def vault_required
|
|
47
|
+
Aspera.assert(!vault.nil?, type: Cli::BadArgument) { 'Missing mandatory option: vault' }
|
|
48
|
+
vault
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [String] value from vault matching <name>.<param>
|
|
52
|
+
def vault_value(name)
|
|
53
|
+
m = name.split('.')
|
|
54
|
+
Aspera.assert(m.length.eql?(2), type: BadArgument) { 'vault name shall match <name>.<param>' }
|
|
55
|
+
info = vault_required.get(label: m[0])
|
|
56
|
+
value = info[m[1].to_sym]
|
|
57
|
+
raise "no such entry value: #{m[1]}" if value.nil?
|
|
58
|
+
return value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Keychain::Base, nil] vault instance, lazily created from options, or nil if not configured
|
|
62
|
+
def vault
|
|
63
|
+
return @vault_instance if @vault_instance_initialized
|
|
64
|
+
@vault_instance_initialized = true
|
|
65
|
+
info = options.get_option(:vault, mandatory: false)
|
|
66
|
+
return @vault_instance = nil if info.nil? || (info.is_a?(Hash) && info.empty?)
|
|
67
|
+
info = info.symbolize_keys
|
|
68
|
+
info[:type] ||= 'file'
|
|
69
|
+
require 'aspera/keychain/factory'
|
|
70
|
+
@vault_instance = Keychain::Factory.create(
|
|
71
|
+
info,
|
|
72
|
+
Info::CMD_NAME,
|
|
73
|
+
context.main_folder,
|
|
74
|
+
options.get_option(:vault_password)
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -2,27 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
require 'aspera/oauth/jwt'
|
|
4
4
|
require 'aspera/assert'
|
|
5
|
+
require 'aspera/cli/option_declarator'
|
|
5
6
|
require 'aspera/cli/plugins/factory'
|
|
6
7
|
|
|
7
8
|
module Aspera
|
|
8
9
|
module Cli
|
|
9
10
|
# The wizard detects applications and generates a config
|
|
10
11
|
class Wizard
|
|
12
|
+
extend OptionDeclarator
|
|
13
|
+
|
|
11
14
|
WIZARD_RESULT_KEYS = %i[preset_value test_args].freeze
|
|
12
15
|
DEFAULT_PRIV_KEY_FILENAME = 'my_private_key.pem' # pragma: allowlist secret
|
|
13
|
-
private_constant :WIZARD_RESULT_KEYS,
|
|
14
|
-
|
|
16
|
+
private_constant :WIZARD_RESULT_KEYS, :DEFAULT_PRIV_KEY_FILENAME
|
|
17
|
+
|
|
18
|
+
option :override, description: 'Wizard: override existing value', allowed: Type::BOOLEAN, default: false
|
|
19
|
+
option :default, description: 'Wizard: set as default configuration for specified plugin (also: update)', allowed: Type::BOOLEAN, default: true
|
|
20
|
+
option :key_path, description: 'Wizard: path to private key for JWT'
|
|
15
21
|
|
|
16
22
|
def initialize(parent, main_folder)
|
|
17
23
|
@parent = parent
|
|
18
24
|
@main_folder = main_folder
|
|
19
|
-
|
|
20
|
-
options.declare(:override, 'Wizard: override existing value', allowed: Allowed::TYPES_BOOLEAN, default: false)
|
|
21
|
-
options.declare(:default, 'Wizard: set as default configuration for specified plugin (also: update)', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
22
|
-
options.declare(:key_path, 'Wizard: path to private key for JWT')
|
|
25
|
+
self.class.declare_options(options)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
|
-
# @return false if in test mode to avoid interactive input
|
|
28
|
+
# @return [Boolean] false if in test mode to avoid interactive input
|
|
26
29
|
def required
|
|
27
30
|
!ENV['ASCLI_WIZ_TEST']
|
|
28
31
|
end
|
|
@@ -35,20 +38,15 @@ module Aspera
|
|
|
35
38
|
@parent.formatter
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
def config
|
|
39
|
-
@parent.config
|
|
40
|
-
end
|
|
41
|
-
|
|
42
41
|
def check_email(email)
|
|
43
|
-
Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError){"Username shall be an email: #{email}"}
|
|
42
|
+
Aspera.assert(email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, type: ParameterError) { "Username shall be an email: #{email}" }
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
# Find a plugin, and issue the "require"
|
|
47
46
|
# @return [Hash] plugin info: { product:, name:, url:, version: }
|
|
48
|
-
def identify_plugins_for_url
|
|
49
|
-
app_url =
|
|
50
|
-
check_only =
|
|
51
|
-
check_only = check_only.to_sym unless check_only.nil?
|
|
47
|
+
def identify_plugins_for_url(url:, plugin_name: nil)
|
|
48
|
+
app_url = url
|
|
49
|
+
check_only = plugin_name&.to_sym
|
|
52
50
|
found_apps = []
|
|
53
51
|
my_self_plugin_sym = self.class.name.split('::').last.downcase.to_sym
|
|
54
52
|
Plugins::Factory.instance.plugin_list.each do |plugin_name_sym|
|
|
@@ -61,26 +59,26 @@ module Aspera
|
|
|
61
59
|
next unless plugin_klass.respond_to?(:detect)
|
|
62
60
|
detection_info = nil
|
|
63
61
|
begin
|
|
64
|
-
Log.log.debug{"detecting #{plugin_name_sym} at #{app_url}"}
|
|
62
|
+
Log.log.debug { "detecting #{plugin_name_sym} at #{app_url}" }
|
|
65
63
|
RestParameters.instance.spinner_cb.call(plugin_name_sym.to_s)
|
|
66
64
|
detection_info = plugin_klass.detect(app_url)
|
|
67
65
|
rescue OpenSSL::SSL::SSLError => e
|
|
68
66
|
Log.log.warn(e.message)
|
|
69
67
|
Log.log.warn('Use option --insecure=yes to allow unchecked certificate') if e.message.include?('cert')
|
|
70
68
|
rescue StandardError => e
|
|
71
|
-
Log.log.debug{"detect error: [#{e.class}] #{e}"}
|
|
69
|
+
Log.log.debug { "detect error: [#{e.class}] #{e}" }
|
|
72
70
|
next
|
|
73
71
|
end
|
|
74
72
|
next if detection_info.nil?
|
|
75
73
|
Aspera.assert_type(detection_info, Hash)
|
|
76
74
|
Aspera.assert_type(detection_info[:url], String) if detection_info.key?(:url)
|
|
77
|
-
app_name = plugin_klass.
|
|
75
|
+
app_name = plugin_klass.application_name
|
|
78
76
|
# If there is a redirect, then the detector can override the url.
|
|
79
77
|
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
80
78
|
end
|
|
81
79
|
RestParameters.instance.spinner_cb.call(action: :success)
|
|
82
|
-
|
|
83
|
-
Aspera.assert(found_apps.all?{ |a| a.keys.all?(Symbol)}, 'all app info keys must be symbols')
|
|
80
|
+
Aspera.assert(!found_apps.empty?) { "No known application found at #{app_url}" }
|
|
81
|
+
Aspera.assert(found_apps.all? { |a| a.keys.all?(Symbol) }, 'all app info keys must be symbols')
|
|
84
82
|
return found_apps
|
|
85
83
|
end
|
|
86
84
|
|
|
@@ -122,18 +120,18 @@ module Aspera
|
|
|
122
120
|
|
|
123
121
|
# Wizard function, creates configuration
|
|
124
122
|
# @param apps [Array] list of detected apps
|
|
125
|
-
def find(apps)
|
|
123
|
+
def find(apps, preset_name: '')
|
|
126
124
|
identification = if apps.length.eql?(1)
|
|
127
|
-
Log.log.debug{"Detected: #{identification}"}
|
|
125
|
+
Log.log.debug { "Detected: #{identification}" }
|
|
128
126
|
apps.first
|
|
129
127
|
else
|
|
130
128
|
formatter.display_status('Multiple applications detected, please select from:')
|
|
131
129
|
require 'aspera/cli/result'
|
|
132
130
|
formatter.display_results(Result::ObjectList.new(apps, fields: %w[product url version]))
|
|
133
|
-
answer = options.prompt_user_input_in_list('product', apps.map{ |a| a[:product]})
|
|
134
|
-
apps.find{ |a| a[:product].eql?(answer)}
|
|
131
|
+
answer = options.prompt_user_input_in_list('product', apps.map { |a| a[:product] })
|
|
132
|
+
apps.find { |a| a[:product].eql?(answer) }
|
|
135
133
|
end
|
|
136
|
-
wiz_preset_name =
|
|
134
|
+
wiz_preset_name = preset_name
|
|
137
135
|
Log.dump(:identification, identification)
|
|
138
136
|
wiz_url = identification[:url]
|
|
139
137
|
formatter.display_status("Using: #{identification[:name]} at #{wiz_url}".bold)
|
|
@@ -141,11 +139,11 @@ module Aspera
|
|
|
141
139
|
options.add_option_preset({url: wiz_url}, 'wizard')
|
|
142
140
|
# Instantiate plugin: command line options will be known, e.g. private_key, and wizard can be called
|
|
143
141
|
plugin_instance = Plugins::Factory.instance.plugin_class(identification[:product]).new(context: @parent.context)
|
|
144
|
-
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument){"Detected: #{identification[:product]}, but this application has no wizard"}
|
|
142
|
+
Aspera.assert(plugin_instance.respond_to?(:wizard), type: Cli::BadArgument) { "Detected: #{identification[:product]}, but this application has no wizard" }
|
|
145
143
|
# Call the wizard
|
|
146
144
|
wizard_result = plugin_instance.wizard(self, wiz_url)
|
|
147
|
-
Log.log.debug{"wizard result: #{wizard_result}"}
|
|
148
|
-
Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)){"missing or extra keys in wizard result: #{wizard_result.keys}"}
|
|
145
|
+
Log.log.debug { "wizard result: #{wizard_result}" }
|
|
146
|
+
Aspera.assert(WIZARD_RESULT_KEYS.eql?(wizard_result.keys.sort)) { "missing or extra keys in wizard result: #{wizard_result.keys}" }
|
|
149
147
|
# Get preset name from user or default
|
|
150
148
|
if wiz_preset_name.empty?
|
|
151
149
|
elements = [
|
|
@@ -160,7 +158,7 @@ module Aspera
|
|
|
160
158
|
# Init defaults if necessary
|
|
161
159
|
option_override = options.get_option(:override, mandatory: true)
|
|
162
160
|
option_default = options.get_option(:default, mandatory: true)
|
|
163
|
-
|
|
161
|
+
@parent.context.presets.defaults_set(identification[:product], wiz_preset_name, wizard_result[:preset_value].stringify_keys, option_default, option_override)
|
|
164
162
|
test_args = wizard_result[:test_args]
|
|
165
163
|
test_args = "-P#{wiz_preset_name} #{test_args}" unless option_default
|
|
166
164
|
# TODO: actually test the command
|
data/lib/aspera/colors.rb
CHANGED
|
@@ -52,9 +52,9 @@ class String
|
|
|
52
52
|
0 # by default reset all
|
|
53
53
|
end
|
|
54
54
|
)
|
|
55
|
-
define_method(name){"#{begin_seq}#{self}#{end_seq}"}
|
|
55
|
+
define_method(name) { "#{begin_seq}#{self}#{end_seq}" }
|
|
56
56
|
else
|
|
57
|
-
define_method(name){self}
|
|
57
|
+
define_method(name) { self }
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ class String
|
|
|
62
62
|
# @param colors [Array<Symbol>] List of decorations.
|
|
63
63
|
# @return [String] Enhanced String.
|
|
64
64
|
def apply(*colors)
|
|
65
|
-
colors.reduce(self){ |s, c| s.public_send(c)}
|
|
65
|
+
colors.reduce(self) { |s, c| s.public_send(c) }
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Transform capitalized to snake case
|