aspera-cli 4.18.0 → 4.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -2
- data/CHANGELOG.md +10 -0
- data/README.md +96 -59
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +4 -4
- data/lib/aspera/agent/connect.rb +3 -4
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/api/httpgw.rb +4 -1
- data/lib/aspera/api/node.rb +110 -77
- data/lib/aspera/ascp/products.rb +1 -1
- data/lib/aspera/cli/extended_value.rb +27 -14
- data/lib/aspera/cli/formatter.rb +11 -10
- data/lib/aspera/cli/main.rb +11 -11
- data/lib/aspera/cli/manager.rb +99 -84
- data/lib/aspera/cli/plugin.rb +2 -5
- data/lib/aspera/cli/plugins/aoc.rb +15 -14
- data/lib/aspera/cli/plugins/config.rb +20 -19
- data/lib/aspera/cli/plugins/faspex.rb +5 -4
- data/lib/aspera/cli/plugins/faspex5.rb +16 -13
- data/lib/aspera/cli/plugins/node.rb +46 -38
- data/lib/aspera/cli/plugins/orchestrator.rb +3 -2
- data/lib/aspera/cli/plugins/preview.rb +1 -1
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +4 -4
- data/lib/aspera/cli/transfer_agent.rb +2 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +64 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/rest.rb +46 -15
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/open_application.rb +0 -69
data/lib/aspera/api/node.rb
CHANGED
@@ -14,23 +14,28 @@ module Aspera
|
|
14
14
|
module Api
|
15
15
|
# Provides additional functions using node API with gen4 extensions (access keys)
|
16
16
|
class Node < Aspera::Rest
|
17
|
-
# node api permissions
|
18
|
-
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
19
|
-
HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
|
20
17
|
SCOPE_SEPARATOR = ':'
|
21
|
-
SCOPE_USER = 'user:all'
|
22
|
-
SCOPE_ADMIN = 'admin:all'
|
23
18
|
SCOPE_NODE_PREFIX = 'node.'
|
24
19
|
# prefix for ruby code for filter (deprecated)
|
25
20
|
MATCH_EXEC_PREFIX = 'exec:'
|
26
21
|
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
27
|
-
PATH_SEPARATOR = '/'
|
28
22
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
29
23
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
# fields in @app_info
|
25
|
+
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
26
|
+
# methods of @app_info[:api]
|
27
|
+
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
28
|
+
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_EXEC_PREFIX, :MATCH_TYPES,
|
29
|
+
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
|
30
|
+
:REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
31
|
+
|
32
|
+
# node api permissions
|
33
|
+
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
34
|
+
HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
|
35
|
+
HEADER_X_TOTAL_COUNT = 'X-Total-Count'
|
36
|
+
SCOPE_USER = 'user:all'
|
37
|
+
SCOPE_ADMIN = 'admin:all'
|
38
|
+
PATH_SEPARATOR = '/'
|
34
39
|
|
35
40
|
# register node special token decoder
|
36
41
|
OAuth::Factory.instance.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
|
@@ -59,7 +64,7 @@ module Aspera
|
|
59
64
|
end
|
60
65
|
|
61
66
|
def file_matcher_from_argument(options)
|
62
|
-
return file_matcher(options.get_next_argument('filter',
|
67
|
+
return file_matcher(options.get_next_argument('filter', validation: MATCH_TYPES, mandatory: false))
|
63
68
|
end
|
64
69
|
|
65
70
|
# node API scopes
|
@@ -86,7 +91,7 @@ module Aspera
|
|
86
91
|
# manage convenience parameters
|
87
92
|
expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
|
88
93
|
payload.delete('_validity')
|
89
|
-
scope = payload['_scope'] ||
|
94
|
+
scope = payload['_scope'] || SCOPE_USER
|
90
95
|
payload.delete('_scope')
|
91
96
|
payload['scope'] ||= token_scope(access_key, scope)
|
92
97
|
payload['auth_type'] ||= 'access_key'
|
@@ -117,19 +122,13 @@ module Aspera
|
|
117
122
|
end
|
118
123
|
end
|
119
124
|
|
120
|
-
# fields in @app_info
|
121
|
-
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
122
|
-
# methods of @app_info[:api]
|
123
|
-
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
124
|
-
private_constant :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
125
|
-
|
126
125
|
attr_reader :app_info
|
127
126
|
|
127
|
+
# @param app_info [Hash,NilClass] Special processing for AoC
|
128
|
+
# @param add_tspec [Hash,NilClass] Additional transfer spec
|
128
129
|
# @param base_url [String] Rest parameters
|
129
130
|
# @param auth [String,NilClass] Rest parameters
|
130
131
|
# @param headers [String,NilClass] Rest parameters
|
131
|
-
# @param app_info [Hash,NilClass] Special processing for AoC
|
132
|
-
# @param add_tspec [Hash,NilClass] Additional transfer spec
|
133
132
|
def initialize(app_info: nil, add_tspec: nil, **rest_args)
|
134
133
|
# init Rest
|
135
134
|
super(**rest_args)
|
@@ -162,25 +161,42 @@ module Aspera
|
|
162
161
|
workspace_id: @app_info[:workspace_id],
|
163
162
|
workspace_name: @app_info[:workspace_name])
|
164
163
|
end
|
165
|
-
Log.log.warn{"
|
164
|
+
Log.log.warn{"Cannot resolve link with node id #{node_id}, no resolver"}
|
166
165
|
return nil
|
167
166
|
end
|
168
167
|
|
168
|
+
# Check if a link entry in folder has target information
|
169
|
+
# @param entry [Hash] entry in folder
|
170
|
+
# @return [Boolean] true if target information is available
|
171
|
+
def entry_has_link_information(entry)
|
172
|
+
# if target information is missing in folder, try to get it on entry
|
173
|
+
if entry['target_node_id'].nil? || entry['target_id'].nil?
|
174
|
+
link_entry = read("files/#{entry['id']}")[:data]
|
175
|
+
entry['target_node_id'] = link_entry['target_node_id']
|
176
|
+
entry['target_id'] = link_entry['target_id']
|
177
|
+
end
|
178
|
+
return true unless entry['target_node_id'].nil? || entry['target_id'].nil?
|
179
|
+
Log.log.warn{"Missing target information for link: #{entry['name']}"}
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
|
169
183
|
# Recursively browse in a folder (with non-recursive method)
|
170
184
|
# sub folders are processed if the processing method returns true
|
185
|
+
# links are processed on the respective node
|
171
186
|
# @param state [Object] state object sent to processing method
|
172
187
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
173
188
|
# @param top_file_path [String] path of top folder (default = /)
|
174
189
|
# @param block [Proc] processing method, arguments: entry, path, state
|
175
|
-
def process_folder_tree(state:, top_file_id:, top_file_path: '/'
|
190
|
+
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
|
176
191
|
Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
|
177
|
-
|
192
|
+
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
178
193
|
# start at top folder
|
179
194
|
folders_to_explore = [{id: top_file_id, path: top_file_path}]
|
180
195
|
Log.log.debug{Log.dump(:folders_to_explore, folders_to_explore)}
|
181
196
|
until folders_to_explore.empty?
|
197
|
+
# consume first in job list
|
182
198
|
current_item = folders_to_explore.shift
|
183
|
-
Log.log.debug{"
|
199
|
+
Log.log.debug{"Exploring #{current_item[:path]}".bg_green}
|
184
200
|
# get folder content
|
185
201
|
folder_contents =
|
186
202
|
begin
|
@@ -192,19 +208,21 @@ module Aspera
|
|
192
208
|
Log.log.debug{Log.dump(:folder_contents, folder_contents)}
|
193
209
|
folder_contents.each do |entry|
|
194
210
|
relative_path = File.join(current_item[:path], entry['name'])
|
195
|
-
Log.log.debug{"process_folder_tree checking #{relative_path}"}
|
196
|
-
# continue only if method returns true
|
197
|
-
next unless
|
211
|
+
Log.log.debug{"process_folder_tree: checking #{relative_path}"}
|
212
|
+
# call block, continue only if method returns true
|
213
|
+
next unless send(method_sym, entry, relative_path, state)
|
198
214
|
# entry type is file, folder or link
|
199
215
|
case entry['type']
|
200
216
|
when 'folder'
|
201
217
|
folders_to_explore.push({id: entry['id'], path: relative_path})
|
202
218
|
when 'link'
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
219
|
+
if entry_has_link_information(entry)
|
220
|
+
node_id_to_node(entry['target_node_id'])&.process_folder_tree(
|
221
|
+
method_sym: method_sym,
|
222
|
+
state: state,
|
223
|
+
top_file_id: entry['target_id'],
|
224
|
+
top_file_path: relative_path)
|
225
|
+
end
|
208
226
|
end
|
209
227
|
end
|
210
228
|
end
|
@@ -216,60 +234,23 @@ module Aspera
|
|
216
234
|
# @return [Hash] {.api,.file_id}
|
217
235
|
def resolve_api_fid(top_file_id, path)
|
218
236
|
Aspera.assert_type(top_file_id, String)
|
237
|
+
Aspera.assert_type(path, String)
|
238
|
+
# if last element is a link and followed by "/", we list the content of that folder, else we return the link
|
219
239
|
process_last_link = path.end_with?(PATH_SEPARATOR)
|
240
|
+
# keep only non-empty elements
|
220
241
|
path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
|
221
242
|
return {api: self, file_id: top_file_id} if path_elements.empty?
|
222
|
-
resolve_state = {path: path_elements, result: nil}
|
223
|
-
process_folder_tree(state: resolve_state, top_file_id: top_file_id)
|
224
|
-
# this block is called recursively for each entry in folder
|
225
|
-
# stop digging here if not in right path
|
226
|
-
next false unless entry['name'].eql?(state[:path].first)
|
227
|
-
# ok it matches, so we remove the match
|
228
|
-
state[:path].shift
|
229
|
-
case entry['type']
|
230
|
-
when 'file'
|
231
|
-
# file must be terminal
|
232
|
-
raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless state[:path].empty?
|
233
|
-
# it's terminal, we found it
|
234
|
-
state[:result] = {api: self, file_id: entry['id']}
|
235
|
-
next false
|
236
|
-
when 'folder'
|
237
|
-
if state[:path].empty?
|
238
|
-
# we found it
|
239
|
-
state[:result] = {api: self, file_id: entry['id']}
|
240
|
-
next false
|
241
|
-
end
|
242
|
-
when 'link'
|
243
|
-
if state[:path].empty?
|
244
|
-
if process_last_link
|
245
|
-
# we found it
|
246
|
-
other_node = node_id_to_node(entry['target_node_id'])
|
247
|
-
raise 'cannot resolve link' if other_node.nil?
|
248
|
-
state[:result] = {api: other_node, file_id: entry['target_id']}
|
249
|
-
else
|
250
|
-
# we found it but we do not process the link
|
251
|
-
state[:result] = {api: self, file_id: entry['id']}
|
252
|
-
end
|
253
|
-
next false
|
254
|
-
end
|
255
|
-
else
|
256
|
-
Log.log.warn{"Unknown element type: #{entry['type']}"}
|
257
|
-
end
|
258
|
-
# continue to dig folder
|
259
|
-
next true
|
260
|
-
end
|
243
|
+
resolve_state = {path: path_elements, result: nil, process_last_link: process_last_link}
|
244
|
+
process_folder_tree(method_sym: :process_api_fid, state: resolve_state, top_file_id: top_file_id)
|
261
245
|
raise "entry not found: #{resolve_state[:path]}" if resolve_state[:result].nil?
|
246
|
+
Log.log.debug{"resolve_api_fid: #{path} -> #{resolve_state[:result][:api].base_url} #{resolve_state[:result][:file_id]}"}
|
262
247
|
return resolve_state[:result]
|
263
248
|
end
|
264
249
|
|
265
250
|
def find_files(top_file_id, test_block)
|
266
251
|
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
267
252
|
find_state = {found: [], test_block: test_block}
|
268
|
-
process_folder_tree(state: find_state, top_file_id: top_file_id)
|
269
|
-
state[:found].push(entry.merge({'path' => path})) if state[:test_block].call(entry)
|
270
|
-
# test all files deeply
|
271
|
-
true
|
272
|
-
end
|
253
|
+
process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
|
273
254
|
return find_state[:found]
|
274
255
|
end
|
275
256
|
|
@@ -306,7 +287,7 @@ module Aspera
|
|
306
287
|
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
307
288
|
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
|
308
289
|
# get bearer token, possibly use cache
|
309
|
-
ak_token = oauth.token
|
290
|
+
ak_token = oauth.token
|
310
291
|
else Aspera.error_unexpected_value(auth_params[:type])
|
311
292
|
end
|
312
293
|
transfer_spec = {
|
@@ -352,6 +333,58 @@ module Aspera
|
|
352
333
|
unless transfer_spec['remote_user'].eql?(Transfer::Spec::ACCESS_KEY_TRANSFER_USER)
|
353
334
|
return transfer_spec
|
354
335
|
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
def process_api_fid(entry, path, state)
|
340
|
+
# this block is called recursively for each entry in folder
|
341
|
+
# stop digging here if not in right path
|
342
|
+
return false unless entry['name'].eql?(state[:path].first)
|
343
|
+
# ok it matches, so we remove the match, and continue digging
|
344
|
+
state[:path].shift
|
345
|
+
path_fully_consumed = state[:path].empty?
|
346
|
+
case entry['type']
|
347
|
+
when 'file'
|
348
|
+
# file must be terminal
|
349
|
+
raise "#{entry['name']} is a file, expecting folder to find: #{state[:path]}" unless path_fully_consumed
|
350
|
+
# it's terminal, we found it
|
351
|
+
Log.log.debug{"resolve_api_fid: found #{path} -> #{entry['id']}"}
|
352
|
+
state[:result] = {api: self, file_id: entry['id']}
|
353
|
+
return false
|
354
|
+
when 'folder'
|
355
|
+
if path_fully_consumed
|
356
|
+
# we found it
|
357
|
+
state[:result] = {api: self, file_id: entry['id']}
|
358
|
+
return false
|
359
|
+
end
|
360
|
+
when 'link'
|
361
|
+
if path_fully_consumed
|
362
|
+
if state[:process_last_link]
|
363
|
+
# we found it
|
364
|
+
other_node = nil
|
365
|
+
if entry_has_link_information(entry)
|
366
|
+
other_node = node_id_to_node(entry['target_node_id'])
|
367
|
+
end
|
368
|
+
raise 'Cannot resolve link' if other_node.nil?
|
369
|
+
state[:result] = {api: other_node, file_id: entry['target_id']}
|
370
|
+
else
|
371
|
+
# we found it but we do not process the link
|
372
|
+
state[:result] = {api: self, file_id: entry['id']}
|
373
|
+
end
|
374
|
+
return false
|
375
|
+
end
|
376
|
+
else
|
377
|
+
Log.log.warn{"Unknown element type: #{entry['type']}"}
|
378
|
+
end
|
379
|
+
# continue to dig folder
|
380
|
+
return true
|
381
|
+
end
|
382
|
+
|
383
|
+
def process_find_files(entry, _path, state)
|
384
|
+
state[:found].push(entry.merge({'path' => path})) if state[:test_block].call(entry)
|
385
|
+
# test all files deeply
|
386
|
+
return true
|
387
|
+
end
|
355
388
|
end
|
356
389
|
end
|
357
390
|
end
|
data/lib/aspera/ascp/products.rb
CHANGED
@@ -44,7 +44,7 @@ module Aspera
|
|
44
44
|
app_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server'),
|
45
45
|
log_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server', 'var', 'log')
|
46
46
|
}]
|
47
|
-
when Aspera::Environment::
|
47
|
+
when Aspera::Environment::OS_MACOS then [{
|
48
48
|
expected: CONNECT,
|
49
49
|
app_root: File.join(Dir.home, 'Applications', 'Aspera Connect.app'),
|
50
50
|
log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# cspell:ignore csvt jsonpp
|
3
|
+
# cspell:ignore csvt jsonpp stdbin
|
4
4
|
require 'aspera/uri_reader'
|
5
5
|
require 'aspera/environment'
|
6
6
|
require 'aspera/log'
|
@@ -17,11 +17,6 @@ module Aspera
|
|
17
17
|
class ExtendedValue
|
18
18
|
include Singleton
|
19
19
|
|
20
|
-
# special values
|
21
|
-
INIT = 'INIT'
|
22
|
-
ALL = 'ALL'
|
23
|
-
DEF = 'DEF'
|
24
|
-
|
25
20
|
MARKER_START = '@'
|
26
21
|
MARKER_END = ':'
|
27
22
|
MARKER_IN_END = '@'
|
@@ -74,10 +69,22 @@ module Aspera
|
|
74
69
|
zlib: lambda{|v|Zlib::Inflate.inflate(v)},
|
75
70
|
extend: lambda{|v|ExtendedValue.instance.evaluate_all(v)}
|
76
71
|
}
|
72
|
+
@default_decoder = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Regex to match an extended value
|
76
|
+
def handler_regex_string
|
77
|
+
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
77
78
|
end
|
78
79
|
|
79
80
|
public
|
80
81
|
|
82
|
+
def default_decoder=(value)
|
83
|
+
Log.log.debug{"setting default decoder to #{value} (#{value.class})"}
|
84
|
+
Aspera.assert(value.nil? || @handlers.key?(value))
|
85
|
+
@default_decoder = value
|
86
|
+
end
|
87
|
+
|
81
88
|
def modifiers; @handlers.keys; end
|
82
89
|
|
83
90
|
# add a new handler
|
@@ -87,16 +94,13 @@ module Aspera
|
|
87
94
|
@handlers[name] = method
|
88
95
|
end
|
89
96
|
|
90
|
-
#
|
91
|
-
def ext_re
|
92
|
-
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
93
|
-
end
|
94
|
-
|
95
|
-
# parse an option value if it is a String using supported extended value modifiers
|
97
|
+
# parse an string value to extended value, if it is a String using supported extended value modifiers
|
96
98
|
# other value types are returned as is
|
99
|
+
# @param value [String] the value to parse
|
100
|
+
# @param expect [Class,Array] one or a list of expected types
|
97
101
|
def evaluate(value)
|
98
102
|
return value unless value.is_a?(String)
|
99
|
-
regex = Regexp.new("^#{
|
103
|
+
regex = Regexp.new("^#{handler_regex_string}(.*)$", Regexp::MULTILINE)
|
100
104
|
# first determine decoders, in reversed order
|
101
105
|
handlers_reversed = []
|
102
106
|
while (m = value.match(regex))
|
@@ -113,9 +117,18 @@ module Aspera
|
|
113
117
|
return value
|
114
118
|
end
|
115
119
|
|
120
|
+
# parse string value as extended value
|
121
|
+
# use default decoder if none is specified
|
122
|
+
def evaluate_with_default(value)
|
123
|
+
if value.is_a?(String) && value.match(/^#{handler_regex_string}.*$/).nil? && !@default_decoder.nil?
|
124
|
+
value = [MARKER_START, @default_decoder, MARKER_END, value].join
|
125
|
+
end
|
126
|
+
return evaluate(value)
|
127
|
+
end
|
128
|
+
|
116
129
|
# find inner extended values
|
117
130
|
def evaluate_all(value)
|
118
|
-
regex = Regexp.new("^(.*)#{
|
131
|
+
regex = Regexp.new("^(.*)#{handler_regex_string}([^#{MARKER_IN_END}]*)#{MARKER_IN_END}(.*)$", Regexp::MULTILINE)
|
119
132
|
while (m = value.match(regex))
|
120
133
|
sub_value = "@#{m[2]}:#{m[3]}"
|
121
134
|
Log.log.debug{"evaluating #{sub_value}"}
|
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# cspell:ignore jsonpp
|
4
|
+
require 'aspera/cli/special_values'
|
4
5
|
require 'aspera/preview/terminal'
|
5
6
|
require 'aspera/secret_hider'
|
6
7
|
require 'aspera/environment'
|
@@ -110,7 +111,7 @@ module Aspera
|
|
110
111
|
class << self
|
111
112
|
def all_but(list)
|
112
113
|
list = [list] unless list.is_a?(Array)
|
113
|
-
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(
|
114
|
+
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
|
114
115
|
end
|
115
116
|
|
116
117
|
def tick(yes)
|
@@ -207,9 +208,9 @@ module Aspera
|
|
207
208
|
options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
|
208
209
|
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
209
210
|
options.declare(
|
210
|
-
:fields, "Comma separated list of: fields, or #{
|
211
|
+
:fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
|
211
212
|
types: [String, Array, Regexp, Proc],
|
212
|
-
default:
|
213
|
+
default: SpecialValues::DEF)
|
213
214
|
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
214
215
|
options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
|
215
216
|
options.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
@@ -256,7 +257,7 @@ module Aspera
|
|
256
257
|
# the requested list of fields, but if can contain special values
|
257
258
|
request =
|
258
259
|
case @options[:fields]
|
259
|
-
# when NilClass then [
|
260
|
+
# when NilClass then [SpecialValues::DEF]
|
260
261
|
when String then @options[:fields].split(',')
|
261
262
|
when Array then @options[:fields]
|
262
263
|
when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
|
@@ -272,10 +273,10 @@ module Aspera
|
|
272
273
|
item = item[1..-1]
|
273
274
|
end
|
274
275
|
case item
|
275
|
-
when
|
276
|
+
when SpecialValues::ALL
|
276
277
|
# get the list of all column names used in all lines, not just first one, as all lines may have different columns
|
277
278
|
request.unshift(*all_fields(data))
|
278
|
-
when
|
279
|
+
when SpecialValues::DEF
|
279
280
|
default = all_fields(data).select{|i|default.call(i)} if default.is_a?(Proc)
|
280
281
|
default = all_fields(data) if default.nil?
|
281
282
|
request.unshift(*default)
|
@@ -293,11 +294,11 @@ module Aspera
|
|
293
294
|
# filter the list of items on the fields option
|
294
295
|
def filter_list_on_fields(data)
|
295
296
|
# by default, keep all data intact
|
296
|
-
return data if @options[:fields].eql?(
|
297
|
+
return data if @options[:fields].eql?(SpecialValues::DEF) && @options[:select].nil?
|
297
298
|
Aspera.assert_type(data, Array){'Filtering fields or select requires result is an Array of Hash'}
|
298
299
|
Aspera.assert(data.all?(Hash)){'Filtering fields or select requires result is an Array of Hash'}
|
299
300
|
filter_columns_on_select(data)
|
300
|
-
return data if @options[:fields].eql?(
|
301
|
+
return data if @options[:fields].eql?(SpecialValues::DEF)
|
301
302
|
selected_fields = compute_fields(data, @options[:fields])
|
302
303
|
return data.map{|i|i[selected_fields.first]} if selected_fields.length == 1
|
303
304
|
return data.map{|i|i.select{|k, _|selected_fields.include?(k)}}
|
@@ -358,8 +359,8 @@ module Aspera
|
|
358
359
|
raise URI::InvalidURIError, 'not uri' if !(blob =~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/)
|
359
360
|
# it's a url
|
360
361
|
url = blob
|
361
|
-
unless
|
362
|
-
|
362
|
+
unless Environment.instance.url_method.eql?(:text)
|
363
|
+
Environment.instance.open_uri(url)
|
363
364
|
return ''
|
364
365
|
end
|
365
366
|
# remote_image = Rest.new(base_url: url).read('')
|
data/lib/aspera/cli/main.rb
CHANGED
@@ -93,11 +93,11 @@ module Aspera
|
|
93
93
|
@plug_init[:only_manual] = false
|
94
94
|
# create formatter, in case there is an exception, it is used to display.
|
95
95
|
@plug_init[:formatter] = Formatter.new
|
96
|
-
|
97
|
-
|
98
|
-
options.parse_command_line(@argv)
|
96
|
+
# create command line manager with arguments
|
97
|
+
@plug_init[:options] = Manager.new(PROGRAM_NAME, @argv)
|
99
98
|
# formatter adds options
|
100
|
-
formatter.declare_options(options)
|
99
|
+
@plug_init[:formatter].declare_options(options)
|
100
|
+
ExtendedValue.instance.default_decoder = options.get_option(:struct_parser)
|
101
101
|
# compare $0 with expected name
|
102
102
|
current_prog_name = File.basename($PROGRAM_NAME)
|
103
103
|
formatter.display_message(
|
@@ -162,9 +162,9 @@ module Aspera
|
|
162
162
|
options.declare(:version, 'Display version', values: :none, short: 'v') { formatter.display_message(:data, Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon, Layout/LineLength
|
163
163
|
options.declare(
|
164
164
|
:ui, 'Method to start browser',
|
165
|
-
values:
|
166
|
-
handler: {o:
|
167
|
-
default:
|
165
|
+
values: Environment::USER_INTERFACES,
|
166
|
+
handler: {o: Environment.instance, m: :url_method},
|
167
|
+
default: Environment.default_gui_mode)
|
168
168
|
options.declare(:log_level, 'Log level', values: Log.levels, handler: {o: Log.instance, m: :level})
|
169
169
|
options.declare(:logger, 'Logging method', values: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
|
170
170
|
options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', coerce: Integer, types: Integer)
|
@@ -189,7 +189,7 @@ module Aspera
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def generate_bash_completion
|
192
|
-
if options.get_next_argument('',
|
192
|
+
if options.get_next_argument('', multiple: true, mandatory: false).nil?
|
193
193
|
PluginFactory.instance.plugin_list.each{|p|puts p}
|
194
194
|
else
|
195
195
|
Log.log.warn('only first level completion so far')
|
@@ -197,11 +197,11 @@ module Aspera
|
|
197
197
|
Process.exit(0)
|
198
198
|
end
|
199
199
|
|
200
|
-
def exit_with_usage(
|
201
|
-
Log.log.debug(
|
200
|
+
def exit_with_usage(include_all_plugins)
|
201
|
+
Log.log.debug{"exit_with_usage(#{include_all_plugins})".bg_red}
|
202
202
|
# display main plugin options
|
203
203
|
formatter.display_message(:error, options.parser)
|
204
|
-
if
|
204
|
+
if include_all_plugins
|
205
205
|
# list plugins that have a "require" field, i.e. all but main plugin
|
206
206
|
PluginFactory.instance.plugin_list.each do |plugin_name_sym|
|
207
207
|
next if plugin_name_sym.eql?(COMMAND_CONFIG)
|