aspera-cli 4.24.2 → 4.25.0.pre2
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 +1067 -758
- data/CONTRIBUTING.md +93 -120
- data/README.md +817 -510
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/transferd.rb +4 -4
- data/lib/aspera/api/aoc.rb +71 -43
- data/lib/aspera/api/cos_node.rb +3 -2
- data/lib/aspera/api/faspex.rb +6 -5
- data/lib/aspera/api/node.rb +10 -12
- data/lib/aspera/ascmd.rb +1 -2
- data/lib/aspera/ascp/installation.rb +55 -41
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +28 -6
- data/lib/aspera/cli/error.rb +4 -2
- data/lib/aspera/cli/extended_value.rb +94 -62
- data/lib/aspera/cli/formatter.rb +55 -22
- data/lib/aspera/cli/main.rb +21 -14
- data/lib/aspera/cli/manager.rb +349 -248
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +94 -51
- data/lib/aspera/cli/plugins/base.rb +62 -49
- data/lib/aspera/cli/plugins/config.rb +85 -96
- data/lib/aspera/cli/plugins/console.rb +15 -9
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +34 -27
- data/lib/aspera/cli/plugins/faspex5.rb +47 -44
- data/lib/aspera/cli/plugins/faspio.rb +7 -6
- data/lib/aspera/cli/plugins/httpgw.rb +3 -2
- data/lib/aspera/cli/plugins/node.rb +132 -120
- data/lib/aspera/cli/plugins/oauth.rb +1 -1
- data/lib/aspera/cli/plugins/orchestrator.rb +116 -33
- data/lib/aspera/cli/plugins/preview.rb +26 -46
- data/lib/aspera/cli/plugins/server.rb +9 -10
- data/lib/aspera/cli/plugins/shares.rb +77 -43
- data/lib/aspera/cli/sync_actions.rb +49 -38
- data/lib/aspera/cli/transfer_agent.rb +16 -34
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +8 -5
- data/lib/aspera/command_line_builder.rb +20 -17
- data/lib/aspera/coverage.rb +6 -2
- data/lib/aspera/environment.rb +71 -84
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/factory.rb +1 -2
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/markdown.rb +31 -0
- data/lib/aspera/nagios.rb +6 -5
- data/lib/aspera/oauth/base.rb +17 -27
- data/lib/aspera/oauth/factory.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +2 -1
- data/lib/aspera/preview/file_types.rb +23 -37
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/products/connect.rb +3 -3
- data/lib/aspera/rest.rb +51 -39
- data/lib/aspera/rest_error_analyzer.rb +4 -4
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/ssl.rb +41 -0
- data/lib/aspera/sync/conf.schema.yaml +182 -34
- data/lib/aspera/sync/database.rb +2 -1
- data/lib/aspera/sync/operations.rb +128 -72
- data/lib/aspera/transfer/parameters.rb +3 -4
- data/lib/aspera/transfer/spec.rb +2 -3
- data/lib/aspera/transfer/spec.schema.yaml +49 -19
- data/lib/aspera/transfer/spec_doc.rb +14 -14
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/transferd_pb.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +33 -6
- metadata.gz.sig +0 -0
|
@@ -5,8 +5,12 @@ require 'aspera/assert'
|
|
|
5
5
|
module Aspera
|
|
6
6
|
module Ascp
|
|
7
7
|
# processing of ascp management port events
|
|
8
|
+
# Reference: `mgmtmess.c`
|
|
8
9
|
class Management
|
|
9
|
-
#
|
|
10
|
+
# References:
|
|
11
|
+
# https://www.google.com/search?q=FASP+error+codes
|
|
12
|
+
# https://www.ibm.com/support/pages/error-code-reference-tables
|
|
13
|
+
# mgmtmess.c : as_mgmt_err_is_retryable
|
|
10
14
|
# Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
|
|
11
15
|
# rubocop:disable Layout/FirstHashElementLineBreak
|
|
12
16
|
ERRORS = {
|
|
@@ -289,7 +293,7 @@ module Aspera
|
|
|
289
293
|
# cspell: enable
|
|
290
294
|
|
|
291
295
|
class << self
|
|
292
|
-
#
|
|
296
|
+
# Translate native event name to snake case
|
|
293
297
|
def field_native_to_snake(name)
|
|
294
298
|
case name
|
|
295
299
|
when 'Elapsedusec' then 'elapsed_usec'
|
|
@@ -298,7 +302,7 @@ module Aspera
|
|
|
298
302
|
end
|
|
299
303
|
end
|
|
300
304
|
|
|
301
|
-
#
|
|
305
|
+
# Translate snake case event name to native
|
|
302
306
|
# @param name [String] Field name
|
|
303
307
|
def field_snake_to_native(name)
|
|
304
308
|
field = name.delete('_')
|
|
@@ -320,7 +324,7 @@ module Aspera
|
|
|
320
324
|
end
|
|
321
325
|
|
|
322
326
|
# Build command to send on management port
|
|
323
|
-
# @param data [Hash] e.g. {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
|
327
|
+
# @param data [Hash] keys are snake case: e.g. {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
|
324
328
|
# @return [String] frame to send on management port
|
|
325
329
|
def command_to_stream(data)
|
|
326
330
|
data
|
|
@@ -339,7 +343,7 @@ module Aspera
|
|
|
339
343
|
end
|
|
340
344
|
attr_reader :last_event
|
|
341
345
|
|
|
342
|
-
#
|
|
346
|
+
# Process line of mgt port event
|
|
343
347
|
# @param line [String] line of mgt port event
|
|
344
348
|
# @return [Hash] event hash or nil if event is not yet complete
|
|
345
349
|
def process_line(line)
|
data/lib/aspera/assert.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Aspera
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Assert that a condition is true, else raise exception
|
|
34
|
-
# @param assertion [
|
|
34
|
+
# @param assertion [TrueClass, FalseClass] Must be true
|
|
35
35
|
# @param info [String,nil] Fixed message in case assert fails, else use `block`
|
|
36
36
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
37
37
|
# @param block [Proc] Produces a string that describes the problem for complex messages
|
|
@@ -41,8 +41,8 @@ module Aspera
|
|
|
41
41
|
return if assertion
|
|
42
42
|
message = 'assertion failed'
|
|
43
43
|
info = yield if block_given?
|
|
44
|
-
message = "#{message}: #{info}" if info
|
|
45
|
-
message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
|
|
44
|
+
message = type.eql?(AssertError) ? "#{message}: #{info}" : info if info
|
|
45
|
+
# message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
|
|
46
46
|
report_error(type, message)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -52,11 +52,33 @@ module Aspera
|
|
|
52
52
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
53
53
|
# @param block [Proc] Additional description in front of message
|
|
54
54
|
def assert_type(value, *classes, type: AssertError)
|
|
55
|
-
assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting #{classes.join(', ')}, but have #{value.inspect}"}
|
|
55
|
+
assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting #{classes.join(', ')}, but have (#{value.class})#{value.inspect}"}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Assert that all value of array are of the same specified type
|
|
59
|
+
# @param array [Array] The array to check
|
|
60
|
+
# @param klass [Class] The expected type of elements
|
|
61
|
+
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
62
|
+
# @param block [Proc] Additional description in front of message
|
|
63
|
+
def assert_array_all(array, klass, type: AssertError)
|
|
64
|
+
assert_type(array, Array, type: type)
|
|
65
|
+
assert(array.all?(klass), type: type){"#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}"}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Assert value is Hash, keys have type, and Values have type
|
|
69
|
+
# @param hash [Hash] The hash to check
|
|
70
|
+
# @param key_class [Class] The expected type of keys (or nil)
|
|
71
|
+
# @param value_class [Class] The expected type of values (or nil)
|
|
72
|
+
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
73
|
+
# @param block [Proc] Additional description in front of message
|
|
74
|
+
def assert_hash_all(hash, key_class, value_class, type: AssertError)
|
|
75
|
+
assert_type(hash, Hash, type: type)
|
|
76
|
+
assert_array_all(hash.keys, key_class, type: AssertError){"#{"#{yield}: " if block_given?}keys"} unless key_class.nil?
|
|
77
|
+
assert_array_all(hash.values, value_class, type: AssertError){"#{"#{yield}: " if block_given?}values"} unless value_class.nil?
|
|
56
78
|
end
|
|
57
79
|
|
|
58
80
|
# Assert that value is one of the given values
|
|
59
|
-
# @param value [
|
|
81
|
+
# @param value [Object] Value to check
|
|
60
82
|
# @param values [Array] Accepted values
|
|
61
83
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
62
84
|
# @param block [Proc] Additional description in front of message
|
|
@@ -69,7 +91,7 @@ module Aspera
|
|
|
69
91
|
end
|
|
70
92
|
|
|
71
93
|
# The value is not one of the expected values
|
|
72
|
-
# @param value [
|
|
94
|
+
# @param value [Object] The wrong value
|
|
73
95
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
74
96
|
# @param block [Proc] Additional description in front of message
|
|
75
97
|
def error_unexpected_value(value, type: InternalError)
|
data/lib/aspera/cli/error.rb
CHANGED
|
@@ -6,11 +6,13 @@ module Aspera
|
|
|
6
6
|
class Error < StandardError; end
|
|
7
7
|
# Raised when an unexpected argument is provided.
|
|
8
8
|
class BadArgument < Error; end
|
|
9
|
+
class MissingArgument < Error; end
|
|
9
10
|
class NoSuchElement < Error; end
|
|
10
11
|
|
|
11
12
|
class BadIdentifier < Error
|
|
12
|
-
def initialize(res_type, res_id)
|
|
13
|
-
|
|
13
|
+
def initialize(res_type, res_id, field: 'identifier', count: 0)
|
|
14
|
+
msg = count.eql?(0) ? 'not found' : "found #{count}"
|
|
15
|
+
super("#{res_type} with #{field}=#{res_id}: #{msg}")
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -17,16 +17,8 @@ module Aspera
|
|
|
17
17
|
class ExtendedValue
|
|
18
18
|
include Singleton
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
MARKER_IN_END = '@'
|
|
23
|
-
|
|
24
|
-
# Special handlers stop processing of handlers on right
|
|
25
|
-
# :extend includes processing of other handlers in itself
|
|
26
|
-
# :val keeps the value intact
|
|
27
|
-
SPECIAL_HANDLERS = %i[extend val].freeze
|
|
28
|
-
|
|
29
|
-
private_constant :MARKER_START, :MARKER_END, :MARKER_IN_END, :SPECIAL_HANDLERS
|
|
20
|
+
# First is default
|
|
21
|
+
DEFAULT_DECODERS = %i[none json ruby yaml]
|
|
30
22
|
|
|
31
23
|
class << self
|
|
32
24
|
# Decode comma separated table text
|
|
@@ -45,8 +37,41 @@ module Aspera
|
|
|
45
37
|
return hash_array
|
|
46
38
|
end
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
# JSON Parser, with more information on error location
|
|
41
|
+
# extract a context: 10 chars before and after the error on the given line and display a pointer "^"
|
|
42
|
+
# :reek:UncommunicativeMethodName
|
|
43
|
+
def JSON_parse(value) # rubocop:disable Naming/MethodName
|
|
44
|
+
JSON.parse(value)
|
|
45
|
+
rescue JSON::ParserError => e
|
|
46
|
+
m = /at line (\d+) column (\d+)/.match(e.message)
|
|
47
|
+
raise if m.nil?
|
|
48
|
+
line = m[1].to_i - 1
|
|
49
|
+
column = m[2].to_i - 1
|
|
50
|
+
lines = value.lines
|
|
51
|
+
raise if line >= lines.size
|
|
52
|
+
error_line = lines[line].chomp
|
|
53
|
+
context_col_beg = [column - 10, 0].max
|
|
54
|
+
context_col_end = [column + 10, error_line.length].min
|
|
55
|
+
context = error_line[context_col_beg...context_col_end]
|
|
56
|
+
cursor_pos = column - context_col_beg
|
|
57
|
+
pointer = ' ' * cursor_pos + '^'.blink
|
|
58
|
+
raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The value must be empty
|
|
62
|
+
# @param value [String] The value as parameter
|
|
63
|
+
# @param ext_type [Symbol] The method of extended value
|
|
64
|
+
def assert_no_value(value, ext_type)
|
|
65
|
+
Aspera.assert(value.empty?, type: BadArgument){"no value allowed for extended value type: #{ext_type}"}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def read_stdin(mode)
|
|
69
|
+
case mode
|
|
70
|
+
when '' then $stdin.read
|
|
71
|
+
when 'bin' then $stdin.binmode.read
|
|
72
|
+
when 'chomp' then $stdin.chomp
|
|
73
|
+
else raise BadArgument, "`stdin` supports only: '', 'bin' or 'chomp'"
|
|
74
|
+
end
|
|
50
75
|
end
|
|
51
76
|
end
|
|
52
77
|
|
|
@@ -54,7 +79,8 @@ module Aspera
|
|
|
54
79
|
|
|
55
80
|
def initialize
|
|
56
81
|
# Base handlers
|
|
57
|
-
# Other handlers can be set using
|
|
82
|
+
# Other handlers can be set using `on`
|
|
83
|
+
# e.g. `preset` is reader in config plugin
|
|
58
84
|
@handlers = {
|
|
59
85
|
val: lambda{ |i| i},
|
|
60
86
|
base64: lambda{ |i| Base64.decode64(i)},
|
|
@@ -62,7 +88,7 @@ module Aspera
|
|
|
62
88
|
env: lambda{ |i| ENV.fetch(i, nil)},
|
|
63
89
|
file: lambda{ |i| File.read(File.expand_path(i))},
|
|
64
90
|
uri: lambda{ |i| UriReader.read(i)},
|
|
65
|
-
json: lambda{ |i| JSON_parse(i)},
|
|
91
|
+
json: lambda{ |i| ExtendedValue.JSON_parse(i)},
|
|
66
92
|
lines: lambda{ |i| i.split("\n")},
|
|
67
93
|
list: lambda{ |i| i[1..-1].split(i[0])},
|
|
68
94
|
none: lambda{ |i| ExtendedValue.assert_no_value(i, :none); nil}, # rubocop:disable Style/Semicolon
|
|
@@ -70,67 +96,63 @@ module Aspera
|
|
|
70
96
|
re: lambda{ |i| Regexp.new(i, Regexp::MULTILINE)},
|
|
71
97
|
ruby: lambda{ |i| Environment.secure_eval(i, __FILE__, __LINE__)},
|
|
72
98
|
secret: lambda{ |i| prompt = i.empty? ? 'secret' : i; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
|
|
73
|
-
stdin: lambda{ |i| ExtendedValue.
|
|
74
|
-
stdbin: lambda{ |i| ExtendedValue.assert_no_value(i, :stdbin); $stdin.binmode.read}, # rubocop:disable Style/Semicolon
|
|
99
|
+
stdin: lambda{ |i| ExtendedValue.read_stdin(i)},
|
|
75
100
|
yaml: lambda{ |i| YAML.load(i)},
|
|
76
101
|
zlib: lambda{ |i| Zlib::Inflate.inflate(i)},
|
|
77
|
-
extend: lambda{ |i| ExtendedValue.instance.
|
|
102
|
+
extend: lambda{ |i| ExtendedValue.instance.evaluate_extend(i)}
|
|
78
103
|
}
|
|
104
|
+
@regex_single = nil
|
|
105
|
+
@regex_extend = nil
|
|
79
106
|
@default_decoder = nil
|
|
107
|
+
update_regex
|
|
80
108
|
end
|
|
81
109
|
|
|
82
|
-
# Regex to match an extended value
|
|
83
|
-
def
|
|
84
|
-
"#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# JSON Parser, with more information on error location
|
|
88
|
-
# :reek:UncommunicativeMethodName
|
|
89
|
-
def JSON_parse(value) # rubocop:disable Naming/MethodName
|
|
90
|
-
JSON.parse(value)
|
|
91
|
-
rescue JSON::ParserError => e
|
|
92
|
-
m = /at line (\d+) column (\d+)/.match(e.message)
|
|
93
|
-
raise if m.nil?
|
|
94
|
-
line = m[1].to_i - 1
|
|
95
|
-
column = m[2].to_i - 1
|
|
96
|
-
lines = value.lines
|
|
97
|
-
raise if line >= lines.size
|
|
98
|
-
error_line = lines[line].chomp
|
|
99
|
-
context_col_beg = [column - 10, 0].max
|
|
100
|
-
context_col_end = [column + 10, error_line.length].min
|
|
101
|
-
context = error_line[context_col_beg...context_col_end]
|
|
102
|
-
cursor_pos = column - context_col_beg
|
|
103
|
-
pointer = ' ' * cursor_pos + '^'.blink
|
|
104
|
-
raise BadArgument, "#{e.message}\n#{context}\n#{pointer}"
|
|
110
|
+
# Update the Regex to match an extended value based on @handlers
|
|
111
|
+
def update_regex
|
|
112
|
+
handler_regex = "#{MARKER_START}(#{modifiers.join('|')})#{MARKER_END}"
|
|
113
|
+
@regex_single = Regexp.new("^#{handler_regex}(.*)$", Regexp::MULTILINE)
|
|
114
|
+
@regex_extend = Regexp.new("^(.*)#{handler_regex}([^#{MARKER_IN_END}]*)#{MARKER_IN_END}(.*)$", Regexp::MULTILINE)
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
public
|
|
108
118
|
|
|
119
|
+
attr_reader :default_decoder
|
|
120
|
+
|
|
109
121
|
def default_decoder=(value)
|
|
110
122
|
Log.log.debug{"Setting default decoder to (#{value.class}) #{value}"}
|
|
111
|
-
Aspera.
|
|
123
|
+
Aspera.assert_values(value, DEFAULT_DECODERS)
|
|
124
|
+
value = nil if value.eql?(:none)
|
|
112
125
|
@default_decoder = value
|
|
113
126
|
end
|
|
114
127
|
|
|
128
|
+
# List of Extended Value methods
|
|
115
129
|
def modifiers; @handlers.keys; end
|
|
116
130
|
|
|
117
131
|
# Add a new handler
|
|
118
|
-
def
|
|
119
|
-
Log.log.debug{"setting handler for #{name}"}
|
|
132
|
+
def on(name, &block)
|
|
120
133
|
Aspera.assert_type(name, Symbol){'name'}
|
|
121
|
-
|
|
134
|
+
Aspera.assert(block)
|
|
135
|
+
Log.log.debug{"Setting handler for #{name}"}
|
|
136
|
+
@handlers[name] = block
|
|
137
|
+
update_regex
|
|
122
138
|
end
|
|
123
139
|
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# @param
|
|
128
|
-
|
|
140
|
+
# Parses a `String` value to extended value.
|
|
141
|
+
# If it is a String using supported extended value modifiers, then evaluate them.
|
|
142
|
+
# Other value types are returned as is.
|
|
143
|
+
# @param value [String] the value to parse
|
|
144
|
+
# @param context [String] Context in which evaluation is done
|
|
145
|
+
# @param allowed [Array<Class>,NilClass] Expected types
|
|
146
|
+
# @return [Object] Evaluated value
|
|
147
|
+
def evaluate(value, context:, allowed: nil)
|
|
129
148
|
return value unless value.is_a?(String)
|
|
130
|
-
|
|
149
|
+
Aspera.assert_array_all(allowed, Class) unless allowed.nil?
|
|
150
|
+
# use default decoder if not an extended value and expect complex types
|
|
151
|
+
using_default_decoder = allowed&.all?{ |t| DEFAULT_PARSER_TYPES.include?(t)} && !@regex_single.match?(value) && !@default_decoder.nil?
|
|
152
|
+
value = [MARKER_START, @default_decoder, MARKER_END, value].join if using_default_decoder
|
|
131
153
|
# First determine decoders, in reversed order
|
|
132
154
|
handlers_reversed = []
|
|
133
|
-
while (m = value.match(
|
|
155
|
+
while (m = value.match(@regex_single))
|
|
134
156
|
handler = m[1].to_sym
|
|
135
157
|
handlers_reversed.unshift(handler)
|
|
136
158
|
value = m[2]
|
|
@@ -139,27 +161,37 @@ module Aspera
|
|
|
139
161
|
Log.log.trace1{"evaluating: #{handlers_reversed}, value: #{value}"}
|
|
140
162
|
handlers_reversed.each do |handler|
|
|
141
163
|
value = @handlers[handler].call(value)
|
|
164
|
+
rescue => e
|
|
165
|
+
raise BadArgument, "Evaluation of #{handler} for #{context}: #{e.message}"
|
|
142
166
|
end
|
|
143
167
|
return value
|
|
144
168
|
end
|
|
145
169
|
|
|
146
|
-
# Parse string value as extended value
|
|
147
|
-
# Use default decoder if none is specified
|
|
148
|
-
def evaluate_with_default(value)
|
|
149
|
-
value = [MARKER_START, @default_decoder, MARKER_END, value].join if value.is_a?(String) && value.match(/^#{handler_regex_string}.*$/).nil? && !@default_decoder.nil?
|
|
150
|
-
return evaluate(value)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
170
|
# Find inner extended values
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
while (m = value.match(
|
|
171
|
+
# Only used in above lambda
|
|
172
|
+
def evaluate_extend(value)
|
|
173
|
+
while (m = value.match(@regex_extend))
|
|
157
174
|
sub_value = "@#{m[2]}:#{m[3]}"
|
|
158
175
|
Log.log.debug{"evaluating #{sub_value}"}
|
|
159
|
-
value = "#{m[1]}#{evaluate(sub_value)}#{m[4]}"
|
|
176
|
+
value = "#{m[1]}#{evaluate(sub_value, context: 'composite extended value')}#{m[4]}"
|
|
160
177
|
end
|
|
161
178
|
return value
|
|
162
179
|
end
|
|
180
|
+
# marker "@"
|
|
181
|
+
MARKER_START = '@'
|
|
182
|
+
# marker ":"
|
|
183
|
+
MARKER_END = ':'
|
|
184
|
+
# marker "@"
|
|
185
|
+
MARKER_IN_END = '@'
|
|
186
|
+
|
|
187
|
+
# Special handlers stop processing of handlers on right
|
|
188
|
+
# :extend includes processing of other handlers in itself
|
|
189
|
+
# :val keeps the value intact
|
|
190
|
+
SPECIAL_HANDLERS = %i[extend val].freeze
|
|
191
|
+
|
|
192
|
+
# Array and Hash types:
|
|
193
|
+
DEFAULT_PARSER_TYPES = [Array, Hash].freeze
|
|
194
|
+
private_constant :MARKER_START, :MARKER_END, :MARKER_IN_END, :SPECIAL_HANDLERS, :DEFAULT_PARSER_TYPES
|
|
163
195
|
end
|
|
164
196
|
end
|
|
165
197
|
end
|
data/lib/aspera/cli/formatter.rb
CHANGED
|
@@ -7,6 +7,7 @@ require 'aspera/secret_hider'
|
|
|
7
7
|
require 'aspera/environment'
|
|
8
8
|
require 'aspera/log'
|
|
9
9
|
require 'aspera/assert'
|
|
10
|
+
require 'aspera/markdown'
|
|
10
11
|
require 'terminal-table'
|
|
11
12
|
require 'tty-spinner'
|
|
12
13
|
require 'yaml'
|
|
@@ -64,8 +65,23 @@ module Aspera
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# used by spec_doc
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
# @param match [MatchData,String]
|
|
69
|
+
def markdown(match)
|
|
70
|
+
if match.is_a?(String)
|
|
71
|
+
match = Markdown::FORMATS.match(match)
|
|
72
|
+
Aspera.assert(match)
|
|
73
|
+
end
|
|
74
|
+
Aspera.assert_type(match, MatchData)
|
|
75
|
+
if match[:entity]
|
|
76
|
+
Aspera.assert_values(match[:entity], 'bsol')
|
|
77
|
+
'\\'
|
|
78
|
+
elsif match[:bold]
|
|
79
|
+
match[:bold].to_s.blue
|
|
80
|
+
elsif match[:code]
|
|
81
|
+
match[:code].to_s.bold
|
|
82
|
+
else
|
|
83
|
+
Aspera.error_unexpected_value(match.to_s)
|
|
84
|
+
end
|
|
69
85
|
end
|
|
70
86
|
|
|
71
87
|
# replace empty values with a readable version on terminal
|
|
@@ -100,30 +116,49 @@ module Aspera
|
|
|
100
116
|
end
|
|
101
117
|
end
|
|
102
118
|
|
|
119
|
+
# Given a list of string, display that list in a single cell
|
|
120
|
+
def list_to_string(list)
|
|
121
|
+
list.join(',')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Build new prefix
|
|
125
|
+
def add_prefix(prefix, key)
|
|
126
|
+
[prefix, key].compact.join('.')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Add elements of enumerator to the stack, in reverse order
|
|
130
|
+
def add_elements(stack, prefix, enum)
|
|
131
|
+
enum.reverse_each do |key, value|
|
|
132
|
+
stack.push([add_prefix(prefix, key), value])
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
103
136
|
# Flatten a Hash into single level hash
|
|
104
137
|
def flatten_hash(input)
|
|
105
138
|
Aspera.assert_type(input, Hash)
|
|
106
139
|
return input if input.empty?
|
|
107
140
|
flat = {}
|
|
141
|
+
# tail (pop,push) contains the next element to display
|
|
108
142
|
stack = [[nil, input]]
|
|
109
143
|
until stack.empty?
|
|
110
144
|
prefix, current = stack.pop
|
|
145
|
+
# empty things will be displayed as such
|
|
111
146
|
if current.respond_to?(:empty?) && current.empty?
|
|
112
147
|
flat[prefix] = current
|
|
113
148
|
next
|
|
114
149
|
end
|
|
115
150
|
case current
|
|
116
151
|
when Hash
|
|
117
|
-
|
|
152
|
+
add_elements(stack, prefix, current)
|
|
118
153
|
when Array
|
|
119
|
-
if current.
|
|
120
|
-
flat[prefix] = current.
|
|
154
|
+
if current.none?{ |i| i.is_a?(Array) || i.is_a?(Hash)}
|
|
155
|
+
flat[prefix] = list_to_string(current.map(&:to_s))
|
|
121
156
|
elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
|
|
122
|
-
flat[prefix] = current.map{ |i| i['name']}
|
|
157
|
+
flat[prefix] = list_to_string(current.map{ |i| i['name']})
|
|
123
158
|
elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
|
|
124
|
-
stack
|
|
159
|
+
add_elements(stack, prefix, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
|
|
125
160
|
else
|
|
126
|
-
current.each_with_index.
|
|
161
|
+
add_elements(stack, prefix, current.each_with_index.map{ |v, i| [i, v]})
|
|
127
162
|
end
|
|
128
163
|
else
|
|
129
164
|
flat[prefix] = current
|
|
@@ -166,23 +201,23 @@ module Aspera
|
|
|
166
201
|
else
|
|
167
202
|
{}
|
|
168
203
|
end
|
|
169
|
-
options.declare(:format, 'Output format',
|
|
170
|
-
options.declare(:output, 'Destination for results',
|
|
171
|
-
options.declare(:display, 'Output only some information',
|
|
204
|
+
options.declare(:format, 'Output format', allowed: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
|
205
|
+
options.declare(:output, 'Destination for results', handler: {o: self, m: :option_handler})
|
|
206
|
+
options.declare(:display, 'Output only some information', allowed: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
|
172
207
|
options.declare(
|
|
173
208
|
:fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
|
|
174
|
-
|
|
209
|
+
allowed: [String, Array, Regexp, Proc],
|
|
175
210
|
default: SpecialValues::DEF
|
|
176
211
|
)
|
|
177
|
-
options.declare(:select, 'Select only some items in lists: column, value',
|
|
178
|
-
options.declare(:table_style, '(Table) Display style',
|
|
179
|
-
options.declare(:flat_hash, '(Table) Display deep values as additional keys',
|
|
212
|
+
options.declare(:select, 'Select only some items in lists: column, value', allowed: [Hash, Proc], handler: {o: self, m: :option_handler})
|
|
213
|
+
options.declare(:table_style, '(Table) Display style', allowed: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
|
|
214
|
+
options.declare(:flat_hash, '(Table) Display deep values as additional keys', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_handler}, default: true)
|
|
180
215
|
options.declare(
|
|
181
|
-
:multi_single, '(Table) Control how object list is displayed as single table, or multiple objects',
|
|
216
|
+
:multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', allowed: %i[no yes single],
|
|
182
217
|
handler: {o: self, m: :option_handler}, default: :no
|
|
183
218
|
)
|
|
184
|
-
options.declare(:show_secrets, 'Show secrets on command output',
|
|
185
|
-
options.declare(:image, 'Options for image display',
|
|
219
|
+
options.declare(:show_secrets, 'Show secrets on command output', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_handler}, default: false)
|
|
220
|
+
options.declare(:image, 'Options for image display', allowed: Hash, handler: {o: self, m: :option_handler}, default: {})
|
|
186
221
|
end
|
|
187
222
|
|
|
188
223
|
# method accessed by option manager
|
|
@@ -326,8 +361,7 @@ module Aspera
|
|
|
326
361
|
end
|
|
327
362
|
when :object_list
|
|
328
363
|
# :object_list is an Array of Hash, where key=column name
|
|
329
|
-
Aspera.
|
|
330
|
-
Aspera.assert(data.all?(Hash)){"expecting Array of Hash: #{data.inspect}"}
|
|
364
|
+
Aspera.assert_array_all(data, Hash){'result'}
|
|
331
365
|
data = data.map{ |obj| self.class.flatten_hash(obj)} if @options[:flat_hash]
|
|
332
366
|
display_table(data, compute_fields(data, fields), single: type.eql?(:single_object))
|
|
333
367
|
when :value_list
|
|
@@ -406,8 +440,7 @@ module Aspera
|
|
|
406
440
|
def filter_list_on_fields(data)
|
|
407
441
|
# by default, keep all data intact
|
|
408
442
|
return data if @options[:fields].eql?(SpecialValues::DEF) && @options[:select].nil?
|
|
409
|
-
Aspera.
|
|
410
|
-
Aspera.assert(data.all?(Hash)){'Filtering fields or select requires result is an Array of Hash'}
|
|
443
|
+
Aspera.assert_array_all(data, Hash){'filter or select'}
|
|
411
444
|
filter_columns_on_select(data)
|
|
412
445
|
return data if @options[:fields].eql?(SpecialValues::DEF)
|
|
413
446
|
selected_fields = compute_fields(data, @options[:fields])
|
data/lib/aspera/cli/main.rb
CHANGED
|
@@ -214,6 +214,7 @@ module Aspera
|
|
|
214
214
|
rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
|
|
215
215
|
rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
|
|
216
216
|
rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
|
|
217
|
+
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing', usage: true}
|
|
217
218
|
rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
|
|
218
219
|
rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
|
|
219
220
|
rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
|
|
@@ -228,6 +229,7 @@ module Aspera
|
|
|
228
229
|
unless exception_info.nil?
|
|
229
230
|
Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
|
|
230
231
|
@context.formatter.display_message(:error, "#{Formatter::ERROR_FLASH} #{exception_info[:t]}: #{exception_info[:e].message}")
|
|
232
|
+
Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
|
|
231
233
|
@context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
|
|
232
234
|
# Is that a known error condition with proposal for remediation ?
|
|
233
235
|
Hints.hint_for(exception_info[:e], @context.formatter)
|
|
@@ -287,7 +289,6 @@ module Aspera
|
|
|
287
289
|
@context.options = Manager.new(Info::CMD_NAME, @argv)
|
|
288
290
|
# Formatter adds options
|
|
289
291
|
@context.formatter.declare_options(@context.options)
|
|
290
|
-
ExtendedValue.instance.default_decoder = @context.options.get_option(:struct_parser)
|
|
291
292
|
# Compare $0 with expected name
|
|
292
293
|
current_prog_name = File.basename($PROGRAM_NAME)
|
|
293
294
|
@context.formatter.display_message(
|
|
@@ -349,28 +350,34 @@ module Aspera
|
|
|
349
350
|
# Define header for manual
|
|
350
351
|
def declare_global_options
|
|
351
352
|
Log.log.debug('declare_global_options')
|
|
352
|
-
@context.options.declare(:help, 'Show this message',
|
|
353
|
-
@context.options.declare(:bash_comp, 'Generate bash completion for command',
|
|
354
|
-
@context.options.declare(:show_config, 'Display parameters used for the provided action',
|
|
355
|
-
@context.options.declare(:version, 'Display version',
|
|
353
|
+
@context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
|
|
354
|
+
@context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
|
|
355
|
+
@context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
|
|
356
|
+
@context.options.declare(:version, 'Display version', allowed: Allowed::TYPES_NONE, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
|
|
356
357
|
@context.options.declare(
|
|
357
358
|
:ui, 'Method to start browser',
|
|
358
|
-
|
|
359
|
+
allowed: USER_INTERFACES,
|
|
359
360
|
handler: {o: Environment.instance, m: :url_method}
|
|
360
361
|
)
|
|
361
362
|
@context.options.declare(
|
|
362
363
|
:invalid_characters, 'Replacement character and invalid filename characters',
|
|
363
364
|
handler: {o: Environment.instance, m: :file_illegal_characters}
|
|
364
365
|
)
|
|
365
|
-
@context.options.declare(:log_level, 'Log level',
|
|
366
|
-
@context.options.declare(:log_format, 'Log formatter',
|
|
367
|
-
@context.options.declare(:logger, 'Logging method',
|
|
368
|
-
@context.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron',
|
|
369
|
-
@context.options.declare(:once_only, 'Process only new items (some commands)',
|
|
370
|
-
@context.options.declare(:log_secrets, 'Show passwords in logs',
|
|
371
|
-
@context.options.declare(:clean_temp, 'Cleanup temporary files on exit',
|
|
366
|
+
@context.options.declare(:log_level, 'Log level', allowed: Log::LEVELS, handler: {o: Log.instance, m: :level})
|
|
367
|
+
@context.options.declare(:log_format, 'Log formatter', allowed: [Proc, Logger::Formatter, String], handler: {o: Log.instance, m: :formatter})
|
|
368
|
+
@context.options.declare(:logger, 'Logging method', allowed: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
|
|
369
|
+
@context.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', allowed: Allowed::TYPES_INTEGER)
|
|
370
|
+
@context.options.declare(:once_only, 'Process only new items (some commands)', allowed: Allowed::TYPES_BOOLEAN, default: false)
|
|
371
|
+
@context.options.declare(:log_secrets, 'Show passwords in logs', allowed: Allowed::TYPES_BOOLEAN, handler: {o: SecretHider.instance, m: :log_secrets})
|
|
372
|
+
@context.options.declare(:clean_temp, 'Cleanup temporary files on exit', allowed: Allowed::TYPES_BOOLEAN, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
|
|
372
373
|
@context.options.declare(:temp_folder, 'Temporary folder', handler: {o: TempFileManager.instance, m: :global_temp})
|
|
373
|
-
@context.options.declare(:pid_file, 'Write process identifier to file, delete on exit'
|
|
374
|
+
@context.options.declare(:pid_file, 'Write process identifier to file, delete on exit')
|
|
375
|
+
@context.options.declare(
|
|
376
|
+
:parser, 'Default parser for structured parameters and options',
|
|
377
|
+
handler: {o: ExtendedValue.instance, m: :default_decoder},
|
|
378
|
+
allowed: ExtendedValue::DEFAULT_DECODERS,
|
|
379
|
+
default: ExtendedValue::DEFAULT_DECODERS.first
|
|
380
|
+
)
|
|
374
381
|
# Parse declared options
|
|
375
382
|
@context.options.parse_options!
|
|
376
383
|
end
|