aspera-cli 4.18.0 → 4.18.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 +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)
|